3. 相机标定基础:标定板设计、张正友标定法原理、OpenCV标定流程、畸变校正
相机标定,说白了就是给相机做一次「视力矫正」。你想想看,相机镜头不是完美的,它拍出来的图像会有各种变形。如果不做标定,你后面做的立体匹配、三维重建,全都会跑偏。
我个人习惯把标定看作是整个立体匹配流程的「地基」。地基没打好,楼盖得再高也是危房。今天我们就来聊聊这个地基怎么打。
3.1 标定板设计:棋盘格与圆点格
标定板是标定的「尺子」。你得有一把已知尺寸的尺子,才能去量相机的参数。
常见的标定板有两种:
- 棋盘格标定板:黑白相间的方格,角点检测简单、稳定。我项目中90%的情况都用它。
- 圆点标定板:圆形图案,圆心定位精度更高,适合高精度标定。
设计要点:
- 棋盘格的行列数最好一奇一偶(比如9×6),这样角点方向唯一
- 方格边长要精确测量,误差控制在0.01mm以内
- 标定板要平整,贴在硬质板上,别用软纸
我曾经遇到过一个问题:用A4纸打印的标定板,贴在不平的桌面上标定,结果重投影误差一直下不去。后来换了亚克力板,误差直接降了一个数量级。嗯,细节决定成败。
3.2 张正友标定法原理
张正友标定法,是1998年张正友教授提出的。这个方法牛在哪里?它只需要你拿着标定板在相机前摆几个不同姿态,拍几张照片,就能算出相机参数。
核心思想其实不复杂:
- 建立对应关系:标定板上的3D点(世界坐标系)和图像上的2D点(像素坐标系)一一对应
- 求解单应矩阵:每张照片都有一个单应矩阵H,它描述了3D平面到2D平面的映射
- 分解内参:利用多个单应矩阵的约束,解出相机内参矩阵K
- 优化:用非线性最小二乘法(比如Levenberg-Marquardt)做全局优化
我的经验:张正友法对标定板姿态有要求。不要只拍正对着相机的照片,要拍倾斜的、旋转的、不同距离的。我一般拍15-20张,覆盖视野的各个区域。
为什么会这样?因为如果所有照片都是正面拍摄,单应矩阵的约束不够,内参解出来会不稳定。你想想看,只从一个角度看一个物体,你怎么知道它的真实形状?
3.3 OpenCV标定流程
OpenCV把张正友法封装得很好,用起来很方便。我直接上代码,你跟着跑一遍就能出结果。
import cv2
import numpy as np
import glob
# 1. 准备标定板参数
chessboard_size = (9, 6) # 内角点数量
square_size = 25.0 # 方格边长(mm)
# 2. 生成世界坐标系中的3D点
objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2)
objp *= square_size
# 3. 存储所有图像的点
objpoints = [] # 3D点
imgpoints = [] # 2D点
images = glob.glob('calib_images/*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 4. 查找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
if ret:
objpoints.append(objp)
# 5. 亚像素级角点优化
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners2)
# 6. 可视化角点
cv2.drawChessboardCorners(img, chessboard_size, corners2, ret)
cv2.imshow('Corners', img)
cv2.waitKey(100)
cv2.destroyAllWindows()
# 7. 执行标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
print("重投影误差:", ret)
注意:findChessboardCorners 有时会找不到角点。我建议你检查一下:
- 标定板是否完整出现在画面中
- 光照是否均匀,不要有强反光
- 棋盘格尺寸参数是否和实际一致
3.4 畸变校正
畸变校正,就是把拍歪了的图像「掰直」。常见的畸变有两种:
| 畸变类型 | 产生原因 | 视觉效果 |
|---|---|---|
| 径向畸变 | 镜头曲率不完美 | 图像边缘向内或向外弯曲(桶形/枕形) |
| 切向畸变 | 镜头与成像平面不平行 | 图像像被拉伸或挤压 |
OpenCV里校正畸变很简单,就两行代码:
# 获取校正后的映射表
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]
这里有个细节:getOptimalNewCameraMatrix 的 alpha 参数控制是否保留所有像素。alpha=1 保留全部(会有黑边),alpha=0 只保留有效区域(会损失边缘信息)。我一般设成0.5,折中一下。
避坑指南:我曾经在标定后直接用了原始内参矩阵去校正,结果图像边缘出现了奇怪的扭曲。后来发现,getOptimalNewCameraMatrix 返回的 newcameramtx 才是校正后的内参,别搞混了。
3.5 知识体系总览
下面这张图,是我自己整理的相机标定知识体系。你可以把它当作一个「地图」,随时回来看看自己走到哪了。
这张图把整个标定流程串起来了。从标定板设计开始,到张正友法的原理,再到OpenCV的具体实现,最后落到畸变校正。每一步都有坑,但每一步也都有解法。
好了,相机标定这块就聊到这儿。你回去把代码跑一遍,看看自己的相机内参是多少。我保证,当你看到重投影误差小于0.5像素时,那种成就感是很爽的。