1. 针孔相机模型
1.1. 坐标转换
C点表示相机的中心点,也是相机坐标系的中心点; Z轴表示相机的主轴; p点所在的平面表示相机的像平面,也就是图片坐标系所在的二维平面; p点表示像主点,主轴与像平面相交的点 C点到p点的距离,也就是图中的f表示相机的焦距; 像平面上的x和y坐标轴与相机坐标系上的X和Y坐标轴互相平行; 相机坐标系是以X、Y 、Z三个轴组成的且原点在C点,度量值为米(m); 像平面坐标系是以x、y两个轴组成且原点在p点,度量值为米(m); 图像坐标系一般指图片相对坐标系,在这里可以认为和像平面坐标系在一个平面上,不过原点是在图片的角上,而且度量值为像素的个数(pixel)。
由上图我们可以根据相机、像素、世界坐标系之间的关系,通过数学计算(相似三角形等等)很容易得出如下关系矩阵: 引入齐次坐标简单来说就是为了统一,方便计算,可自行去了解齐次坐标的作用。
1.2. 像主点偏移
由于各种原因,生产原因,个人使用原因等等,相机光心要想都处在原点可能性较小,会发生一定的偏移。如下图,
u
0
u_0
u0?和
v
0
v_0
v0?分别是x、y方向上的偏移量。相机标定就是确定相机的内部参数和外部参数。这里K表示内参矩阵,接下来我们还要知道外参矩阵。
1.3. 畸变现象
由于透镜制造精度以及组装工艺的偏差会引入畸变,导致原始图像的失真。镜头的畸变分为径向畸变和切向畸变两类: 1.图像径向畸变:沿着透镜半径方向分布的畸变,产生原因是光线在原理透镜中心的地方比靠近中心的地方更加弯曲,这种畸变在普通廉价的镜头中表现更加明显,径向畸变主要包括桶形畸变和枕形畸变两种。
2.图像切向畸变:由于透镜本身与相机传感器平面(成像平面)或图像平面不平行而产生的,这种情况多是由于透镜被粘贴到镜头模组上的安装偏差导致。
1.4. 内参矩阵
因此K中还需要加入畸变参数,进行畸变矫正
1.5. 外参矩阵
一般情况下,世界坐标系和相机坐标系不重合,这时,世界坐标系中的某一点P PP要投影到像面上时,先要将该点的坐标转换到相机坐标系下。刚体从世界坐标系转换到相机坐标系的过程,可以通过旋转和平移来得到。因此相机的外部参数就包括了旋转、平移矩阵。 其中
[
R
∣
t
]
[R|t]
[R∣t]为外参矩阵
2. 相机参数标定
2.1. 实验数据
共拍摄14张图片
2.2. 代码实现
import cv2
import numpy as np
import glob
np.set_printoptions(suppress=True)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
w = 6
h = 4
objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
objpoints = []
imgpoints = []
images = glob.glob('*.jpg')
i = 0
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
if ret == True:
i += 1
cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
objpoints.append(objp)
imgpoints.append(corners)
cv2.drawChessboardCorners(img, (w, h), corners, ret)
cv2.imshow('findCorners', img)
cv2.imwrite('h' + str(i) + '.jpg', img)
cv2.waitKey(10)
cv2.destroyAllWindows()
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
print(("ret:"), ret)
print(("内参矩阵:\n"), mtx)
print(("畸变参数:\n"), dist)
print(("旋转向量(外参数):\n"), rvecs)
print(("平移向量(外参数):\n"), tvecs)
img2 = cv2.imread('1.jpg')
h, w = img2.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 0, (w, h))
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
cv2.imwrite('calibresult.jpg', dst)
total_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
print(("total error: "), total_error / len(objpoints))
2.3. 结果及分析
(1)角点检测结果 使用findChessboardCorners函数提取角点,这里的角点专指的是标定板上的内角点,保存了每一张棋盘格的角点检测结果,每一张均可检测到角点。随便拿了一个结果展示。
(2)内参矩阵 (3)外参矩阵
(4)分析 1.标定图片最好多点,不然可能导致不准确。 2.平移拍摄参数变化较小,棋盘若是倾斜,参数变化较大。
|