相机标定,内参数与外参数

相机标定

简介

所谓的相机标定就是将外界世界的坐标信息转化为计算机(自带相机/摄像头)可以理解的“距离”,将世界坐标系转换到相机坐标系。我们可以理解为从一个坐标系转换到另一个坐标系所需要的转换关系就是相机标定。

简单滴说:A=F(B),其中F()就是相机标定要做的工作。 其标定的目的就是为了相机内参、外参、畸变参数。

基本原理

求解Homographic矩阵



其中 H是描述Homographic矩阵
H 矩阵可以根据特征点/棋盘格角点的空间坐标,以及其图像坐标用最小二乘法很容易求解。

Homography 有 8 个自由度,
由r1和r2正交,且r1和r2的模相等,可以得到如下约束:

计算内参数矩阵


计算外参数矩阵

相机标定步骤:

1、打印一张棋盘格,把它贴在一个平面上,作为标定物。
2、通过调整标定物或摄像机的方向,为标定物拍摄一些不同方向的照片。
3、从照片中提取棋盘格角点。
4、估算理想无畸变的情况下,五个内参和六个外参。
5、应用最小二乘法估算实际存在径向畸变下的畸变系数。
6、极大似然法,优化估计,提升估计精度。

标定过程

准备标定板

OpenCV使用棋盘格板进行标定。为了标定相机,我们需要输入一系列三维点和它们对应的二维图像点。在黑白相间的棋盘格上,二维图像点很容易通过角点检测找到。而对于真实世界中的三维点呢?由于我们采集中,是将相机放在一个地方,而将棋盘格定标板进行移动变换不同的位置,然后对其进行拍摄。所以我们需要知道(X,Y,Z)的值。但是简单来说,我们定义棋盘格所在平面为XY平面,即Z=0。

检测棋盘格角点

为了找到棋盘格模板,我们使用openCV中的函数cv2.findChessboardCorners()。我们也需要告诉程序我们使用的模板是什么规格的,例如88的棋盘格或者55棋盘格等,建议使用x方向和y方向个数不相等的棋盘格模板。下面实验中,我们使用的是107的棋盘格,每个方格边长是20mm,即含有96的内部角点。这个函数如果检测到模板,会返回对应的角点,并返回true。当然不一定所有的图像都能找到需要的模板,所以我们可以使用多幅图像进行定标。除了使用棋盘格,我们还可以使用圆点阵,对应的函数为cv2.findCirclesGrid()。
  找到角点后,我们可以使用cv2.cornerSubPix()可以得到更为准确的角点像素坐标。我们也可以使用cv2.drawChessboardCorners()将角点绘制到图像上显示。

ret, corners = cv2.findChessboardCorners(gray1, (9, 6), None)
img = cv2.drawChessboardCorners(gray1, (9,6), corners,ret)
cv2.waitKey(1000)
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)

标定

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray1.shape[::-1], None, None)

通过上面的步骤,我们得到了用于标定的三维点和与其对应的图像上的二维点对。我们使用cv2.calibrateCamera()进行标定,这个函数会返回标定结果、相机的内参数矩阵、畸变系数、旋转矩阵和平移向量。
第三步我们已经得到了相机内参和畸变系数,在将图像去畸变之前,我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,通过设定自由自由比例因子alpha。当alpha设为0的时候,将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉。
然后我们就可以使用新得到的内参数矩阵和畸变系数对图像进行去畸变了。有两种方法进行去畸变:

实验结果

只有正面照片时

内参数矩阵,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))

来源:漫渚伊德

物联沃分享整理
物联沃-IOTWORD物联网 » 相机标定,内参数与外参数

发表评论