1. 手眼标定到底在标什么:先把坐标系这件事聊透
做机器视觉项目,绕不开的一个坎就是手眼标定。很多人第一次听到这个词觉得玄乎,其实说白了就一件事:让相机看到的像素坐标,和机械手能走到的物理坐标,建立起一个靠谱的换算关系。没有这个关系,相机告诉机械手“目标在图像第 320 行、第 240 列”,机械手根本不知道往哪儿动。
我接触过的项目里,标定没做好导致抓偏、贴歪、点胶跑位的案例,比算法本身出问题的还多。所以这篇就把 Halcon 手眼标定从“眼在手外”到“眼在手上”的九点标定全流程拆开讲,尽量把每个环节的“为什么”说清楚。
先明确两个核心概念,这是后面所有操作的基础:
- 眼在手外(Eye-to-Hand):相机固定在支架上,不动,看着工作台。机械手在相机视野里运动。相机坐标系和机械手基坐标系是固定的,标定的是相机与机械手基座之间的关系。
- 眼在手上(Eye-in-Hand):相机装在机械手末端,跟着手一起动。相机看到的画面随机械手位姿变化,标定的是相机与机械手末端工具坐标系之间的关系。
这两个场景的标定思路完全不同。眼在手外,标定一次基本就固定了;眼在手上,每次换工具或者碰撞后都可能需要重新标。九点标定是这两种场景下最常用的入门方法,因为它只需要机械手走九个点,配合相机识别九个点的像素坐标,就能解出变换矩阵。
适合谁看这篇?如果你正在做上下料、定位抓取、点胶、贴合这类项目,手里有 Halcon 和一台能走点位记录的机械手,那这篇就是给你写的。不需要你有多深的数学背景,但需要你愿意动手把九个点老老实实走完。
2. 九点标定为什么是九点:原理与方案选型拆解
2.1 九点标定的数学本质
九点标定本质上是在求解一个二维仿射变换。相机看到的是像素坐标 (u, v),机械手走的是物理坐标 (x, y),两者之间的关系可以写成:
x = a*u + b*v + c y = d*u + e*v + f这里有 6 个未知数。理论上 3 个不共线的点就能解出来,那为什么大家都用九点?因为实际现场有镜头畸变、机械手重复定位误差、标定点识别误差,点越多,最小二乘拟合越稳。九个点组成 3x3 网格,覆盖整个工作视野,能把视野边缘的畸变也纳入拟合,比三个点靠谱得多。
Halcon 里对应的算子是vector_to_hom_mat2d,输入像素点和物理点两组坐标,输出一个 2D 齐次变换矩阵。这个矩阵就是后面所有坐标转换的核心。
2.2 标定板标定和九点标定的区别
这是热词里问得最多的问题之一。我直接给结论:
| 对比项 | 九点标定 | 标定板标定 |
|---|---|---|
| 标定对象 | 像素坐标到物理坐标的映射 | 相机内参(焦距、畸变)和外参 |
| 需要机械手 | 需要,必须走点 | 不需要,静态拍多张即可 |
| 输出结果 | 2D 仿射/透视矩阵 | 相机内参矩阵 + 畸变系数 |
| 适用场景 | 平面定位抓取 | 需要去畸变、3D 测量 |
| 精度影响 | 受机械手精度影响大 | 受标定板精度影响大 |
简单说,九点标定解决的是“像素到物理”的映射,标定板标定解决的是“相机本身准不准”。如果你的项目是平面抓取,镜头畸变不大,九点标定足够。如果要做高精度测量或者 3D 应用,得先做标定板标定把内参和畸变修正好,再做九点标定。
2.3 眼在手外和眼在手上的方案差异
眼在手外的标定流程相对简单:相机固定,机械手末端装一个尖点工具(比如针尖),走九个点,相机同时拍到这九个点,记录像素坐标和机械手基座坐标,直接拟合。标定完成后,相机视野内任意像素点都能换算成机械手基座坐标。
眼在手上就麻烦一些。相机跟着手走,标定时通常有两种做法:
- 机械手末端装标定针,相机看固定在工作台上的标定板,机械手走九个不同位姿,每个位姿拍一次标定板,记录机械手末端位姿和标定板在相机中的位姿,解 AX=XB 方程。
- 简化做法:固定一个标记点在工作台上,机械手带着相机走九个位姿,每个位姿让标记点出现在图像不同位置,记录机械手末端坐标和标记点像素坐标,拟合出相机到末端的变换。
第一种精度高但复杂,第二种适合快速上手。这篇主要讲第二种简化九点法,因为它更容易在现场落地。
注意:眼在手上的标定结果和工具坐标系强相关。如果你换了夹爪或者工具,标定矩阵必须重做,否则抓取会系统性偏移。
3. 实操前的硬准备:环境、工具与参数确认
3.1 Halcon 环境与 license 确认
Halcon 的安装和 license 配置是第一步。我见过太多人卡在 license 上,代码写完了跑不起来。Halcon 的 license 通常绑定网卡 MAC 地址,换机器或者换网卡后需要重新申请。安装完成后,建议先跑一下 Halcon 自带的示例程序,确认HDevelop能正常打开和运行。
环境变量方面,如果你要用 C++ 或 Qt 调用 Halcon,需要配置HALCONROOT、HALCONARCH等变量,并把bin目录加入PATH。Qt 调用 Halcon 的典型做法是在.pro文件里链接halconcpp.lib,然后包含HalconCpp.h。这一步配置不对,编译会报一堆链接错误。
3.2 标定工具与机械手配合
九点标定需要一个能在图像中稳定识别的标记。常见选择:
- 尖点工具:机械手末端装针尖,相机从上方拍,识别针尖位置。优点是直接对应工具中心点(TCP),缺点是针尖在图像中可能不够清晰。
- 圆形标记:在工作台或末端装一个高对比度圆点,用
find_circle或find_shape_model识别圆心。稳定性好,推荐。 - 棋盘格角点:精度高,但需要机械手走点时角点正好在视野内,操作稍麻烦。
机械手方面,你需要能手动示教九个点,并记录每个点的基座坐标(眼在手外)或末端坐标(眼在手上)。建议用机械手的示教器走点,同时用 Halcon 的采集助手实时抓图,确保每个点都能被稳定识别。
3.3 标定前的参数确认清单
动手之前,把这几个参数确认清楚,能省掉后面大量返工:
- 相机分辨率与视野范围:确保九个点都在视野内,且边缘点不要贴边。
- 镜头畸变情况:如果畸变明显,先做标定板标定去畸变,再九点标定。
- 机械手重复定位精度:一般要求优于标定精度的 1/3。比如你要 0.1mm 精度,机械手重复定位得在 0.03mm 以内。
- 工作平面高度:九点标定是 2D 的,所有标定点必须在同一高度平面。如果高度变化,需要做 3D 标定或者分层标定。
- 光照稳定性:标定时的光照要和实际运行时一致,否则标记识别会漂。
4. 眼在手外九点标定全流程实操
4.1 九个点的走位策略
九个点怎么走,直接影响标定质量。我的习惯是走一个 3x3 网格,覆盖相机视野的 70% 到 80% 区域,不要贴边。具体做法:
- 在视野中心走第一个点,记录机械手基座坐标 (x0, y0),同时用 Halcon 抓图识别标记像素坐标 (u0, v0)。
- 沿 X 方向等间距走三个点,间距根据视野宽度定,比如视野 200mm,间距取 50mm。
- 沿 Y 方向同样走三行,形成 3x3 网格。
走点时注意:机械手每次移动后要等稳定再抓图,避免振动导致图像模糊。抓图后立即保存图像和对应坐标,不要等九个点走完再一起处理,容易乱。
4.2 Halcon 识别标记并提取像素坐标
假设我们用圆形标记,Halcon 代码大致如下:
* 读取图像 read_image (Image, 'calib_point_1.png') * 转灰度 rgb1_to_gray (Image, GrayImage) * 阈值分割 threshold (GrayImage, Regions, 100, 255) * 连通域 connection (Regions, ConnectedRegions) * 筛选圆形区域 select_shape (ConnectedRegions, SelectedRegions, 'circularity', 'and', 0.8, 1.0) * 拟合圆 fit_circle_contour_xld (SelectedRegions, 'algebraic', -1, 0, 0, 3, 2, Row, Column, Radius, StartPhi, EndPhi, PointOrder)这里Row和Column就是标记中心的像素坐标。实际项目中,我建议用find_shape_model做模板匹配,稳定性比阈值分割好,尤其是光照有波动的时候。
4.3 坐标数据整理与矩阵拟合
九个点走完,你会得到两组数据:
- 像素坐标:
(u1, v1)到(u9, v9) - 机械手基座坐标:
(x1, y1)到(x9, y9)
在 Halcon 里拟合:
* 像素坐标数组 Px := [u1, u2, u3, u4, u5, u6, u7, u8, u9] Py := [v1, v2, v3, v4, v5, v6, v7, v8, v9] * 机械手坐标数组 Qx := [x1, x2, x3, x4, x5, x6, x7, x8, x9] Qy := [y1, y2, y3, y4, y5, y6, y7, y8, y9] * 拟合变换矩阵 vector_to_hom_mat2d (Px, Py, Qx, Qy, HomMat2D)得到的HomMat2D就是标定矩阵。后面用affine_trans_point_2d就能把任意像素坐标转成机械手坐标:
affine_trans_point_2d (HomMat2D, u, v, x, y)4.4 标定精度验证方法
标定完不能直接上生产,必须验证。我的做法是:
- 在视野内随机选 5 个不在标定点上的位置。
- 用相机识别这些位置的标记,通过标定矩阵算出机械手坐标。
- 让机械手实际走到这些坐标,用针尖比对偏差。
- 记录每个点的偏差,计算平均误差和最大误差。
一般平面抓取项目,平均误差控制在 0.1mm 以内算合格。如果超过 0.2mm,需要排查是机械手精度问题还是标定点识别问题。
实操心得:验证时一定要用标定点之外的点,用标定点验证等于自己骗自己。我见过有人用九个标定点回代,误差 0.01mm,结果实际抓取偏了 0.5mm,就是因为标定点拟合过度。
5. 眼在手上九点标定全流程实操
5.1 眼在手上标定的核心差异
眼在手上,相机装在机械手末端,标定的是相机坐标系到机械手末端工具坐标系的变换。简化九点法的思路是:在工作台上固定一个标记点,机械手带着相机走九个位姿,每个位姿让标记点出现在图像的不同位置,记录机械手末端坐标和标记点像素坐标,拟合出变换关系。
这里有个关键点:机械手末端坐标是工具坐标系,不是基座坐标系。如果你记录的是基座坐标,需要先通过工具坐标系转换到末端。Halcon 里可以用hom_mat3d_compose组合变换。
5.2 九个位姿的设计原则
眼在手上的九个位姿设计比眼在手外讲究。因为相机跟着手动,标记点在图像中的位置会随位姿变化。我的建议:
- 九个位姿覆盖机械手工作空间的一个平面区域,Z 高度保持一致。
- 每个位姿下,标记点都要在相机视野内,且尽量分布在图像不同区域。
- 位姿变化时,尽量让相机光轴保持垂直向下,避免大角度倾斜导致标记变形。
具体走位可以是:在 XY 平面上走 3x3 网格,每个点机械手末端旋转角度保持一致(比如都保持 0 度),这样标定出来的是纯平移关系。如果需要考虑旋转,可以增加位姿变化,但九点法一般只解平移和缩放。
5.3 Halcon 实现代码与数据记录
眼在手上的 Halcon 代码和眼在手外类似,区别在于记录的机械手坐标是末端坐标:
* 假设已获取九个位姿的像素坐标和末端坐标 Px := [u1, u2, u3, u4, u5, u6, u7, u8, u9] Py := [v1, v2, v3, v4, v5, v6, v7, v8, v9] * 末端坐标(工具坐标系) Qx := [tx1, tx2, tx3, tx4, tx5, tx6, tx7, tx8, tx9] Qy := [ty1, ty2, ty3, ty4, ty5, ty6, ty7, ty8, ty9] * 拟合 vector_to_hom_mat2d (Px, Py, Qx, Qy, HomMat2D)得到的矩阵表示:像素坐标 -> 末端坐标。实际抓取时,相机看到目标像素坐标,通过矩阵算出末端需要移动到的坐标,再结合当前末端位姿,计算出机械手应该走的基座坐标。
5.4 眼在手上标定的验证与补偿
眼在手上的验证比眼在手外麻烦,因为相机在动。我的做法是:
- 标定完成后,让机械手走到一个任意位姿,相机拍标记点,通过矩阵算出末端坐标。
- 对比机械手实际末端坐标,计算偏差。
- 如果偏差大,检查标定点识别是否稳定,或者机械手末端坐标记录是否准确。
眼在手上还有一个常见问题:相机安装角度偏差。如果相机光轴和工具轴线不平行,标定矩阵会包含一个旋转分量,导致不同位姿下抓取偏移不一致。解决办法是在标定前用机械手走一个纯旋转,观察标记点在图像中的移动轨迹,如果轨迹是圆弧而不是点,说明有角度偏差,需要先机械调整相机安装。
注意:眼在手上标定后,如果机械手碰撞或者相机松动,标定矩阵会失效。建议每次开机或者换班时,用标记点快速验证一次,偏差超过阈值就重新标定。
6. 常见问题与排查技巧实录
6.1 标定误差大的排查思路
标定误差大是最常见的问题。我整理了一个排查表:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 整体偏移 | 标定点识别中心不准 | 检查标记识别算法,用模板匹配替代阈值 |
| 边缘误差大 | 镜头畸变未校正 | 先做标定板标定去畸变 |
| 随机误差大 | 机械手重复定位差 | 同一点走多次,看坐标波动 |
| 局部误差大 | 标定点分布不均 | 重新设计九点分布,覆盖全视野 |
| 旋转后误差大 | 相机安装角度偏差 | 检查相机光轴与工具轴线平行度 |
6.2 标记识别不稳定的处理
标记识别不稳定,标定矩阵就会漂。常见原因和解决办法:
- 光照变化:固定光源,标定和运行时保持一致。如果现场光照不可控,用
find_shape_model做模板匹配,比阈值分割抗干扰。 - 标记对比度低:换高对比度标记,比如黑白圆点。或者用背光照明。
- 标记部分遮挡:确保九个点走位时标记完整出现在视野内,不要被工具或夹具挡住。
- 图像模糊:机械手移动后等稳定再抓图,或者用短曝光加光源。
6.3 机械手坐标记录错误的避免
机械手坐标记录错误是隐蔽性最强的问题。我踩过的坑:
- 记录的是基座坐标,但标定需要末端坐标,导致矩阵完全错误。
- 九个点的坐标记录顺序和图像顺序不一致,拟合出来乱七八糟。
- 机械手走点时用了不同的工具坐标系,导致坐标基准不统一。
避免方法:走点时用表格记录,每走一个点,立即填写像素坐标和机械手坐标,并标注序号。走完九个点后,先检查坐标数据是否有明显异常值,再拟合。
6.4 Halcon 版本与算子兼容问题
Halcon 不同版本的算子可能有差异。比如vector_to_hom_mat2d在旧版本和新版本中参数基本一致,但find_shape_model的参数在不同版本中有调整。如果你从网上抄代码,注意版本匹配。另外,Halcon 的 license 如果过期,程序会在运行时突然报错,建议在项目里加 license 检查逻辑。
7. 标定结果的工程化落地与维护
7.1 标定矩阵的保存与加载
标定矩阵不能每次开机重新算,必须保存下来。Halcon 里可以用write_tuple保存矩阵:
write_tuple (HomMat2D, 'calib_matrix.tup')加载时:
read_tuple ('calib_matrix.tup', HomMat2D)如果项目用 C++ 或 Qt 调用 Halcon,可以把矩阵保存到配置文件或者数据库,程序启动时加载。建议同时保存标定日期、标定点数据、误差统计,方便追溯。
7.2 标定失效的快速检测
生产线上,标定失效如果不能及时发现,会导致批量不良。我的做法是在工作台固定一个验证标记,每次开机或者换班时,让相机拍这个标记,通过标定矩阵算出机械手坐标,和实际走到的坐标比对。偏差超过阈值就报警,提示重新标定。
这个验证标记的位置要固定,且不在标定点上,这样能真实反映标定状态。
7.3 多相机多工位的标定管理
如果项目有多个相机或者多个工位,每个相机和工位都需要独立标定。建议建立标定档案,记录每个相机的标定矩阵、标定时间、标定人、误差数据。换相机或者换工位时,直接调用对应的标定文件,避免混淆。
7.4 标定精度的长期稳定性
标定精度会随时间和环境变化。机械手磨损、相机松动、温度变化都会影响。建议定期复检,比如每周一次。复检方法和验证一样,用固定标记比对。如果偏差趋势性增大,说明硬件有问题,需要检修。
我个人在实际操作中的体会是,九点标定看起来简单,但真正做好需要耐心。九个点走位、识别、记录、拟合、验证,每一步都不能马虎。尤其是验证环节,很多人省掉,结果上线后问题一堆。另外,标定矩阵不是一劳永逸的,现场环境变化后一定要复检。最后分享一个小技巧:标定时用手机拍下九个点的走位和标记识别画面,后面排查问题时能快速定位是哪个点出了问题。