HALCON相机标定全解析:标定板、内参外参与畸变模型
简介:面向计算机视觉与视觉检测从业者的Halcon第六章Calibration中英文直译资料,聚焦相机标定的完整知识框架与工程落地要点,适合需要理解相机模型、畸变补偿、内外参含义以及单目、双目、多视图与手眼标定流程的初、中级开发者查阅。资源为1个PDF文件,压缩包约813KB,篇幅集中、便于按章节检索,可用于对照英文原版梳理术语与操作符语义。已有362人学习下载,说明其在Halcon标定学习与项目排错中具备一定参考价值。内容覆盖校准对象与校准板选择、输入数据准备、calibrate_cameras实际校准、RMSE结果检查、相机参数解释,以及区域扫描相机、远心与鱼眼等模型限制,并保留Calibration/Projection、Rectification、Parameters、self-calibration等模块脉络。读者可获得从概念到函数调用的直译对照,快速定位标定流程中的关键步骤和常见注意事项,为视觉测量、定位与手眼系统调试提供查阅依据。
1. 相机标定到底在标什么:从一张「尺子不准」的照片说起
用手机随手拍张照片量袖长,差个一两厘米没人计较;换成产线上用相机测零件孔径,差 0.02 毫米就是一批活报废。同一台相机,为什么有时候「看着挺准」,有时候「错得离谱」?问题不在镜头贵不贵,而在于你并不知道这台相机把三维世界「压」成一张二维图时,究竟动了哪些手脚。HALCON 参考手册第六章 Calibration(标定)讲的正是这件事:拿一组已知尺寸的标定板图像,反推出相机的成像规则,把这只「眼睛」的脾气摸清楚——它的焦距多少、像元多大、镜头把直线弯成了什么样、它又摆在世界的哪个位置。这份英文直译中文版无删减地把整章搬了过来,从标定板怎么选、图像怎么拍,一路讲到内参外参、畸变模型和各类相机的限制。搞懂这一章,你再看视觉检测算法报出来的毫米数,心里才有底:这个数是量出来的,还是猜出来的。
2. 为什么标定先得有一块板:标定对象与它的坐标系
标定的本质是一次「反解」:已知世界里的点在哪,又知道它们在图像里落在哪个像素,反过来求相机的参数。这个「已知世界里的点」不能靠猜,必须由一块尺寸精确已知的物体提供,也就是标定板。HALCON 的思路很直接——板子上印一堆标记,每个标记的圆心在板子坐标系里的三维坐标是出厂就写进描述文件的,图像处理要做的只是把这些标记的圆心在图像里的像素坐标提出来,一一配对,剩下的交给优化算法。所以整条链路上,板子选错、拍糊了、坐标系搞反了,后面的计算再精巧也是白搭。
2.1 六边形标记板和矩形标记板,选错会怎样
标准标定板有两种。一种是六边形排列标记的板子,标记按蜂窝状铺开,其中若干组构成「取景器图案」(finder pattern),有的标记上还带点状小孔,描述文件由 create_caltab 生成。它的好处是宽容:图像里不必把整块板拍全,只要至少有一个取景器图案露出来就能定位;如果同时看到两个取景器图案,还能判断板子有没有被镜像拍到。另一种是矩形排列标记的板子,取景器图案是四周的边框加三个三角形角标,描述文件由 gen_caltab 生成。它要求板子必须完整出现在图像里——因为定位靠的就是那圈外框,缺一条边就找不着了。
这个差别直接决定拍摄难度。拍大视场、镜头又贴近的时候,六边形板明显更省事;而矩形板因为边框约束强,在图像范围内姿态估计更稳。还有一个容易被忽略的坑:六边形板不适用于旧算子 camera_calibration,那套老流程配的是 find_caltab 加 find_marks_and_pose 的组合,走的是矩形板路线;新流程用 find_calib_object 一个算子统一处理,两种板都吃。
| 对比项 | 六边形排列标记板 | 矩形排列标记板 |
|---|---|---|
| 描述文件生成 | create_caltab | gen_caltab |
| 取景器图案 | 六边形标记组,部分带点状孔 | 外围边框加三角形角标 |
| 是否需整板可见 | 不需要,至少一个取景器图案可见 | 必须完整可见 |
| 最少图像数 | 6 张,其中至少 4 张倾斜 | 15 张 |
| 镜像判断 | 看到两个取景器图案即可判断 | 依赖边框完整性 |
| 老算子 camera_calibration | 不适用 | 兼容 find_caltab 加 find_marks_and_pose |
图像张数这条线值得单独记:六边形板 6 张起步、其中至少 4 张是斜着拍的;矩形板 15 张起步。这不是随便定的门槛,倾斜视角才能把焦距和板子距离这两类信息解耦开,全拍正视图的话,很多参数在数学上根本解不出来,后面第 4 章会细说。
2.2 原点、z 轴与「必须看见什么」:标定板坐标系怎么定
标定板不只是几个圆点,它自带一套三维坐标系,这套坐标系在单目标定时就是世界坐标系。六边形板的原点设在第一个取景器图案的中心标记的圆心;矩形板的原点设在板面正中间。两者的轴向约定一致:z 轴垂直指向板面,x 轴向右,y 轴向下,视线方向沿着 z 轴。记住「y 向下」这一点很关键,它对应的是图像的行走方向,行坐标往下增大,不是数学课本里那套 y 向上的习惯。
还有一条硬性要求:标记不能被反向获取。玻璃材质的板子如果从背面拍,图案就左右翻转了,这时候算法要么报镜像错误,要么给出一个看着正常、其实全错的结果。HDevelop 在检测到镜像时会抛出相应错误,这是个保护机制,别把它当成程序 bug 去绕开。
提示:板子坐标系一旦定下来,所有外参都是相对它算的。中途换板、换描述文件、甚至换了同一型号但不同批次的板子,都要重新标定,不能沿用旧结果。
2.3 图像质量才是标定的天花板:光圈、焦距、倾斜与张数
手册把拍摄要求写得很细,细到像是血泪经验攒出来的。相机设置上有三条铁律:采集过程中光圈不能动,动了就得重新标;相机位置不能动;焦点不能变,图像必须清晰对焦,物体与背景的过渡要分明。这三条针对的其实是同一件事——标定假设整个图像序列出自同一套成像几何,任何一项在过程中变化,这组数据就自相矛盾了。
板子的摆放要求更具体。视场的每个部分至少要被打板子覆盖一次,板子可以填满整幅图像;板子的朝向要在一组图像里有变化,倾斜角度控制在 30 到 45 度之间,朝不同方向倾斜。如果景深有限做不到这个角度,那就尽量往陡了倾,别为了凑角度把图像拍虚。图像数量按上面的 6 张和 15 张走,但不能只凑数不管姿态分布。
图像属性这块给了几个可以直接对照的数值。标记直径在每张图里至少要有 20 像素,这条对成败至关重要,标记太小,圆心提取的精度就崩了;明暗区域的对比度至少要 100 个灰度值(按 8 位图像算);亮部灰度值不要超过 240,标记附近尤其要避免过曝,过曝会把圆心「削」掉一块;整块板子的照明要均匀,避免反光,亮部灰度值的波动范围不要超过 45。
把这些数字连起来看,其实是在保同一件事:标记圆心的亚像素提取精度。0.1 像素的标定误差听起来很玄学,但它就是从这 20 像素直径、100 灰度对比、45 灰度均匀性里一点点抠出来的。拍图这一步偷懒,后面用再好的优化算法也补不回来。
3. 从拍到算:标定数据是怎样一步步攒成结果的
很多人第一次用 calibrate_cameras 会懵:就一个算子,参数呢?其实真正的活在前面的「攒数据」阶段。HALCON 用了一个叫校准数据模型的容器,把相机、标定板、每张图的观测点、哪些参数要排除优化,全都塞进这个容器里,最后 calibrate_cameras 只是把容器里的东西一次性算完。理解这个结构,比背算子名有用得多。
3.1 五步搭好校准数据模型
流程固定为五步,顺序不能乱。建模型、写相机参数、写标定板描述、逐张收集观测、配置优化项,然后才是执行校准。
# 1. 建模型:1 台相机、1 块标定板 CalibDataID = create_calib_data('calibration_object', 1, 1) # 2. 写入相机类型和初始内参 # 顺序必须与相机类型严格对应:Focus, Kappa, Sx, Sy, Cx, Cy, Width, Height # 初始值只求量级正确,焦距填镜头标称的 0.008 米,畸变系数从 0 起 set_calib_data_cam_param(CalibDataID, 0, 'area_scan_division', [0.008, 0.0, 3.45e-6, 3.45e-6, 640.0, 512.0, 1280, 1024]) # 3. 挂上标定板描述文件,由 create_caltab 生成 set_calib_data_calib_object(CalibDataID, 0, 'caltab_30mm.descr') # 4. 逐张图提取标记,结果自动回写进模型 for i in range(len(Images)): find_calib_object(Images[i], CalibDataID, 0, 0, i, [], []) get_calib_data(CalibDataID, 'calib_obj_pose', (0, i), 'pose', Pose) # 5. 执行校准,误差数组承接每台相机的 RMSE calibrate_cameras(CalibDataID, Errors)create_calib_data 的两个数字分别是相机数量和标定对象数量,多相机、多板子的系统在这里声明清楚。set_calib_data_cam_param 里的元组顺序是死规矩,跟相机类型一一绑定,填错位置不会报错,只会让结果离谱。find_calib_object 的第二、第三个参数是相机索引和标定对象索引,第四个是观测索引,索引对不上,观测就挂到别的相机名下。如果不用 find_calib_object 自动提取,也可以拿 set_calib_data_observ_points 把外面算好的图像坐标和粗姿态灌进来,适合标记提取要自己定制预处理的情况。
3.2 哪些参数必须锁死:Sy、Sx 背后的「解不出来」
校准数据模型里有个 excluded_settings 参数,用来把某些参数从优化中踢出去。这不是偷懒的开关,而是解决「数学上解不出来」的问题。手册给的例子很典型:如果相机 0 的像元尺寸 sx 和 sy 已经精确知道,只需要标其余参数,就把它们排除掉。
# 明确已知 Sx、Sy,只优化剩下的参数 set_calib_data(CalibDataID, 'camera', 0, 'excluded_settings', ['sx', 'sy'])这个排除逻辑背后是一类反复出现的「不可同时确定」。针孔相机模型下,焦距、Sx、Sy 三者不能同时确定,算法固定住 Sy 不动;远心镜头下,放大倍率、Sx、Sy 不能同时确定,同样固定 Sy;像侧远心的倾斜镜头更严格,焦距、Sx、Sy、Tilt、Rot 不能同时确定,除了 Sy 还会把 Sx 也固定;双侧远心的倾斜镜头,放大倍率、Sx、Sy、Tilt、Rot 不能同时确定,Sx 一并固定。线扫描相机全部固定 Sx 和 Sy,远心线扫还额外固定 Vz。
注意:这些「固定」不是算法保守,而是同一组观测数据里信息量不够。硬要一起解,优化会跑出数值上天花板的解,RMSE 可能还很好看,参数却是错的。
3.3 用 RMSE 判断成败:0.1 像素这条经验线的来历
calibrate_cameras 跑完会给出反投影的均方根误差,单位是像素。它的含义是:把优化后的相机参数代回去,把每个标定标记的三维坐标重新投影到图像上,再与当初提取到的图像坐标比对,所有偏差的均方根。这是个自洽性检查——参数能不能把同一批数据解释圆。
单相机标定时,0.1 像素左右的误差属于正常,大致相当于标记圆心提取本身的典型检测误差。如果这个数明显偏离 0.1,别急着调算法,先怀疑三件事:图像质量差(虚焦、过曝、反光)、图像数量不够或姿态分布太单一、标定板本身不准(比如自己打印的板子)。多相机、多视角的设置怎么判断,手册指向了对应章节的专门说明,判据不完全等同于单目。
误差偏大还有一个隐性原因常被忽略:标定板覆盖的区域不够。优化只在你给的数据范围内有效,板子从来没去过的视场角落,参数在那片区域是外推出来的,残差检查也查不出来。
4. 内参和外参:一台相机被拆成两组数字
标定结果最终落在一堆参数上,这些参数分成两组。内部参数描述相机本身——传感器、镜头、采集卡的投影特性;外部参数描述相机与世界的关系——它站在哪、朝哪看。这两组参数的性质完全不同,用法也不同,混在一起讲最容易让人犯晕。
4.1 内参:焦距、放大倍率、畸变系数、主点和像元尺寸
内参的组成取决于相机类型。面扫描相机根据类型不同有 9 到 16 个内部参数,线扫描是 12 或 16 个。参数元组的第一位是相机类型字符串,后面才是数值。
最基础的透视投影面阵相机用 area_scan_division,参数是:Focus、Kappa、Sx、Sy、Cx、Cy、ImageWidth、ImageHeight。其中 Focus 是焦距,只在物体侧做透视投影的镜头上有效;Kappa 是径向畸变系数,初值填 0.0,单位是米的负二次方;Sx、Sy 是像元间距;Cx、Cy 是图像主点坐标,也是径向畸变的中心,初值取半幅图像的宽和高。换成多项式模型 area_scan_polynomial,Kappa 被 K1、K2、K3(径向)和 P1、P2(离心)五个系数取代,初值全部填 0.0。远心镜头把 Focus 换成 Magnification,即放大倍率,初值取镜头的标称倍率,比如 0.2。
倾斜镜头会多出三个参数:ImagePlaneDist 是镜头出瞳到成像平面的距离,典型值几厘米到极大;Tilt 是光轴相对传感器平面的倾斜角,范围 0 到 90 度;Rot 是光轴绕 z 轴的旋转角,范围 0 到 360 度,Rot 为 0 时朝正下方倾斜,90 度朝左水平倾斜,180 度朝上,270 度朝右。这三个值通常能从镜头倾斜机构上读出来,不需要全靠优化去猜。
线扫描相机再多三个参数:Vx、Vy、Vz,即运动矢量在相机坐标系下的三个分量。
| 模型类型 | 关键参数 | 说明 |
|---|---|---|
| area_scan_division | Focus、Kappa、Sx、Sy、Cx、Cy | 单参数径向畸变,最常用 |
| area_scan_polynomial | Focus、K1、K2、K3、P1、P2、Sx、Sy、Cx、Cy | 径向加离心畸变,精度高 |
| area_scan_telecentric_division | Magnification、Kappa、Sx、Sy、Cx、Cy | 远心镜头,无焦距 |
| 倾斜镜头系列 | 在对应基础模型上加 ImagePlaneDist、Tilt、Rot | 光轴与传感器不垂直 |
| line_scan_division | Focus、Kappa、Sx、Sy、Cx、Cy、Vx、Vy、Vz | 需描述相对运动 |
4.2 外参:六个姿态参数和 pose 元组
外部参数是六个:TransX、TransY、TransZ 描述沿相机坐标系三个轴的平移,RotX、RotY、RotZ 描述绕三个轴的旋转。相机坐标系的 x、y 轴平行于图像的列、行方向,z 轴垂直图像平面。这六个参数合起来就是世界坐标系相对相机坐标系的位姿。
pose 元组里还有第七个元素,它不是数值,而是对三个字符串——OrderOfTransform、OrderOfRotation、ViewOfTransform——的组合编码,告诉后面所有算子该怎么解释这六个数字的运算顺序。这个编码一旦被手工篡改,整套变换就会静默出错,算出来的坐标看着像模像样,其实完全是另一套约定。
线扫描相机的外参有个独立约定:世界坐标系的位姿指的是第一条图像线对应的相机坐标系。因为线扫是相机与物体相对运动逐行成像,每一行都有自己的位姿,HALCON 把所有世界坐标到相机坐标的转换统一锚定在第一行上,运动的影响则通过运动矢量补偿。
标准标定板做单目标定时,世界坐标系就由板子坐标系定义,所以你标出来的 TransZ 其实包含了板子到相机的距离。这也是为什么远心镜头解不出这个距离——后面会碰到。
4.3 单位和次采样:最容易翻车的地方
标定板的尺寸以米为单位,相机参数也统一用米。这条听起来平淡,但它是踩坑重灾区:描述文件写的是 0.03 米,你在代码里按 30 毫米填,结果就是整组外参差三个数量级。想用毫米做单位当然可以,前提是所有相关参数一起换算,不能只改一处。
次采样是第二个坑。如果图像被降采样过,Sx、Sy 的数值会变大,Cx、Cy、ImageWidth、ImageHeight 会变小。手册在参数说明里给这三类都加了同样的感叹号提醒,显然是被问过太多次。换分辨率的相机、加个 2 倍降采样做加速,这些改动都会动到这几个值,而它们又是内参里最基础的部分。
提示:拿到一台新相机,先把技术规格里的像元尺寸、分辨率抄下来,按米换算好填进初始值,再做次采样相关的换算。初始值只影响优化起点,填错了不影响最终结果,但填错单位会让优化从完全错误的量级起步,收敛到奇怪的地方。
5. 容易搞混的四组概念:除法模型和多项式模型不是「新旧版本」
标定这一章的名词密度很高,几个词长得像、意思差很远,初学者最容易被带偏。下面这四组,基本覆盖了实际使用中八成的困惑。
5.1 除法模型和多项式模型:快和准的取舍
易混点:以为多项式模型是新版本、除法模型是过时版本,或者以为参数多的那个一定更好。
为什么混:两者都能描述镜头畸变,函数签名也长得像,官方默认推荐的那个参数还更少,直觉上像是"简配版"。
怎么区分:看用途。除法模型(手册里译作分割模型,也有地方写分裂模型,英文是 division model)只用一个 Kappa 参数模拟径向畸变,公式可以解析反解,所以正畸变和逆畸变都算得快;图像数量少、视场覆盖不充分的时候,它反而给出更稳定的结果。多项式模型用 K1、K2、K3 管径向、P1、P2 管离心,五项一起上,能更准确地描述畸变,代价是不能解析反解,逆畸变得迭代算,慢。常规做法是默认用除法模型;标定精度不够再上多项式,但要记得多项式对覆盖区域更挑,板子没覆盖到的地方,包括图像边界和视场内的空白带,畸变可能被模拟得很不准。
5.2 校正、投影、反投影:三个词三件事
易混点:把校正(rectification)当成投影的一种,或者以为反投影就是把投影倒过来算。
为什么混:这三个词都跟「点怎么动」有关,中文译名又都很像。
怎么区分:校正是在图像层面动手,把畸变的图拉回理想针孔模型该有的样子,输出还是一张图;投影是把三维世界点按相机模型算到图像平面,落到亚像素坐标;反投影是从图像点反推世界点,而单张图反推三维点必须有额外约束,通常是假设点落在某个已知平面上,否则一个像素对应一条射线,无穷多个解。三者的输入输出维度完全不同,别指望一个算子替你全干完。
5.3 针孔与远心:焦距和放大倍率不能互换
易混点:拿着远心镜头,还在参数表里找焦距填哪儿。
为什么混:两类镜头都成像,常规软件里又常把「焦距」当成相机的标志性参数。
怎么区分:看投影方式。物体侧是透视投影的,用针孔模型,参数里是 Focus;物体侧是平行投影的,用远心模型,参数里是 Magnification,即像尺寸除以物尺寸。这里有个连带后果要知道:远心镜头下,标定板到相机的距离无法确定,结果里板子位姿的 z 分量会被设成 1 米,别把这个 1 米当成真实距离去用。远心线扫相机还多一条,Vz 对三维点的图像位置没有影响,解不出来,会保留初始值,所以初值应该填 0。
5.4 标定误差小,不等于测量就准
易混点:看到 RMSE 是 0.08 像素,就认为测量精度达到 0.08 像素对应的实际尺寸。
为什么混:两个数字都叫「误差」,单位还都能换算到像素。
怎么区分:RMSE 衡量的是参数能不能解释训练用的那批数据,是自洽性;测量精度还受标定板本身的制造精度、板子覆盖范围、被测物是否在覆盖区内、温度与机械稳定性影响。板子从没去过的地方,畸变模型是外推出来的。倾斜镜头的倾斜参数还有个特点:镜头畸变越大,Tilt 和 Rot 越能标得准;畸变很小的镜头,这两个参数根本确定不了,优化出来的值可能和标称值差很远。碰到这种情况先看误差,误差小说明这组参数在标定体积内能一致地描述成像几何,照样能用于精确测量。
6. 什么时候该标、什么时候别急着标:一条边界判断
标定不是免费的仪式,它要一块精度达标的板子、一组摆位讲究的图像、一次可能失败的优化。判断要不要走这一趟,看你的输出到底需不需要「毫米」。
需要标定的典型场景有三类。第一类是从像素换算到物理尺寸,比如测孔径、测间距、测角度,只要结果要落到毫米或度,就必须有内外参把像素和世界连起来。第二类是多相机协同,双目、多视角、手眼系统,几台相机要在同一个世界坐标系里说话,不标定就没有公共语言。第三类是畸变已经开始影响判定,广角镜头下图像边缘的直线明显弯曲,靠固定阈值分割会把好件判成坏件,这时候先把图校直比调算法划算。
不着急标定的场景同样明确。只做有无检测、缺陷分类,畸变不改变「这里有没有一个黑点」的结论,标定带来的收益很薄。视野固定、只做同一台相机的前后相对比较,不涉及绝对尺寸,标定的意义也不大。还有一种常见的错误做法:为了省成本自己打印标定板追求高精度,手册明确说了,自打印的标定对象通常不够精确,用于高精度应用并不合适;真要做高精度测量,高精度板子是绕不开的成本。
| 你的需求 | 建议 | 原因 |
|---|---|---|
| 像素换毫米、测尺寸角度 | 必须标定 | 没有内外参就没有物理量 |
| 双目、多视角、手眼 | 必须标定 | 多坐标系需要统一基准 |
| 广角边缘测量、图像明显弯曲 | 先校正再测 | 畸变会污染阈值与匹配 |
| 只做有无、分类检测 | 可以跳过 | 判定结果不依赖绝对坐标 |
| 同一相机前后相对比较 | 视情况 | 无绝对量需求时收益有限 |
| 用自打印板子做高精度 | 不建议 | 板子精度会直接限制结果上限 |
有几个坑我在实际项目里反复见到,值得单独拎出来。一种是所有标定图都拍成板子正对相机,觉得这样最清楚,结果六个外参里有几个压根解不出来,焦距和板距混在一起,RMSE 还很小,人就被骗过去了。另一种是拍完一轮发现误差偏大,回头把焦距、放大倍率这类本来该固定的参数放开一起优化,参数是「优化」了,物理意义也没了。还有一种是换了个降采样倍率做加速,忘了同步改 Sx、Sy、Cx、Cy 和图像宽高,标出来的结果整体偏了一截,查了两天才发现是这几个数字没跟着动。
从那以后我每次标定前都强制走一遍清单:板子型号和描述文件对得上、像元尺寸按米换算过、降采样倍数确认无误、图像张数够且倾斜方向分散、标记直径在每张图里都过了 20 像素、亮部灰度没顶到 240。这套动作跑下来多花五分钟,能省掉后面好几天的返工。手册第六章把该说的都写在了里面,从标定板坐标系到畸变模型的公式,从参数元组顺序到各类相机的限制,一字未删地译成中文,遇到拿不准的地方翻一翻,比在网上翻十篇二手总结快得多。希望帮到你。
本文还有配套的精品资源,点击获取