
做视觉引导抓取项目最让人头大的一幕往往是这样相机明明清清楚楚看到了工件位置把像素坐标发给机器人机器人却死活抓不准偏几个毫米甚至偏了一两个厘米。换了镜头、调了光源、升级了相机问题依旧。直到有人问了你一句“你做手眼标定了吗”你才意识到压根没建立相机坐标和机器人坐标之间的关系。这篇就是围绕Halcon里的九点标定法把这套关系从原理到代码、从现场操作到精度排查完整讲一遍。无论你是刚接手视觉项目的自动化工程师还是正在为机器人抓取定位发愁的技术爱好者看完都能自己动手标出能用的结果。1. 先解决两个坐标系的问题手眼标定的本质和两种部署方式1.1 视觉给的是像素机器人要的是坐标中间缺个“翻译”先想清楚一个最基础的问题相机看到的图像本质上是一个二维的像素矩阵。图像里一个工件中心在(1358, 726)这只是说这个点在图像第726行、第1358列和机器人该往哪个方向走多少毫米没有任何直接关系。而机器人运动时用的是自己的机械坐标系单位是毫米原点通常在机器人底座或某个固定的工具零点。要把图像的像素位置变成机器人能用的(X, Y, Z)坐标必须知道两个坐标系之间怎么换算。这个换算关系就是手眼标定要解的问题。在Halcon语境下九点标定法做的事情很直白在一张覆盖工作区域的图像上取9个已知位置的特征点一边记录它们的像素坐标一边对应记录机器人实际到达这些点时的机械坐标然后让Halcon用一个变换矩阵把这两组坐标拟合起来。这个矩阵就是那个“翻译官”。1.2 眼在手外还是眼在手上直接影响算法选型手眼系统的部署方式大体分两种九点标定法适用的是其中一种。眼在手外固定相机相机装在机械臂外面比如架在工位正上方或斜上方相机不动。这种情况下相机看到的工作平面基本不变成像模型可以近似成一个二维平面映射。九点标定法正是针对这个场景设计的简单、稳定、见效快。眼在手上随动相机相机装在机械臂末端随着机械臂一起动。这种情况下相机每换一个位姿看到的视野都不同单纯做二维平面映射是搞不定的需要解一个AX XB形式的手眼方程。Halcon里对应的是hand_eye_calibration算子配合create_calib_data创建hand_eye_moving_cam类型的数据模型。两种方式的差异我整理成了下面这张表对比项眼在手外固定相机眼在手上随动相机相机安装固定在支架/机台上装在机械臂末端法兰成像稳定性视野固定不会因机械臂运动变化视野随末端位姿改变标定方式九点标定法为主hand_eye_calibration系列算子典型应用固定工位抓取、定位、测量移动式巡检、机器人引导装配难度低入门首选高需要同时处理相机位姿和机器人位姿如果你刚开始接触手眼标定我强烈建议先用固定相机的九点标定法把整套流程跑通理解了坐标变换的本质之后再去看眼在手上的完整手眼标定会轻松很多。2. 九点标定的数学底牌不要被“仿射变换”四个字吓到2.1 一次搞定旋转、平移和缩放九点标定法求解的本质上是一个二维仿射变换矩阵Halcon里用vector_to_hom_mat2d算子求得到了一个2×3的齐次变换矩阵[ x ] [ a b tx ] [ u ] [ y ] [ c d ty ] [ v ] [ 1 ] [ 0 0 1 ] [ 1 ]其中(u, v)是像素坐标(x, y)是机器人坐标。矩阵里的a, b, c, d负责旋转、缩放和切变tx, ty负责平移。展开来看就是两个简单的线性方程x a * u b * v tx y c * u d * v ty你可以把它理解为Photoshop里对图层做“自由变换”操作把一张图在平面上旋转一个角度缩放一个比例再平移一段距离。九点标定做的就是把像素平面“变换”到机器人平面只不过这里的变换参数由9组已知点反推出来。2.2 为什么是9个点而不是3个或者4个从数学上看仿射变换有6个未知参数理论上找到3个不共线的对应点就能解出唯一解。但现实中像素坐标提取有误差机器人走位有误差示教器读数也有误差。如果你只用3个点任何一点的小误差都会被毫无保留地放大进矩阵标定结果就是“看起来能对上实际工作起来偏得离谱”。9个点解决的是冗余问题。6个参数9组方程这是一个超定方程组Halcon内部会用最小二乘法求解让所有点的整体误差平方和最小。相当于你在现场量了9次“零件的尺寸”取了一个加权平均的最优结果而不是只信单次读数。点位的空间分布也很有讲究。哪怕你放20个点如果全部挤在相机视野中央一个很小的区域得到的矩阵在视野边缘的外推误差依然会很大。正确做法是让9个点尽量分散铺满相机实际工作范围。2.3 什么时候九点标定法不够用这里必须说清楚九点标定法的边界避免有人拿它硬套复杂场景。第一它假设相机光轴垂直于工作平面成像模型接近正投影。如果安装时相机斜得厉害工件在视野不同位置的高度差又大仿射变换就会吃力。这时可以考虑用vector_to_proj_hom_mat2d求透视变换矩阵它有8个参数能处理一定的透视变形。第二它只能解决二维平面问题。如果项目要求机器人抓取有高度差、需要俯仰角补偿的工件就得回到完整的手眼标定用标定板和机械臂位姿数据一起解算三维空间关系。第三镜头畸变不能太夸张。廉价广角镜头在视野边缘的桶形畸变明显仿射变换是线性模型纠正不了这种非线性变形。这种情况下要么先做相机内参标定并用undistort_image矫正图像要么换畸变小的镜头。3. Halcon九点标定代码实战从圆点提取到矩阵生成3.1 入门方案圆形Mark点提取坐标这是现场用得最多、也最容易理解的方案。相机一次拍下一张包含9个圆形Mark点的图像通过阈值分割找到所有圆点计算圆心坐标作为像素坐标。完整代码流程如下* 关闭窗口实时更新加快执行速度 dev_update_off () * 读取标定图像 read_image (Image, E:/calib/points_9.png) * 如果采集的是彩色图先转灰度 rgb1_to_gray (Image, GrayImage) * 阈值分割提取暗色圆点区域 threshold (GrayImage, Regions, 0, 100) * 将连通区域拆开 connection (Regions, ConnectedRegions) * 筛选出面积合适、圆度达标的区域 select_shape (ConnectedRegions, SelectedRegions, [area, circularity], and, [800, 0.7], [10000, 1.0]) * 按顺序排序保证和机器人坐标对应 sort_region (SelectedRegions, SortedRegions, character, true, row) * 求每个圆点的面积、中心行列坐标 area_center (SortedRegions, Area, Rows, Columns) * 注意Halcon中Rows是纵坐标Columns是横坐标 * 后面做变换时把Columns作为PxRows作为Py这一段跑完Rows和Columns两个数组里就是9个圆心的像素坐标。这里有个新手最容易踩的坑sort_region按行排序时如果9个点不是严格的九宫格布局排序结果可能和你预期的不一样。稳妥的做法是在标定时让9个点从左到右、从上到下依次排列标完肉眼核对一遍对应顺序。机器人端对应的9个坐标可以通过示教器手动记录也可以通过PLC或上位机自动读整理成两个数组* 机器人坐标系下的X坐标数组 RobotX : [100.0, 150.0, 200.0, 100.0, 150.0, 200.0, 100.0, 150.0, 200.0] * 机器人坐标系下的Y坐标数组 RobotY : [50.0, 50.0, 50.0, 100.0, 100.0, 100.0, 150.0, 150.0, 150.0]然后就是重头戏用vector_to_hom_mat2d求解变换矩阵* 求仿射变换矩阵 * 参数依次是源点X、源点Y、目标点X、目标点Y vector_to_hom_mat2d (Columns, Rows, RobotX, RobotY, HomMat2D)3.2 把标定结果用起来在线坐标转换标定完矩阵不是拿来收藏的最终要用于在线运行时的坐标换算。当视觉算法在图像上找到一个工件的中心点(TargetRow, TargetColumn)后把它丢给affine_trans_point_2d* 在线定位时将一个像素坐标转为机器人坐标 affine_trans_point_2d (HomMat2D, TargetColumn, TargetRow, RobotTargetX, RobotTargetY)affine_trans_point_2d的输出就是机器人可以直接执行的X和Y坐标。如果你发现转换出来的坐标和机器人实际到位位置有固定偏差多半是像素坐标和机器人坐标的对应顺序搞反了或者是机器人工具坐标系的参考点没有和标定针尖对齐。3.3 进阶方案标定板find_calib_object如果项目对精度要求高或者想同时做完整的相机标定推荐用Halcon标准标定板配合find_calib_object提取特征点。这个方法能拿到亚像素精度的特征点坐标远好过阈值分割求圆心。* 使用Halcon标准标定板描述文件 * 根据实际标定板规格选择常见的有30mm、10mm等 find_caltab (Image, Caltab, caltab_30mm.descr, 3, 112, 5) * 如果做过相机内参标定可以创建标定数据模型做更精确的特征点提取 create_calib_data (calibration_object, 1, 1, CalibDataID) set_calib_data_calib_object (CalibDataID, 0, caltab_30mm.descr) * 这里要填入相机内参来自之前的内参标定结果 set_calib_data_cam_param (CalibDataID, 0, area_scan_division, [FocalLength, Kappa, Sx, Sy, Cx, Cy, ImageWidth, ImageHeight]) * 提取标定板特征点 find_calib_object (Image, CalibDataID, 0, 0, 0, [], []) get_calib_data (CalibDataID, image, 0, x, Px) get_calib_data (CalibDataID, image, 0, y, Py)提取到特征点后同样根据标定板原点在机器人坐标系中的位置构造对应的RobotX、RobotY数组再调用vector_to_hom_mat2d。3.4 验证代码标定结果好不好先回代检验矩阵求完之后第一件事不是拿工件去试而是用原来的9个点回代看看理论输出和实际记录的机器人坐标差多少* 回代验证计算每个点的定位误差 NumPoints : |RobotX| MaxErr : 0.0 for I : 0 to NumPoints - 1 by 1 affine_trans_point_2d (HomMat2D, Columns[I], Rows[I], CheckX, CheckY) DiffX : RobotX[I] - CheckX DiffY : RobotY[I] - CheckY Err : sqrt(DiffX * DiffX DiffY * DiffY) if (Err MaxErr) MaxErr : Err endif endforMaxErr就是这组标定点里的最大回代误差。如果这个值超过项目精度要求的1/3左右说明标定过程有某个环节出了问题不要急着上线先回看像素提取和机器人坐标的对应关系。4. 现场操作全流程从摆放标定板到记录机器人坐标4.1 标定点位布局覆盖作业面比点位密度更重要很多人在实验室标定效果很好一到现场就拉胯最常见的原因就是标定点只覆盖了视野中央一小块区域。九点标定的点位布局应该遵循“工作区域全覆盖”原则9个点分布成一个覆盖整个作业范围的九宫格点位越靠近实际生产时工件可能出现的位置在线转换精度越好。举个例子如果相机视野是 600mm × 500mm而工件通常出现在视野右侧那标定点的分布就应尽量把右侧区域加密甚至可以适当超出实际作业范围避免出现“外推”情况。标定是内插法精度高、外推法精度低这个规律在视觉引导里十分显著。4.2 机器人坐标怎么记录示教器与上位机两种方式记录机器人坐标有两种主流方式。第一种示教器手动记录。把机器人切换到手动模式用示教器把工具末端移动到某个圆点中心记录当前坐标值。这种方式适合点位少、无上位机通信的项目缺点是容易记错、效率低。第二种上位机自动记录。在程序里通过TCP/IP、Modbus或Profinet与机器人控制器通信机器人每到达一个标定点就把当前坐标发给上位机。这种方式效率高还能避免人工抄录的错误但需要提前把通信协议调通。无论用哪种方式有一个核心前提必须确认记录的坐标值对应的参考点和视觉定位的参考点必须一致。比如视觉算的是工件中心那机器人记录的也必须是工具中心TCP到达工件中心时示教器显示的值而不是机器人法兰盘中心。4.3 像素坐标和机器人坐标的顺序必须一一对应这是九点标定里最隐蔽也最致命的坑像素坐标提取出了9个点机器人坐标也记录好了9个值但两者顺序对不上。发生顺序错乱的常见原因有两个第一图像上9个圆点的提取顺序和机器人记录顺序不一致。比如机器人记录顺序是按“从左到右、从上到下”但Halcon提取圆点时因为光照不均或圆点缺损导致某个点没被提取到所有后续点的顺序整体错位。第二sort_region排序模式选错。character模式适合比较规整的网格布局一旦点在视觉上有轻微旋转或倾斜排序结果可能把不同行的点混排。我自己的做法是标定时在机器人示教器上按固定路线依次走一遍9个点每个点对应的图像位置也严格按同样顺序排列走位路线的第一个点固定选在视野左上角。这样就可以确保第N张图像对应的像素特征点和第N条机器人坐标记录一一对应。再配合回代误差检查一旦有某个点对错位最大回代误差会非常夸张立刻能发现问题。4.4 现场必做检查清单相机固定是否牢靠有没有可能的微小位移标定板或Mark点是否平整贴合工作平面镜头表面是否干净光照是否均匀圆点边缘有没有严重阴影机器人TCP是否标定准确末端工具是否松动每个记录点的机器人坐标是否和像素点一一对应。不要嫌这些检查啰嗦我在项目里见过太多次最后查出来是“螺丝松了”“示教器读数抄串行”这种低级问题。5. 标定完怎么知道准不准交叉验证与误差排查清单5.1 回代误差与额外验证点回代误差只能检验拟合结果和标定点之间的自洽性如果标定点本身取错了回代误差再小也没意义。所以我习惯在标定完成后再额外取3~5个“验证点”——不参与矩阵求解只用来做最终检验。验证点取法在工作范围内随便选几个位置机器人走到位记录机械坐标图像上记录对应像素坐标再用标定矩阵把像素坐标转成机械坐标和实测机械坐标对比。如果偏差在项目允许范围内这套手眼关系才算真正可靠。5.2 误差来源排查顺序一旦验证点偏差超差按下面这个顺序排查比盲目重标定有效得多现象可能原因排查方向所有点偏差接近且成固定向量机器人坐标记录参考点选错或工具中心偏移检查TCP标定回代误差小验证点误差大标定点范围覆盖不足存在外推区域扩大标定范围重新标定边缘误差明显大于中心镜头畸变或相机光轴倾斜做内参标定并畸变矫正或调整安装角度某几个点误差异常大像素点顺序与机器人坐标顺序错位逐点核对对应关系整体误差偏大且没有规律特征点提取精度差、光照不佳改善光照换用标定板提取亚像素点5.3 提升标定精度的几个进阶做法如果项目要求的精度在亚毫米级仅仅用阈值分割提取圆心是不够的。一个做法是改用Halcon标准标定板配合find_calib_object这个算子在内部做了亚像素边缘提取特征点精度比普通阈值分割高一到两个数量级。另一个做法是先用create_calib_data做一次完整的相机内参标定得到镜头畸变系数然后在采集标定图像时先用undistort_image矫正图像再提取特征点。这个做法能有效消除镜头畸变在视野边缘带来的非线性误差。此外如果相机安装角度无法保证光轴绝对垂直工作台可以考虑用vector_to_proj_hom_mat2d求透视变换矩阵替代仿射变换。它比仿射变换多两个参数对透视变形有更好的拟合能力代价是需要至少在4个位置放置标定点特征点数量多时依然用最小二乘。6. 换成我上项目时踩过的坑九点标定容易被忽视的六个细节6.1 Halcon环境问题能卡住大半天新手最容易卡住的反而不是算法本身而是环境。Halcon分试用版和带加密狗的商业版没有license的情况下算子会直接报错。另外Halcon安装路径别带中文导出的C/C#程序在目标机器上部署时记得把运行时DLL一起带上。记得有一次我在现场调试Halcon程序在自己电脑上跑得好好的到了客户工控机上闪退排查了半天发现是少了hdevengine.dll。6.2 九点标定的结果和相机固定位置是绑定的标定矩阵只有在相机的安装位置、角度完全不变时才有效。调过一次光路、拧动过相机支架、哪怕只是重新装了镜头之前的标定结果都作废必须重新标定。所以现场维护流程里一定要写上“相机位移必重新标定”这条规则否则项目上线后出现漂移问题排查起来极其痛苦。6.3 不要用九点标定强行做三维场景九点标定法本质上是“平面近似”思路适合的工作条件是工件基本在一个平面上运动比如传送带上平面抓取、桌面定位贴合。如果工件有多层的堆叠、或者机械臂需要在不同高度抓取老老实实去用完整手眼标定create_calib_data (hand_eye_moving_cam, ...)或者hand_eye_stationary_cam类型再配合hand_eye_calibration求解虽然复杂但方向是对的。6.4 标定矩阵保存与自动加载标定结果不是一次性成果项目重启、程序重启后都要复用。Halcon里可以把矩阵用write_tuple保存到文件程序初始化时用read_tuple加载这样就不需要每次开机都重新标定一遍。* 保存标定矩阵和对应的时间戳 write_tuple (HomMat2D, E:/calib/hom_mat_2d.tup) write_tuple (CalibDate, E:/calib/calib_date.tup) * 程序启动时加载 read_tuple (E:/calib/hom_mat_2d.tup, HomMat2D)保存时顺手写一个calib_info.txt记录标定日期、相机高度、镜头型号、操作人。后期如果出现精度漂移至少能判断是不是因为期间有人动过相机。6.5 记得在程序里加“标定状态”开关上线程序尽量保留一个“标定模式”和“运行模式”的切换开关。标定模式下程序只负责特征点提取和坐标展示方便现场调试人员看清楚每个点位是否提取正确运行模式下再执行完整流程。这个习惯能省很多联调时间。6.6 多次标定取平均是有效但常被忽略的做法如果现场条件允许同一套点位做两次或三次标定每次相机和机器人重新回零然后对比几组矩阵的差异。差异很小说明系统重复性好差异大说明某个环节不稳定。实际应用时取多组标定的平均结果通常比单次标定更稳。手眼标定这件事说难不难说简单也不简单。它不像深度学习那样需要调大量的网络结构更多是考验你对坐标系变换的理解和现场操作的细致程度。我做了这么多视觉引导项目最深的一个体会就是标定结果不好九成不是因为Halcon算法不行而是前期的安装固定、点对记录、光路环境这些“笨功夫”没做到位。把这篇文章里的坑都避掉按流程一步一步来你也能得到一个稳定可靠的手眼关系。