1998年,张正友提出了基于二维平面靶标的标定方法,使用相机在不同角度下拍摄多幅平面靶标的图像,比如棋盘格的图像,然后通过对棋盘格的角点进行计算分析来进行相机标定(求解相机的内外参数)。
标准棋盘格图像
第一步:对每一幅图像得到一个映射矩阵(单应矩阵)H
一个二维点表示,一个三维点可以用表示,其增广矩阵(齐次坐标表示)为以及。三维点与其投影图像点之间的关系为:
式中,s为任意标准矢量,A矩阵为相机内参;R(旋转矩阵),t(平移向量)为外参
内参:
式中是相机在图像坐标系的主点,和是图像上和坐标轴的尺度因子,表示图像坐标轴的垂直度(取决于相机制造工艺,好的为0)。
假定模板平面在世界坐标系的平面上,则有:
在标定模板(棋盘格)平面上的齐次坐标,而是棋盘格平面上的点投影到摄像机的成像(图像平面)对应点的齐次坐标。
此时,可以得到一个3*3的矩阵:
利用单应矩阵可得内参矩阵A的约束条件为
第二步:利用约束条件线性求解内参矩阵A
假设存在:
式中,B为对称矩阵,基于绝对二次曲面原理求出B以后,再对B矩阵求逆,并从中导出内参矩阵A,再由A和单应矩阵H计算外参R和t,公式如下:
第三步:最大似然估计
采用最大似然准则优化上述参数。假设图像有n幅,模板平面标定点有m个,则最大似然估计值就可以通过最小化以下公式得到:
式中,为第j个点在第i幅图像中的像点;为第i幅图像的旋转矩阵;为第i幅图像的平移向量;为第j个点的空间坐标;初始估计值利用上面线性求解的结果,径向畸变系数、初始值为0。
张正友标定没有考虑切向畸变,他认为可以忽略。
实验流程:
1.制作棋盘格标定板,图片如上,选用的是10*7的
2.标定板离相机距离,要保证图片的20%被标定板覆盖
3.拍摄至少10~20张
4.相对相机不同方向拍摄标定板
5.拍摄过程不要采用自定对焦方式,也不要改变焦距(作为摄影爱好者,之后我会写一篇关于单反相机的科普知识)
基于Python-openCV的标定原码如下:
import cv2 as cv
import numpy as np
import glob
# 标定板的大小,标定板内角点的个数
CHCKERBOARD = (9,6)
# 角点优化,迭代的终止条件,一个是角点优化的最大迭代次数,另一个是角点的移动位移小于则终止
criteria = (cv.TERM_CRITERIA_EPS + cv.TermCriteria_MAX_ITER, 30, 0.001)
# 定义标定板在真实世界的坐标
# 创建一个向量来保存每张图片中角点的3D坐标
objpoints = []
# 创建一个向量来保存每张图片中角点的2D坐标
imgpoints = []
# 定义3D坐标: [row,col,z]
objp = np.zeros((1,CHCKERBOARD[0]*CHCKERBOARD[1],3),np.float32)
objp[0,:,:2] = np.mgrid[0:CHCKERBOARD[0],0:CHCKERBOARD[1]].T.reshape(-1,2)
# 提取不同角度拍摄的图片
images = glob.glob('D:/datasets/camera_calibration/*.jpg')
images = sorted(images)
for i,fname in enumerate(images):
img = cv.imread(fname) # 读取图片
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY) # RGB转换成灰度图
# 计算标定板的角点的2D坐标
# 寻找角点坐标,如果找到ret返回True, corners:[col,row], 原点在左上角
ret, corners = cv.findChessboardCorners(gray,CHCKERBOARD,cv.CALIB_CB_ADAPTIVE_THRESH+
cv.CALIB_CB_FAST_CHECK+cv.CALIB_CB_NORMALIZE_IMAGE)
if ret == True:
objpoints.append(objp)
# 调用cornerSubpix对2D角点坐标位置进行优化
corners2 = cv.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
imgpoints.append(corners2)
# 绘制寻找到的角点,从红色开始绘制,紫色结束
img = cv.drawChessboardCorners(img,CHCKERBOARD,corners2,ret)
cv.imshow(fname+ 'succeed', img)
else:
print(f'第{i}张图,{fname}未发现足够角点')
cv.imshow(fname + 'failed', img)
cv.waitKey(0)
cv.destroyAllWindows()
h,w = img.shape[:2]
# 相机标定
ret,mtx,dist,rvecs,tvecs = cv.calibrateCamera(objpoints,imgpoints,gray.shape
[::-1],None,None)
print('相机内参: ') # [[fx,0,cx],[0,fy,cy],[0,0,1]]
print(mtx,'\n')
print('畸变参数: ') # k1,k2,p1,p2,k3
print(dist,'\n')
print('旋转矩阵: ')
print(rvecs,'\n')
print('平移矩阵: ')
print(tvecs,'\n')
# 去畸变
# 要先用getOptimalNewCameraMatrix来获取矫正后的有效像素面积,以及对参数进行优化
img = cv.imread('D:/datasets/camera_calibration/02.jpg')
h,w = img.shape[:2]
newcameramtx,roi = cv.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
# 使用undistort矫正图像
dst = cv.undistort(img,mtx,dist,None,newcameramtx)
# 图片裁剪
x,y,w,h = roi
dst2 = dst[y:y+h,x:x+w] # x宽,y高
print(f'ROI: x:{x},y:{y},w:{w},h:{h}')
cv.imshow('original',img)
cv.imshow('image_undistorted1',dst)
cv.imshow('image_undistorted1 ROI',dst2)
cv.waitKey(0)
cv.destroyAllWindows()
# 使用remapping
mapx,mapy = cv.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),
5)
dst3 = cv.remap(img,mapx,mapy,cv.INTER_LINEAR)
cv.imshow('remap_method',dst3)
cv.waitKey(0)
cv.destroyAllWindows()
# 计算重投影误差:
# 计算重投影误差来评估相机标定的结果,将3D坐标投影到成像平面
# 投影得到2D点后,便可以利用检测到的二维坐标角点计算他们之间的均方误差
mean_error = 0
for i in range(len(objpoints)):
# 使用内外惨和畸变参数对点进行重投影
imgpoints2,_ = cv.projectPoints(objpoints[i],rvecs[i],tvecs[i],mtx,
dist)
error = cv.norm(imgpoints[i],imgpoints2,cv.NORM_L2)/len(imgpoints2)
# L2范数,均方误差
mean_error += error
mean_error /= len(objpoints)
print('total error: {}'.format(mean_error))
动手去玩吧,希望大家学废张正友标定法!!!