3. 单相机内参标定:张正友标定法原理、棋盘格角点检测、内参矩阵与畸变系数
单相机内参标定,说白了就是搞清楚你的相机到底是怎么「看」世界的。每个镜头都有脾气——焦距多少、光心偏不偏、画面边缘有没有变形。这些参数不标定清楚,后面四个相机联合起来肯定乱套。
我刚开始做多相机系统时,觉得内参标定就是个例行公事,随便拍几张棋盘格跑一下就行。结果有一次,四个相机各自标定完,拼接出来的全景图在接缝处总是对不齐。折腾了两天,最后发现是其中一个相机的畸变系数没收敛好。嗯,从那以后我再也不敢小看这一步了。
3.1 张正友标定法:为什么它成了行业标准?
张正友标定法,核心思路其实很巧妙——你不需要知道棋盘格在空间中的精确位置,只需要它是个平面。你拿着棋盘格在相机前变换姿态,算法就能从多张图像中解出内参。
为什么这招管用?因为棋盘格上的角点坐标是已知的(比如每个格子30mm),它们在图像上的投影位置又可以被检测到。每张图像提供一个单应性矩阵,多个单应性矩阵联立,就能把内参约束出来。
核心优势:
- 只需要一个平面棋盘格,不需要精密的三维标定块
- 操作简单,手持棋盘格变换姿态即可
- 鲁棒性好,对噪声和畸变都有不错的容忍度
- 数学上封闭解+非线性优化,收敛稳定
我个人习惯用至少15-20张不同姿态的棋盘格图像。太少的话,内参容易过拟合,尤其是畸变系数。
3.2 棋盘格角点检测:从图像到亚像素坐标
角点检测是标定的第一步,也是最容易出问题的一步。你想想看,如果角点坐标本身就有偏差,后面算出来的内参能准吗?
OpenCV里用的是cv::findChessboardCorners(),它的大致流程是这样的:
- 图像预处理:转灰度、自适应阈值化,增强棋盘格对比度
- 四边形检测:找到图像中所有可能的四边形区域
- 棋盘格拓扑验证:检查四边形是否按棋盘格规律排列(黑白交替)
- 角点精化:用
cv::cornerSubPix()把角点坐标推到亚像素精度
这里有个坑——我曾经遇到过棋盘格反光太强,导致部分角点被高光淹没。检测出来的角点数量不对,程序直接报错。后来我加了一条经验规则:
避坑指南:
我曾经在强光环境下拍棋盘格,角点检测总是漏掉边缘的几个点。后来发现,把棋盘格贴在哑光板上,或者用偏振片滤掉反光,检测成功率能提升到95%以上。另外,棋盘格不要太小——在图像中至少占1/4画面,否则角点间距太近,亚像素精化效果会变差。
亚像素精化这一步很多人会忽略。实际上,cornerSubPix()用的是灰度梯度法,在角点附近拟合一个鞍点模型,精度能从像素级提升到0.1像素以内。我建议迭代次数设到30,终止精度设到0.001。
3.3 内参矩阵:相机是如何投影的?
内参矩阵K,就是描述相机内部几何结构的3x3矩阵。它长这样:
K = [fx, 0, cx
0, fy, cy
0, 0, 1]
其中:
- fx, fy:焦距,单位是像素。注意,它不是物理焦距(毫米),而是物理焦距除以像元尺寸。比如焦距12mm,像元大小3.45μm,那fx ≈ 12 / 0.00345 ≈ 3478像素
- cx, cy:光心坐标,理论上在图像正中心,但实际会有偏移
为什么fx和fy可能不一样?因为CMOS的像元不一定是正方形。我遇到过一些工业相机,像元是矩形,fx和fy能差出1-2%。如果不标定出来,后续的三维重建会引入系统性误差。
内参矩阵的作用,就是把相机坐标系下的三维点投影到像素平面。公式很简单:
s * [u, v, 1]^T = K * [X_c, Y_c, Z_c]^T
s是尺度因子,因为深度信息在投影过程中丢失了。
3.4 畸变系数:镜头不是完美的
没有镜头是完美的。畸变主要分两类:
| 畸变类型 | 产生原因 | 数学模型 | 典型表现 |
|---|---|---|---|
| 径向畸变 | 透镜曲面不完美 | k1, k2, k3 | 画面边缘直线变弯(桶形/枕形) |
| 切向畸变 | 镜头与CMOS不平行 | p1, p2 | 画面倾斜,像被拧了一下 |
OpenCV里常用的畸变模型是5参数模型:(k1, k2, p1, p2, k3)。k3一般只在鱼眼镜头或畸变很大的广角镜头里才需要。普通镜头用4参数(k1, k2, p1, p2)就够了。
注意:
畸变系数不是越大越好,也不是越小越好。我曾经标定过一个镜头,k1跑到了-0.45,看起来很大,但实际就是这个镜头的真实畸变。不要为了追求「好看」而限制畸变系数的范围。另外,如果标定出来的畸变系数符号和预期相反(比如桶形畸变应该是负k1,结果跑出正数),说明标定图像质量有问题,需要重新采集。
3.5 标定流程与质量评估
完整的单相机内参标定流程,我一般这么走:
- 采集图像:15-20张,覆盖画面各个区域,姿态包括俯仰、旋转、平移
- 角点检测:检查每张图的角点是否全部正确检出
- 初始估计:用封闭解计算内参和畸变的初值
- 非线性优化:用Levenberg-Marquardt算法最小化重投影误差
- 质量评估:检查重投影误差,一般应小于0.5像素
重投影误差是衡量标定质量的核心指标。它的含义是:把棋盘格上的三维角点,用标定出的内参和畸变系数投影回图像,和实际检测到的角点位置差多少。平均误差小于0.3像素算优秀,0.5像素以内可接受,超过1像素就要重新标定了。
我个人的经验阈值:
- 重投影误差 < 0.3 px:标定质量很好,可以用于高精度测量
- 重投影误差 0.3 - 0.5 px:标定质量合格,适合一般视觉应用
- 重投影误差 > 0.5 px:建议检查图像质量或增加标定图像
- 重投影误差 > 1.0 px:标定失败,需要重新采集数据
3.6 本章知识结构
下面这张图把单相机内参标定的核心逻辑串起来了。从棋盘格图像出发,经过角点检测、单应性矩阵求解、内参估计、畸变优化,最后得到完整的内参和畸变系数。每一步都有对应的OpenCV函数和注意事项。
3.7 实战中的几个关键细节
最后分享几个我在项目中踩过的坑,希望能帮你少走弯路:
- 棋盘格不要太小:在图像中至少占1/4画面,否则角点间距太近,亚像素精化效果差
- 姿态要丰富:不要只拍正对着相机的姿态,要多拍倾斜、旋转、靠近边缘的。我一般会拍一组「绕X轴转」、「绕Y轴转」、「前后移动」的组合
- 剔除坏图:如果某张图的角点检测明显不准(比如边缘角点偏移),果断删掉。10张好图比20张烂图强
- 检查重投影误差分布:不要只看平均值,要看每张图的误差。如果某张图误差特别大,说明那张图的姿态或角点有问题
一个小技巧:
标定完成后,我习惯用cv::undistort()把一张棋盘格图像去畸变,然后目视检查画面边缘的直线是否变直了。如果原来弯曲的棋盘格线变直了,说明畸变标定是有效的。这个方法虽然粗糙,但很直观,能快速发现明显的问题。