简介:这份资源是 Halcon 官方参考手册第六章 Calibration 的英文直译中文版,无删减,面向从事计算机视觉、视觉检测算法与相机标定的工程师及学习者,用于系统理解相机模型、畸变补偿与标定流程。内容围绕标定对象描述与查找、3D 点投影与反投影、角度与径向畸变校正、相机内外参处理、自校准,以及单目、双目、多视图和手眼标定等设置展开,并给出校准数据模型创建、calibrate_cameras 实际标定、RMSE 结果检查与相机参数说明。资源包共 1 个 PDF 文件,约 813KB,便于检索与离线查阅。已有 363 人学习下载,适合需要对照官方手册深入掌握 Halcon 标定模块、补齐参数与流程细节的读者参考。
1. 标定:为什么“像素误差 0.1”是视觉检测的分水岭
很多人第一次跑完calibrate_cameras,看到返回的 RMSE 是 0.08 像素,就以为万事大吉;也有人拿到 0.9 像素,觉得“差不多能用”。这两种反应都说明一件事:没搞懂标定到底在拟合什么。相机标定的本质,是求一组参数,让 3D 世界点经过“刚体变换 → 透视/远心投影 → 镜头畸变 → 像素栅格化”这条链路后,落在图像上的位置与真实提取到的标记中心尽可能重合。RMSE 就是这条链路末端残差的均方根,单位是像素。0.1 像素左右,基本等于标记中心提取算法本身的检测噪声下限——也就是说,模型已经拟合到“观测数据能提供的精度极限”,再往下压没有意义。远高于 0.1,说明模型和观测对不上,可能是图像质量、姿态覆盖、标定板精度出了问题。搞懂这个数字从哪来、为什么是这个量级,比记住“要小于 0.1”有用得多。这份 Halcon 第六章的直译文档,价值就在于把这条链路的每一环——标定物、数据模型、参数含义、畸变模型、3D 相机模型——拆开讲清楚了,而不是只丢给你一个算子列表。
2. 标定数据模型:为什么不能直接调 calibrate_cameras
2.1 标定不是一次函数调用,而是一个状态机
刚上手的人容易把calibrate_cameras当成一个“输入图像、输出参数”的黑匣子。实际上 Halcon 的设计是:先建一个标定数据模型(CalibDataID),把相机数量、标定物描述、每张图的观测点、初始参数全部塞进去,最后才触发优化。这个设计不是多此一举,而是因为标定本质是一个带约束的非线性最小二乘问题,优化器需要知道哪些参数参与优化、哪些固定、初值是多少。数据模型就是这个问题的完整状态容器。
文档里给出的五步流程,对应的是状态机的五个阶段:
# 伪代码:对应文档 6.3 的五步,用 Python 风格表达调用顺序 calib_data = create_calib_data('calibration_object', num_cameras, num_calib_objects) # 1. 声明有几台相机、几块标定板 set_calib_data_cam_param(calib_data, 0, 'area_scan_division', [0.008, 0.0, 3.45e-6, 3.45e-6, 640, 512, 1280, 1024]) # 2. 相机 0 的类型 + 初始内参:焦距、Kappa、Sx、Sy、Cx、Cy、宽、高 set_calib_data_calib_object(calib_data, 0, calib_obj_handle) # 3. 把标定板描述(标记的 3D 坐标)挂到模型上 find_calib_object(image, calib_data, 0, 0, 0, [], []) # 4. 每张图调用一次:提取标记、估计粗略姿态、写入观测数据 set_calib_data(calib_data, 'camera', 0, 'excluded_settings', ['sx', 'sy']) # 5. 如果传感器像元尺寸已知,把 sx/sy 排除出优化 calibrate_cameras(calib_data) # 6. 真正执行优化注意第 5 步的excluded_settings。这是整个流程里最容易被忽略、却最能体现设计意图的一环。文档举的例子是:如果相机 0 的像元尺寸精确已知,就排除sx、sy。为什么?因为针孔模型里焦距和像元尺寸是尺度耦合的——把焦距放大两倍、像元尺寸也放大两倍,投影结果完全一样。数学上这叫不可辨识。Halcon 的默认策略是固定Sy(文档里带星号的参数),只优化Sx,靠传感器行列像元通常一致这个物理先验来打破简并。如果你连Sx都精确知道,那就该显式排除,让优化器少一个自由度,结果更稳。这就是“为什么不能直接调”的答案:优化器需要你告诉它问题的约束结构,而不是让它在一个病态问题里瞎猜。
2.2 观测数据从哪来:find_calib_object 与 set_calib_data_observ_points 的分工
文档提到两条收集观测的路径:find_calib_object和set_calib_data_observ_points。前者是“全自动”:输入图像,它负责找标定板、提取标记中心、估计标定板相对相机的粗略姿态,一并写入数据模型。后者是“手动挡”:如果你用自己的特征提取算法拿到了标记的图像坐标,或者标定物不是标准板,就用它把坐标和粗略姿态直接塞进去。
这个分工背后是一个现实约束:标定板检测本身也会失败。六边形排列的板子要求至少一个 finder 图案可见,矩形排列的板子要求整板可见——文档在 6.7.2 里写得很清楚。如果某张图光照不均、板子被遮挡一角,find_calib_object可能直接报错。这时候要么弃掉这张图,要么用set_calib_data_observ_points手工补。我一般会这么理解:find_calib_object是给标准流程用的,set_calib_data_observ_points是给“标定物不是 Halcon 标准板”或“检测算法要自己控制”的场景留的后门。文档 6.7.1 也说了,可以用任意物体标定,唯一要求是特征点能在图像里鲁棒检测、且 3D 世界坐标高精度已知——这种场景下,观测点只能自己喂。
2.3 初始参数为什么重要:非线性优化的局部极小陷阱
set_calib_data_cam_param要求你给初始内参,find_calib_object会估粗略外参。很多人随手填个焦距标称值就完事,觉得优化器会自己收敛。大多数时候确实会,但文档在 6.6.1 里对线扫描相机的运动矢量 Vx、Vy、Vz 专门警告了一句:“初始值的质量对整个校准的成功至关重要。如果它们不够精确,校准可能会失败。”这句话对区域扫描相机同样成立,只是没那么极端。
原因是calibrate_cameras用的是迭代非线性优化(常见实现是 Levenberg-Marquardt 一类),它只能保证收敛到局部极小。初值离真值太远,或者畸变系数初值给得离谱,优化器可能卡在一个“看起来残差还行、但参数物理上荒谬”的点上。文档建议 Kappa 初值用 0.0、K1~P2 全用 0.0,就是因为零畸变是一个安全的起点——真实畸变通常不大,从零出发线性化近似仍然有效。焦距初值用标称值、主点初值用图像半宽半高,都是同一个逻辑:用物理先验把初值放在真值附近,让优化器只需要做局部修正。这是踩过坑才懂的:曾经有人把焦距初值填成毫米和米搞混,优化跑完 RMSE 看着不大,但重投影到远距离物体上偏了几十像素——局部极小的典型翻车。
3. 相机参数体系:星号背后的不可辨识性
3.1 内参列表不是随便排的:每一类相机对应一个投影模型
文档 6.6.1 把区域扫描相机分成普通镜头、倾斜镜头、超中心镜头,每类又分 division 和 polynomial 两种畸变模型,参数元组长度从 9 到 16 不等。这个列表乍看繁琐,其实每一项都对应投影链路里的一个具体环节:
| 参数 | 作用环节 | 是否常被固定 |
|---|---|---|
| Focus | 透视投影的焦距 | 与 Sx/Sy 耦合,视情况 |
| Magnification | 远心投影的放大倍率 | 与 Sx/Sy 耦合,视情况 |
| Kappa | division 模型的径向畸变 | 可优化 |
| K1,K2,K3,P1,P2 | polynomial 模型的径向+离心畸变 | 可优化 |
| Sx, Sy | 传感器像元间距 | Sy 默认固定 |
| Cx, Cy | 主点(畸变中心) | 可优化 |
| ImageWidth/Height | 图像尺寸 | 固定 |
| ImagePlaneDist, Tilt, Rot | 倾斜镜头几何 | 视镜头类型 |
带星号的Sy是文档反复强调的固定项。原因在 6.6.1 的注释里:射影相机用针孔模型描述时,焦距、Sx、Sy 三者不能同时确定,算法保持 Sy 不变;远心镜头同理,放大倍率、Sx、Sy 不能同时确定。倾斜镜头更极端——像侧远心的倾斜镜头,连 Sx 和 Tilt、Rot 都不能同时确定,所以 Sx 也被固定。这些“不能同时确定”不是实现偷懒,而是模型本身的自由度简并:多个参数组合产生相同的投影,观测数据无法区分它们。Halcon 的选择是固定物理上最可能有先验的那个(像元尺寸通常由传感器规格书给出),把不确定性留给真正需要标定的量。
3.2 外参:世界坐标系到底绑在哪
外参是 6 个量:TransX/Y/Z 加 RotX/Y/Z,描述世界坐标系相对相机坐标系的位姿。文档特别点明:用标准 Halcon 标定板时,世界坐标系由标定板的坐标系定义——六边形板的原点在第一个 finder 图案中心标记的中心,矩形板的原点在板面中间,z 轴指向板外,x 向右,y 向下。这个约定直接决定了你后续测量的坐标含义。
这里有个容易混淆的点:外参是“每张图一组”。因为标定板在每张图里的位姿不同,calibrate_cameras会为每张图优化出一个外参,同时所有图共享同一组内参。这就是为什么标定需要多张不同姿态的图——单张图只能约束内参和外参的某种组合,多张图从不同角度观测同一块板,才能把内参从外参里“解耦”出来。文档 6.9 的警告正是这个逻辑的极端情况:如果所有图里标定板都平行(尤其都在同一平面),六个外参和焦距就无法一起确定——因为平面标定板提供的约束退化了。所以“倾斜在不同方向”不是建议,是数学上的必要条件。
3.3 单位约定:米、像素、米每行
文档 6.6.3 说标定板以米为单位,相机参数用相应单位。这意味着 Focus 是米、Sx/Sy 是米、TransX/Y/Z 是米,而 Cx/Cy 和 RMSE 是像素。线扫描相机的运动矢量 V 单位是“米/行”,这个细节很关键:它把传送带速度、行频、物理尺度绑在一起。单位搞混是标定翻车的经典原因——焦距填成毫米、平移填成米,优化器要么不收敛,要么收敛到一个尺度错误的解,RMSE 可能还过得去,但实际测量全错。我一般会在建数据模型前把所有量统一到米和像素两套单位里,写死在注释里,避免中途混用。
4. 畸变模型与 3D 投影链路:division 和 polynomial 怎么选
4.1 投影链路的五个变换步骤
文档 6.8.4 把 3D 世界点到 2D 像素的转换拆成:世界→相机(刚体变换)、相机→像平面(透视或远心投影)、镜头畸变、倾斜像面变换(仅倾斜镜头)、像平面→像素。这个拆法不是教学装饰,而是直接对应代码里的计算顺序。以区域扫描针孔相机 + division 模型为例,核心两步是:
# 步骤 2:相机坐标投影到像平面(针孔,归一化) u = xc / zc v = yc / zc # 单位仍是米制像平面坐标,未加焦距缩放 # 步骤 3:division 模型去畸变(解析可逆) r2 = u*u + v*v factor = 1.0 / (1.0 + Kappa * r2) u_undist = u * factor v_undist = v * factor # 注意:这是“畸变→无畸变”方向,文档给出的是可解析反转的公式division 模型只有一个参数 Kappa,公式简单到可以手算,而且解析可逆——这意味着从无畸变坐标反算畸变坐标不需要迭代。文档 6.7.3 明确说这是 division 的主要优势:逆畸变快,尤其在把世界坐标投影到图像平面时。polynomial 模型用五个参数(K1,K2,K3 径向 + P1,P2 离心),拟合能力更强,但公式不能解析反转,逆畸变必须数值迭代,慢。所以文档的建议是:默认用 division,精度不够再上 polynomial,但上 polynomial 就要保证标定板覆盖区域更充分——因为高阶项在覆盖不足的区域会外推得离谱,图像边界和未覆盖区域可能被“模拟”出根本不存在的畸变。
4.2 倾斜镜头:为什么多出一步射影变换
倾斜镜头(Scheimpflug 结构)的光轴不垂直于传感器平面,所以像平面是斜的。文档把倾斜镜头再细分成“像侧透视投影”和“像侧平行投影”两类:前者用 3×3 齐次矩阵 H 做射影变换,后者用 2×2 线性矩阵。这一步在普通镜头标定里不存在,参数 Tilt、Rot、ImagePlaneDist 就是为它准备的。Tilt 是光轴相对传感器法线的倾角(0°到 90°),Rot 是倾斜方向绕光轴的旋转角。文档 6.9 有个很实在的提醒:Tilt 透镜的畸变越大,Tilt 越能准确确定;畸变小的透镜,Tilt 本身不可辨识,优化出来的 Tilt 可能和标称值差很远。这时候别慌,看 RMSE——如果误差小,说明这组参数在标定体积内能一致描述成像几何,测量仍然可用。这是“参数物理意义”和“参数数值”分离的典型例子,也是很多人看到 Tilt 偏离标称值就以为标定失败的原因。
4.3 线扫描相机:运动矢量把时间维塞进了空间模型
线扫描相机的特殊性在于:每条图像线是在不同时刻、不同相机位置拍的。文档的建模假设是相机匀速直线运动、方向恒定、所有图像运动相同,运动矢量 V=(Vx,Vy,Vz) 单位米/行。相机坐标系定义成 y 轴垂直于传感器线且运动矢量有正 y 分量,x、y、z 构成右手系。关键简化是:所有世界↔相机的变换只基于第一条图像线的姿态,运动矢量在投影时补偿。这解释了为什么线扫描相机的内参列表里多了 Vx、Vy、Vz,而且远心线扫描的 Vz 固定为 0——远心投影下 z 方向平移不影响成像位置,Vz 不可辨识。文档 6.6.1 还给了个实用技巧:相机正对传送带、传感器线垂直于运动方向时,Vx=Vz=0,Vy 可以用已知尺寸物体(标定板、标尺)的线扫描图像估出来。这个初值质量直接决定标定成败,前面说过,不再重复。
5. 标定避坑:那些 RMSE 不会告诉你的翻车现场
5.1 现象:RMSE 很小,但测量远处物体偏差大
原因:局部极小或单位混用。优化器收敛到一个残差小但参数物理错误的点,常见于焦距初值离谱、或毫米/米混填。解决:检查 Focus、TransX/Y/Z 的量级是否符合物理直觉(焦距几毫米、工作距离几十厘米到几米),用get_calib_data把参数打出来和镜头规格书对照;必要时把可疑参数排除出优化、固定成标称值再跑。
5.2 现象:标定直接报错,提示找不到标定板或标记不足
原因:违反文档 6.7.2 的图像质量要求。六边形板要求至少一个 finder 图案可见、标记直径至少 20 像素、明暗对比至少 100 灰度、亮部不超过 240、亮部灰度范围不超过 45。矩形板还要求整板可见。解决:逐条对照——标记太小就拉近或换大板,过曝就调曝光或加漫射,对比度不够就改善照明,板子出画就重拍。别试图用set_calib_data_observ_points硬补质量差的图,观测噪声会直接进 RMSE。
5.3 现象:所有图里标定板都摆得差不多平,标定结果不稳定
原因:文档 6.9 说的平面退化——标定板彼此平行时,焦距和板到相机距离无法分离,六个外参和焦距不能一起确定。解决:重拍,让板子在约 30-45° 范围内向不同方向倾斜,视场每个部分至少被覆盖一次。倾斜不是可选项,是打破简并的必要条件。
5.4 现象:用了 polynomial 模型,图像边缘测量反而更差
原因:文档 6.7.3 的警告——polynomial 高阶项在标定板未覆盖区域会外推失真,图像边界和视场空白区最明显。解决:要么保证标定板覆盖到实际测量区域(包括边缘),要么退回 division 模型。division 虽然拟合能力弱,但外推行为温和得多,覆盖不足时更稳。
5.5 现象:远心镜头标定后,标定板 z 距离测出来是 1 米
原因:这不是 bug。文档 6.9 明确说远心透镜下板与相机距离不可确定,标定板姿态的 z 分量被设为 1 米。解决:接受这个约定,远心系统的测量本来就不依赖绝对 z 距离;如果业务需要绝对距离,得换有透视的镜头或加其他传感器。
6. 设计取舍:为什么 Halcon 把“不可辨识”交给固定参数而不是正则化
标定系统的核心矛盾是:模型参数越多,拟合能力越强,但可辨识性越差。面对简并,常见实现思路有两种:一是加正则项(比如对畸变系数加惩罚,让优化偏向小畸变),二是直接固定某些参数。Halcon 选了后者——Sy 固定、远心 Vz 固定、倾斜像侧远心的 Sx 也固定,文档里用星号标得明明白白。这个选择的取舍在于:固定参数牺牲了“理论上可能更优”的解,换来了确定性和可复现性。正则化的权重是个玄学,调不好会把真实畸变也压掉;固定参数则把不确定性显式暴露给用户——你看到 Sy 带星号,就知道它没被优化,它的误差会传播到其他参数上,你得自己保证它的初值来自可信的规格书。
这套设计对使用者的要求其实更高:你得知道哪些参数被固定、为什么固定、固定的值从哪来。文档 6.6.1 把每个相机类型的参数元组完整列出、把星号含义讲透,就是在逼你建立这个认知。我自己的习惯是,每次标定前先把参数元组按相机类型抄一遍,把带星号的项标出来,确认每个固定值的来源(规格书、机构读数、还是默认 0),再决定excluded_settings里加什么。这个动作花不了几分钟,但能挡掉大部分“RMSE 好看、测量翻车”的情况。标定这件事,优化器只负责在你给的约束里找最优,约束对不对,它不会告诉你。希望这份直译文档能帮你把约束想清楚,少走几次回头路。
本文还有配套的精品资源,点击获取