
做机器人视觉集成十有八九要过手眼标定这道坎。我之前接过几个项目图像识别都做到了亚像素模板匹配稳得很偏偏机械臂一抓就跑偏折腾了两周才发现是手眼标定没做对。这篇文章我把Halcon手眼标定的完整流程、核心坐标变换逻辑以及我自己踩过的坑全部摊开讲一遍适合刚接触Halcon的机器视觉工程师也适合那些标了好几次但精度一直不达标、正打算重新梳理坐标系关系的朋友。1. 手眼标定的本质先搞懂坐标系关系再动手很多人一上来就找代码、找算子结果标完当场能对上换一个位置就飘。问题往往出在没搞明白手眼标定到底在求什么。我希望你在敲任何一行代码之前先花十分钟把这一节看明白。1.1 两种手眼模型相机装手上和相机固定区别在哪手眼标定分为两大基本场景。第一种是眼在手上英文叫Eye-in-HandHalcon里对应的字符串是moving_camera。相机装在机械臂末端跟着机械臂一起动标定板固定在工作台面上。第二种是眼在手外英文叫Eye-to-HandHalcon里对应stationary_camera。相机固定安装在支架上标定板固定在机械臂末端机械臂带着标定板在相机视野里运动。这两种方式求解的未知量不一样同一套数据不能混用。眼在手上时我们需要知道相机坐标系相对机械臂末端工具坐标系的固定变换因为相机是拴在工具上的这个变换一旦确定相机随动过程里就能随时知道相机在空间里的实际位置。眼在手外时相机固定不动机械臂工具在整个工作空间里移动我们需要知道的是机器人基座坐标系相对固定相机坐标系的变换这样相机拍到的目标点才能换算进机器人基坐标系里。我见过不少人把两种模型搞反。最典型的错误是相机明明固定在上面却选了moving_camera模式去标结果手眼矩阵乱七八糟标定报错或者误差巨大。所以动手之前先确认一件事你的相机到底在跟着机械臂动还是死都不动这个问题回答错了后面全白干。1.2 数学方程AXXB背后的真实含义手眼标定的数学本质是解一个AXXB的矩阵方程或者眼在手外模型下的AXZB。这里不展开推导但你需要理解一个核心思想机械臂每次运动末端位姿会变化标定板在相机坐标系里的位姿也会变化但相机与末端之间的固定变换是始终不变的。拿眼在手上来说设工具坐标系到相机坐标系的变换为X。对于第i个位姿机械臂控制器告诉我们工具在机器人基坐标系下的位姿Ti视觉算法告诉我们标定板在相机坐标系下的位姿Ci。因为标定板固定在世界里所以可以建立等式通过工具到基座的变换、工具到相机的变换、相机到标定板的变换最终得到的标定板在基座坐标系下的位置应该恒定。把两组姿态联立起来就能消掉这个恒定值得到一个AXXB形式的方程。为什么至少需要多组姿态因为单组姿态下方程欠约束解不唯一。实际工程中我建议采集15到25组姿态姿态之间要有明显的平移和旋转差异。这里说的旋转差异不是相机自动改变曝光那种而是标定板在图像里呈现出的角度变化尤其是绕相机光轴方向的旋转以及前后俯仰、左右摆动。这些信息直接决定了旋转分量能不能被稳定解出。1.3 为什么必须先标定相机内参手眼标定解决的是相机与机器人之间的外参关系但你用它之前相机内参必须可靠。内参包括焦距、主点、畸变系数。如果内参不准视觉系统求出的标定板位姿本身就有系统性偏差这个偏差会直接灌进手眼标定的观测数据里最后解出来的手眼矩阵当然也不准。我习惯的流程是先用Halcon的Calibration Assistant单独做一次相机内参标定拍摄20张左右不同角度的标定板图像覆盖视野中心和边缘再检查平均重投影误差。重投影误差控制在0.1到0.3像素之间是比较健康的水平。如果你手里的镜头有变焦功能标定之后一定要锁死焦环、光圈和任何可调环否则焦距变了内参就失效了手眼标定相当于在流沙上盖楼。另外如果项目涉及更换镜头或相机内参必须重新标一次别偷懒沿用旧参数。2. Halcon手眼标定完整流程从准备到出结果理解原理之后就可以进入实操了。这一节我按我自己做项目的顺序把整个流程从零走一遍。你手里需要有一台能输出精确位姿的机械臂、一台工业相机、一个Halcon可识别的标定板。2.1 标定板选择与描述文件生成Halcon的手眼标定推荐使用它的圆形标定板英文文档里叫caltab也就是由规则排列的黑色实心圆点组成的标定板。标定板表面通常有白色背景和黑色圆点部分高精度板子用陶瓷或玻璃基底。这里有个大坑提醒你不要拿OpenCV用的棋盘格直接来做Halcon手眼标定Halcon的find_caltab/find_marks_and_pose这一套算子是基于圆点阵列的几何结构设计的你给它一张棋盘格图它大概率直接罢工。Halcon标定板需要配套一个描述文件扩展名是.descr。你可以从标定板厂商那里拿到现成的描述文件也可以用Halcon自己生成。生成描述文件的算子是gen_caltab例如gen_caltab(7, 7, 0.0125, 0.5, caltab_70mm.descr, caltab_70mm.ps)这行代码的意思是生成一个7行7列圆点阵列的标定板描述文件圆点间距12.5毫米圆点直径占间距的0.5倍。输出两个文件descr文件用于Halcon识别ps文件用于打印制版。如果你要定制标定板尺寸需要根据实际加工尺寸修改间距和行列数。描述文件必须和实物严格一致间距差一毫米结果就差一大截。选择标定板尺寸时我的经验是让标定板在相机视野中占据约三分之一的面积。太小了圆点分辨率不足识别不稳定太大了标定板容易出视野寻找区域时容易被裁掉。工业场景经常有70mm、100mm、150mm这些规格具体看你的工作距离和相机视野。2.2 数据采集的姿势与数量要求数据采集是整个手眼标定里最耗时间、也最影响结果的一步。Halcon的算法再强你喂给它的是一堆退化姿态它也解不出好结果。我建议按下面几个原则来采集数量控制在15到25组。少于10组方程约束不够多于40组没有太多额外收益反而可能混入一些劣质数据。标定板在图像中的位置要覆盖视野中心和四个角落。如果只在中间区域移动相机畸变外沿的数据没参与约束边缘区域的定位精度会很差。工作高度要有起伏。让标定板在近景、中景、远景三个深度范围都出现这样求解出的Z方向关系更可靠。姿态角度要尽量多样。让标定板平面相对相机光轴产生明显的倾斜同时在标定板自身平面内旋转不同的角度。这一条最容易被人忽略也是最影响旋转精度的因素。每组数据里标定板的全部圆点必须完整可见。被裁掉一排圆点或者边缘严重遮挡这组数据直接弃用。每次移动机械臂后等设备稳定了再采集图像不要让机械臂在高速运动中抓拍。另外机械臂位姿的记录方式直接影响数据有效性。我不建议在示教器上手动记录数据再一个个敲进代码里那样既容易抄错也无法保证图像和位姿的时间同步。更稳妥的做法是写一个脚本通过机械臂的SDK或TCP/IP通信接口在采集图像的同一时刻读取当前末端位姿然后存成文本或直接传入Halcon脚本。2.3 使用HDevelop脚本完成标定下面这段HDevelop代码是我常用的眼在手上标定流程核心逻辑同样适用于眼在手外只要把create_calib_data时传入的模型字符串换成stationary_cam即可。* 标定板描述文件 CalibPlateDescr : caltab_70mm.descr * 相机内参来自之前单独的内参标定 CamParam : [area_scan_division, 0.012, 0, 0.0024, 0.0024, 1280, 1024, 0, 0] * 创建手眼标定数据模型moving_cam表示眼在手上 create_calib_data (hand_eye_moving_cam, 1, 1, CalibDataID) set_calib_data_cam_param (CalibDataID, 0, [], CamParam) set_calib_data_calib_object (CalibDataID, 0, CalibPlateDescr) * 循环读入所有采集的姿态 NumImages : 20 for i : 1 to NumImages by 1 read_image (Image, pose_ i$02d) * 找到标定板区域 find_caltab (Image, CalibPlateDescr, CalibRegion, 3, 113, 5) * 提取所有圆点并计算标定板位姿 find_marks_and_pose (Image, CalibRegion, CalibPlateDescr, 0, 360, 30, 4, 0.9, MarkRow, MarkCol, PlatePose, Return) * 加入该组图像中圆点与标定板位姿 set_calib_data_observ_points (CalibDataID, 0, 0, i - 1, MarkRow, MarkCol, MarkIndex, PlatePose) * 设置同一时刻机械臂末端在机器人基坐标系中的位姿 set_calib_data (CalibDataID, tool, i - 1, pose, ToolPose) endfor * 执行手眼标定 calibrate_hand_eye (CalibDataID, moving_camera, Errors, ResultPose) * 输出每个姿态的误差 Errors * 输出相机在工具坐标系中的位姿 ResultPosefind_caltab里的几个参数需要根据实际图像微调。中间那个113是阈值相关参数如果标定板区域提取不到可以试着调低最后那个5是圆点扩展参数图像分辨率不同也可能要调整。find_marks_and_pose里的0.9是圆点灰度比例阈值在反光严重或者光线不足时这个值需要适当调整。ToolPose在代码里表示机械臂末端在机器人基坐标系下的位姿。你要确保自己从机器人控制器读出的数据确实是工具坐标系相对基坐标系的7个值或者一个4x4齐次矩阵而不是把机器人某个关节角度误当成了位姿。Halcon的位姿通常用七元组表示包含三个平移量和四个四元数分量从机器人SDK读出的欧拉角需要先转成Halcon pose或者直接把机器人给出的四元数填进去。2.4 读结果ResultPose到底代表什么标定完成之后ResultPose的含义容易让人迷糊。眼在手上模式ResultPose给出的是相机坐标系相对工具坐标系的位姿也就是说从工具坐标系出发经过这个变换就能到达相机坐标系。Halcon文档里把它描述为相机在工具坐标系中的位姿。眼在手外模式ResultPose给出的是工具坐标系在固定相机坐标系中的位姿。很多资料里提到的基座在相机坐标系下的位姿通常是通过这个结果进一步换算得到的不用死记结论但要清楚自己拿到的是哪个方向。实际视觉引导时我们经常要做的事情是相机识别到目标物体得到目标在相机坐标系下的位姿ObjectInCamPose希望机械臂能去抓它。这时需要把它变换到机器人基坐标系。眼在手上的完整链路是先利用工具坐标到手眼矩阵把目标点变换到工具坐标系再通过机器人控制器返回的当前工具在基座下的位姿变换一次最终得到目标在基坐标系中的坐标。变换的载体是齐次矩阵Halcon里用pose_to_hom_mat3d把位姿转成矩阵再用hom_mat3d_transform_pose把点或位姿乘进去。我不建议直接在代码里手工拼矩阵容易把平移和旋转的顺序搞反。Halcon的位姿转矩阵然后矩阵作用在位姿上这套接口已经很成熟直接用就行。要注意的是每次变换前想清楚“当前点是在哪个坐标系下表示的”“乘上这个矩阵之后会跑到哪个坐标系下”只要这条链路在脑子里是清晰的代码很难写错。3. 实战避坑这些坑我替你先踩一遍这一节是全文最想让你记住的部分。理论谁都能讲有些坑只有在实际车间里蹲过、被现场问题折磨过才知道。3.1 标定板只平移不旋转结果直接飘我第一次独立做手眼标定的时候为了省事让机械臂带着标定板在视野里走了一个井字形每个位置姿态完全相同只是左右上下平移。当时Errors看起来不大我还挺高兴结果拿去抓工件中心点附近勉强能对上一跑到视野边缘就偏出好几毫米。后来重新读资料才想明白手眼标定要同时解平移和旋转纯粹平移的数据只能约束平移分量旋转分量几乎没有有效信息参与求解。这就像你让别人猜一把钥匙的形状但你只给他看钥匙在不同桌面上平移的照片角度永远是同一个他当然猜不准钥匙柄怎么弯。正确做法是让标定板在每个采集位置都有不同的姿态。我的习惯是每个位置至少加入一次绕相机光轴的旋转然后每隔几个位置加入一次30度以内的俯仰或偏摆。姿态越丰富旋转矩阵的解越稳定。这里可以做一个简单筛选采集完数据后把标定板在图像里的位姿输出出来肉眼扫一遍角度值如果发现所有姿态的旋转分量几乎一样回去重新采集别舍不得那点工作量。3.2 光照反光导致标定板识别失败Halcon的圆点标定板大多表面光洁车间里的强光一打图片里出现大面积镜面反射圆点区域变成白花花一片find_caltab直接找不到标定板或者find_marks_and_pose提取出的圆点比实际少了一大截。遇到这种问题第一反应不是调算法参数而是先改光照。工业镜头前加偏振片是常用手段能大幅削减金属标定板表面的定向反光。光源方面用漫射LED环形灯或圆顶光源比点光源和条形光源更均匀。实在没有偏振片可以稍微旋转标定板或者相机角度找到一个反光最弱的方向再开始采集。标定板本身也不要选镜面不锈钢基底磨砂或陶瓷基底的抗反光能力强很多贵一点点但省心很多。我在现场还会习惯性检查一下图像直方图标定板区域的灰度不能过曝。如果过曝先把相机曝光时间降下来。Halcon里也可以对图像做一次灰度拉伸预处理再送进find_caltab但我始终觉得图像源头质量不行靠算法硬拉效果有限。3.3 机械臂位姿记录错位标定成功但精度全无有一种情况特别气人标定过程没有任何报错Errors数值也漂亮但放在实际工位上就是偏。排查到最后发现是机械臂位姿数据没对齐。我当时遇到的问题是图像采集由视觉工控机触发机器人位姿却在示教器上手工抄的抄的时候已经是图像拍完之后几秒机械臂末端早就不在那个位置了。视觉数据和机器人数据根本不是同一时刻的量标定结果自然是个四不像。解决思路是把“图像采集”和“位姿记录”绑在同一个时间戳上。只要机械臂支持Socket或Modbus通信建议直接写上位机脚本每次采集图像的瞬间同步读取机器人当前工具位姿并落盘到同一个数据文件里。不要手动抄、不要分两次记录。另外注意机器人控制器返回的位姿通常是当前激活的工具坐标系TCP下的值如果你的现场在末端装了夹具夹具的长度和旋转偏置必须已经写进机器人的工具坐标系里否则读出来的TCP位姿本身就和相机看到的标定板位置对不上。这一条我再强调一次标定结果看起来正常不代表数据没问题。手眼标定是一个全局优化的过程个别数据错位会被“平均”掉一部分但系统性偏差会被保留最终体现在真实抓取误差上。3.4 内参不准、相机松动、安装变形前面说过手眼标定依赖相机内参。如果在内参不准的情况下做手眼标定最常见的现象是视野中心区域定位还可以越往边缘偏差越大。这是因为畸变模型本身是错的标定板在图像边缘的位姿计算已经被带偏手眼方程只能拟合出一个折中解。还有两个机械层面的坑。第一个是相机安装不牢。手眼标定假设相机和机械臂末端之间的变换是固定的如果相机支架在标定过程中发生了微小位移这个假设直接崩塌。所以标定之前把相机支架的每一颗螺丝都紧一遍标定过程中不要碰到相机和线缆。第二个是机械臂负载。如果你在末端装了比较重的夹具机械臂在不同姿态下的下垂变形量不一样末端实际位置和控制器理论位置会有偏差。我建议带着实际负载去做手眼标定而不是空载标完再装夹具。另外镜头的问题也容易被忽略。自动光圈、自动对焦镜头在手眼标定过程中如果发生微小变化内参就会漂移。工业项目里我一般要求使用定焦手动光圈镜头光圈调好后锁死并且把对焦环用胶带固定住。那些看似高级的自动功能在这种精度标定场景下反而是隐患。3.5 标定板本身别大意尺寸、平整度与固定方式标定板不是一张打印纸贴上去就能用的。打印纸的热胀冷缩、弯曲褶皱都会直接改变圆点间距等于篡改了描述文件里的物理尺寸。我曾经图方便用过普通A4打印的标定板环境温度稍微变一下重新标定出来的结果就变一个样。高精度项目我推荐用铝基或陶瓷基的工业标定板热膨胀系数低、表面平整。标定板固定在机械臂末端时要确保它不会因为机械臂运动而晃动。有些项目用双面胶贴运动一圈回来位置就偏了这种数据采集出来也基本是废的。用刚性连接件固定是值得花的成本。如果你从厂商那里买了标定板务必确认厂商提供的descr文件参数和实物标称值一致。有些标定板背面印了圆点间距和描述文件对不上也是常有的事。只要有一个参数对不上整个标定结果就是错的而这种错有时很难通过误差数值察觉。3.6 数据筛选比多采集更关键采集了20组数据不代表20组都能用。我在实验室里通常会做一个快速筛选把每一组数据的重投影误差或者Errors输出出来看看有没有哪一组明显大于其他组。如果有一组数据误差比平均误差高出好几倍优先怀疑这组数据在采集过程中出现了机械臂抖动、图像模糊、标定板部分出视野等问题直接删掉比留着他拉低整体精度更明智。另外如果采集的姿态过于重复比如20组数据里有15组的标定板位姿几乎一模一样这不会带来更多约束反而会让优化过程偏向这个姿态附近其他区域的精度反而变差。姿态的多样性比姿态的数量更重要这个原则贯穿我所有手眼标定项目。4. 精度验证与常见问题速查标定完成不是终点。手眼标定有没有标对必须通过独立验证来确认而不是看着Errors小就放心。4.1 标定结果如何验证针尖法、三点法、重投影我常用的验证方法有三种按成本从低到高排列。第一种是针尖法。在机械臂末端固定一根尖针标定板平放在工作台上用相机画面找到标定板上某个圆点的图像坐标。然后手动移动机械臂让针尖从不同方向去触碰这个圆点的物理位置同时记录每次触碰时机械臂的末端位姿。理论上不管从哪个方向靠近针尖在机器人基坐标系下的位置应该是一致的。利用手眼标定结果把相机观测到的圆点位置换算到机器人基坐标系再和针尖实际触碰时的末端位置做对比偏差大小就是手眼标定精度的直观体现。这个方法的优点是不依赖额外传感器缺点是操作比较费时间但很可靠。第二种是三点法。选择一个固定的特征目标比如一个高对比度的小圆柱在相机视野里能被稳定识别。让机械臂带着一个已知长度的探针去触碰圆柱表面上三个不同位置记录机器人末端位姿同时结合手眼矩阵推算目标点在机器人基坐标系下的坐标。通过三点重合情况评估XY平面和高度方向的实际误差。第三种是重投影法。标定完成后固定相机不动让机械臂带着标定板在相机视野中运动几个新位置利用手眼标定结果和机械臂位姿把这个位置下标定板中心理论上的图像坐标算出来再与实际图像识别出来的中心坐标对比。像素偏差在1个像素以内通常说明手眼标定质量可以接受。这个方法速度快适合每次重新标定后做例行检查。4.2 手眼标定常见问题速查表下面这个表是我根据项目经验整理的遇到问题先对着查一遍能省不少排查时间。现象可能原因解决办法find_caltab找不到标定板光照反光、标定板过曝、descr文件与实物不符改善光源降曝光检查descr参数必要时偏振片find_marks_and_pose提取圆点残缺圆点过曝、有污渍、标定板倾斜角度过大清洁标定板、调整角度在30度以内、手动检查图像质量标定完成但实际抓取位置偏移较大姿态集合退化旋转约束不足重新采集数据增加旋转和俯仰变化不要纯平移Errors输出很大内参不准、机器人和图像数据不同步、标定板松动先重新标定相机内参再确认数据时间戳一致视野中心准、边缘偏相机畸变模型不准或内参标定时边缘覆盖率不够用更多姿态覆盖视野边缘重新标内参再标手眼手眼标定结果每次都不一样相机支架松动、镜头焦距未锁死、机械臂负载变形锁紧机械结构固定镜头带实际负载重新采集标定通过但定位精度周期性波动机械臂本身重复精度不够或工具坐标系存在偏置检查机械臂TCP标定配合激光跟踪仪校验证机器人的绝对精度4.3 标定完还能怎么用从手眼矩阵到视觉引导手眼标定真正的作用是解放后续视觉引导程序。标定完成后图像识别到的目标位置只是一个中间结果你可以通过手眼矩阵把它转换到机器人坐标系再结合机器人的运动规划库去驱动机械臂。这就是视觉引导抓取、装配、码垛等应用的基础。如果你用的是3D相机原理也是一样的。只是2D相机时标定板位姿来自find_marks_and_pose3D相机时标定板的圆点中心往往通过深度图或点云提取或者直接利用Halcon的3D手眼标定算子处理点对数据。这里我想提一句很多人在热词里搜深度图转点云、3D高度图缩放显示本质都是3D视觉数据处理的一部分但坐标变换的核心依然是手眼标定建立起来的那套齐次变换体系。把2D的手眼标定吃透再去接3D相机思路是平滑过渡的。我个人在实际操作中的体会是手眼标定不是一次性的工作。每次项目换工装、换镜头、挪动相机支架甚至机械臂工具坐标系重新标定过手眼关系都可能需要重新验证一遍。与其每次出问题再焦头烂额地排查不如每次标定完都做一次重投影验证把检查结果记录在项目文档里。这样后续出了问题你至少能快速判断是视觉部分漂了还是机械结构动了排查范围能缩得很小。