4. 相机标定实战:OpenCV相机标定流程、calibrateCamera函数详解、标定结果评估(重投影误差)
好,咱们直接进入正题。前面讲了那么多理论,什么内参外参、畸变模型,说白了都是为了这一刻——用OpenCV把相机的“底牌”给翻出来。
我个人习惯把相机标定比作“给相机做体检”。你拿着体检报告,才知道它哪里“近视”、哪里“散光”。而OpenCV就是那个体检医生,我们只需要把数据喂给它就行。
4.1 OpenCV相机标定的完整流程
整个流程其实就五步,我当年第一次跑通时,觉得这玩意儿真没想象中那么玄乎。你跟着走一遍就明白了:
- 准备标定板——打印一张棋盘格,贴在硬纸板上。我建议用A4纸,别太小,不然角点检测容易翻车。
- 采集图像——从不同角度拍20-30张照片。注意:要让棋盘格出现在画面的各个角落,尤其是边缘。我见过有人只拍中间区域,结果标出来的畸变参数完全不对。
- 提取角点——用
findChessboardCorners找到棋盘格的内角点。嗯,这里有个坑:棋盘格尺寸是“内角点数”,比如9x6的棋盘,内角点是8x5,别搞反了。 - 执行标定——调用
calibrateCamera,一步到位。 - 评估结果——算重投影误差,看看标定质量。
核心逻辑图:下面这张图是我用SVG画的,把整个流程串起来了。你看一眼,心里就有谱了。
4.2 calibrateCamera函数详解
这个函数是整个标定的核心。说白了,它就是个“黑盒优化器”——你给它一堆2D-3D对应点,它给你算出相机参数。咱们来看看它的真面目:
retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera(
objectPoints, # 世界坐标系中的3D点
imagePoints, # 图像坐标系中的2D点
imageSize, # 图像尺寸 (width, height)
cameraMatrix, # 内参矩阵初始值(可传None)
distCoeffs, # 畸变系数初始值(可传None)
flags=None, # 标定选项
criteria=None # 迭代终止条件
)
我来拆解一下每个参数,都是实战中容易踩坑的地方:
| 参数 | 类型 | 说明 | 我的经验 |
|---|---|---|---|
| objectPoints | list of np.array | 每张图的3D点,Z=0 | 单位用毫米,别用像素 |
| imagePoints | list of np.array | 每张图的2D角点坐标 | 亚像素精度更好 |
| imageSize | tuple | (width, height) | 别传反了,是宽×高 |
| cameraMatrix | np.array (3x3) | 内参矩阵 | 传None让函数自己算 |
| distCoeffs | np.array (1x5/1x8) | 畸变系数 | 默认5参数就够了 |
| flags | int | 标定选项 | 一般用默认值 |
| criteria | tuple | (type, maxCount, epsilon) | 建议(3, 30, 1e-6) |
我的小技巧:objectPoints的生成别手写,用np.mgrid一步搞定。比如棋盘格内角点8x5,格子大小30mm:
objp = np.zeros((8*5, 3), np.float32)
objp[:, :2] = np.mgrid[0:8, 0:5].T.reshape(-1, 2) * 30.0
这样写,清晰又不容易出错。
4.3 标定结果评估:重投影误差
标定完了,怎么知道结果靠不靠谱?重投影误差就是那个“照妖镜”。
它的原理很简单:用标定出的参数,把3D点投影回图像上,算算跟实际检测到的2D点差了多少像素。这个差值越小,说明标定越准。
我一般这样算:
total_error = 0
for i in range(len(objectPoints)):
imgpoints2, _ = cv2.projectPoints(
objectPoints[i], rvecs[i], tvecs[i],
cameraMatrix, distCoeffs
)
error = cv2.norm(imagePoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
mean_error = total_error / len(objectPoints)
print(f"重投影误差: {mean_error:.4f} 像素")
误差多少算合格?我个人经验:
- < 0.1 像素:非常优秀,工业级精度
- 0.1 - 0.3 像素:良好,大多数场景够用
- 0.3 - 0.5 像素:及格,需要检查标定过程
- > 0.5 像素:不合格,重新标定吧
注意:我曾经遇到过一次,误差0.8像素,怎么调都降不下来。后来发现是标定板没贴平,有个角翘起来了。你想想看,3D点坐标都是假设在同一个平面上的,板子一翘,这个假设就破了,误差自然大。
4.4 实战中的避坑指南
做相机标定这么多年,我踩过的坑比走过的路还多。挑几个典型的说说:
- 图像数量不是越多越好。20-30张足够,关键是角度要丰富。我见过有人拍了100张,全是同一个角度,那跟拍1张没区别。
- 标定板要占画面1/4以上。太小了角点检测不准,太大了又拍不全。我一般控制在1/3左右。
- 注意光照均匀。反光、阴影都会影响角点检测。我习惯在漫射光下拍,或者用偏振片。
- 每张图都要检查角点。别偷懒,用
drawChessboardCorners画出来看看。有时候检测错了你都不知道。
嗯,说到这个我想起来,有一次在工厂现场,光照条件很差,角点检测老是失败。后来我干脆用LED补光灯从侧面打光,效果立竿见影。有时候解决问题的方法就是这么简单粗暴。
好了,相机标定这块就聊到这儿。你按照这个流程走一遍,基本不会出大问题。记住:标定是后面所有视觉算法的基础,这一步马虎不得。