《简记摄像机标定》
CV的数据源头是摄像机,我们根据不同的场景需要选用不同的摄像机,如果视野范围优先,我们考虑使用广角;如果精度优先,我们考虑使用无畸变的相机,或者微畸变的相机再进行图像的矫正;由于透镜制造精度以及组装工艺的偏差会引入畸变,就会导致原始图像的失真,而我们的任务是想大概知道一个像素对应多少mm,所以需要畸变矫正。
Key Words:相机标定、畸变、OpenCV
Beijing, 2020
作者:RaySue
Code:
前言
相机的畸变主要取决于镜头,短焦镜头的视野更广,但更容易产生畸变。而长焦镜头产生很少的畸变,但是视野却更小了,所以畸变的矫正有时候是一个相对折中的办法。
如果把工作距离(距离目标的高度)设置太高,那么精度就会下降,精度=分辨率/范围。
相机的内参、外参、畸变系数
-
6个外部参数(取决于相机在世界坐标系的位置)
- 3个旋转参数R
- 3个平移参数T
-
5个内部矩阵参数K:
- f f f - 焦距(单位mm)
- d x d_x dx - 1像素对应的宽(单位mm/pixel)
- d y d_y dy - 1像素对应的长(单位mm/pixel)
- u 0 u_0 u0, v 0 v_0 v0 - 相机主点(光学中心)
- 内部矩阵参数也可以用四个参数表示为:
- f x = f d x f_x = \frac{f}{d_x} fx=dxf - 焦距1
- f y = f d y f_y = \frac{f}{d_y} fy=dyf - 焦距2
- u 0 u_0 u0, v 0 v_0 v0 - 相机主点(光学中心)
-
5个畸变参数D:
- k 1 , k 2 , k 3 k_1,k_2,k_3 k1,k2,k3 - 径向畸变
- p 1 , p 2 p_1,p_2 p1,p2 - 切向畸变
准备工作
准备一个棋盘格图像,如下图,然后,指定程序中内角点的size,比如下图的内角点size是(7, 9)。对棋盘格子拍照的时候尽量多样一些,取15~20张图左右。在标定相机的时候,可以将事先知道的相机矩阵或畸变矩阵输入进函数calibrateCamera。

相机标定流程
相机的标定过程实际上就是在4个坐标系转化的过程中求出相机的内参和外参的过程。这4个坐标系分别是:世界坐标系(描述物体真实位置),相机坐标系(摄像头镜头中心),图像坐标系(图像传感器成像中心,图片中心,影布中心,单位mm),像素坐标系(图像左上角为原点,描述像素的位置,单位是多少行,多少列,单位pixel)。
-
世界坐标系 -> 相机坐标系:求解摄像头外参(旋转和平移矩阵);
-
相机坐标系 -> 图像坐标系:求解相机内参(摄像头矩阵和畸变系数);
-
图像坐标系 -> 像素坐标系:求解像素转化矩阵(可简单理解为原点从图片中心到左上角,单位mm变行列)
参考代码
import cv2
import glob
import pickle as pkl
import numpy as np
# 一个用于测试的畸变图像路径
distort_image_path = "/Users/Pictures/test.png"
# 用于存放采集的棋盘格子的数据,尽可能的保证距离不要有太大变化
calibration_images = glob.glob("/Users/Pictures/calibration_td/*.png")
distort_image = cv2.imread(distort_image_path)
# 指定棋盘格子内角点的个数
patternSize = (7, 9)
# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((patternSize[0] * patternSize[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:patternSize[0], 0:patternSize[1]].T.reshape(-1, 2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 真实世界的3维坐标
imgpoints = [] # 图像的二维平面
# 开始通过角点来计算图像的二维平面坐标
i = 0
for fname in calibration_images:
img = cv2.imread(fname)
print(img.shape)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the chess board corners
try:
ret, corners = cv2.findChessboardCorners(gray, patternSize, None)
except:
ret, corners = cv2.findChessboardCorners(gray, patternSize[::-1], None)
if ret: print(fname)
# If found, add object points, image points (after refining them)
if ret:
i += 1
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners)
# 把找到的角点画出来
cv2.drawChessboardCorners(img, patternSize, corners2, ret)
cv2.imshow('img', img)
# 指定存储路径
cv2.imwrite('/Users/surui/Pictures/calibration_show/' + str(i) + '.jpg', img)
# cv2.waitKey(50)
# cv2.destroyAllWindows()
# print(objpoints)
# print(imgpoints)
## 缓存中间结果
pkl.dump([objpoints, imgpoints], open("./calibration.pkl", "wb"))
objpoints, imgpoints = pkl.load(open("./calibration.pkl", "rb"))
print(gray.shape[::-1])
# 利用三维坐标和二维坐标来标定相机
retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# print(cameraMatrix)
# print(distCoeffs)
# 畸变图像矫正
h, w = distort_image.shape[:2]
print(h, w)
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, (w, h), 1, (w, h))
dst = cv2.undistort(distort_image, cameraMatrix, distCoeffs, None, newcameramtx)
x, y, w, h = roi
print(roi) # 如果roi都是0的话那么就把这行注释掉,不用ROI进行裁剪
dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult.png', dst)
# undistort
mapx, mapy = cv2.initUndistortRectifyMap(cameraMatrix, distCoeffs, None, newcameramtx, (w, h), 5)
dst = cv2.remap(distort_image, mapx, mapy, cv2.INTER_LINEAR)
# crop the image
x, y, w, h = roi # 如果roi都是0的话那么就把这行注释掉,不用ROI进行裁剪
dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult2.png', dst)
# 计算反向投影误差
mean_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
mean_error += error
print("total error: {}".format(mean_error / len(objpoints)))
效果
- 检测棋盘格角点结果

- 畸变前

- 矫正后

建议
- 尽量保证摄像头的工作距离变化不要太大(距离棋盘格子的距离)
- 尽量保证摄像头垂直于棋盘格子移动
- 采集棋盘格子数据20张左右
参考
[2]镜头畸变现象及其校正方法
[4]第35章:摄像头标定