4. 深度相机外参标定
外参标定,说白了就是搞清楚相机和机器人手臂之间的位置关系。你想想看,相机看到工件在空间里的坐标,但机器人要去抓它,这两个坐标系如果不统一,那抓取肯定偏到姥姥家去。
我刚开始做3D视觉引导项目时,就吃过这个亏。当时觉得标定差不多就行了,结果抓取精度死活上不去。后来才发现,外参差了2毫米,末端执行器就偏了5毫米。嗯,从那以后我再也不敢马虎了。
4.1 两种手眼配置:Eye-to-Hand vs Eye-in-Hand
先搞清楚基本概念。这两种配置,决定了你的标定流程和算法完全不同。
| 对比项 | Eye-to-Hand(眼在手外) | Eye-in-Hand(眼在手上) |
|---|---|---|
| 相机安装位置 | 固定在工作台上方或侧面 | 固定在机器人末端法兰上 |
| 相机与机器人关系 | 相机固定,机器人运动 | 相机随机器人一起运动 |
| 标定目标矩阵 | 相机坐标系 → 机器人基坐标系 | 相机坐标系 → 机器人末端坐标系 |
| 标定方程形式 | AX = XB(经典手眼方程) | AX = XB(形式相同,但含义不同) |
| 适用场景 | 大范围、高精度、固定工位 | 小范围、灵活、需要跟随视角 |
核心区别一句话:Eye-to-Hand标定的是相机到机器人基座的变换;Eye-in-Hand标定的是相机到机器人末端的变换。两者都解AX=XB,但A和B的构造方式不同。
4.2 手眼标定的数学原理:AX = XB
这个方程看着简单,但坑不少。我来拆解一下。
假设你让机器人运动到N个不同的位姿。在每个位姿下,你都能得到两组数据:
- A矩阵:机器人末端相对于基座的变换(从机器人控制器读取)
- B矩阵:标定板相对于相机的变换(从视觉算法计算)
对于相邻的两个位姿i和j,有:
A_ij = inv(T_base_end_i) * T_base_end_j
B_ij = inv(T_cam_calib_i) * T_cam_calib_j
其中:
A_ij:机器人从位姿i到位姿j的运动
B_ij:标定板从位姿i到位姿j的运动
手眼方程:A_ij * X = X * B_ij
X:就是我们要标定的外参矩阵
为什么是AX=XB?我个人习惯这样理解:机器人动了,相机看到的标定板也动了,这两个运动通过一个固定的变换X联系起来。说白了,X就是那个"翻译官"。
4.3 Eye-to-Hand标定实现步骤
我在项目中用过最多的就是Eye-to-Hand。为什么?因为稳定。相机固定不动,标定一次管很久。
- 准备标定板:Halcon里用calplate或者自定义的圆点标定板。我建议用陶瓷材质的,反光小,精度高。
- 安装相机:确保相机视野覆盖机器人工作区域。注意,不要让机器人本体遮挡视野。
- 采集数据:让机器人带着标定板运动到10~15个不同位姿。每个位姿下:
- 记录机器人末端位姿(从控制器读取)
- 拍摄标定板图像,计算标定板在相机坐标系下的位姿
- 构造A和B矩阵:按上面的公式计算相邻位姿间的变换。
- 求解X:Halcon里用
hand_eye_calibration算子。 - 验证精度:用一组新的位姿数据做重投影误差验证。
我的经验:采集数据时,让机器人在视野的四个角落和中心各取几个位姿。姿态变化要大,平移和旋转都要有。我曾经只做了平移运动,结果标定出来的旋转分量误差很大,后来加了旋转运动才解决。
4.4 Eye-in-Hand标定实现步骤
Eye-in-Hand适合需要灵活视角的场景,比如焊接、喷涂。但标定过程稍微麻烦一点。
- 固定标定板:标定板放在工作台上固定不动。
- 安装相机:相机固定在机器人末端法兰上。
- 采集数据:让机器人运动到10~15个不同位姿,确保标定板始终在视野内。每个位姿下:
- 记录机器人末端位姿
- 拍摄标定板,计算标定板在相机坐标系下的位姿
- 构造A和B矩阵:注意!这里的A和B含义与Eye-to-Hand不同。
- A_ij = inv(T_base_end_i) * T_base_end_j(相同)
- B_ij = inv(T_cam_calib_i) * T_cam_calib_j(相同)
- 但X的含义不同:X = T_cam_end(相机到末端的变换)
- 求解X:同样用
hand_eye_calibration算子,但参数设置不同。
注意:Eye-in-Hand标定中,标定板必须固定不动。如果标定板移动了,整个标定就废了。我曾经有一次没注意,标定板被风吹动了,结果标定出来的外参完全不对,排查了半天才发现。
4.5 Halcon实现代码示例
下面给出一段完整的Eye-to-Hand标定代码。我习惯把数据采集和标定分开写,这样调试方便。
* 创建标定数据模型
create_calib_data ('hand_eye_moving_cam', 'calibration_object', CalibDataID)
* 设置相机参数(内参已标定)
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', CamParam)
* 设置标定板描述
set_calib_data_calib_object (CalibDataID, 0, 'calplate_30mm', CalibObjDescr)
* 循环添加标定位姿
for i := 1 to NumPoses by 1
* 读取机器人末端位姿(从控制器获取)
* 格式:[X, Y, Z, Rx, Ry, Rz] 或 4x4矩阵
read_robot_pose (RobotPoses[i], RobotPose)
* 读取标定板位姿(从图像计算)
find_calib_object (Image, CalibDataID, 0, 0, i, [])
get_calib_data_observ_pose (CalibDataID, 0, 0, i, CalibPose)
* 添加到位姿列表
tuple_concat (RobotPoseList, RobotPose, RobotPoseList)
tuple_concat (CalibPoseList, CalibPose, CalibPoseList)
endfor
* 执行手眼标定
hand_eye_calibration (RobotPoseList, CalibPoseList, \
'moving_cam', 'nonlinear', \
'all', CamParam, CalibObjDescr, \
Error, HandEyePose)
* 输出结果
disp_message (WindowHandle, '标定误差: ' + Error + ' pixel', \
'window', 12, 12, 'black', 'true')
disp_message (WindowHandle, '外参矩阵已保存', \
'window', 12, 40, 'black', 'true')
关键参数说明:
'moving_cam':表示相机运动(Eye-in-Hand用'moving_cam',Eye-to-Hand用'stationary_cam')'nonlinear':非线性优化,精度更高,但需要好的初始值'all':同时优化旋转和平移
4.6 标定精度验证与常见问题
标定完了,怎么知道准不准?我一般做三步验证:
- 重投影误差:用标定结果把3D点投影到图像上,看偏差。Halcon的
hand_eye_calibration会直接输出Error,一般小于0.5 pixel就算不错。 - 抓取测试:让机器人去抓一个已知位置的工件。如果抓取偏差在允许范围内,说明标定有效。
- 重复性测试:同一个位姿重复标定多次,看结果的波动。波动越小越好。
避坑指南:
- 标定板不要太小,否则视野边缘的畸变会影响精度。我一般用30mm以上的标定板。
- 采集位姿时,避免所有位姿都在同一个平面上。要有深度变化。
- 机器人运动精度会影响标定结果。如果机器人本身重复定位精度差,标定结果也不会好。
- 光照变化会影响标定板检测。建议用漫反射光源,避免强光直射。
4.7 知识结构图
下面这张图总结了手眼标定的核心逻辑,你可以对照着理解整个流程。
4.8 总结与个人建议
手眼标定是3D视觉引导项目的关键一步。我个人建议:
- 优先选Eye-to-Hand:如果现场条件允许,固定相机比移动相机更稳定,标定一次能用很久。
- 数据质量比数量重要:10个好的位姿胜过20个差的位姿。确保位姿分布均匀,姿态变化充分。
- 标定后一定要验证:不要相信标定结果直接上线。用实际抓取测试来验证,这才是金标准。
- 定期复标:相机松动、机器人磨损、温度变化都会影响外参。我一般建议每3个月复标一次。