资讯详情 眼在手上与眼在手外:手眼标定选型、实操与UR机器人数据转换全解析
📅 2026/10/6 6:31:38
眼在手上 vs. 眼在手外一次讲透两种手眼标定模式的选择与实操细节附UR机器人数据转换做机器人视觉集成的朋友一定绕不开手眼标定这个问题。不管是做抓取、装配、分拣还是涂胶引导只要相机和机器人之间需要靠坐标变换来“对齐世界观”手眼标定就是躲不过去的一步。入行头两年我在这上面踩过不少坑尤其是两种模式的选择以及UR机器人数据格式转换那一环稍不注意就会得到一套“看似标定成功、实际偏差大得离谱”的结果。这篇文章我打算把两种手眼标定模式——眼在手上Eye-in-Hand和眼在手外Eye-to-Hand——讲透。从原理、选型、标定实操到UR机器人数据转换的完整处理流程全部用项目里验证过的方法和代码来说话。如果你是刚接触手眼标定的机器人工程师、视觉工程师或者在做项目方案时拿不准该用哪种模式这篇文章能帮你省下不少试错时间。1. 眼在手上与眼在手外两种模式的原理拆解1.1 物理结构差异与坐标系定义很多资料上来就给你画坐标系、列公式结果新手直接看懵。我先用大白话把这两种模式的区别讲清楚。眼在手上就是把相机固定在机器人末端法兰盘上相机跟着机器人手臂一起动。你可以想象成给机器人手上绑了一个手电筒手电筒照到哪里眼睛就看到哪里。这种结构下相机和末端工具之间的相对位置是固定的但相机相对于机器人底座、相对于工件的位姿是随时变化的。眼在手外则是把相机安装在机器人工作空间之外的一个固定支架上。相机看着机器人干活它自身不动。这种结构下相机相对于机器人底座的位置是固定的但相对于机器人末端工具的位置是变化的因为工具在动。在正式标定之前先要把涉及的坐标系理清楚。一套完整的机器人视觉系统里通常涉及这么几个坐标系机器人基座坐标系记为Base机器人末端法兰坐标系记为Tool或者End相机坐标系记为Camera标定板坐标系记为Object或者Calib。标定板是棋盘格或者圆点板它在图像中被识别后视觉算法能给出相机坐标系到标定板坐标系的变换关系。搞清楚这些坐标系之间的固定关系是理解手眼标定的关键。眼在手上结构里Tool到Camera的变换是固定的所以标定的目标就是求这个Tool到Camera的变换矩阵。眼在手外结构里Base到Camera的变换是固定的所以标定的目标是求Base到Camera的变换矩阵。一句话概括手眼标定求的就是那个在系统运行过程中始终固定不变的相机和机器人之间的位姿关系。1.2 标定方程的推导为什么都是AXXB这个AXXB的方程几乎所有讲手眼标定的资料都会提但很多人只是记住了形式不知道它是怎么来的。我在这里用矩阵变换的逻辑推一遍理解了之后你调参、排错都会有底气得多。先说眼在手上。机器人每运动到一个位姿系统就能知道两个变换关系机器人控制器给出的Tool到Base的矩阵记为( T_{tool \to base} )视觉算法从图像中计算出的Calib到Camera的矩阵记为( T_{calib \to camera} )。同时存在一个固定的未知矩阵( T_{camera \to tool} )这就是要标定的手眼矩阵。对于同一个固定的标定板机器人末端在运动时标定板相对于Base的变换( T_{calib \to base} )是不变的。于是有[ T_{calib \to base} T_{tool \to base} \cdot T_{camera \to tool} \cdot T_{calib \to camera} ]这个式子右边的三个矩阵左边两个是已知的右边三个是未知的。对两个不同的机器人位姿分别列出这个等式两个式子左边相等联立之后就能消掉( T_{calib \to base} )整理化简后得到( AX XB )的形式。这里的X就是那个固定的手眼矩阵。再说眼在手外。相机固定不动Base到Camera的变换( T_{camera \to base} )固定未知。标定板通常固定在机器人末端法兰上Tool到Calib的变换( T_{calib \to tool} )固定未知。在某一个位姿下有[ T_{calib \to base} T_{camera \to base}^{-1} \cdot T_{calib \to camera} ]同时也可以写成[ T_{calib \to base} T_{tool \to base} \cdot T_{calib \to tool} ]联立两个位姿的等式左边相等整理后同样能化成AXXB的形式只不过X代表的物理含义不同。这就是为什么所有手眼标定算法最后都绕不开AXXB的求解——两种结构虽然物理形式不同但数学模型是统一的。2. 选型决策什么场景该用哪一套2.1 眼在手上的优势、短板与应用场景眼在手上最大的优点是视野跟着机械臂走灵活性高。机械臂可以带着相机移动到工件附近的任意角度去拍照非常适合目标位置不固定、需要在不同方位观察的场合。比如料筐里随机摆放的工件机械臂可以先移到料筐上方用相机扫一遍规划抓取点然后再去抓。这种场景下相机如果固定在天花板上边缘部分很容易被料筐侧壁挡住视线而眼在手上可以绕到合适角度去看。另一个好处是精度相对可控。相机离工件近图像分辨率对物理尺寸的映射精度就高。同样是500万像素的相机拍一米外的物体和拍三十厘米外的物体每个像素代表的实际物理尺寸完全不是一个量级。眼在手上结构能让相机贴近工件拍摄这在精度要求较高的精密装配、小零件抓取场景里优势非常明显。但眼在手上也有麻烦的地方。相机的标定结果跟机器人运动精度强相关机械臂本身有绝对定位精度误差这会直接带入标定方程。如果机器人本体重复定位精度差标定出来的手眼矩阵就不会太准。另外相机跟着机械臂来回运动线缆的反复弯折是一个不可忽视的工程问题我用过不少国产协作机器人和UR机器人相机线缆如果不做专门的拖链固定几个月后图像就会出现不稳定原因就是线缆内部断裂或接触不良。眼在手上比较适合的场景包括抓取位置不固定的散乱工件分拣、需要在多个角度检测的焊缝寻位、大型工件表面检测与引导、移动机械臂平台的视觉定位。在这些场景里相机动起来带来的灵活性收益远大于运动精度带来的损失。2.2 眼在手外的优势、短板与应用场景眼在手外的最大优势是稳。相机固定不动标定完成后图像中每个像素对应的机器人大地坐标位置就在一个固定的映射关系里只要不移动相机这个映射关系就一直有效。它不受机器人运动精度影响因为标定方程里机器人末端的位姿只是作为变量参与计算但相机固定的外参是稳定的。对于重复定位精度高、运动速度快的场景眼在手外结构往往能获得更稳定的整体系统精度。安装上眼在手外通常用支架把相机架在工位上方或者侧方不用考虑线缆跟着机械臂走的问题现场走线更干净、更可靠。调试的时候也不用担心机械臂运动到某个姿态时相机视角发生突变视觉系统只需要处理静态图像画面稳定性和一致性都更好。不过眼在手外的局限性也很明显。视野是固定的覆盖范围受安装高度和相机镜头视场角限制。如果工件被抓取后要放到另一个远离相机视野的位置眼在手外的相机可能就看不到了。还有就是遮挡问题机械臂本体会挡住相机视线这是眼在手外结构里常见的烦恼尤其是在机械臂工作范围比较大、相机只能装在一个方向上的时候。此外为了覆盖较大的工作区域通常要把相机装得比较高这就会牺牲像素精度对需要高精度定位的小型工件不太友好。眼在手外适合的场景包括固定工位上的上下料引导、传送带上的来料定位与抓取、装配工作台上的定位引导、AGV装载站上的对接引导。这类场景要求相机覆盖区域不大、工件到达位置相对固定并且相机视野不易被机械臂遮挡。2.3 一张表搞定选型判断在实际做项目方案时我习惯用下面这张表来辅助决策。它不是一个绝对规则而是一个快速过滤的工具帮你在一开始就排除掉明显不合适的选项。决策维度眼在手上眼在手外视野覆盖范围覆盖面大可随机械臂移动观察固定视野覆盖范围受安装限制目标位置灵活性适合散乱、多角度目标适合位置相对固定的目标标定精度影响因素受机器人绝对/重复定位精度影响基本不受机器人运动精度影响安装空间要求只需末端法兰安装位占用空间小需要外部支架和固定空间相机线缆管理需考虑随动线缆和拖链固定线缆可靠性高遮挡问题可主动调整角度规避遮挡机械臂可能遮挡视野须仔细设计安装位典型应用散件分拣、焊缝寻位、移动平台导航固定工位上下料、传送带定位、装配引导一个很常见的经验是如果目标物体到达工位时位置离散度不大并且现场能提供一个良好的固定安装支架位置优先考虑眼在手外系统调试和后期维护都省心。如果目标位置杂乱、需要机器人主动去“找”目标那就是眼在手上的主场了。当然也有不少项目会同时用两种相机比如一个手外相机负责大范围定位、引导机械臂接近目标一个手上相机负责近距离精调这种混合方案在汽车零部件装配领域很常见。3. 实操细节标定流程、数据采集与计算3.1 标定板选择与采集策略标定板是手眼标定的核心参照物它的精度直接决定了标定结果的上限。我的建议是不要用打印在普通A4纸上的棋盘格来标定除非你只是做个验证性实验。纸张受潮、热胀冷缩会导致格子尺寸变化贴在不平整的底板上更会让标定板平面失真。项目里要标定至少要用玻璃基板或者陶瓷基板表面平整度好图案精度高。如果你用的是Halcon或者OpenCV的标定流程它们都要求输入棋盘格的方格边长或者圆点间距这个值必须准确最好用卡尺量一下实际打印出来的尺寸而不是直接用设计值。采集位姿是整个标定过程中最影响成功率的一环。很多人上来就把机器人摆一堆奇怪的姿态也不管相机能不能看清标定板结果标定结果很差还不知道问题出在哪。合理的采集策略有几个原则第一每次采集时标定板都必须完整出现在相机画面里不能有遮挡第二标定板在图像中的占比要适中太小了角点检测精度不够太大了又容易出界一般建议占画面的三分之一到一半第三机械臂末端姿态要有足够的变化俯仰、偏航、翻滚都要有不能只是在同一个平面上平移换位置第四多次采集之间标定板的姿态差异要足够大如果30次采集的姿态分布都挤在一起方程组的约束不够标定结果就会不稳定。眼在手上模式下标定板固定在工作台上机器人带着相机拍。眼在手外模式下标定板装在机器人末端法兰上机器人带着标定板在相机视野里变换姿态。这两种方式对机器人走位的要求正好相反一个是相机动一个是板子动但采集原则是相通的姿态丰富、覆盖均匀、尽量避免连续采集时的路径交叉导致机器人奇异点附近的大角度抖动。我一般采集30到50组数据耗时大概十五到二十分钟。太少标定结果波动大太多边际收益递减而且中间容易混入机器人奇异点附近的大误差数据。数据采集完成后我会先做一次简洁的“体检”把所有数据里视觉检测置信度过低的、机器人位姿重复度太高的样本剔除再用剩下的样本跑标定。这个习惯帮我规避了很多次“标定结果看起来正常、实际抓取全偏”的事故。3.2 标定计算的两种落地方式OpenCV与Halcon手眼标定的求解算法工程上最常用的是OpenCV的cv2.calibrateHandEye和Halcon的calibrate_hand_eye。两者底层都实现了AXXB的多种求解方法只是接口和数据处理方式不同。我在不同项目里都用过简单对比一下。OpenCV的calibrateHandEye需要你传入两组数据一组是机器人末端到基座的变换矩阵列表一组是标定板到相机坐标系的变换矩阵列表。这两个列表里的每个矩阵都是4x4的齐次变换矩阵并且顺序要一一对应。函数内部支持TSAI、PARK、HORAUD、ANDREFF等方法一般我习惯用TSAI或者HORAUD实际对比下来在大多数场景下结果差别不大。要注意的是OpenCV要求输入的数据是二维数组形式如果你的原始数据是欧拉角或者旋转向量需要先转换成旋转矩阵再拼成齐次矩阵。Halcon的calibrate_hand_eye使用起来更工程化。它有两种模式通过参数HandEyeCalibMethod选择calibrate_hand_eye是处理“eye in hand”还是“eye to hand”。Halcon的数据输入不是矩阵而是它自己的位姿元组格式位置加旋转角。它要求用户提供机器人控制器读出的位姿和视觉算出的标定板位姿两个列表一一对应然后它会自动完成标定。Halcon的好处是处理异常数据比较方便有专门的算子对位姿序列进行一致性检查能在标定之前帮你找出有问题的数据。缺点是它是商业软件授权费用不低小项目不一定划算。其实不管是OpenCV还是Halcon真正决定标定精度的是输入数据的质量。数据采集环节如果做得不好再高级的求解算法也无力回天。反过来只要数据采集质量高OpenCV默认的求解方法就能满足绝大多数项目的精度需求。所以我的观点是别纠结算法库前期数据采集的功夫到位了结果都不会差。4. UR机器人的数据转换从位姿文件到标定矩阵4.1 UR位姿格式与TCP定义这个部分是UR机器人用户最容易踩坑的地方。UR控制器输出的位姿格式是[x, y, z, rx, ry, rz]前三项是TCP在机器人基座坐标系下的三维位置单位是米后三项是TCP姿态用旋转矢量表示单位是弧度。很多人没注意到这里的rx、ry、rz不是欧拉角而是旋转矢量的三个分量。如果你直接拿这三个值当成欧拉角去构建旋转矩阵标定结果一定错得离谱。先解释一下旋转矢量。一个三维空间中的任意旋转都可以等价于绕着某个单位轴向量旋转一定角度。旋转矢量就是用一个向量来表示这个旋转向量的方向代表旋转轴向量的模长代表旋转角度。UR控制器输出的rx、ry、rz就是这个旋转矢量的三个分量。例如[0, 0, 1.5708]表示绕机器人基座坐标系的Z轴正方向旋转90度而[1.5708, 0, 0]则表示绕X轴旋转90度两者是完全不同的姿态。另外一个重要概念是TCP即工具中心点。UR机器人默认的TCP在法兰盘中心如果你在末端装了相机或者其他工具需要在示教器上设置好TCP偏移。手眼标定过程中使用的机器人位姿必须是实际安装工具之后的TCP位姿而不是默认法兰中心的位姿。标定完成后如果后续更换了工具或者重新设置了TCP手眼矩阵就需要重新标定。有几次项目出问题根源就是同事在仿真的机器人模型上做标定但现场实际TCP设置和仿真不一致。4.2 旋转矢量与旋转矩阵互转的实现要把UR的数据用于手眼标定第一步就是把UR的位姿表示转换成4x4齐次变换矩阵。核心是旋转矢量到旋转矩阵的转换工程上通常用罗德里格斯变换实现。这里我给出一个在Python里实现的转换函数输入UR的六个参数输出对应的齐次变换矩阵。import numpy as np def ur_pose_to_matrix(ur_pose): x, y, z, rx, ry, rz ur_pose theta np.sqrt(rx * rx ry * ry rz * rz) if theta 1e-12: R np.eye(3) else: kx, ky, kz rx / theta, ry / theta, rz / theta K np.array([ [0, -kz, ky], [kz, 0, -kx], [-ky, kx, 0] ]) R np.eye(3) np.sin(theta) * K (1 - np.cos(theta)) * (K K) T np.eye(4) T[:3, :3] R T[:3, 3] [x, y, z] return T反过来如果手眼标定算出了旋转矩阵要把结果转回UR的位姿格式部署到程序里就需要旋转矩阵到旋转矢量的逆转换。这里有个细节要注意从旋转矩阵提取旋转矢量时旋转角度和旋转轴方向不是唯一对应的因为绕着轴转θ度和绕着反向轴转2π−θ度是同一个旋转。实际处理时取θ在0到π范围内的值即可。def matrix_to_ur_pose(T): R T[:3, :3] x, y, z T[:3, 3] theta np.arccos(np.clip((np.trace(R) - 1) / 2, -1.0, 1.0)) if theta 1e-12: rx ry rz 0.0 elif np.pi - theta 1e-6: # 处理旋转角度接近180度的情况 # 从RI的列向量中找最大分量的轴 r (R np.eye(3)) / 2 diag np.diag(r) idx np.argmax(diag) axis r[:, idx] axis axis / np.linalg.norm(axis) rx, ry, rz axis * theta else: axis np.array([ R[2, 1] - R[1, 2], R[0, 2] - R[2, 0], R[1, 0] - R[0, 1] ]) / (2 * np.sin(theta)) rx, ry, rz axis * theta return [x, y, z, rx, ry, rz]这段代码里我特别处理了旋转角度接近180度的情况。实际标定过程中如果两个姿态之间的旋转角度接近180度直接用反对称矩阵方式提取轴向量会退化导致结果不稳定。这种情况在真实项目里不常遇到但一旦遇到不处理就会得到完全错误的旋转矢量。4.3 数据预处理与标定结果验证UR的位姿从控制器导出后通常是一个text文件或者CSV文件。数据预处理的第一步是弄清楚文件里每一列的真实含义。UR示教器的程序里get_actual_tcp_pose()返回的是当前TCP的位姿单位是米和弧度而get_actual_joint_positions()返回的是关节角单位是弧度。手眼标定需要的是TCP位姿不是关节角。另外如果你在URScript里使用pose_trans()函数做过位姿变换导出的数据可能是变换之后的结果需要确认它对应的坐标系关系。数据预处理的第二步是排序和对齐。视觉系统采集标定板位姿时的顺序必须和机器人控制器记录TCP位姿时的顺序一一对应。我自己吃过亏的教训是相机采集程序用相机触发信号自动记录机器人程序在每移动一个位姿后延时2秒再记录TCP位姿两边时间戳没有硬性同步导致部分数据错位。后来改成机器人每到一个位姿先发送一个硬触发信号给相机再等待相机采集完成返回确认然后记录TCP位姿顺序就完全对齐了。标定计算完成之后一定要做验证不要直接拿到项目里用。我的验证方法很简单重新选一组没有参与标定的测试数据把标定出的手眼矩阵作用上去计算出工件在机器人基座坐标系下的理论位置再实际控制机器人移动到该位置测量误差。误差在几毫米以内说明标定可靠。有一个实操技巧是在标定时采集两组数据一组参与计算一组专门用来验证这样能避免“用同一批数据既拟合又验证”带来的乐观偏差。UR程序里部署手眼矩阵时我通常把标定得到的4x4矩阵拆成位置和旋转矢量两部分分别通过脚本变量传入视觉引导程序。有个细节值得提醒UR的pose_trans函数要求传入的位姿是[x, y, z, rx, ry, rz]格式而不是矩阵。所以标定得到的矩阵必须先转换成UR位姿格式再写入程序。如果你在UR程序里做视觉引导一般流程是视觉系统计算工件在相机坐标系下的位姿通过TCP通信把结果发给UR控制器UR程序里用pose_trans把工件位姿从相机坐标系转换到基座坐标系。这里的pose_trans第一个参数是相机到基座的变换通过标定矩阵和机器人当前TCP位姿推算出来第二个参数是相机坐标系下的工件位姿。如果这两个参数搞反了系统就会“完美地”把工件引导到错误的位置上。5. 常见问题与排查实录5.1 标定结果精度差的排查路径标定完成后发现精度差先不要急着怀疑算法库90%的问题出在数据采集阶段。我总结了几个高频原因按排查优先级排列如下。机器人位姿和视觉位姿没有一一对应。这是最常见也最致命的问题。排查方法随机抽三组数据手动从原始记录里核对确保机器人记录的位姿和图像采集的时刻是一一配对的。不要盲目相信程序的时间戳对齐现场丢包、缓存延迟都会造成错位。标定板在图像中的姿态分布太集中。如果所有采集图像里标定板的朝向都差不多手眼标定方程组的约束不足得到的解虽然在数学上成立但在某些方向上非常敏感。判断方法很简单把采集的标定板位姿数据打印出来看看旋转部分的变化范围是不是覆盖了各个方向。我遇到过一位工程师标定了五次精度都不好最后发现他设置的机器人路径只是在同一个高度平移标定板的姿态几乎没有变化。机器人位姿精度差或者奇异点附近采集了数据。UR这类协作机器人的绝对定位精度比传统工业机器人要差一些越靠近工作空间边界和奇异点误差越大。处理办法是规划标定路径时尽量让机器人工作在中部区域远离奇异点。如果在采集过程中听到电机声音异常或者观察到位姿抖动明显果断丢弃这组数据不要抱侥幸心理。标定板的物理尺寸精度不够或者检测到的角点坐标精度不够。前者用精密测量仪器重新测量实际尺寸后者可以检查图像的清晰度、光照均匀度和标定板表面是否有反光。反光问题是现场最常见的光照干扰柔光罩或者偏振片能有效改善。如果图像中角点附近有高光检测到的角点位置会系统性偏移标定结果自然不准。5.2 UR数据转换中的典型踩坑现场UR数据转换这一环我见过的坑主要集中在旋转矢量的理解和坐标系定义混淆上。第一个坑是把旋转矢量当欧拉角用。有人从UR控制器拿到[rx, ry, rz]后直接用某个库的欧拉角转旋转矩阵函数结果标定计算报错或者结果完全错误。UR的rx、ry、rz是旋转矢量不是绕X、Y、Z轴的欧拉角。正确的做法是我前面给的罗德里格斯转换函数先判断旋转矢量的模长是否为0再做变换。第二个坑是TCP设置不一致。标定时用的TCP和运行时用的TCP不一致导致标定结果在定义上就失效。UR示教器的TCP设置里如果你用set_tcp或者set_mech_kin设置了工具偏移控制器返回的get_actual_tcp_pose()就已经是经过工具偏移补偿后的位姿了。但如果中途有人改了TCP设置或者程序里和示教器里的TCP配置不同就会出问题。我建议标定前先确认TCP配置并且在整个标定和验证过程中不要改动它。第三个坑是坐标系方向理解混淆。UR的基座坐标系定义是从机器人的安装底面看向机器人的正面X轴指向正前方Y轴指向左方从安装面往下看时为防止混淆一定要参考官方手册Z轴垂直向上。如果你的视觉系统计算出的标定板位姿用的坐标系方向和UR不一致比如镜像了或者旋转了90度标定出来的矩阵在数学上可能是“合理”的但物理上完全错误。判断方法是在标定板上放一个已知方向的标记物验证视觉输出的位姿方向和UR坐标系定义是否一致。5.3 现场调试时积累的几条经验分享几条实操经验每一条都是从项目里折腾出来的。第一标定板的存储和保养要当回事。标定板表面划伤、脏污、反光都会影响角点检测精度。用过之后用镜头布擦干净放在专门的盒子或袋子里。我去现场调试时都会带一块备用标定板防止意外损坏导致项目停摆。第二相机曝光和光源设置要在标定前调好。标定过程中不要动曝光时间、增益、光圈这些参数不然前后图像亮度差异大检测精度不一致。我在一个项目里发现标定结果总是有1毫米左右的随机波动排查很久才发现是自动曝光在光线变化时自行调整导致角点检测精度不稳定。把它改成固定曝光后问题立即消失。第三标定完成后在机器人示教器上做一次“回读验证”。让机器人运动到某个位姿相机拍摄标定板视觉系统计算出标定板在相机坐标系下的位姿再把标定出的手眼矩阵代进去计算出标定板在基座坐标系下的理论位姿与机器人示教的实际位姿做对比。这一步能直观地看到标定结果是否符合预期比只在电脑上看矩阵数值靠谱得多。第四不要忽略机器人负载对精度的影响。相机和连接件是有重量的装上后会影响机器人末端的实际位姿。UR机器人在标定时如果没做负载辨识默认的动力学参数可能不准确导致控制器内部对位姿的补偿不准确。我在UR10上遇到过类似问题通过UR的负载辨识功能重新标定载荷参数后位姿精度明显改善。写在最后的个人体会手眼标定这个东西原理说复杂也复杂说简单也简单。只要把坐标系关系搞清楚了把数据采集做扎实了剩下的计算交给现成的算法库就行。很多人标定失败不是算法不行而是数据源有问题——要么是数据对齐错了要么是坐标系理解错了。我在项目中养成了一个习惯每次标定前先花十分钟把坐标系关系图画出来标定后再花十分钟做回读验证。这两步看着简单却帮我省下了大量返工时间。最后再分享一个小技巧。在做手眼标定数据转换时记得把旋转矢量的单位、角度范围和坐标系方向标注清楚尤其是传给其他工程师或者写进项目文档时。一个负号、一个单位的差错可能就会让整个系统的视觉引导方向全反。细节决定成败在机器人视觉这个领域这句话永远适用。