做视觉项目的人,基本都躲不开手眼标定这一关。尤其是用Halcon做机器人引导项目时,手眼标定往往是整个系统能否跑起来的第一道门槛。我见过不少人卡在这一步:要么标出来的矩阵精度差得没法用,要么程序报错半天查不出原因,要么明明标定了但实际抓取就是偏。老实说,手眼标定本身的原理并不复杂,真正的坑全藏在数据采集、坐标系转换和参数设置这些细节里。
这篇博文我会从零开始,完整拆解一遍Halcon手眼标定的实战流程。内容包括两种安装方式的选择、标定板选型、机器人位姿记录、Halcon算子调用顺序、结果验证方法,以及我这些年踩过的各种坑。准备做视觉引导、上下料、装配定位,或者正在被标定结果折磨的朋友,这篇文章应该能帮你省下不少时间。
1. 先搞懂手眼标定到底在算什么
1.1 眼在手上和眼在手外:两种安装方式的坐标系逻辑
手眼标定,核心就是解决一个问题:相机看到的物体坐标,怎么换算成机器人能用的坐标。根据相机安装位置不同,分成了两大类,一个是眼在手上(Eye-in-Hand),一个是眼在手外(Eye-to-Hand)。
眼在手上,就是相机装在机械臂末端法兰上,跟着机械臂一起动。这种方式的优势是视野灵活,机械臂动到哪里,相机就能看到哪里,适合大范围、多工位、需要移动拍照的场景。缺点是相机跟着动,每次拍照时相机位姿都在变化,标定结果要能适应不同机械臂姿态下的坐标转换。
眼在手外,就是相机固定安装在一个位置,机械臂在相机视野范围内干活。这种方案稳定,不会因为机械臂运动产生振动和视野变化,适合精度要求高、工位固定、拍照位置不变的应用。缺点是需要保证机械臂工作区域始终在相机视野内,视野范围可能受限。
这两种方式在Halcon里的标定流程几乎一样,但内部计算逻辑和结果的含义不同。眼在手解出来的矩阵是相机坐标系和机械臂末端坐标系之间的固定变换,眼在手外解出来的是相机坐标系和机械臂基座坐标系之间的固定变换。这个区别一定要清楚,不然后面验证结果时容易张冠李戴。
1.2 手眼矩阵和机器人位姿之间的关系
手眼标定本质上是求解一个矩阵方程,AX = XB。A是机械臂末端位姿的变化,B是标定板在相机坐标系下位姿的变化,X就是我们要的手眼变换矩阵。
具体展开来说,眼在手上的场景里,标定板固定不动,机械臂带着相机在不同位置拍照。每一组数据包含两个信息:机械臂末端的位姿(tool_in_base_pose),以及标定板在相机坐标系下的位姿(image_pose)。这两个位姿之间通过一个固定不变的手眼矩阵关联起来。当机械臂运动到不同位置时,这个固定矩阵都不会变,联立多组数据就能把它解出来。
眼在手外的场景稍有不同,标定板装在机械臂末端,相机固定不动。机械臂带着标定板在相机视野里摆出不同姿态,每一组数据也包含两个信息:机械臂末端的位姿,以及此时标定板在相机坐标系下的位姿。同样,手眼矩阵是不变的,它可以被多组数据联合求解出来。
Halcon里用自定义的数据结构来管理这些数据,其中create_calib_data创建标定数据对象,然后用set_calib_data录入相机参数、标定板参数、机器人位姿和图像位姿,最后用calibrate_hand_eye求解。整个过程的编程思路很清晰,但每一步的细节都会影响最终精度。
1.3 为什么选择Halcon做手眼标定
市面上能做手眼标定的方案不少,最常见的有OpenCV、Halcon,还有国内一些视觉平台(比如Vision Master)。我自己的项目里三种都尝试过,简单聊聊各自特点。
OpenCV的优势是免费开源,跨平台,社区资料丰富。cv2.calibrateHandEye接口用起来也不难,适合成本敏感、对许可要求不高的项目。但OpenCV的标定板识别、亚像素提取这些前置步骤需要自己写代码,调试成本高,对于没有专门视觉工程师的团队来说上手偏慢。
Halcon的优势在于算子封装完整,标定板识别、位姿计算、手眼标定都是一条龙,开发效率极高。尤其find_calib_object这个算子,对标定板的识别几乎是傻瓜式的,打光不太理想的情况下也能稳定提取。缺点当然就是贵,License费用不低,但这对于工业级项目来说通常是可以接受的成本。
Vision Master之类的国产平台一般内置了标定流程,界面化操作,鼠标点一点就能完成。它的优点是上手最快,不需要写代码。但灵活性差,遇到非标准场景(比如特殊标定板、特殊相机模型)时容易卡住。
选哪个取决于项目实际情况,但如果你是做工业视觉项目、需要稳定的精度和较高的开发效率,Halcon依然是值得优先考虑的选择。
2. 标定前的准备工作:硬件的坑比软件多
2.1 确定相机安装方式和标定板选型
很多人一上来就写代码,结果到后面才发现硬件安装方式跟代码假设不一致,整个标定推倒重来。所以标定前的第一件事,是确认相机安装方式,到底是eye-in-hand还是eye-to-hand。这个确认不是看一眼就行的,要考虑实际应用场景。
如果机械臂抓取时需要对准来料位置,相机装在手上,可以实现“走过去看”的效果,对视觉系统的灵活性要求高;如果工位固定、来料位置固定,相机装在外面更稳定,标定后精度也会更好。我个人的原则是:能装固定就装固定,需要灵活再看手上。
标定板的选择也有讲究。Halcon标定板是圆点阵列式,每颗圆点之间的间距有严格标准,对应一个描述文件(.cpd)。这个描述文件在标定时必须传给Halcon,因为它描述了标定板的物理尺寸和点阵布局。
选择标定板大小,主要看相机视野。标定板在视野里最好不要小于视野的三分之一,也不要大于三分之二。太小了,圆点提取数量不够,标定结果容易飘;太大了,边缘圆点变形严重,反而影响精度。比如500万像素相机配16mm镜头,视野大概300mm见方,用100mm左右的标定板就挺合适。
还有个容易忽略的点:标定板要平整、干净、不反光。最好用玻璃基板的陶瓷标定板,热稳定性好,不容易变形。廉价亚克力板用久了会弯,标定结果会慢慢漂移。
2.2 机器人位姿怎么记录
手眼标定需要的机器人数据是机械臂末端的位姿,在Halcon里叫tool_in_base_pose,也就是工具坐标系在机器人基座坐标系下的位置和姿态。这个数据从哪来?从机器人控制器里读。
读取方式通常有两种。一种是在示教器上人工记录,机械臂每摆一个姿态,把当前位姿抄下来,然后跟图像对应起来录入程序。这种方式适合手动标定,数据量少(十来组),但效率低,容易抄错。另一种是写一个上位机程序,通过TCP/IP或者Modbus等协议自动读取机器人当前位姿,然后跟相机拍照同步记录下来,效率和准确率都高得多。
无论哪种方式,单位一定要统一。Halcon里位姿默认单位是米,角度是弧度。而很多机器人示教器上显示的是毫米和度,这就要在记录时做换算,或者机器人端直接输出转换后的数据。
旋转部分更要注意。机器人输出的姿态表示方式五花八门:欧拉角、四元数、旋转矩阵都有。Halcon的位姿类型支持多种旋转表示,常用的是gba(绕固定轴的全局旋转)和abg(欧拉角)。我在项目里的做法是,让机器人端把姿态转成四元数或者欧拉角传上来,再在Halcon里统一转成gba类型,避免不同表示方式之间的混淆。
2.3 标定数据对象与相机参数准备
真正开始标定前,需要先把相机的内参标定好。如果相机是固定焦距的工业相机,可以用Halcon的标定助手,采集十来张不同角度的标定板图像,自动算出一组内参。如果相机是自动变焦的,先锁死焦距再做标定,不然内参一变,手眼矩阵全部作废。
内参准备好后,在代码里创建标定数据对象并设置参数。以眼在手上为例,代码大概是这样的:
* 创建手眼标定数据对象,'moving_cam'表示眼在手上 create_calib_data ('hand_eye_moving_cam', 1, 1, CalibDataID) * 设置相机内参,使用division模型 set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', CameraParam) * 设置标定板描述文件 set_calib_data_calib_object (CalibDataID, 0, 'calplate_100mm.cpd')注意,如果是眼在手外,创建数据对象时要用'hand_eye_stationary_cam'。这两个模式对应的数据管理方式一样,但最后求解出来的手眼矩阵含义不同,千万别搞混。
还有一个点:标定板描述文件和实际标定板必须完全匹配,包括圆点数量、间距、板子尺寸。如果用了不匹配的文件,find_calib_object即使能识别出来,计算出来的位姿也不对,手眼标定结果会差到离谱。
3. 实操全程:从采集图像到输出手眼矩阵
3.1 图像采集与标定板识别
接下来的操作流程,我按眼在手上的场景来演示,眼在手外的逻辑完全对称,区别只在数据对象创建和结果含义。
首先,把标定板固定在工作区域内的某个稳定位置,比如用台钳夹住或者用双面胶固定在工作台上。确保标定板在机械臂运动的所有拍照位置都能被相机完整看到,且视野边缘不要出现标定板切边的情况。
然后控制机械臂移动到第一个拍照位置,机械臂稳定后触发相机拍照。拍照后,对图像调用find_calib_object,提取标定板的位姿:
* 查找标定板,提取位姿 find_calib_object (Image, CalibDataID, 0, 0, StartHandle, []) get_calib_data (CalibDataID, 'calib_obj', 0, 'pose', PoseCalib)这里PoseCalib就是标定板在相机坐标系下的位姿,也就是前面说的image_pose。每组图像都要对应记录这个位姿。
几个拍照时的注意事项:
第一,对焦一定要准。标定板圆点边缘如果模糊,亚像素提取精度会下降,最终影响手眼标定的旋转精度。
第二,曝光要合适,不能过曝。圆点内部如果出现高光饱和,提取出的圆心位置会偏移。最好让圆点区域和背景的灰度差明显,但高光区不溢出。
第三,尽量避免反光。标定板表面如果有镜面反光,find_calib_object可能识别失败,或者识别出的圆点位置出现偏移。可以调整光源角度,或者用偏振片消除反光。
3.2 采集位姿的姿势策略
手眼标定不是随便摆几个位置就能标好的,机械臂的姿态要有足够的变化。我见过不少人为了省事,只让机械臂在几个固定位置平移,姿态几乎不变,结果标出来的矩阵精度非常差。
为什么姿态变化这么重要?因为手眼标定本质上是联立方程求解,如果所有数据都来自同一姿态,方程之间的信息冗余严重,解出来的矩阵在某个方向上约束不足,精度就会很差。
具体操作上,我建议每个拍照位置要包含平移和旋转两方面的变化。平移方面,让机械臂末端在相机视野的不同位置移动,覆盖视野的左上、右上、中心、左下、右下,尽量把视野的各个区域都走到。旋转方面,让机械臂末端绕着不同方向转动,每次转动角度建议在30度左右,大幅度转动更有助于约束旋转部分。
一组比较典型的数据采集流程是:先在当前位置拍照,然后平移一段距离再拍,接着转一个角度再拍,再回到中间位置换一个方向转,就这样来回组合,采集15到20组数据。少于10组数据时,标定结果不稳定,多于30组收益也不明显。我实际项目里一般控制在20组左右,精度和效率都比较理想。
3.3 手眼标定的执行与结果输出
数据全部采集完成后,把每组图像对应的标定板位姿和机器人位姿录入标定数据对象,然后执行标定:
* 设置当前图像对应的标定板位姿 set_calib_data (CalibDataID, 'hand_eye', 'image_pose', PoseCalib) * 设置对应的机器人末端位姿 set_calib_data (CalibDataID, 'hand_eye', 'tool_in_base_pose', PoseRobot) * 全部数据录入完成后执行标定 calibrate_hand_eye (CalibDataID, PoseHandEye) * 获取手眼矩阵 get_calib_data (CalibDataID, 'hand_eye', 'hand_eye_transform', PoseHandEye)注意,set_calib_data调用时,image_pose和tool_in_base_pose要一组一组配对录入。顺序不能乱,第一组图像对应第一组机器人位姿,第二组对应第二组,一旦错位,标定出来的矩阵完全不对。
PoseHandEye就是最终得到的手眼矩阵。对于眼在手上,它表示相机坐标系相对于机械臂末端坐标系的变换,可以理解为相机在机械臂末端坐标系下的位姿。实际应用时,要把图像坐标先转成相机坐标,再乘上这个手眼矩阵,转到末端坐标,再乘上机器人基座到末端的变换,就能得到物体在机器人基座坐标系下的坐标。
PoseHandEye的单位和格式,跟录入的机器人位姿有关。如果机器人位姿用的都是米,那这个位姿的平移部分也是米。如果录入的是毫米,结果就是毫米。建议统一使用米,跟Halcon默认一致,省去换算的麻烦。
3.4 标定结果的验证方法
标定完不能直接上线,要先验证精度。验证的方法很简单:让机械臂带着相机(眼在手上)或者带着标定板(眼在手外)运动到一个新位置,拍照识别标定板位姿,把标定板上的某个已知点反投到机器人基座坐标系下,跟实际位置比较。
以眼在手上为例,验证步骤是这样的:
第一,固定标定板不动,控制机械臂移动到某个新位置,拍照,用find_calib_object获取标定板在相机坐标系下的位姿PoseCalib。
第二,取标定板上某个标记点的坐标(比如标定板左上角第一个圆点,在标定板坐标系下坐标已知),把它从标定板坐标系转到相机坐标系,再乘手眼矩阵转到末端坐标系,再乘机器人基座到末端的变换,转到基座坐标系。
第三,把这个计算出来的基座坐标跟实测的标准值比较,看看偏差是多少。一般平移偏差在1mm以内就算不错,0.3mm以内算优秀。如果偏差很大,基本可以断定前面某个环节出了问题。
Halcon里做矩阵变换常用这组算子:
* 位姿转矩阵 pose_to_hom_mat3d (PoseCalib, HomMat3DCalib) pose_to_hom_mat3d (PoseHandEye, HomMat3DHandEye) pose_to_hom_mat3d (PoseRobot, HomMat3DRobot) * 点从标定板坐标系转到相机坐标系 affine_trans_point_3d (HomMat3DCalib, Px, Py, Pz, Cx, Cy, Cz) * 从相机坐标系转到末端坐标系 affine_trans_point_3d (HomMat3DHandEye, Cx, Cy, Cz, Tx, Ty, Tz) * 从末端坐标系转到基座坐标系 affine_trans_point_3d (HomMat3DRobot, Tx, Ty, Tz, Bx, By, Bz)如果验证结果不理想,别急着怀疑算法,先检查数据采集过程。绝大部分精度问题都出在数据质量上,而不是Halcon的标定算法本身。
4. 避坑指南:这些坑我基本都踩过
4.1 采集位姿的六个关键原则
我踩过不少坑,其中最深的几次都和数据采集有关。总结下来,采集位姿时有六个原则,基本能保证标定结果不会太差。
第一,数据量要够。至少12组,推荐20组左右。太少不行,太多也没必要,算力开销大,精度提升有限。
第二,姿态要多样。平移和旋转都要有,而且旋转角度要大一点,不要只做小角度微调。很多项目标定结果不好,就是因为在采集时机械臂姿态太接近,约束不足。
第三,位置要分散。标定板或相机在视野里的位置要覆盖各个区域,不要只集中在中心附近活动。
第四,姿态变化不要雷同。每一组数据都要有“新意”,不要机械地重复同一类型的动作。
第五,采集时机械臂要稳定。等机械臂完全停稳,抖动消失后再拍照,否则图像模糊会导致位姿提取不准。
第六,记录位姿要和图像严格同步。如果是自动采集,拍照触发和机器人位姿读取之间不能有太大时间差,否则机械臂还在动,位姿就对不上。
4.2 标定结果不准的排查清单
标定完成验证精度不合格时,不要慌,按下面这个表逐项排查,大部分问题都能找到原因。
| 排查项 | 常见原因 | 解决办法 |
|---|---|---|
| 标定板识别失败 | 对焦模糊、反光、曝光过强 | 调整光源、对焦、减少反光 |
| 旋转部分误差大 | 姿态变化不足 | 增加旋转变化量,避免重复姿态 |
| 平移部分误差大 | 数据量太少或视野覆盖不足 | 增加数据量,分散标定板位置 |
| 结果方向不对 | 手眼矩阵用反了方向 | 验证时用变换顺序纠正 |
| 单位不对 | 机器人位姿未统一单位 | 统一使用米或毫米并换算 |
| 相机内参不准 | 标定助手采集图像不够 | 重新标定相机内参 |
| 标定板参数不匹配 | cpd文件和实物不一致 | 确认标定板型号与描述文件匹配 |
这个表是我做标定问题排查时的标准动作。每次标定结果有问题,先过一遍表,基本上都能定位到问题环节。
4.3 坐标系和旋转类型的坑
坐标系搞反是手眼标定里最隐蔽的问题。因为代码不报错,数据也能跑出来,但最终用起来就是不对,甚至有时部分对部分错,让人一头雾水。
最常见的错误是手眼矩阵的方向用反了。Halcon返回的PoseHandEye表示的是相机在末端坐标系下的位姿(眼在手上场景)。有些机器人通讯协议里要的是末端在相机坐标系下的位姿,这时候要把矩阵求逆再传给机器人。求逆用hom_mat3d_invert,或者把位姿取反旋转角度再转换。
另一个常见问题是旋转类型不一致。Halcon的位姿支持多种旋转类型,包括gba、abg、xyz等。如果机器人端给的是abg欧拉角,而Halcon里按gba理解,结果会出现明显误差。最稳妥的做法是在录入机器人位姿前,统一用convert_pose_type转成同一种类型,再录入标定数据对象。
还有个容易忽略的点:机械臂的零位和坐标方向要确认。不同厂家的机器人基座坐标系定义不一样,有的X轴朝前,有的Y轴朝前,如果跟视觉系统的坐标系定义不一致,标定结果也会偏差。处理办法是在标定前先约定好坐标系方向,或者用一根校准棒做一次粗略的手动验证,确认坐标系映射关系没错再继续。
4.4 深度图转点云和3D手眼标定的延伸
前面讲的主要是2D相机标定,但很多项目做到后面会升级到3D视觉。Halcon里深度图转点云相关的话题和手眼标定经常一起出现,这里也稍微提一下。
如果是3D相机(比如结构光、线激光)做手眼标定,原理和2D是类似的,区别在于从深度图转出的点云数据里如何提取标定板的3D位姿。Halcon支持用点云做标定板匹配,算子流程复杂一些,但标定思路完全一致。
如果用的是2D相机加额外深度传感器,那就要做2D相机和深度相机之间的外参标定,再配合手眼标定,链路更长,坑也更多。我建议这类项目尽量选成熟的3D视觉方案,减少自己拼装标定的麻烦。
反过来,如果项目还在评估阶段,先用2D相机做做看,后续再升级3D,那在相机安装支架、光源、标定板选型这些环节就要提前考虑兼容性,避免后期改造大动干戈。
4.5 与OpenCV、Vision Master方案的对比取舍
最后聊聊方案选型。很多人问:Halcon手眼标定和OpenCV比,到底好在哪里?我自己的体会是,Halcon的标定工具链更完整,从标定板识别到位姿优化集成度高,出问题的概率低;OpenCV则更灵活,可以跟深度学习、自定义算法无缝集成,但工程量翻倍。
举个例子,用OpenCV做手眼标定,你得自己处理aruco棋盘格的角点提取、位姿解算,还要解决相机畸变模型差异。这些步骤每一步都有坑,中间任何一步出错都会让最终结果很奇怪。Halcon从find_calib_object到calibrate_hand_eye是同一套数据管理,标定板参数、相机参数、位姿录入全部在CalibData对象里,流程清晰,不容易半路出错。
至于Vision Master这类平台,拿来跑标准应用确实省事,但它的自定义能力有限。如果你需要把标定结果嵌入到自己写的Qt或者C#上位机里,Halcon的二次开发接口就方便多了。Halcon不仅可以在HDevelop里操作,也可以用export导出代码到C++/C#,直接集成进自己的程序框架。
我个人在每个项目里做视觉方案选型时,会综合评估三点:项目精度要求、团队视觉开发能力、预算。预算充足且对精度有硬指标,直接上Halcon;预算有限但团队有人写过OpenCV,用OpenCV也能做出来;完全不想写代码,上现成的视觉平台是最稳的选择。
最后再分享一点小经验
手眼标定这个事,原理在纸面上看都挺简单,真正做起来却总有意外。我做过的项目里,标定一次就完美通过的情况很少,大多数都要经过数据补采、参数调整、重新标定的循环。这很正常,不用因此怀疑自己。
我给你一个建议:每次标定前,把相机内参、机器人位姿、标定板描述文件这三样东西单独保存好,做好版本管理。一旦标定结果有问题,可以快速复盘,而不是一头扎进代码里找原因。另外,标定板不要随手乱放,更不要拿手摸圆点区域,保持表面清洁,标定板的物理精度会直接传递到最终结果里。
如果你正准备做Halcon手眼标定,照着这篇文章的流程走一遍,遇到问题再对着排查表逐项检查,大概率能顺利解决。做完之后你会发现,手眼标定其实是个熟能生巧的活,第二次做就快很多了。