本文采用张氏标定法标定了一个USB相机(网购,一百多块钱),记录了辛酸历程。
废话不多说,直接开始标定!
第一步:拍十几张棋盘格图片
棋盘格是从网上找的图片打印的,整体还是个歪的......
这一步就有坑,后面解释
第二步:标定相机内参
直接上代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import glob
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 棋盘格对应的世界坐标下的三维坐标
# xx = [371., 396., 421., 446., 471., 496.]
# yy = [402., 427., 452., 477., 502., 527., 552., 577.]
# 这两种坐标得到的内参一样
xx = [0., 1., 2., 3., 4., 5.]
yy = [0., 1., 2., 3., 4., 5., 6., 7.]
xyz = []
for i in range(len(xx)):
for j in range(len(yy)):
xyz.append(np.array([xx[i]*25, yy[j]*25, 0.], dtype=np.float32))
xyz = np.array(xyz, dtype=np.float32)
print(objp)
objppoints = [] # 真实世界下的三维坐标
imgpoints = []
images = glob.glob(r'./chess/*.JPG')
for fname in images:
img = cv2.imread(fname)
print(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# print(gray.shape)
ret, corners = cv2.findChessboardCorners(gray, (8, 6), None) # 寻找棋盘内角点
# print('corners', corners)
if ret == True:
objppoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) # 提取亚像素信息,提高标定精度
print(corners2)
imgpoints.append(corners2)
img = cv2.drawChessboardCorners(img, (8, 6), corners2, ret)
cv2.imshow('gray', img)
while cv2.waitKey(100) != 27: # esc
if cv2.getWindowProperty('gray', cv2.WND_PROP_AUTOSIZE) < 0: # 获取窗口状态,如果窗口关闭 执行break
break
cv2.destroyAllWindows()
# mrx 内参数矩阵,dist 畸变系数,rvecs 旋转向量,tvecs 平移向量
ret, mrx, dist, rvecs, tveces = cv2.calibrateCamera(objppoints, imgpoints, (1920, 1080), None, None) # 相机标定
print(mrx)
print(dist)
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mrx, dist, (1920, 1080), 1) # 矫正畸变,优化内参
print(roi)
total_error = 0
for i in range(len(objppoints)):
imgpoints2, _ = cv2.projectPoints(objppoints[i], rvecs[i], tveces[i], mrx, dist) # 映射
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
print("total error: ", total_error / len(objppoints))
# 保存内参、畸变参数、新内参
np.savez('c2_parameter_1080', k=mrx, dist=dist, newK=newcameramtx)
代码是没有问题的,运行的结果如下
贴两张图意思一下,其他图也是这样的。
然后程序会输出内参、畸变参数,我的外参是单独标定的。
第三步:图像去畸变
先上代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from tqdm import tqdm
import os
npzfile = np.load('./calibrateCamera/c2_parameter_1080.npz')
K = npzfile['k']
dist = npzfile['dist']
newcameramtx=npzfile['newK']
print(npzfile['k'])
print(npzfile['dist'])
img_root=r'./photos_camera3'
save_root=r'./undistort_photos'
img_list=os.listdir(img_root)
for name in tqdm(img_list):
img_path=os.path.join(img_root,name)
img = cv2.imread(img_path)
dst = cv2.undistort(img, K, dist, None, newcameramtx)
cv2.imwrite(os.path.join(save_root,name), dst)
填坑来了
第一步、第二步从拍摄到跑代码,看着都很流畅,大家也都是这么做的,本来我也没太注意,直到用标定好的内参给图像去畸变。结果保存出来的是个这鬼样子
这根本就不能用好吧!然后心里就开始默默的问候代码的祖宗,
开始解决问题
首先我是查看了保存的内参和优化后的内参,发现优化后的内参的cx和cy有些大的离谱。
然后又发现在优化内参时 返回的roi是(0,0,0,0)!!!
代码都知道图像里没一个像素是能用的。
现在就基本锁定了是内参的问题,然后重新拍数据。经过多批数据的拍摄总结出来的教训。
我们都知道相机的照片越靠近边缘,畸变程度越大,所以拍摄的时候要尽量把棋盘格放到边上去。
然后得到的结果就和预期的一样了。
第四步:定外参
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import math
def rotation_vector_to_euler_angles(rvec):
# 将旋转向量转换成旋转矩阵
R, _ = cv2.Rodrigues(rvec)
# 计算欧拉角(yaw、pitch、roll)
yaw = np.arctan2(R[1, 0], R[0, 0])
pitch = np.arctan2(-R[2, 0], np.sqrt(R[2, 1] ** 2 + R[2, 2] ** 2))
roll = np.arctan2(R[2, 1], R[2, 2])
# 将弧度转换为角度
yaw = np.rad2deg(yaw)
pitch = np.rad2deg(pitch)
roll = np.rad2deg(roll)
return yaw, pitch, roll # z, y, x
# 旋转矩阵到欧拉角(角度制)
def rotateMatrixToEulerAngles2(rvec):
RM, _ = cv2.Rodrigues(rvec)
theta_z = np.arctan2(RM[1, 0], RM[0, 0]) / np.pi * 180
theta_y = np.arctan2(-1 * RM[2, 0], np.sqrt(RM[2, 1] * RM[2, 1] + RM[2, 2] * RM[2, 2])) / np.pi * 180
theta_x = np.arctan2(RM[2, 1], RM[2, 2]) / np.pi * 180
print(f"Euler angles:\ntheta_x: {theta_x}\ntheta_y: {theta_y}\ntheta_z: {theta_z}")
return theta_x, theta_y, theta_z
npzfile = np.load('c3_parameter_1080.npz')
cameraMatrix = npzfile['k']
distCoeffs = npzfile['dist']
print(npzfile['k'])
print(npzfile['dist'])
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objppoints = [] # 真实世界下的三维坐标
imgpoints = []
# 拿尺子量的真实坐标
xx = [406., 431., 456., 481., 506., 531.]
yy = [305., 330., 355., 380., 405., 430., 455., 480.]
xyz = []
for i in range(len(xx)):
for j in range(len(yy)):
xyz.append(np.array([xx[i], yy[j], 0.], dtype=np.float32))
xyz = np.array(xyz, dtype=np.float32)
objp = xyz
objp = xyz[::-1]
print(objp)
img = cv2.imread(r'D:\gzz\data\monkey_hand\20230523three\camera3\Rt/1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (8, 6), None) # 寻找棋盘内角点
print(ret)
if ret:
objppoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) # 提取亚像素信息,提高标定精度
print(corners2)
# print(len(corners2))
# corners2 = mypoints
imgpoints.append(corners2)
img = cv2.drawChessboardCorners(img, (8, 6), corners2, ret)
cv2.imshow('gray', img)
while cv2.waitKey(100) != 27: # esc
if cv2.getWindowProperty('gray', cv2.WND_PROP_AUTOSIZE) < 0: # 获取窗口状态,如果窗口关闭 执行break
break
cv2.destroyAllWindows()
retval, rvec, tvec = cv2.solvePnP(objp, corners2, cameraMatrix, distCoeffs)
print(rvec)
print(tvec)
# 测试原点
imgp, _ = cv2.projectPoints(np.array([[0, 0, 0], [100, 0, 0], [0, 100, 0], [0, 0, 100]], dtype=np.float64), rvec,
tvec, cameraMatrix, distCoeffs)
print('imgp:', imgp)
total_error = 0
for i in range(len(objppoints)):
imgpoints2, _ = cv2.projectPoints(objp, rvec, tvec, cameraMatrix, distCoeffs) # 映射
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
print("total error: ", total_error / len(objppoints))
rvec_matrix = cv2.Rodrigues(rvec)[0]
proj_matrix = np.hstack((rvec_matrix, tvec))
eulerAngles = cv2.decomposeProjectionMatrix(proj_matrix)[6] # 欧拉角
pitch, yaw, roll = [math.radians(_) for _ in eulerAngles]
pitch = math.degrees(math.asin(math.sin(pitch)))
roll = -math.degrees(math.asin(math.sin(roll)))
yaw = math.degrees(math.asin(math.sin(yaw)))
print(pitch, yaw, roll)
roll, yaw, pitch = rotation_vector_to_euler_angles(rvec)
print(pitch, yaw, roll)
a = rotateMatrixToEulerAngles2(rvec)
# 保存内参、畸变参数、新内参
np.savez('c3_parameter_1080', k=cameraMatrix, newK= npzfile['newK'], dist=distCoeffs, rvec=rvec, tvec=tvec)
又来坑了,总会有这样那样的原因出错,继续问候代码的祖宗
这能咋办,我手动调的角点的坐标,
避坑1,这里要说一下,检测的棋盘格角点是有顺序的,所以给定的真实坐标也要一一对应;
避坑2,注意你定义的是左手坐标系还是右手坐标系,会影响结果。
完结,撒花❀