相机标定
简介
在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立摄像机 成像 的几何模型,这些几何模型参数就是摄像机参数。在大多数条件下这些参数必须通过实验与计算才能得到,这个求解参数的过程就称之为相机标定。简单来说是从世界坐标系换到图像坐标系的过程,也就是求最终的投影矩阵 P P P 的过程。
无论是在图像测量或者机器视觉应用中,摄像机参数的 标定 都是非常关键的环节,其标定结果的精度及算法的稳定性直接影响摄像机工作产生结果的准确性。因此,做好摄像机标定是做好后续工作的前提,是提高标定精度是科研工作的重点所在。其标定的目的就是为了相机内参、外参、畸变参数。
标定过程
准备标定板
OpenCV使用棋盘格板进行标定,如下图所示。为了标定相机,我们需要输入一系列三维点和它们对应的二维图像点。在黑白相间的棋盘格上,二维图像点很容易通过角点检测找到。而对于真实世界中的三维点呢?由于我们采集中,是将相机放在一个地方,而将棋盘格定标板进行移动变换不同的位置,然后对其进行拍摄。所以我们需要知道(X,Y,Z)的值。但是简单来说,我们定义棋盘格所在平面为XY平面,即Z=0。
检测棋盘格角点
为了找到棋盘格模板,我们使用openCV中的函数cv2.findChessboardCorners()。我们也需要告诉程序我们使用的模板是什么规格的,例如88的棋盘格或者55棋盘格等,建议使用x方向和y方向个数不相等的棋盘格模板。下面实验中,我们使用的是107的棋盘格,每个方格边长是20mm,即含有96的内部角点。这个函数如果检测到模板,会返回对应的角点,并返回true。当然不一定所有的图像都能找到需要的模板,所以我们可以使用多幅图像进行定标。除了使用棋盘格,我们还可以使用圆点阵,对应的函数为cv2.findCirclesGrid()。
找到角点后,我们可以使用cv2.cornerSubPix()可以得到更为准确的角点像素坐标。我们也可以使用cv2.drawChessboardCorners()将角点绘制到图像上显示。
标定
通过上面的步骤,我们得到了用于标定的三维点和与其对应的图像上的二维点对。我们使用cv2.calibrateCamera()进行标定,这个函数会返回标定结果、相机的内参数矩阵、畸变系数、旋转矩阵和平移向量。
第三步我们已经得到了相机内参和畸变系数,在将图像去畸变之前,我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,通过设定自由自由比例因子alpha。当alpha设为0的时候,将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉。
然后我们就可以使用新得到的内参数矩阵和畸变系数对图像进行去畸变了。有两种方法进行去畸变:
相机标定步骤:
1、打印一张棋盘格,把它贴在一个平面上,作为标定物。
2、通过调整标定物或摄像机的方向,为标定物拍摄一些不同方向的照片。
3、从照片中提取棋盘格角点。
4、估算理想无畸变的情况下,五个内参和六个外参。
5、应用最小二乘法估算实际存在径向畸变下的畸变系数。
6、极大似然法,优化估计,提升估计精度。
实验结果
只有正面照片时
内参数矩阵,newcameramtx :
[[1.39247888e+03 0.00000000e+00 4.21531719e+02]
[0.00000000e+00 1.39737781e+03 9.65615613e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
归一化焦距
fx= 1392.4788818359375 ,fy= 1397.3778076171875
像主点(光心)的坐标
cx,cy 421.53171941416076 965.6156127999784
dist为畸变系数。dist:
[[-3.02007106e-01 7.88399855e+00 -2.46474817e-02 1.70686761e-03
-4.04756976e+01]]
ret:
1.543195731560363
mtx,
[[1.39695861e+03 0.00000000e+00 4.21660147e+02]
[0.00000000e+00 1.39810589e+03 9.66118704e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
revcs,旋转矩阵,
[[[-0.44309197]
[ 1.07147812]
[ 2.54591039]]
[[-0.84261395]
[-0.41259128]
[-0.67023628]]
[[-0.68681096]
[ 0.48542523]
[ 1.39249712]]
[[-0.46926989]
[ 0.85300712]
[ 2.35431065]]
[[-0.90272359]
[-0.16052316]
[-0.06843877]]
[[-0.53683367]
[ 0.72170338]
[ 1.88219057]]
[[-0.58768252]
[-0.86576238]
[-1.78717314]]
[[-0.11030619]
[-0.95418799]
[-2.14221165]]
[[-0.75441289]
[-0.58611659]
[-1.21926087]]]
tvecs,平移矩阵
(array([[ 4.88345717],
[ 0.05394641],
[26.82703677]]), array([[-4.50241296],
[-2.47457431],
[24.1376541 ]]), array([[ 1.6678211 ],
[-6.65197784],
[21.42807732]]), array([[ 6.1818145 ],
[-3.33303195],
[26.24933552]]), array([[-4.74254289],
[-2.1579989 ],
[25.51213894]]), array([[ 4.19599853],
[-6.14602571],
[24.7580413 ]]), array([[-1.69668164],
[-0.61900485],
[18.5261494 ]]), array([[ 1.12094953],
[ 3.90569145],
[16.84087091]]), array([[-2.72197755],
[-2.52729824],
[20.64903984]]))
total error为总体误差:
0.20800721454025364
```python
只有侧面的照片时
```python
[[1.82535645e+03 0.00000000e+00 4.18737546e+02]
[0.00000000e+00 3.35064893e+03 8.69153073e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
归一化焦距
fx= 1825.3564453125 ,fy= 3350.64892578125
像主点(光心)的坐标`
cx,cy 418.73754642918357 869.153072791858`
dist为畸变系数。dist:
[[-1.40512508e+00 5.11022470e+01 -7.72332110e-02 -4.38502255e-02
-2.59448179e+02]]
ret:
3.5459118388096216
mtx,相机内参:
[[1.81551877e+03 0.00000000e+00 4.41070962e+02]
[0.00000000e+00 3.27082753e+03 9.49563451e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
revcs,旋转矩阵,
[[[-0.80727033]
[-1.01171386]
[-1.38129347]]
[[-0.75218536]
[ 1.14355184]
[ 1.84500315]]
[[-0.52719775]
[ 1.45639709]
[ 2.29389315]]
[[-0.63501651]
[-1.21903686]
[-1.67978362]]
[[-1.09993196]
[-0.21990954]
[-0.11986996]]]
tvecs,平移矩阵
(array([[-2.89017706],
[ 0.98237799],
[21.24180682]]), array([[ 4.81100307],
[-3.58585171],
[26.84381837]]), array([[ 4.79268410e+00],
[-5.12204200e-03],
[ 2.77421342e+01]]), array([[-0.75778873],
[ 1.25678028],
[22.1323928 ]]), array([[-4.34387342],
[-0.49682161],
[25.88038286]]))
total error为总体误差:
`0.4767580123269295`
正面和侧面都有时
内参数矩阵,newcameramtx :
[[1.47756372e+03 0.00000000e+00 3.84641645e+02]
[0.00000000e+00 1.47626343e+03 8.22453102e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
归一化焦距
`fx= 1477.563720703125 ,fy= 1476.263427734375`
像主点(光心)的坐标
cx,cy 384.6416450763427 822.4531022500742
dist为畸变系数。dist:
[[-9.26352628e-01 2.87704635e+01 -6.32812203e-02 -1.19180863e-02
-2.38869326e+02]]
ret:
2.153142220889635
mtx,
[[1.50446561e+03 0.00000000e+00 3.76968572e+02]
[0.00000000e+00 1.71088936e+03 9.53167429e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
revcs,旋转矩阵,
[[[-0.40954189]
[ 1.23275464]
[ 2.499671 ]]
[[-0.57533214]
[-0.83954196]
[-1.41737043]]
[[-0.95020771]
[-0.41739403]
[-0.64296516]]
[[-0.76361682]
[ 0.60652417]
[ 1.37935282]]
[[-0.53011171]
[ 1.12347077]
[ 2.48832118]]
[[-0.4729612 ]
[ 1.03220648]
[ 2.31536491]]
[[-1.00259535]
[-0.12403123]
[-0.05485035]]
[[-0.58901605]
[ 0.86765807]
[ 1.85667864]]
[[-0.67763739]
[-0.94516277]
[-1.73384762]]
[[-0.23942408]
[-1.05697544]
[-2.09534043]]]
tvecs,平移矩阵
(array([[ 5.75125712],
[ 0.30145146],
[28.50095635]]), array([[-2.11435859],
[ 1.47584997],
[17.05490001]]), array([[-3.72925642],
[-1.98627606],
[26.15021609]]), array([[ 2.35337777],
[-5.64738809],
[23.16349946]]), array([[ 5.69328695],
[-0.04058948],
[22.8784759 ]]), array([[ 7.02740175],
[-2.64972244],
[27.99528916]]), array([[-3.93080855],
[-1.686195 ],
[27.55035256]]), array([[ 4.99191567],
[-5.15586085],
[26.61341699]]), array([[-1.10128061],
[-0.39738934],
[19.96949129]]), array([[ 1.68065367],
[ 3.62482022],
[17.86602482]]))
total error为总体误差:
0.27106081554424943
总结
正面侧面都有时误差 0.20800721454025364
正面侧面都有时误差0.27106081554424943
侧面都有时误差0.4767580123269295
所以正面误差最小
程序代码
import cv2
import glob
import numpy as np
from ipython_genutils.py3compat import xrange
import matplotlib.pyplot as plt
from pylab import *
# from work1.Harris import plot_harris_points
# from work1.refine_all_param import refinall_all_param
cbraw = 9 # 有6行角点
cbcol = 6 # 有4列角点
objp = np.zeros((cbraw * cbcol, 3), np.float32)
objp[:, :2] = np.mgrid[0:cbraw, 0:cbcol].T.reshape(-1, 2)
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
# glob是个文件名管理工具
images = glob.glob(r"./image/zm/*.jpg")
print("images",images)
for fname in images:
# 对每张图片,识别出角点,记录世界物体坐标和图像坐标
img = cv2.imread(fname) # source image
# 我用的图片太大,缩小了一半
# img = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_CUBIC)
gray1 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转灰度
# 寻找角点,存入corners,ret是找到角点的flag
ret, corners = cv2.findChessboardCorners(gray1, (9, 6), None)
img = cv2.drawChessboardCorners(gray1, (9,6), corners,ret)
cv2.waitKey(1000)
# criteria:角点精准化迭代过程的终止条件
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 执行亚像素级角点检测
corners2 = cv2.cornerSubPix(gray1, corners, (9, 6), (-1, -1), criteria)
objpoints.append(objp)
imgpoints.append(corners2)
# 在棋盘上绘制角点,只是可视化工具
img = cv2.drawChessboardCorners(gray1, (9, 6), corners2, ret)
cv2.imshow('img', img)
# cv2.waitKey(1000)
'''
传入所有图片各自角点的三维、二维坐标,相机标定。
每张图片都有自己的旋转和平移矩阵,但是相机内参和畸变系数只有一组。
mtx,相机内参;dist,畸变系数;外参数:revcs,旋转矩阵;tvecs,平移矩阵。
'''
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray1.shape[::-1], None, None)
img = cv2.imread(r'./image/zm/4ffe6fa6c7732896b58c0ef7cfd8cb7.jpg')
# print(img)
# extrinsics_param\
rvecs = np.array(rvecs)
# print("rvecs="+str(rvecs))
# rvecsnp, _ = cv2.Rodrigues(rvecs)
# print("rvecsnp="+str(rvecsnp))
# print("tvecs="+str(tvecs))
# 注意这里跟循环开头读取图片一样,如果图片太大要同比例缩放,不然后面优化相机内参肯定是错的。
# img = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_CUBIC)
# img1 = (9,6)
# h ,w = img1
h, w = img.shape[:2]
'''
优化相机内参(camera matrix),这一步可选。
参数1表示保留所有像素点,同时可能引入黑色像素,
设为0表示尽可能裁剪不想要的像素,这是个scale,0-1都可以取。
'''
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))#显示更大范围的图片(正常重映射之后会删掉一部分图像)
# 纠正畸变
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# 这步只是输出纠正畸变以后的图片
x, y, w, h = roi
dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult.png', dst)
# 打印我们要求的两个矩阵参数
"""参数newcameramtx为内参数矩阵,
dist为畸变系数,total error为总体误差。
newcameramtx:
归一化焦距fx=1.25045215e+03,fy=7.76162048e+02,
像主点(光心)的坐标cx=1.48972123e+03,cy=1.31742293e+03。
所有图像投影坐标和亚像素角点坐标之间的总体的平均误差大概为0.10,误差较小,标定结果不错"""
print("内参数矩阵,newcameramtx :\n", newcameramtx)
print("归一化焦距fx,fy=",newcameramtx[0][0],newcameramtx[1][1])
print("像主点(光心)的坐标cx,cy",newcameramtx[0][2],newcameramtx[1][2])
print("dist为畸变系数。dist:\n", dist)
print("ret:\n",ret)
print("mtx,相机内参:\n",mtx)
print("revcs,旋转矩阵,",rvecs)
print("tvecs,平移矩阵",tvecs)
# mtx,相机内参;dist,畸变系数;外参数:revcs,旋转矩阵;tvecs,平移矩阵
# 计算误差
tot_error = 0
for i in xrange(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
tot_error += error
print("total error为总体误差: ", tot_error / len(objpoints))
文章出处登录后可见!