5 相机标定
到目前为止,本书的一切结果都以像素为单位:边缘在第 312.46 列,圆心偏了 0.8 个像素。但产线上的验收标准从来不写像素——图纸标注的是毫米,公差是 ±0.02 mm,机械手需要的是工件坐标系下的位置。相机标定(camera calibration)就是从”像素”到”毫米”的那座桥:它测出相机自身的成像参数,从此每一个像素坐标都能换算成有物理意义的几何量。没有标定,章节 21 的一切测量都只是像素游戏;而标定做得不好,再高明的算法也会把镜头畸变当成工件变形输出出去。
本章回答三个问题:真实相机的成像过程在数学上长什么样(针孔模型 + 内参 + 畸变);如何从几幅标定板图像反推出这些参数(张氏标定的原理与流程);以及最容易被忽略的一环——怎样验证标定结果是否可信。贯穿前四节的是一个”真值已知”的合成实验:我们亲手构造一台虚拟相机拍摄标定板(图 5.1),再用 SciVision 把参数标回来,与真值逐一对账——因为真实相机的内参无处查询,唯有合成真值才能逐项验证。最后一节则换到真实标定板上,把开篇”像素→毫米”之桥落到一块实拍圆点靶的九点坐标标定(小节 5.5)。
5.1 完整成像模型
章节 3 给出过针孔模型的直觉:空间点、光心、像点三点一线。用 章节 2 的齐次坐标把它写成矩阵形式,一个世界点 \((X,Y,Z)\) 投影到像素 \((u,v)\) 的过程是
\[ s\begin{bmatrix}u\\ v\\ 1\end{bmatrix} =\underbrace{\begin{bmatrix}f_x & 0 & c_x\\ 0 & f_y & c_y\\ 0 & 0 & 1\end{bmatrix}}_{K} \begin{bmatrix}R & \mathbf t\end{bmatrix} \begin{bmatrix}X\\ Y\\ Z\\ 1\end{bmatrix}. \]
右侧的 \([R\ \,\mathbf t]\) 是外参(extrinsics)——相机相对世界坐标系的位姿,每换一个机位它就变;左侧的 \(K\) 是内参(intrinsics)矩阵,只取决于相机与镜头本身。\(f_x,f_y\) 是以像素为单位的焦距(物理焦距除以像元的横、纵尺寸,方形像元时两者相等);\((c_x,c_y)\) 是主点(principal point),即光轴与传感器的交点,受装配公差影响通常并不在图像正中心。内参的意义可以一句话说尽:\(f_x\) 把”归一化坐标”(横向偏角的正切)放大成像素,\(c_x\) 再把原点挪到图像左上角——知道了 \(K\),像素坐标与光线方向就可以互相换算。
真实镜头还差最后一块拼图:畸变(distortion)。透镜组的放大率随视场角变化,导致像点沿径向偏离针孔模型的预测位置。Brown 径向模型用归一化坐标的多项式描述这一偏离:
\[ x_d = x_u\,(1+k_1 r^2 + k_2 r^4),\qquad y_d = y_u\,(1+k_1 r^2 + k_2 r^4),\qquad r^2=x_u^2+y_u^2, \]
其中 \((x_u,y_u)\) 是理想(无畸变)归一化坐标,\((x_d,y_d)\) 是实际成像位置。\(k_1<0\) 时放大率随视场递减,直线向外凸成桶形(barrel);\(k_1>0\) 时向内凹成枕形(pincushion)。图 5.2 用一幅规则网格图直观展示两者:施加本章虚拟相机的 \(k_1=-0.18,\ k_2=0.05\) 后,靠近边缘的”直线”明显弯曲。弯曲程度可以量化——在图像顶部一条水平网格线上取 61 个解析采样点做正交回归,畸变后各点到拟合直线的偏差最大 4.678 px、RMS 2.249 px。对照 章节 14 里 0.1 px 量级的亚像素定位精度,这是大了一个半数量级的系统误差:不校正畸变,亚像素就毫无意义。
完整的 Brown 模型还包含切向畸变项 \(p_1,p_2\),来源于透镜组与传感器装配的不严格平行。机器视觉镜头装配精度较高,切向分量通常比径向小一个量级以上,故本章实验只取径向前两阶 \(k_1,k_2\)——这也是大多数工业标定的默认配置。SciVision 的标定输出中同样保留了 \(p_1,p_2\) 的位置(见 小节 5.6)。
5.2 标定原理
标定要估计的未知量是 \(f_x,f_y,c_x,c_y\) 与 \(k_1,k_2\),外加每幅图像各自的外参。直接想法是拍一个三维已知结构反解,但三维标定物制造昂贵。张正友的方法 (Zhang 2000) 只用一块平面靶,把问题解开了,至今仍是工业标定的主流,其思路分三步。
第一步:每个位姿给出一个单应。平面靶上的点 \(Z=0\),代入投影方程后 \([R\ \,\mathbf t]\) 只剩三列,靶面到图像的映射退化为一个 3×3 的单应(homography)矩阵 \(H = K\,[\mathbf r_1\ \mathbf r_2\ \mathbf t]\)(相差一个尺度)。每幅图像用点对应即可解出自己的 \(H\)。
第二步:多位姿联立解内参初值。\(\mathbf r_1,\mathbf r_2\) 是旋转矩阵的列,必须单位正交——这给每个 \(H\) 施加了两个关于 \(K\) 的约束。内参有 4 个未知数,3 个不同位姿的靶面就足以闭式求解;位姿越多,最小二乘解越稳。
第三步:非线性精化。闭式解没有考虑畸变,只作初值。最终结果由非线性优化给出:把畸变系数与所有位姿外参一起放进参数向量,最小化重投影误差(reprojection error)——把世界点按当前参数投影回图像,与实测点位置之差:
\[ \text{RMSE}=\sqrt{\frac{1}{N}\sum_{i=1}^{M}\sum_{j=1}^{N_i}\bigl\|\mathbf m_{ij}-\hat{\mathbf m}(K,k_1,k_2,R_i,\mathbf t_i,\mathbf M_j)\bigr\|^2}, \]
其中 \(\mathbf m_{ij}\) 是第 \(i\) 幅图像中第 \(j\) 个靶点的实测像素坐标,\(\hat{\mathbf m}(\cdot)\) 是完整成像模型(含畸变)的预测,\(N\) 为总点数。这个量也是标定质量的首要指标:它直接回答”这套参数把成像过程解释到了什么程度”。
为什么标定板必须倾斜多个角度?想象靶面始终正对相机:把相机拉远一倍、焦距放大一倍,成的像完全相同——正对位姿无法区分 \(f\) 与距离 \(Z\),焦距方向上没有信息。倾斜位姿打破这种简并:靶面近端与远端的透视收缩量由 \(f\) 与倾角共同决定,焦距才变得可观测。这是可观测性(observability)直觉最朴素的例子:参数能不能标得准,取决于数据里有没有它的信息,而不是优化器够不够好。
5.3 黄金实验:已知真值的合成标定
真实标定有一个根本的尴尬:你永远不知道真值。标出 \(f_x=1226.3\),它离真实焦距差多少?没人能回答——相机的”真实内参”本身就无处查询。所以验证标定算法的金标准是合成实验:亲手构造一台参数已知的虚拟相机,让它”拍摄”标定板,再把标定算法当成黑盒喂进去,输出与真值逐项对账。这一手段不仅用于验证算法,也用于验证你对接口的理解是否正确——本章 小节 5.6 的两条工程发现都是靠它抓出来的。
虚拟相机的真值取 \(f_x=f_y=600\)(640×480 分辨率下水平视场约 56°)、\(c_x=326,\ c_y=243\)(主点刻意偏离图像中心)、\(k_1=-0.18,\ k_2=0.05\)(中等强度桶形畸变)。标定板为 9×7 圆点阵、间距 10 mm,摆出 4 个位姿:正对、绕 X 轴俯仰 20°、绕 Y 轴偏摆 −20°、复合姿态(−15°/15°/10°)。每个投影点上叠加标准差 0.1 px 的高斯噪声(固定随机种子保证可复现),模拟真实圆点中心提取的精度。4×63=252 个点对喂给 SciVision 的 CameraCalibrate,结果如下:
| 参数 | 真值 | 标定值 | 误差 |
|---|---|---|---|
| \(f_x\) | 600.0000 | 598.5707 | \(-1.4293\) |
| \(f_y\) | 600.0000 | 598.5468 | \(-1.4532\) |
| \(c_x\) | 326.0000 | 325.5661 | \(-0.4339\) |
| \(c_y\) | 243.0000 | 243.2207 | \(+0.2207\) |
| \(k_1\) | \(-0.1800\) | \(-0.1832\) | \(-0.0032\) |
| \(k_2\) | \(0.0500\) | \(0.0663\) | \(+0.0163\) |
逐项读这张表。焦距误差约 1.43 px,相对误差 0.24%——对绝大多数测量应用绰绰有余;主点误差不到半个像素。重投影 RMSE 为 0.1384 px,而提点噪声的理论底线是 \(\sqrt2\times0.1\approx0.1414\) px:拟合残差已经贴到噪声水平,说明模型把数据中的系统性信息榨干了,剩下的全是我们自己注入的随机噪声。
噪声底为什么是 \(\sqrt2\,\sigma\)?重投影误差统计的是二维残差向量的模,\(u,v\) 两个方向各贡献方差 \(\sigma^2\),模的均方根即 \(\sqrt{2}\,\sigma\)。RMSE 略低于这个值(0.1384 < 0.1414)并不奇怪:模型有几十个自由参数,会顺带吸收掉一小部分随机噪声——这是”拟合到噪声水平”的正常表现,若显著低于噪声底反而要警惕过拟合。
唯一不太体面的是 \(k_2\):误差 0.0163,相对真值偏了 33%。这不是算法的失误,而是数据的局限——\(k_2\) 乘在 \(r^4\) 上,只有视场最边缘的点对它有发言权,而本实验的靶面投影没有铺满整个画幅角落,\(k_2\) 处于弱可观测状态:很多组 \((k_1,k_2)\) 的组合在已有数据覆盖的 \(r\) 范围内几乎等效。注意重投影 RMSE 并没有因此变差——在数据覆盖的区域内,这套略偏的参数与真值的预测无法区分。这件事的教学价值超过实验本身:参数的可观测性取决于靶面覆盖与倾角多样性,想标得准 \(k_2\),就要让靶点铺到图像四角。也因此,评价一次标定的好坏,应当看重投影 RMSE 与多次复标的一致性,而不是轻信参数数值本身——真实标定中你既没有真值表可对,参数又可能在弱可观测方向上随意漂移。
5.4 畸变校正
参数到手,下一步是把畸变从图像中除掉。校正图像的正确姿势是逆映射(inverse mapping):对输出(无畸变)图像的每个像素 \((u,v)\),用正向畸变模型算出它在原始畸变图像中的来源位置 \(F(u,v)\),再用 章节 2 介绍的双线性插值取出灰度。方向不能反过来——若从输入像素正向”散射”到输出,落点不在整数格上,输出图会留下空洞。逆映射保证输出每个像素恰好被赋值一次,代价是每像素一次模型求值加一次插值,完全可以预先算成查找表。
用纯模型先验证实现:对桶形畸变网格图做手写的逆映射 + 双线性插值校正,顶部网格线的正交偏差从 max 4.678 px 降到 0.000000 px(解析采样点、无噪声,机器精度归零)——证明逆映射与插值的实现严格正确。图 5.3 (a) 是校正后的图像,弯曲的网格线重新拉直。
SciVision 走的是工程化路线。对正对位姿的 63 个点对调用单平面畸变标定 DistortionCalibrate,返回 Rms = 0.1493;其输出的标定矩阵随后有两个用途。其一是点校正:用 PointProject 把畸变像素坐标变换为校正后像素坐标,标定板最上一行 9 个点的共线性偏差从校正前的 1.635 px 降到 0.121 px——已贴近 0.1 px 的提点噪声水平,畸变的系统性弯曲被基本拔除;把全部 63 个校正点对世界网格做仿射拟合,残差 RMSE 仅 0.0329 mm、最大 0.0782 mm,说明校正后的像面与靶面只差一个线性变换,像素从此可以按比例换算成毫米。其二是图像校正:DistortionCorrection 直接输出去畸变图像(图 5.3 (b)),与手写实现的结果在视觉上一致。
5.5 真实标定板:九点坐标标定
前面四节用一台真值已知的虚拟相机做内参与畸变标定——这是刻意的:真实相机的内参无处查询,唯有合成真值才能逐项对账(小节 5.3)。但本章开篇的”像素→毫米”之桥还有另一条更直接、且能在真实标定板上完整跑通的实现路径——九点坐标标定。它不估计内参,而是直接用一块平面靶上的若干已知点,拟合”像素坐标 → 靶面坐标系”的平面变换,是机器人手眼对位中最常用的标定形式(SDK 手册 4.2)。
本节样图取自 Smart3 自带的”九点标定”示例方案:一块 \(1571\times1053\) 灰度图上的 3×3 暗圆点靶(图 5.4)。流程分两步。提点:先试 SDK 的圆点板自动找点 FindImageCorners(gridType=1),但它在这种只有 9 个点的稀疏靶上返回 122101001(找点失败)——自动找点为密集棋盘/圆点阵设计,点太少便退化;于是改用确定性的暗斑连通域质心提点,稳定得到 9 个圆心,平均格距 236.2 px(行 236.19、列 236.23,说明靶面近乎正对、几无透视)。标定:把 9 个像素质心与靶面单位网格坐标 \((-1,1)\dots(1,-1)\)(每格 1 个世界单位)一并喂给 PointCalibrate,得到 \(3\times3\) 平面标定矩阵
\[ M=\begin{bmatrix}0.004233 & \approx\!0 & -3.0255\\[2pt] \approx\!0 & -0.004234 & 2.3963\\[2pt] 0 & 0 & 1\end{bmatrix}, \]
像素尺度 \(s_x=0.0042332,\ s_y=0.0042338\) 世界单位/像素(即 236.2 px 对应 1 个格距)。把 9 点经 \(M\) 投影回靶面坐标,拟合残差 RMS 仅 0.000106 世界单位(折合约 0.025 px)、最大点偏差 0.000147 世界单位(约 0.035 px)。这块靶是软件渲染的理想点阵、无镜头畸变,残差贴到亚像素提点精度本在意料之中——它的价值在于演示真实图像上”像素 → 坐标系”的完整闭环。
更有说服力的是交叉验证:该示例方案随附了 SDK 图形界面当初算出的标定解(calib_data.xml),其矩阵为 \(a_{11}=0.004233,\ a_{22}=-0.004236,\ t_x=-3.027629,\ t_y=2.398271\)、尺度 \((0.0042328,0.00423579)\)。我们用代码独立提点、独立标定,线性系数与尺度与之一致到 \(10^{-6}\) 量级,平移项因提点方法不同(界面把圆心取整到半像素)相差约 0.002 个世界单位——自己写的流程跑出了与商用界面一致的结果,这是把接口理解落到实处的又一种验证。
CalibImage/0.bmp)。灰色十字标出手写质心提取的 9 个圆心;这 9 点经 PointCalibrate 拟合出像素 → 靶面坐标系的平面变换,复现了示例方案随附的 SDK 界面标定解。
为什么内参/畸变标定保持合成、坐标标定却用真实板?两者的验收方式根本不同:内参标定的金标准是与真值对账,而真实相机的真值不可知,故必须合成(小节 5.3);九点坐标标定的产出只是一条”像素 → 坐标系”的平面变换,其质量用拟合残差与复现已知解即可验收,不依赖相机真值——因此可以、也应当在真实靶面上跑通。本章由此一分为二:可观测性与精度分析在合成侧,真实图像的提点与坐标标定在真实侧。
5.6 SciVision 实现
多位姿内参标定由 SCIMV::SciSvCalibration::CameraCalibrate 完成,对应 小节 5.2 的张氏流程。各位姿的点对应依次串接成两个数组,再用一个索引数组声明每幅视图的点数:
SCIMV::SciSvCalibration calib;
SciPointArray allImg, allObj; // 4 个位姿的图像点/世界点依次串接
SciVarArray ptsIndex; // 每个位姿的点数(本例为 4 个 63)
for (int p = 0; p < 4; ++p) {
for (size_t k = 0; k < wX.size(); ++k) {
SciPoint ip(imU[p][k], imV[p][k]), op(wX[k], wY[k]);
allImg.Append(ip); allObj.Append(op);
}
SciVar n((long)wX.size());
ptsIndex.Append(n);
}
SciMatrix camMat;
double rmsC = -1;
long rc = calib.CameraCalibrate(allObj, allImg, ptsIndex,
/*W*/ 640, /*H*/ 480, &camMat, &rmsC);allObj 是靶面坐标(mm,平面靶 \(Z=0\) 故只填二维点),allImg 是对应的像素坐标,ptsIndex 把扁平数组切分回各个视图,图像宽高用于内参初值,输出为参数矩阵 camMat 与重投影 RMSE。表 5.1 的全部数字即来自这一调用。
这套接口在使用中暴露了两个文档问题,原样记录如下——它们正是工程师对接商用 SDK 时会真实遇到的情形。其一,cameraParam 矩阵的元素布局头文件没有注明。我们靠合成实验的真值反推出实际布局:第 0 行为 \([\,\_,\ f_x,\ c_x,\ f_y,\ c_y\,]\),第 1 行为 \([\,k_1,\ k_2,\ p_1,\ p_2,\ \_\,]\)——若没有真值对照,把 \(f_y\) 与 \(c_x\) 读反也未必能立即察觉。其二,PointProject 的行为与头文件描述不符。文档称其返回世界坐标,但配合畸变标定矩阵使用时,实测返回的是校正后的像素坐标。最初按文档理解把输出直接与世界网格(mm)相减,得到 427 mm 的荒谬”残差”;溯源确认输出量纲后,才重新设计为 小节 5.4 中”校正点对世界网格做仿射拟合”的验证方案。两条发现指向同一个工程习惯:对接口的理解必须用已知答案的数据验证过,才能用在未知答案的产线上——这正是黄金实验的另一重价值。
小节 5.5 的真实板坐标标定走的是同一模块的另一接口 PointCalibrate(imagePoint, worldPoint, type, …):输入 \(N\!\ge\!4\) 对像素点与靶面点,输出 \(3\times3\) 平面变换矩阵、像素尺度 \(s_x,s_y\) 与拟合残差,type=0/1/2 分别对应平面/倾斜/相似(Helmert)标定;自动找点失败时用确定性质心提点即可补上输入端。SciSvCalibration 模块还覆盖贴合/对位与手眼标定等延伸场景(SDK 手册 4.3):把相机坐标系与运动平台坐标系关联起来,标定结果才能驱动机械手。这些内容与 章节 30 的三维测量同属”标定链”的下游,此处不展开。本章全部实验图像与数字的可运行工程位于 code/camera_calibration/。
工业案例:标定为什么会”过期”
某结构件测量工位投产时验收顺利,三个月后抽检发现 0.03 mm 的系统性偏差。排查结论:产线温升使镜筒热胀、焦面漂移,等效焦距改变;输送线振动又让相机支架微微转动——内参与外参双双”过期”,而程序一行未改。对策分两层:一是定期复标,按季度或大修节点重做完整标定;二是成本低得多的日常校验——在视野内固定一块尺寸已知的标准件,每班测一次,超出阈值才触发复标。验证远比标定便宜:一次测量对一个数,就能回答”标定还可不可信”。高精度场合应把这项校验写进设备点检表——标定参数不是软件配置,而是会随温度与振动缓慢漂移的物理测量结果。
5.7 小结
- 完整成像模型 = 针孔 + 内参 + 畸变:内参矩阵把光线方向换算成像素,Brown 径向模型 \(x_d=x_u(1+k_1r^2+k_2r^4)\) 描述真实镜头对针孔的偏离。本章 \(k_1=-0.18\) 的桶形畸变让直线弯出 4.678 px——不校正畸变,亚像素定位毫无意义。
- 张氏标定的流程是”单应 → 闭式初值 → 非线性精化”:平面靶每个位姿贡献一个单应、两个内参约束,多位姿联立解初值,最终最小化重投影误差。靶面必须倾斜多角度——正对位姿无法区分焦距与距离。
- 合成黄金实验是验证标定的金标准:真值已知才能逐项对账。本章实验焦距误差 0.24%,重投影 RMSE 0.1384 px 贴住 \(\sqrt2\sigma=0.1414\) px 的噪声底——信息被榨干;\(k_2\) 误差偏大则暴露了靶面覆盖不足导致的弱可观测性。
- 评价标定看重投影 RMSE 与复测一致性,而不是参数数值本身:真实标定没有真值可对,弱可观测方向上的参数还会漂移;图像校正用逆映射 + 双线性插值,校正质量可用共线性(1.635 → 0.121 px)等几何不变量量化验收。
- 接口理解也要过黄金实验:参数矩阵布局、
PointProject的输出量纲都是靠真值对照才确认的——文档与实现不符时,已知答案的数据是唯一的仲裁者。 - 真实标定板上跑通的是九点坐标标定:对 Smart3 实拍 3×3 圆点靶手写质心提点(自动找点在 9 点稀疏靶上失败)后用
PointCalibrate拟合像素→靶面平面变换,残差约 0.025 px,且线性系数与尺度复现了示例随附的 SDK 界面解到 \(10^{-6}\) 量级——内参标定靠合成真值验证,坐标标定靠真实数据与复现已知解验证,各取所需。
相机标定(含更完整的畸变模型、不确定度分析与三维标定)的系统论述可进一步阅读 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018);张氏标定的原始论文 (Zhang 2000) 本身也十分易读。畸变与内参的现代处理多可上溯到两篇经典:Brown (Brown 1971) 在近景摄影测量中给出了至今仍在沿用的径向/切向畸变模型,本章的 \(k_1,k_2,p_1,p_2\) 即源于此;Tsai (Tsai 1987) 则提出基于单幅靶标图、面向高精度三维度量的两步标定法,是张氏方法之前工业界的主流方案。本章把相机坐标系与运动平台关联的延伸场景(SDK 手册 4.3)正属手眼标定,其奠基性算法见 Tsai 与 Lenz (Tsai 和 Lenz 1989)——通过机器人多个已知位姿下的成像求解相机相对末端执行器的刚体变换。






