OpenCV相机畸变校准

理论

相机包含了径向畸变和切向畸变。
径向畸变可以让实际中的直线在图像中弯曲,这种效应离图像中心越远越强烈。
径向畸变表示为

切向畸变来自于镜片与传感器之间的平行度误差,导致有一些区域图像看起来比实际要近。 切向畸变表示为
因此需要找到如下畸变系数

此外,我们还需要获取相机的内参和外参。内参包括了焦距(f_x, f_y)和光学中心(c_x, c_y),可用来创建一个相机矩阵。而相机矩阵也是消除一个相机畸变需要的。相机矩阵是相机固有的属性,一旦求得,可以复用到同一相机的所有图片。

外参对应了将3D点转换到一个坐标系的平移向量和旋转向量

通常在立体视觉应用中,校正镜头畸变是必须的事情。校正的原理是,提供一些完好定义的样本图片(例如,棋盘图、圆点图),已知其上特征点的真实相对坐标,也知道对应点在图像上的坐标,就可以计算出来畸变系数。至少提供10张样本图片以确保好的效果。

代码

相机校正需要的输入是一系列3D真实点坐标和对应的2D图像坐标。在图像中找到2D坐标没有任何问题。但真实3D点坐标有点难了。为了简化,认为棋盘格都是在XY平面固定的,这样Z全是0,事情好办了起来。
在代码中,3D点是object points, 2D点是image points

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import numpy as np
import cv2 as cv
import glob

criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)

objp = np.zeros((6*9, 3), np.float32)
objp[:,:2] = np.mgrid[0:9, 0:6].T.reshape(-1,2)

objpoints = [] # 真实世界的3D坐标
imgpoints = [] # 图像中的2D坐标

images = glob.glob('chessboard/*.jpg')

for fname in images:
img = cv.imread(fname)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# 寻找棋盘的角点
ret, corners = cv.findChessboardCorners(gray, (9,6),None)

# 如果找到了,加入目标点、图像点
if ret == True:
objpoints.append(objp)
corners2 = cv.cornerSubPix(gray, corners, (11,11),(-1,-1),criteria)
imgpoints.append(corners2)

cv.drawChessboardCorners(img,(9,6),corners2,ret)
cv.imshow('img', img)
cv.waitKey(500)

# 校正 返回:ret、相机矩阵、扭曲系数、旋转向量s、平移向量s
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

# 存储为npz文件,便于读取使用
np.savez('cameracalib',mtx=mtx, dist=dist, rvecs=rvecs, tvecs=tvecs)
calib_file = np.load('cameracalib.npz')
print(calib_file['mtx'])

img = cv.imread('chessboard/left12.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv.getOptimalNewCameraMatrix(calib_file['mtx'], calib_file['dist'], (w,h), 1, (w,h))

# 消除畸变
dst = cv.undistort(img,mtx, dist, None, newcameramtx)
x,y,w,h = roi
dst1 = dst[y:y+h, x:x+w]
cv.imshow('ds1',dst1)
cv.imwrite('calibresult.png', dst1)

# 另一种方法消除畸变
mapx, mapy = cv.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w,h), 5)
dst2 = cv.remap(img, mapx, mapy, cv.INTER_LINEAR)
dst2 = dst2[y:y+h, x:x+w]
cv.imshow('ds2',dst2)

cv.waitKey(0)
cv.destroyAllWindows()