4、手眼标定:眼在手上与眼在手外
手眼标定,说白了就是让机器人知道「相机看到的物体到底在机器人坐标系下的什么位置」。我刚开始接触这个课题时,总觉得不就是个坐标变换嘛,后来真上手做项目才发现,这里面的坑比想象中多得多。
这一章我们重点聊两个经典配置:眼在手上(Eye-in-Hand)和眼在手外(Eye-to-Hand)。以及HALCON里对应的标定算子怎么用。
4.1 两种配置的核心区别
先看一张图,帮你快速建立直观印象:
两种配置的差异,我用一个表格帮你快速对比:
| 对比项 | 眼在手上(Eye-in-Hand) | 眼在手外(Eye-to-Hand) |
|---|---|---|
| 相机安装位置 | 固定在机器人末端(法兰盘) | 固定在工作空间外(天花板/支架) |
| 标定板位置 | 固定在工作空间内 | 固定在机器人末端 |
| 标定目标 | 求相机与机器人末端的变换关系 | 求相机与机器人基座的变换关系 |
| 运动特点 | 相机随机械臂运动,视野变化 | 相机固定,视野不变 |
| 典型应用 | 抓取、装配、焊缝跟踪 | 定位、检测、码垛 |
4.2 眼在手上(Eye-in-Hand)标定
这种配置下,相机装在机器人末端,跟着机械臂一起动。标定板固定在工作台上不动。
我们需要求解的是:相机坐标系 → 机器人末端坐标系的变换矩阵。记作 CamInTool。
标定的核心思路是:让机器人带着相机运动到多个不同位姿,在每个位姿下拍摄标定板。利用「标定板在机器人基座下的位置不变」这个约束,建立方程求解。
核心公式(AX = XB):
眼在手上标定最终归结为求解 A · X = X · B 形式的矩阵方程。
其中 A 是机器人末端的两次运动变换,B 是相机观测到的标定板两次变换,X 就是我们要的 CamInTool。
4.2.1 HALCON 实现步骤
我个人习惯用 calibrate_hand_eye 这个算子,它封装了求解过程。具体步骤分四步走:
- 采集图像:让机器人运动到 10~15 个不同位姿,每个位姿拍一张标定板图像。注意位姿要覆盖不同角度和距离,不要都在一个平面上转。
- 提取标定板:用
find_calib_object提取标定板角点。 - 获取机器人位姿:从机器人控制器读取每个拍摄位姿对应的末端位姿(ToolInBase)。
- 执行标定:调用
calibrate_hand_eye计算 CamInTool。
* 创建标定模型
create_calib_data ('calibration_object', 'hand_eye', CalibDataID)
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', CameraParam)
set_calib_data_calib_object (CalibDataID, 0, 'calplate_30mm.cpd')
* 循环添加图像和机器人位姿
for i := 1 to NumImages by 1
read_image (Image, 'image_' + i$'02d')
find_calib_object (Image, CalibDataID, 0, 0, i, [], [])
get_calib_data (CalibDataID, 0, 'tool_in_base_pose', i, ToolInBasePose)
set_calib_data (CalibDataID, 'tool', i, 'tool_in_base_pose', ToolInBasePose)
endfor
* 执行手眼标定
calibrate_hand_eye (CalibDataID, Errors)
get_calib_data (CalibDataID, 'camera', 0, 'cam_in_tool', CamInToolPose)
我的经验:标定板不要太小,30mm 以上的棋盘格或圆点板比较稳。太小了远距离拍不清楚,标定精度会打折扣。
4.3 眼在手外(Eye-to-Hand)标定
这种配置下,相机固定安装在天花板或支架上,标定板装在机器人末端。
我们需要求解的是:相机坐标系 → 机器人基座坐标系的变换矩阵。记作 CamInBase。
标定思路类似,但约束条件变了:这次是「标定板在相机坐标系下的位置」通过机器人运动间接关联。
核心公式(AX = ZB):
眼在手外标定归结为求解 A · X = Z · B 形式的矩阵方程。
其中 A 是机器人末端的运动变换,B 是相机观测到的标定板变换,X 是 CamInBase,Z 是标定板在机器人末端的变换。
4.3.1 HALCON 实现步骤
步骤和眼在手上非常相似,只是传入的位姿含义不同:
- 采集图像:机器人带着标定板运动到不同位姿,相机拍摄。
- 提取标定板:同样用
find_calib_object。 - 获取机器人位姿:读取每个拍摄位姿对应的末端位姿(ToolInBase)。
- 执行标定:调用
calibrate_hand_eye,但设置标定类型为 'eye_to_hand'。
* 创建标定模型,指定为眼在手外
create_calib_data ('calibration_object', 'eye_to_hand', CalibDataID)
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', CameraParam)
set_calib_data_calib_object (CalibDataID, 0, 'calplate_30mm.cpd')
* 循环添加图像和机器人位姿
for i := 1 to NumImages by 1
read_image (Image, 'image_' + i$'02d')
find_calib_object (Image, CalibDataID, 0, 0, i, [], [])
get_calib_data (CalibDataID, 0, 'tool_in_base_pose', i, ToolInBasePose)
set_calib_data (CalibDataID, 'tool', i, 'tool_in_base_pose', ToolInBasePose)
endfor
* 执行手眼标定
calibrate_hand_eye (CalibDataID, Errors)
get_calib_data (CalibDataID, 'camera', 0, 'cam_in_base', CamInBasePose)
注意:眼在手外标定时,标定板必须牢固固定在机器人末端,不能有松动。我曾经遇到过一个项目,标定板用双面胶粘的,结果运动过程中轻微晃动,标定误差直接飙到 5mm 以上。后来改用螺丝固定才解决。
4.4 HALCON 手眼标定算子详解
HALCON 提供了一套完整的手眼标定工具链,核心算子就这几个:
| 算子 | 功能 | 使用场景 |
|---|---|---|
create_calib_data |
创建标定数据模型 | 所有手眼标定开始前 |
set_calib_data_cam_param |
设置相机内参 | 加载标定好的相机参数 |
set_calib_data_calib_object |
设置标定板描述文件 | 指定标定板型号 |
find_calib_object |
在图像中查找标定板 | 每张图像都要调用 |
set_calib_data |
设置机器人位姿等数据 | 传入 ToolInBase 位姿 |
calibrate_hand_eye |
执行手眼标定计算 | 核心求解步骤 |
get_calib_data |
获取标定结果 | 提取 CamInTool 或 CamInBase |
避坑指南:我曾经在标定结果出来后,直接拿 CamInTool 去算抓取位姿,结果发现总是偏一点。后来排查发现,是我忘了把机器人末端工具长度考虑进去。记住:ToolInBase 是机器人法兰盘中心,不是工具末端。如果你的机器人有夹爪或吸盘,需要额外做工具坐标系标定。
4.5 标定质量评估
标定做完不是就完事了,一定要验证。我一般用两个方法:
- 重投影误差:把标定板角点通过标定结果反投影回图像,看像素偏差。一般小于 0.5 像素就算不错。
- 实际抓取测试:放一个已知位置的工件,让机器人去抓。实测位置和理论位置的偏差就是最终精度。
HALCON 的 calibrate_hand_eye 会返回 Errors 数组,里面包含了每次观测的残差。我习惯先看这个值,如果某个位姿的残差特别大,就检查一下是不是那张图拍糊了或者机器人位姿读错了。
总结一下:
- 眼在手上:相机跟着机械臂动,标定板固定。求 CamInTool。
- 眼在手外:相机固定,标定板跟着机械臂动。求 CamInBase。
- HALCON 用
calibrate_hand_eye一个算子搞定两种模式,关键在创建标定模型时指定类型。 - 标定质量一定要验证,别信一次标定就万事大吉。