任务描述
本关任务:理解张正友标定算法原理,并实现张正友棋盘相机标定图像。
相关知识
为了完成本关任务,你需要掌握:
- 理解张正友标定算法原理;
- 实现张正友棋盘相机标定图像。
张正友标定
”张正友标定”是指张正友教授 1998 年提出的单平面棋盘格的摄像机标定方法。提出的方法介于传统标定法和自标定法之间,但克服了传统标定法需要的高精度标定物的缺点,而仅需使用一个打印出来的棋盘格就可以。同时也相对于自标定而言,提高了精度,便于操作。因此张氏标定法被广泛应用于计算机视觉方面。
回顾一下摄像机模型中的几个坐标系:
- 世界坐标系 :也叫参考坐标系/基准坐标系,用于描述摄像机和物体的位置;
- 摄像机坐标系 :固定在摄像机上,原点在光心,Zc 轴沿光轴方向, Xc/Yc 轴分别平行于成像平面;
- 以物理单位表示的图像坐标系 (x, y): 原点在摄像机光轴与图像平面的交点, x/y 轴与摄像机 Xc/Yc 轴平行,沿图像平面方向;
- 以像素为单位表示的图像坐标系 (u, v): 原点在数字图像的左上角,u/v 轴沿图像平面向右向下为正方向。
张正友平面标定法包含了以下前提:
- 标定物是平面靶标;
- 将世界坐标系置于靶标平面,原点设在靶标一角,Xw/Yw 方向沿靶标平面,Zw 方向垂直于靶标平面;
- 先不考虑畸变,标定摄像机参数,得到参数的线性初值;然后利用线性初值,进行非线性标定,得到畸变参数。
计算内参和外参的初值
根据之前介绍的摄像机模型,设三维世界坐标的点为X=[X,Y,Z,1]T,二维相机平面像素坐标为m=[u,v,1]T,所以标定用的棋盘格平面到图像平面的单应性关系为:
其中 s 为尺度因子,K 为摄像机内参数,R 为旋转矩阵,T 为平移向量。
注意,s 对于齐次坐标来说,不会改变齐次坐标值。张氏标定法中,令棋盘格平面为 Z=0 的平面。则可得
我们把 K[r1,r2,t] 叫做单应性矩阵 H,即
H 是一个齐次矩阵,所以有 8 个未知数,至少需要 8 个方程,每对对应点能提供两个方程,所以至少需要四个对应点,就可以算出世界平面到图像平面的单应性矩阵 H。
计算内、外参数矩阵及畸变矫正
每个单应性矩阵能提供两个方程,而内参数矩阵包含 5 个参数,要求解,至少需要 3 个单应性矩阵。为了得到三个不同的单应性矩阵,我们使用至少三幅棋盘格平面的图片进行标定。通过改变相机与标定板之间的相对位置来得到三个不同的图片。
我们至少需要三幅包含棋盘格的图像,然后通过 cholesky 分解,得到相机的内参数矩阵K。
外参矩阵可以通过内参矩阵 K 及单应性矩阵 H 计算求得。张氏标定法只关注了影响最大的径向畸变,基本思想是使用内参矩阵 K、二维相机平面像素坐标和三维世界坐标推导出前两阶的畸变参数 k1 和 k2 ,使用矩阵变换计算得到畸变系数 k。更多的理论推导请参考这里。
OpenCV 实现棋盘相机标定图像
使用 OpenCV 标定的流程是:角点提取 -> 相机标定。角点提取在任务 1 中已经描述。
利用获取到的图像角点,获取到棋盘标定图的内角点图像坐标之后,就可以使用calibrateCamera()
函数进行标定,计算相机内参和外参系数,其calibrateCamera()
函数原型如下:
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objectPoints,
imagePoints, imageSize, cameraMatrix, distCoeffs[, rvecs[, tvecs[,
flags[, criteria]]]])
它的输入参数为:
objectPoints
: 为世界坐标系中的三维点。需要依据棋盘上单个黑白矩阵的大小,计算出(初始化)每一个内角点的世界坐标;imagePoints
: 为每一个内角点对应的图像坐标点;imageSize
: 为图像的像素尺寸大小,在计算相机的内参和畸变矩阵时需要使用到该参数;cameraMatrix
: 为相机的内参矩阵;distCoeffs
: 为畸变矩阵;rvecs
: 为旋转向量;tvecs
: 为位移向量;flags
: 为标定时所采用的算法。有如下几个参数:CV_CALIB_USE_INTRINSIC_GUESS
:使用该参数时,在cameraMatrix
矩阵中应该有fx,fy,u0,v0
的估计值。否则的话,将初始化(u0,v0)
图像的中心点,使用最小二乘估算出fx,fy
。CV_CALIB_FIX_PRINCIPAL_POINT
:在进行优化时会固定光轴点。当CV_CALIB_USE_INTRINSIC_GUESS
参数被设置,光轴点将保持在中心或者某个输入的值。CV_CALIB_FIX_ASPECT_RATIO
:固定fx/fy
的比值,只将fy
作为可变量,进行优化计算。当CV_CALIB_USE_INTRINSIC_GUESS
没有被设置,fx 和 fy 将会被忽略。只有fx/fy
的比值在计算中会被用到。CV_CALIB_ZERO_TANGENT_DIST
:设定切向畸变参数(p1,p2)
为零。CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6
:对应的径向畸变在优化中保持不变。CV_CALIB_RATIONAL_MODEL
:计算k4,k5,k6
三个畸变参数。如果没有设置,则只计算其它 5 个畸变参数。
criteria
: 是最优迭代终止条件设定。
输出参数有 5 个,分别为:
ret
:校正成功与否,布尔类型变量;mtx
:相机内参;dist
:畸变系数;revcs
:旋转矩阵;tvecs
:平移矩阵。
在本实训中,该函数常用参数为前三个,其它的参数指定为None
。在使用该函数进行标定运算之前,需要对棋盘上每一个内角点的空间坐标系的位置坐标进行初始化,标定的结果是生成相机的内参矩阵cameraMatrix
、相机的 5 个畸变系数distCoeffs
,另外每张图像都会生成属于自己的平移向量和旋转向量。
到此我们已经完成了标定的过程,得到了摄像头的各个参数,后面就可以用这些得到的参数来做摄像头的矫正了。利用求得的相机的内参和外参数据,可以对图像进行畸变的矫正,在 OpenCV 中使用undistort()
函数实现,其函数原型如下:
dst = undistort(src, cameraMatrix, distCoeffs[, dst[, newCameraMatrix]])
输出为目标图像dst
,输入参数为:
src
:代表畸变的原始图像;cameraMatrix
:为之前求得的相机的内参矩阵;distCoeffs
:为之前求得的相机畸变矩阵;dst
:矫正后的输出图像,跟输入图像具有相同的类型和大小;newCameraMatrix
:可以指定新的相机参数,默认跟cameraMatrix
保持一致。
到此,就完成了摄像头的标定和图像的矫正的整个流程。
编程要求
图 1 给定的原始图像
根据提示,在右侧编辑器补充task2()
函数的 Begin-End 区间代码,实现对给定图片(如图 1 所示)实现图像的畸变矫正。具体要求如下:
- 使用
cv2.calibrateCamera()
函数寻找角点,求出图片的相机内参mtx
、畸变系数dist
、旋转矩阵revcs
和平移矩阵tvecs
; cv2.undistort()
纠正图像畸变,newCameraMatrix
参数设置为程序中优化后的相机内参矩阵,即newcameramtx
。最后结果保存在dst
变量。
测试说明
平台会对你补全的代码进行测试,如果实际输出与预期输出匹配则通关。
测试输入:无 ; 预期输出: distortion correction success
参考代码:
import cv2
import numpy as np
def calb():
#criteria:角点精准化迭代过程的终止条件
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((7*7,3), np.float32)
'''
设定世界坐标下点的坐标值,因为用的是棋盘可以直接按网格取;
假定棋盘正好在x-y平面上,这样z值直接取0,简化初始化步骤。
mgrid把列向量[0:cbraw]复制了cbcol列,把行向量[0:cbcol]复制了cbraw行。
转置reshape后,每行都是4×6网格中的某个点的坐标。
'''
objp[:,:2] = np.mgrid[0:7,0:7].T.reshape(-1,2)
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
fname = "/data/workspace/myshixun/task2/5test1.jpg"
#识别出角点,记录世界物体坐标和图像坐标
img = cv2.imread(fname) #source image
cv2.imwrite('/data/workspace/myshixun/task2/out/5test1.png', img)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转灰度
#寻找角点,存入corners,ret是找到角点的flag
ret, corners = cv2.findChessboardCorners(gray,(7,7),None)
if ret == True:
objpoints.append(objp)
#执行亚像素级角点检测
corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
imgpoints.append(corners2)
#在棋盘上绘制角点
img = cv2.drawChessboardCorners(img,(7,7),corners2,ret)
'''
传入所有图片各自角点的三维、二维坐标,相机标定。
每张图片都有自己的旋转和平移矩阵,但是相机内参和畸变系数只有一组。
mtx,相机内参;dist,畸变系数;revcs,旋转矩阵;tvecs,平移矩阵。
'''
########## Begin ##########
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)
########## End ##########
h,w = img.shape[:2]
'''
优化相机内参(camera matrix),这一步可选。
参数1表示保留所有像素点,同时可能引入黑色像素,
设为0表示尽可能裁剪不想要的像素,这是个scale,0-1都可以取。
'''
newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
#纠正畸变
########## Begin ##########
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
########## End ##########
#这步只是输出纠正畸变以后的图片
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('/data/workspace/myshixun/task2/out/calibresult.png',dst)