简介这份 Halcon 第六章 Calibration 英文参考手册直译中文版无删减面向计算机视觉、视觉检测和 Halcon 算法应用人员重点解决相机标定概念理解、标定流程搭建与测量精度提升问题。资源包仅含 1 个 PDF 文件约 813KB便于集中查阅和随用随看。内容围绕标定对象描述与查找、三维点投影和反投影、角度与径向畸变补偿、相机内外参处理、自标定以及单目、双目、多视图和手眼标定展开。已有 362 人学习下载可作为 Halcon 标定主题的参考资料适合需要系统学习相机标定的人员。章节还系统梳理六边形与矩形校准板的选择和拍摄要求、标定输入数据准备、find_calib_object 与 calibrate_cameras 的使用、结果检查及后投影均方根误差评估并说明不同相机模型的限制便于读者把概念、参数配置和项目排错串联起来。1. 标定误差 0.1 像素不是「越准越好」它其实是标记点提取的噪声底很多人跑完一次相机标定第一反应是看 RMSE 数字0.08 就松一口气0.35 就开始怀疑参数写错了。差别通常不在优化算法而在手上那组图。RMSE 衡量的是「重投影回来的标定板标记点」和「图像里真实提取到的标记点」之间的平均像素距离它的下限由标记点中心的提取精度决定亚像素边缘拟合再稳也有零点几个像素的抖动。0.1 像素附近意味着模型已经贴合观测噪声的底再压没有意义0.4 以上则基本说明数据本身有问题不是模型不够复杂。把这条链拆开看3D 世界点 → 相机坐标 → 归一化像面 → 畸变补偿 → 像素坐标每一环都有确定的参数和确定的不可辨识性。搞懂这些标定才从「点一下跑一遍」变成可以诊断的工程步骤。2. 相机模型的参数账本为什么 Sy 永远带星号、Kappa 只能有一个相机标定的产物是一组参数不是一张校正图。这组参数分成内参和外参两本账内参描述「这台相机加这颗镜头如何把光线折到传感器上」外参描述「标定板此刻站在相机的哪个位置、朝哪个方向」。所有标定算子最终都在填这两本账区别只在于账本里哪些格子允许被优化、哪些格子必须由人钉死。2.1 带星号的参数不是省略是不可辨识打开区域扫描相机的参数元组最常见的area_scan_division长这样* area_scan_division 的 8 个内参顺序固定 * [CameraType, Focus, Kappa, Sx, Sy, Cx, Cy, ImageWidth, ImageHeight] [area_scan_division, 0.008, 0.0, 2.2e-6, 2.2e-6, 320.0, 240.0, 640, 480]注意Sy后面那个星号。针孔模型的投影式里水平方向真正起作用的是Focus / Sx这个比值不是 Focus 和 Sx 各自的值——把焦距放大两倍、同时把像元尺寸放大两倍投出来的像素坐标一模一样。三个未知量只有两个独立自由度方程欠定所以必须挑一个钉死。选 Sy 而不是 Sx是因为多数工业相机的行方向采样是硬件同步的像素间距由芯片规格直接给出、查手册就有比让优化器去猜更可靠。远心镜头是同一回事的另一副面孔投影式里只出现Magnification / Sx放大倍率和像元尺寸同样只能定比值于是Sy继续带星号。像侧远心的倾斜镜头更极端Focus、Sx、Sy、Tilt、Rot 五者不能同时确定所以Sx和Sy一起被固定。线扫描相机则是另一套约束Sx固定焦距与横向尺度不可分Sy固定原因是标定方程里Sy只以pv -Sy · Cy的乘积形式出现两者不可分离。提示星号参数不是「算法懒得算」而是数学上算不出来。手工改这些值不会让结果更准只会整体缩放整条投影链。2.2 畸变模型一个参数和五个参数的职责划分畸变这一层HALCON 给了两套模型参数个数差得很远模型参数建模对象能否解析求逆division分割模型Kappa仅径向畸变可以polynomial多项式模型K1, K2, K3, P1, P2径向 离心畸变不可以需迭代分割模型只用一个Kappa描述径向弯曲形式上是把畸变像面坐标乘一个与半径平方相关的因子。它的关键优势在于这个式子可以代数反解——给定一个畸变坐标能直接算出未畸变坐标不需要迭代。多项式模型用三个高阶项逼近径向弯曲、两个参数描述镜头光轴与传感器不垂直造成的离心偏移拟合能力更强但方程没有闭式反解反畸变只能数值迭代。这两套模型的选择直接决定了运行时开销。做测量时每一条从图像反推到空间射线都要走一次反畸变如果模型不可解析反演这条路径上每次调用都在跑迭代。常见的实现思路是先用 division 跑通全流程只有当标定残差明显高于检测噪声、并且确认是畸变建模不足导致的才切到 polynomial同时补拍覆盖更充分的图像。2.3 九种相机类型不是配置项是九套不同的方程参数元组的第一项CameraType决定了解析器读哪一套公式。区域扫描相机按「投影方式 × 是否有倾斜镜头 × 畸变模型」组合出十几种线扫描相机再叠一层运动矢量。选择错误会出现两种典型后果一是参数位置整体错位把Kappa填进了K1的槽位二是公式和物理设置不匹配比如拿area_scan_telecentric_division去描述一颗普通定焦镜头优化器会拼命用放大倍率去补偿本应由透视除法承担的近大远小最后 RMSE 看起来还行但测量结果随工作距离漂移。判断方法很朴素镜头物体侧输出的是会聚光束还是平行光束会聚就是透视投影选area_scan_*平行就是远心投影选area_scan_telecentric_*。镜头是否相对传感器倾斜看机械结构有没有俯仰调节或者 Scheimpflug 铰链。畸变是否需要用高阶项描述先看 division 的残差够不够用。这三个问题答完类型唯一确定。3. 从世界点到像素的四步变换一条投影链是怎么被拆开的一个 3D 世界点pw (xw, yw, zw)落到图像上变成(r, c)中间要过四道工序。把这四道工序拆开才能理解为什么外参和内参可以分离优化以及为什么某些相机类型的参数天然不可确定。3.1 第一步世界坐标到相机坐标R 和 T 的六个自由度这一步是纯粹的刚体变换pc R · pw T。R 是 3×3 旋转矩阵T 是平移向量一共 6 个自由度对应外参里的 TransX、TransY、TransZ、RotX、RotY、RotZ。外参的位姿元组有 7 个元素而不是 6 个多出来的那个是编码项它把旋转的书写顺序OrderOfTransform、欧拉角顺序OrderOfRotation、观察方式ViewOfTransform打包成一个整数。原因很实在三组欧拉角能拼出同一个旋转不记录顺序就无法从 6 个数唯一还原矩阵。标定结果里丢掉这个编码读出来的姿态就是错的。使用标准标定板时世界坐标系由板面定义原点在板面中心或第一个取景图案的中心z 轴垂直指向板子x 轴向右y 轴向下视线沿 z 轴方向。这也是为什么板子必须放正、不能反着拍——坐标系的定义绑在板子的正面上。3.2 第二步相机坐标到像面透视除法与远心缩放的差别针孔模型下投影是两次除法u Focus · xc / zc v Focus · yc / zc分母里的zc就是近大远小的来源。超中心镜头虽然在成像上很特殊但它依然在物体侧做透视投影所以仍归入针孔相机公式形式一致。远心模型的投影式里没有除法u Magnification · xc v Magnification · yczc消失了。这不是简化而是物理事实远心镜头只接收平行于光轴的光束物体沿光轴移动不改变成像位置。代价是深度信息彻底丢失所以远心相机的标定结果里标定板沿 z 方向的平移会被强制设成 1 米——不是测出来的是占位值。这一点在做手眼标定或者多相机拼接时是必须知道的远心相机的外参 z 分量没有物理意义。3.3 第三步畸变补偿为什么公式方向常常是反的标定文档里给出的畸变公式通常写的是「畸变坐标 → 未畸变坐标」而不是反过来。原因在于优化目标calibrate_cameras要算的是「把空间点投到图像上会落在哪」也就是正向投影而find_calib_object拿到的是图像里的真实像素要去反推空间射线。两个方向都需要但只有一个是闭式的。分割模型两个方向都闭式写起来对称多项式模型只有「畸变 → 未畸变」是显式的反方向必须解一个非线性方程、用迭代收敛。这就是为什么同样的图像、同样的标定板切到多项式模型后单次project_3d_point的耗时明显上升。畸变前坐标 --(多项式模型: 不可解析求逆)-- 畸变后坐标 畸变前坐标 --(多项式模型: 显式公式)------ 畸变后坐标注意多项式模型的反畸变迭代通常在图像边缘收敛更慢因为高阶项在那里的贡献最大。如果测量区域贴着图像边界务必检查那里的重投影残差是否仍然可控。3.4 第四步倾斜像面3×3 射影变换和 2×2 线性变换的分界线倾斜镜头光轴相对传感器法线倾斜在畸变之后还要多一层变换把点从「虚拟的无倾斜像面」映射到真实的倾斜像面上。这一步用哪种矩阵取决于倾斜发生在镜头的哪一侧图像侧执行透视投影的倾斜镜头普通倾斜、物侧远心倾斜用 3×3 齐次矩阵 H 做二维射影变换点要转成齐次坐标再除回来多出一个尺度分量。像侧执行平行投影的倾斜镜头像侧远心、双侧远心用 2×2 线性变换没有分母。分界线就是「有没有透视收缩」。平行投影不产生近大远小映射退化成仿射自然不需要齐次坐标。H 的两个来源参数是Tilt和Rot再加一个ImagePlaneDist镜头出瞳到像面的距离。Rot描述倾斜轴在传感器平面内的方向必须先应用Tilt描述倾角本身取值在 0° 到 90° 之间Rot从 0° 到 360°0° 时光轴垂直向下倾斜90° 对应向左水平倾斜。这三个数通常能从机械结构上读个大概作为初值喂给优化器。3.5 线扫描的额外一维运动矢量把一条传感器线扫成一张图线扫描相机每次只曝一行整幅图像是靠相机与物体相对运动「拼」出来的。所以要建模的不只是光学投影还有运动。HALCON 对运动做了三条假设相机沿直线匀速移动、相机方向恒定、所有图像的运动相等。运动由矢量V (Vx, Vy, Vz)描述单位是「米/行」。有意思的是这个矢量描述的是相机的运动假设物体固定等价于相机不动、物体沿-V移动。相机坐标系的定义也随之调整原点在投影中心针孔或畸变中心远心z 轴沿光轴且可见点 z 为正y 轴垂直于传感器线并让运动矢量的 y 分量为正x 轴补齐右手系。由于每一行都是从不同位置成像的理论上每行一个位姿为了简化HALCON 只保留第一图像线的位姿运动由V在投影时补偿。V的初值质量直接决定标定能否收敛。原文说得很直白初值不够精确标定可能直接失败。常规做法是让相机光轴垂直于传送带、传感器线平行于运动方向此时取Vx Vz 0再用已知尺寸的标尺或标定板图像反推Vy。如果相机绕光轴转了 30°或者绕 x 轴俯仰了 20°初值就得按旋转关系重新算不能照抄。远心线扫描还有一个坑Vz对成像位置没有影响因此无法被确定会保留初始值。所以远心线扫的Vz应当显式设成 0。# 四步投影链的示意实现区域扫描针孔 分割畸变 def project(pw, R, T, focus, kappa, sx, sy, cx, cy): pc R pw T # 第一步世界 - 相机 u, v focus * pc[0] / pc[2], focus * pc[1] / pc[2] # 第二步透视除法 r2 u * u v * v k 1.0 / (1.0 kappa * r2) # 第三步分割模型反畸变可解析 u, v u * k, v * k col u / sx cx # 第四步像面 - 像素 row v / sy cy return row, col这段伪代码对应的是「标定完成后给一个已知世界点预测它落在图像哪个像素」。注意sy虽然在校准中被固定投影时仍然要参与计算——固定的是它不被优化不是它不存在。cx、cy是主点使用半图宽度和高度作为初值如果图像做过降采样这四个尺度量和ImageWidth、ImageHeight必须同步缩小否则整条链的尺度会整体跑偏。4. 标定数据模型与 calibrate_cameras一次非线性优化如何被组织标定不是一次函数调用是「建模型 → 灌数据 → 跑优化 → 查结果」四段式。中间那个CalibDataID就是一个句柄把相机、标定板、观测点、优化配置全部装在一个容器里最后一次性丢给优化器。4.1 create_calib_data 的三个入参决定了后面所有调用的下标* 1 台相机、1 块标定板、使用标定对象模型 create_calib_data (calibration_object, 1, 1, CalibDataID)三个入参分别是模型类型、相机数量、标定对象数量。相机数量为 1 是单目为 2 是双目更多就是多视图。标定对象数量指的是「画面里可能同时出现几块不同的板」多相机同时看不同板时才需要大于 1。这两个数字不是摆设它们决定了后续所有算子的下标索引set_calib_data_cam_param (CalibDataID, CameraIdx, ...)里的CameraIdx从 0 到相机数减一find_calib_object里的相机索引和标定对象索引同理。索引写越界不会崩溃但数据会悄悄丢进一个不存在的槽位最后表现为「明明拍了 20 张优化只用了 3 张」。4.2 观测数据的两个入口自动提取和手动接管find_calib_object是最省事的一条路喂一张图它自己找标定板、提取标记中心、估一个粗略位姿把观测点写进数据模型。适合标准板、正常光照、板子完整可见的场景。set_calib_data_observ_points是手动挡你自己算好标记点的图像坐标和对应的世界坐标再灌进去。什么时候需要它标定板上的标记被遮挡了一部分、需要剔除个别坏点、或者根本不用标准板而是拿一个特征点已知的工装件做标定。原文提到「也可以使用任意对象进行校准唯一的要求是物体具有可以在图像中鲁棒检测到的特征点并且这些点的 3D 世界位置是高精度的」——这条路走的就是手动入口。提示自打印的标定板通常精度不够。打印机的定位误差和纸张的热胀冷缩会直接进入世界坐标而世界坐标是标定的基准基准歪了整个结果都歪。高精度场景用蚀刻或光刻工艺做的板子附带描述文件find_calib_object能直接读。4.3 calibrate_cameras 之后三样输出各说明什么优化跑完calibrate_cameras (CalibDataID, Error)返回的Error就是重投影 RMSE单位像素。它对应的是「反投影回来的标定板标记点」和「图像里提取到的标记点」之间的平均距离。另外两样要用get_calib_data单独查camera优化后的内参元组包含所有带星号参数的最终值星号参数保持初值不变。calib_obj_pose每张图像里标定板相对于相机的位姿也就是外参。covariances内参的标准差和协方差。这个容易被忽略但它才是判断「参数是否被数据充分约束」的依据。标准差很大意味着该参数在优化中几乎没被约束住值不可信哪怕 RMSE 很小。RMSE 小但某个参数标准差大是典型的「过拟合式标定」模型用别的参数补偿了这个自由方向。常见于所有标定图里板子姿态过于单一的情况。4.4 排除参数当你已经知道某个值不该被优化如果某个参数有独立的高精度来源就该把它从优化里踢出去减少自由度、提高其余参数的约束强度。做法是用set_calib_data配合excluded_settings* 相机 0 的像元尺寸已知且可信只优化其余内参 set_calib_data (CalibDataID, camera, 0, excluded_settings, [sx, sy]) calibrate_cameras (CalibDataID, Error) get_calib_data (CalibDataID, camera, 0, params, CameraParam) get_calib_data (CalibDataID, calib_data, 0, error, Error)字符串sx、sy要和参数元组里的字段名对应。排除掉之后这两个值原样保留初值其余参数在「已知尺度」的约束下优化。这一步在产线设备上很实用相机芯片的像元尺寸是规格书给的确定值没必要让优化器去猜猜错了反而拖累焦距和主点。排除要有依据。把畸变系数也排除掉比如直接设kappa等于强行假设镜头无畸变短焦或大视场下 RMSE 会立刻变难看。5. 标定现场的踩坑排查从 RMSE 异常到板子镜像的五个现象标定失败很少报错多数时候是「跑通了但数值不对」。下面这几条是按现象反查的经验记录每条都对应一个具体的机制。5.1 现象RMSE 卡在 0.4 以上图看着挺清楚原因通常不是模糊而是标记点的直径不够。标定要求每个标记在图像里的直径至少 20 像素低于这个值中心提取的量化误差会直接吃掉亚像素精度。另一个隐形杀手是对比度明暗区域之间至少要 100 个灰度值8 位图如果照明不均导致亮部只有 60 灰阶差边缘检测就会在噪声里摇摆。解决办法是先量再拍拿一张图数一下标记直径占几个像素不够就加分辨率、缩短工作距离或者换大尺寸标定板。同时检查曝光亮部灰度值不要超过 240避免过曝把标记边缘「烧平」板面照明的均匀性也有要求亮部灰度值的跨图波动控制在 45 灰阶以内。5.2 现象优化收敛了但焦距和外参的 z 平移数值离谱原因是标定板的姿态太单一。如果所有图像里板子都基本平行于传感器平面尤其是全部放在同一平面上透视投影的方程会退化焦距和板到相机的距离互相补偿无法分离。原文明确写了这个限制——所有六个外参加焦距无法一起确定。解法是补拍倾斜图。板子以 30° 到 45° 向不同方向倾斜而且要保证视场的每个区域至少被板子覆盖过一次。六边形排列标记的板子至少 6 张图、其中至少 4 张倾斜矩形排列标记的板子至少 15 张。数量是下限不是目标姿态的多样性比张数更重要。5.3 现象结果整体镜像或者 HDevelop 直接报错退出原因是标定板被反着拍。玻璃基底的板子从背面拍标记图案会左右翻转世界坐标和图像坐标的对应关系整个反了。六边形排列的板子靠取景图案定位图像里至少看到一个取景图案就能定板但要判断是否镜像必须看到至少两个——只有一个的时候镜像检测不出来结果就是悄悄错。矩形排列的板子要求整块完全可见因为取景图案是围着标记的那圈框架框架不全就没法确认朝向。解法是确认板子的正面朝上并且遵守各自的可见性要求六边形板至少两个取景图案入镜矩形板整板入镜。5.4 现象换了标定板之后结果完全不对或者算子直接不认原因是算子与板型不匹配。六边形排列标记的板子不适用于camera_calibration这个老算子要用calibrate_cameras走数据模型流程。反过来如果已经在用calibrate_cameras板子的描述文件必须通过set_calib_data_calib_object正确挂上去文件里的标记世界坐标和实际板子对不上优化结果必然跑偏。还有一个容易混淆的点矩形排列标记的板子find_caltab加find_marks_and_pose两个算子组合起来效果等价于find_calib_object。维护老代码时会看到这种写法不必强行改写但新流程建议统一用find_calib_object。5.5 现象测量结果差一个固定比例或者远心相机的工作距离怎么都测不出来比例问题的根源在单位。标定板尺寸以米为单位所有相机参数也用米焦距初值写8而不是0.008整条链就整体放大 1000 倍。降采样也会带来同类问题图像做了 2 倍降采样Sx、Sy要乘 2Cx、Cy、ImageWidth、ImageHeight要除以 2漏改任何一个都会让尺度不一致。远心相机测不出工作距离不是配置问题是物理限制。远心投影不含zc板到相机的距离不可确定外参的 z 分量被强制设成 1 米。同理远心线扫描相机的Vz也确定不了会保留初始值正确做法是把它显式置 0。6. 除法模型与多项式模型的取舍可逆性、稳定性与覆盖度两套畸变模型的差别表面看是参数个数实质是「你要为精度付出多少运行时代价和多少数据代价」。维度分割模型division多项式模型polynomial参数数量1 个Kappa5 个K1/K2/K3/P1/P2能建模离心畸变否是反畸变方式解析求解数值迭代少量图像下的稳定性较高较低对覆盖度的要求一般高测量区域必须被板子覆盖大畸变下的拟合精度一般较高分割模型的隐性优势在「稳定性」这一行。参数少意味着它不容易把噪声拟合成畸变样本量不足或者视场覆盖不全时结果不会剧烈漂移。多项式模型的高阶项则有很强的外推能力——这是优点也是陷阱校准板没有覆盖到的区域图像边界、视场角落它照样会给出一个数但那个数可能完全脱离物理事实。原文对此说得很清楚多项式模型在标定板覆盖区域之外可能不准确地模拟变形。所以默认路线应该是先用分割模型标看 RMSE 和协方差。如果残差明显高于标记点提取的噪声水平并且确认是畸变建模不足而不是图像质量问题再切多项式同时补拍覆盖更充分的图尤其是你后面真正要做测量的那块区域。切过去之后务必把测量区域的重投影残差单独验一遍不要只看全局 RMSE——全局平均值会把边界的糟糕拟合平均掉。倾斜镜头的取舍是另一条线。Tilt、Rot、ImagePlaneDist这三个参数的可辨识性依赖镜头本身的畸变量畸变越大倾斜造成的不对称越明显参数越能被约束住畸变很小的镜头Tilt基本确定不了优化出来的值可能和机械上读到的标称值差很远。这种情况下不要慌着调参先看误差——如果 RMSE 和协方差都正常说明这组参数在校准体积内能一致地描述成像几何用于测量是没问题的参数值和标称值不一致只是说明这组数据无法区分它们不代表结果错。我自己的习惯是每次标完先做三件事看 RMSE 是否落在 0.1 像素这个量级附近看covariances里有没有哪个参数的标准差异常大再看一组留出图的重投影残差在图像四个角和中心是否一致。这三步走完才敢把这组参数写进测量流程。早年吃过一次亏——RMSE 只有 0.09看着漂亮结果测量区域在图像右下角那块恰好没有被任何一张标定图覆盖多项式模型在那里外推出了一个不存在的弯曲一整批工件的尺寸全部系统性偏大。从那以后我每次切多项式模型之前都会先把标定板的覆盖范围画在图像上确认测量区域被完整包住才允许往下走。希望这些经验帮到你少走一遍我走过的弯路。本文还有配套的精品资源点击获取