这篇只是为了简单记录一下相机标定和畸变矫正的步骤,具体原理和代码参考:
OpenCV-Python-Toturial-中文版.pdf 的243至249页
电子书和棋盘图片 百度网盘下载:https://pan.baidu.com/s/1oAKlGC2o0J9z3QBcK043QQ
提取码:1c6x
整个过程分两步:摄像头矩阵和畸变系数的获取,畸变矫正
一、摄像头矩阵和畸变系数的获取
首先是拍摄十几张棋盘的图片(使用需要标定的相机进行拍摄)
然后下面是根据棋盘图获取 对象点objpoints和图像点imgpoints(因为摄像头矩阵和畸变系数需要这两个数据计算出)
import cv2
import numpy as np
import glob
np.set_printoptions(suppress=True) # 用于控制Python中小数的显示精度 suppress:小数是否需要以科学计数法的形式输出
# 1.找棋盘格角点
# 棋盘格模板规格
W = 11 # 内角点个数,内角点是和其他格子连着的点
H = 8
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 世界坐标系中的棋盘格点,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐标,记为二维矩阵
objp = np.zeros((W * H, 3), np.float32)
objp[:, :2] = np.mgrid[0:W, 0:H].T.reshape(-1, 2)
# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点
images = glob.glob('*.jpg') # 拍摄棋盘的图片
i = 0
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 找到棋盘格角点
# 棋盘图像(8位灰度或彩色图像) 棋盘尺寸 存放角点的位置
ret, corners = cv2.findChessboardCorners(gray, (W, H), None)
# 如果找到足够点对,将其存储起来
if ret == True:
# 角点精确检测
# 输入图像 角点初始坐标 搜索窗口为2*winsize+1 死区 求角点的迭代终止条件
i += 1
# 增加准确度
cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
objpoints.append(objp)
imgpoints.append(corners)
# 将角点在图像上显示
cv2.drawChessboardCorners(img, (W, H), corners, ret)
cv2.imshow('findCorners', img)
# 保存绘画出角点的图片
cv2.imwrite('h' + str(i) + '.jpg', img)
cv2.waitKey(10)
cv2.destroyAllWindows()
下面是标定后绘画出角点的图片中的一张
然后通过之前得到的对象点objpoints和图像点imgpoints,使用cv2.calibrateCamera函数获取相机的相关参数
# 2标定、去畸变
# 2.1输入:世界坐标系里的位置 像素坐标 图像的像素尺寸大小 3*3矩阵,相机内参数矩阵 畸变矩阵
# 2.1输出:标定结果 相机的内参数矩阵 畸变系数 旋转矩阵 平移向量
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
print(("ret:"), ret)
print(("mtx内参数矩阵:\n"), mtx, type(mtx)) # 内参数矩阵
print(("dist畸变值:\n"), dist, type(dist)) # 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print(("rvecs旋转(向量)外参:\n"), rvecs) # 旋转向量(外参数)
print(("tvecs平移(向量)外参:\n"), tvecs) # 平移向量(外参数)
其中的返回值 mtx 是摄像头矩阵 , dist 是 畸变系数 ,另外两个外参 rvecs旋转向量和tvecs平移向量一般用在3D姿态估计
mtx内参数矩阵:
[[344.61290279 0. 324.72516098]
[ 0. 345.03361345 229.58898428]
[ 0. 0. 1. ]] <class 'numpy.ndarray'>
dist畸变值:
[[-0.28599796 0.12353913 0.00122616 0.00025517 -0.04949974]] <class 'numpy.ndarray'>
这两个数据都是numpy.ndarray类型的数据,我刚开始直接把打印出来的数据放到代码里去进行畸变矫正,结果报错了,这两个数据要这样写
mtx = np.array([[344.61290279, 0, 324.72516098], [0, 345.03361345, 229.58898428], [0, 0, 1])
dist = np.array([-0.28599796, 0.12353913, 0.00122616, 0.00025517, -0.04949974])
控制台打印出来的没有圆括号() ,直接写在代码里需要加上圆括号。
还有一种保存和加载这个数据的方法:使用 np.save()和np.load()
可以参考博客:(6条消息) numpy学习(五)——文件的保存和读写(np.save()、np.load()、np.savez()、np.savetxt()、np.loadtxt())_徕胖的博客-CSDN博客
二、畸变矫正
下面就是使用上面获得的摄像头矩阵(内参矩阵)和畸变系数 进行畸变矫正
其中有两种矫正方式 undistort 和 remapping
# 从 .npy文件 读取 内参矩阵和畸变系数 (数据类型:np.narray)
mtx = np.load('mtx.npy')
dist = np.load('dist.npy')
# 2.2去畸变
img = cv2.imread('1.jpg')
h, w = img.shape[:2]
# 我们已经得到了相机内参和畸变系数,在将图像去畸变之前,
# 我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,
# 通过设定自由自由比例因子alpha。当alpha设为0的时候,将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;
# 当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 0, (w, h)) # 自由比例参数
# undistort 这是最简单的方法。只需使用这个函数和上边得到 的 ROI 对结果进行裁剪。
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# crop the image 根据前面ROI区域裁剪图片
# x, y, w, h = roi # (这个我没有用,xywh都为0)
# dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult.png', dst)
# remapping 这应该属于“曲线救国”了。首先我们要找到从畸变图像到 非畸变图像的映射方程。再使用重映射方程。
mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), 5)
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
# crop the image 根据前面ROI区域裁剪图片
# x, y, w, h = roi # (这个我没有用,xywh都为0)
# dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult.png', dst)
矫正前
矫正后
三、反投影误差
通过反投影误差,我们可以来评估结果的好坏。越接近0,说明结果越理想。 通过之前计算的内参数矩阵、畸变系数、旋转矩阵和平移向量,使用cv2.projectPoints()计算三维点到二维图像的投影, 然后计算反投影得到的点与图像上检测到的点的误差,最后计算一个对于所有标定图像的平均误差,这个值就是反投影误差。
total_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
print(("total error: "), total_error / len(objpoints))
误差如下
total error: 0.15192880761380756