三步骤详解张正友标定法

1998年,张正友提出了基于二维平面靶标的标定方法,使用相机在不同角度下拍摄多幅平面靶标的图像,比如棋盘格的图像,然后通过对棋盘格的角点进行计算分析来进行相机标定(求解相机的内外参数)。

三步骤详解张正友标定法_第1张图片

标准棋盘格图像

第一步:对每一幅图像得到一个映射矩阵(单应矩阵)H

一个二维点m=(u,v)^{T}表示,一个三维点可以用M=(X,Y,Z)^{T}表示,其增广矩阵(齐次坐标表示)为\widetilde{m}=(u,v,1)^{T}以及\widetilde{M}=(X,Y,Z)^{T}。三维点与其投影图像点之间的关系为:

s\widetilde{m}=A(R,t)\widetilde{M}

式中,s为任意标准矢量,A矩阵为相机内参;R(旋转矩阵),t(平移向量)为外参

内参:

         A = \begin{bmatrix} \alpha &\gamma &u_{0} \\ 0& \beta & v_{0}\\ 0& 0&1 \end{bmatrix}

式中(u_{0},v_{0})是相机在图像坐标系的主点,\alpha\beta是图像上uv坐标轴的尺度因子,\gamma表示图像坐标轴的垂直度(取决于相机制造工艺,好的为0)。

假定模板平面在世界坐标系Z=0的平面上,则有:

s\begin{bmatrix} u\\ v\\ 1 \end{bmatrix}=A\bigl(\begin{smallmatrix} r_{1} & r_{2} & r_{3}&t\end{smallmatrix}\bigr)\begin{bmatrix} X\\ Y\\ Z\\ 1 \end{bmatrix}=A\bigl(\begin{smallmatrix} r_{1} &r_{2} & t \end{smallmatrix}\bigr)\begin{bmatrix} X\\ Y\\ 1 \end{bmatrix}

在标定模板(棋盘格)平面上的齐次坐标\widetilde{M}=(X,Y,1),而\widetilde{m}=(u,v,1)是棋盘格平面上的点投影到摄像机的成像(图像平面)对应点的齐次坐标。

此时,可以得到一个3*3的矩阵:

H=\bigl(\begin{smallmatrix} h_{1} &h_{2} & h_{3} \end{smallmatrix}\bigr)=\lambda A\bigl(\begin{smallmatrix} r_{1} & r_{2} & t \end{smallmatrix}\bigr)

利用单应矩阵可得内参矩阵A的约束条件为   h_{1}^{T}A^{-T}A^{-1}h_{2}=0

第二步:利用约束条件线性求解内参矩阵A

假设存在:

B=A^{-T}A^{-1}=\begin{bmatrix} B_{11} &B_{12} & B_{13}\\ B_{21}&_{22} &B_{23} \\ B_{31}&B_{32} & B_{33} \end{bmatrix}

式中,B为对称矩阵,基于绝对二次曲面原理求出B以后,再对B矩阵求逆,并从中导出内参矩阵A,再由A和单应矩阵H计算外参R和t,公式如下:

\left\{\begin{matrix} r_{1}=\lambda A^{-1}h_{1}\\ r_{2}=\lambda A^{-1}h_{2} \\ r_{3}=r_{1}\cdot r_{2}\\ t=\lambda A^{-1}h_{3} \end{matrix}\right.

第三步:最大似然估计

采用最大似然准则优化上述参数。假设图像有n幅,模板平面标定点有m个,则最大似然估计值就可以通过最小化以下公式得到:

\sum_{i=1}^{n}\sum_{j=1}^{m}\left \| m_{ij}-m(A,k_{1},k_{2},R_{i},t_{i},M_{j}) \right \|^{2}

式中,m_{ij}为第j个点在第i幅图像中的像点;R_{i}为第i幅图像的旋转矩阵;t_{i}为第i幅图像的平移向量;M_{j}为第j个点的空间坐标;初始估计值利用上面线性求解的结果,径向畸变系数k_{1}k_{2}初始值为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))

动手去玩吧,希望大家学废张正友标定法!!!

你可能感兴趣的:(opencv,python)