手眼标定实战:从坐标系到AX=XB方程全解析
1. 手眼标定到底在解决什么问题1.1 机械臂的“手”和“眼”如何协同手眼标定这个词做机器人视觉的人迟早要面对。不管是视觉引导抓取、装配定位、焊缝跟踪还是动态分拣只要相机看到的东西需要换算成机械臂能执行的坐标就绕不开这一关。很多人第一次接触手眼标定要么被一堆坐标系绕晕要么把标定当成一个“跑一遍函数就完事”的黑盒结果到了现场精度死活不对回头翻资料才发现是最基础的概念没吃透。这篇文章作为手眼标定实战系列的第一篇先把手眼系统和坐标系这两个地基彻底讲清楚。机械臂通过视觉来引导动作本质上要回答一个问题相机看到的某个点在机械臂的坐标系里到底在哪。这个转化不是简单加个偏移量因为机械臂在动、相机的安装方式也不一样必须通过标定来建立两个坐标系之间的固定变换关系。打个比方你坐在驾驶座上看到前方路面上有个东西伸手去拿。你的眼睛位置和手的位置相对固定大脑其实已经自动建立了一套“眼到手”的换算关系你根本不需要思考就知道该往哪个方向伸手。手眼标定就是给机器人做这套换算只不过机器人没有大脑它需要一组精确的数据结构来记录“相机看到的”和“机械臂执行的”之间的数学关系。1.2 两种经典构型眼在手上与眼在手外根据相机安装位置的不同手眼系统分成两大类这直接决定了标定方程的形式和整个系统的行为特性。第一种是眼在手上对应术语 Eye-in-Hand。相机固定在机械臂末端法兰上跟随末端一起运动。这种构型的优势在于视野随动机械臂转到哪相机就能贴近观察哪特别适合近距离的高精度作业比如螺丝锁付、焊缝跟踪、小物件的精准抓取。缺点也很明显相机跟着机械臂晃视野不固定如果机械臂运动到某个角度相机可能看不到目标点。第二种是眼在手外对应术语 Eye-to-Hand。相机固定在工作空间外部通常是支架或天花板不随机械臂运动。这种构型视野固定适合大范围的全局监控比如多个工位共享一个相机或者需要同时观察整个工作台面的场景。缺点是容易有遮挡机械臂自身可能挡住目标视野边缘的精度也往往不如中心区域。实际选型时我的经验是近距离、高精度、灵活多变的作业优先考虑眼在手上大范围、多工位、固定流程的作业优先考虑眼在手外。两种构型没有绝对的好坏关键看你的应用场景对视野和精度的侧重。2. 坐标系全景一张图理清所有“世界”2.1 从机械臂基座到末端法兰坐标系是手眼标定里最容易把人绕晕的部分。很多标定失败案例的根源不是算法不行而是坐标系定义和命名不规范数据拿到手以后根本不知道谁跟谁在比。先理机械臂这一侧的坐标系。最底层的是基座坐标系一般叫 Base Frame它固定在机械臂底座上是整个机器人的世界参考点也是你最终想把视觉信息转化到的目标坐标系。机械臂控制器里读到的所有位姿本质上都是相对于基座坐标系的。然后是关节坐标系每个关节都有一个自己的坐标系用来描述关节的运动学和动力学关系。不过在手眼标定里关节坐标系通常不直接参与我们关心的是由关节角度通过正解计算出来的末端位姿。再往上是末端法兰坐标系也就是 Flange Frame它固定在机械臂末端安装面上是机械臂控制器直接能输出的坐标系。很多人在这一步就开始混淆了控制器输出的到底是法兰坐标系还是工具坐标系TCP这两个不是一个东西。法兰坐标系是机械臂自己定义的它的原点在法兰中心工具坐标系是用户根据自己的实际工具定义的比如焊枪的尖端、吸盘的中心、夹爪的抓取点它相对于法兰坐标系有一个固定的偏移和旋转。如果你在标定前没有正确设置TCP那么从控制器读出来的位姿和实际情况之间就有一层偏差手眼标定结果自然不对。2.2 相机、标定板、工件与用户坐标系视觉这一侧的坐标系核心是相机坐标系。它的原点在相机光心Z轴沿光轴方向朝前X轴和Y轴分别对应图像平面的两个方向。相机坐标系和图像像素坐标系之间的关系由相机内参决定也就是焦距、主点、畸变系数那套东西。所以在做手眼标定之前必须先完成相机内参标定和畸变校正否则你解算出来的标定板位姿本身就是歪的后面的一切都没有意义。标定板坐标系定义在标定板上棋盘格或圆点阵列会在图像里被识别出来通过PnP或类似算法解算出标定板相对于相机坐标系的位姿。标定板坐标系的原点一般取在板的角点或特定圆点上Z轴垂直于板面朝外。除了这些实际项目中还有工件坐标系和用户坐标系。工件坐标系是目标物体所在的坐标系通常是在标定板上或工件特征上建立的参考系用户坐标系是机器人在基座坐标系下额外定义的工作参考系方便编程和操作本质上就是基座坐标系的一个旋转加平移。2.3 建立自己的坐标系清单我强烈建议开始任何标定工作之前先拿一张纸或者直接在文档里列一个坐标系登记表。包括坐标系名称、缩写符号、定义方式、来源是读取的还是算出来的、关键注意事项。这个习惯在我自己做项目时帮我省了太多时间。下面是一个我常用的模板你可以直接抄走坐标系名称常见符号定义方式来源机械臂基座坐标系B机械臂底座控制器固定定义机械臂法兰坐标系E末端安装面中心控制器正解输出工具坐标系T实际工具末端点用户标定四点法等相机坐标系C光心光轴方向相机模型定义标定板坐标系G标定板角点/圆点视觉解算工件坐标系W工件特征用户定义登记完以后把每个坐标系之间的变换关系也标注出来。哪些是已知的、哪些是待求的一目了然。我在现场见过太多因为命名混乱导致的低级错误比如“把小g当成大G”“法兰当成工具”“工具当成基座”这些错误一旦混进标定数据结果偏差几个毫毛都是轻的。3. 数学本质AXXB与AXZB到底在说什么3.1 旋转矩阵与齐次变换手眼标定的数学基础是刚体变换也就是坐标系之间的旋转和平移。旋转用3x3的旋转矩阵R表示它满足正交性转置等于逆行列式为1。平移用3x1的向量t表示。把两者组合起来就得到一个4x4的齐次变换矩阵TT [ R t ] [ 0 1 ]这个矩阵的本质是把一个点在某个坐标系下的坐标变换到另一个坐标系下的坐标。多个变换可以复合比如从相机坐标系到标定板坐标系、再从标定板坐标系到机械臂坐标系最终得到的是这些矩阵的连乘。反过来求逆矩阵就能实现反变换。旋转矩阵的物理意义有三种理解方式描述一个坐标系的姿态、将一个矢量从一个坐标系旋转到另一个坐标系、或者说它是坐标变换的运算符。手眼标定里我们最关心的是第三种——坐标变换。理解了这一点整个标定过程就清晰了一切都是坐标变换的链条标定就是把这个链条上缺失的那一环找出来。3.2 eye-in-hand的AXXB问题现在来看眼在手上的情况。相机固定在机械臂末端标定板固定在外界某个位置。我们在机械臂的不同位姿下拍摄标定板照片同时记录下机械臂的末端位姿。目标就是求相机坐标系相对于末端法兰坐标系的变换关系记为X它应该是固定不变的。取两组不同位姿的数据。第一组位姿下末端在基座坐标系下的位姿是E1相机观察到的标定板位姿是G1第二组位姿下末端位姿是E2标定板位姿是G2。因为标定板固定在世界里所以从机械臂基座到标定板的变换链是一致的于是可以写出E1 * X * G1⁻¹ E2 * X * G2⁻¹整理一下两边同时左乘E2⁻¹并做一些变换最终得到一个标准形式A * X X * B其中A是机械臂末端从位姿1到位姿2的相对变换B是标定板在相机坐标系下从位姿1到位姿2的相对变换。A由机械臂控制器读取的位姿直接算出B由视觉解算出的标定板位姿直接算出。方程里只有一个未知量X这就是手眼标定的核心方程。这个方程看着简单但求解并不平凡因为旋转矩阵本身有正交性约束不能直接当普通线性方程来解。经典的做法是先利用旋转部分解出X的旋转矩阵再用平移部分解出X的平移向量。目前工程上常用的开源实现比如OpenCV的calibrateHandEye函数就封装了多种算法其中Tsai-Lenz是最经典的一种。3.3 eye-to-hand的AXZB问题眼在手外的情况稍微不同。相机固定在外界标定板装在机械臂末端。此时我们需要求的是相机坐标系相对于机械臂基座坐标系的变换关系记为X它同样是固定不变的。同时标定板相对于机械臂末端的变换Z也是固定不变的因为标定板锁死在末端上。写出变换链从机械臂基座到标定板可以通过两条路径到达。一条是先到末端再经过Z到标定板另一条是先到相机再经过X到标定板。两条路径应该相等E * Z X * GE是机械臂末端位姿G是相机观察到的标定板位姿。同样取两组位姿做差消掉中间变量之后可以得到A * X Z * B这里的A是相邻两组末端位姿的相对变换B是相邻两组标定板位姿的某种组合X和Z都是未知的。这个方程比AXXB复杂一些因为未知量变成了两个。求解这类问题有专门的方法比如把两个未知量联合起来求解或者先通过多次采样构造超定方程。OpenCV同样提供了支持但参数含义和返回结果要仔细核对我见过很多人在eye-to-hand场景下套用eye-in-hand的调用方式结果完全错了。3.4 求解思路与经典算法不管是AXXB还是AXZB核心思路都是一样的通过多组数据构造超定方程再用最小二乘或类似手段求解最优的X。为什么需要多组数据因为单组数据只有一个方程未知量有6个自由度3个旋转自由度加3个平移自由度必须有多组相互独立的数据才能把问题约束住。工程上推荐至少采集15到20组数据而且姿态要有足够的变化。如果机械臂只是平移、不旋转或者旋转的角度很小数据之间的约束就会退化解出来的X精度很差。这一点怎么强调都不过分姿态的多样性是标定精度的第一决定因素。经典的求解算法里Tsai-Lenz方法是把旋转部分与平移部分分离先用旋转轴和旋转角来构造线性方程组解出旋转矩阵后再代入求解平移向量。Park和Daniilidis等方法则分别基于Lie代数和四元数/对偶数数值稳定性各有差异。实践里在数据质量优秀的前提下不同算法解出的结果应当非常接近——如果差异巨大十有八九是数据本身出了问题而不是算法不行。4. 实操准备与数据采集决定标定精度的一半4.1 标定前必须完成的准备工作很多初学者一上来就拍照求解结果是标定结果忽好忽坏这种情况几乎都是准备阶段欠了债。我总结的标定前清单有以下几项一项都不能省。第一必须先标定相机内参。内参包括焦距、主点坐标、畸变系数。常用的方法是张正友标定法也就是用棋盘格在不同角度下拍十几张图通过OpenCV的calibrateCamera求解。畸变校正尤其重要如果你的镜头有明显畸变而不做校正标定板的角点位置在图像里就是歪的后续解算出的位姿误差会被放大很多倍。尽量使用高分辨率的棋盘格图片覆盖整个视野特别是边缘区域。第二确认机械臂的TCP是准确的。严格来说手眼标定公式里用的是法兰坐标系还是工具坐标系取决于你从控制器读的是哪种位姿。但为了保证最终视觉引导的精度实操前最好先把TCP标定好比如用四点法或多点法把工具尖端的位置标精确。机械臂的运动学误差、安装误差都会在标定数据里体现出来TCP不准会直接污染手眼标定结果。第三检查标定板本身。棋盘格要平整、对比度高、不能有反光建议使用陶瓷板或玻璃基板覆膜印刷在普通纸张上的棋盘格在灯光变化下很容易出现角点误检。圆点阵列的标定板在某些场景下鲁棒性更好但要做圆点中心提取原理类似。4.2 数据采集策略与实操记录数据采集这一步直接决定了标定精度的上限。我看到过不少项目标定结果精度在2到3毫米左右就怎么都上不去了最后检查数据发现全都是同一个角度、同一个高度拍的照片。姿态太单一约束不足算法根本没法把六个自由度完全约束住。我个人的采集策略是这样的先把标定板固定在工作空间一个合适的位置让相机在正上方或合适的角度能看到它然后控制机械臂移动让相机在不同位置、不同姿态下拍摄标定板。每组数据之间不仅要有平移更要有明显的旋转变化。我的常用做法是分几个高度层每一层里让末端偏航、俯仰、滚转分别变化一定角度尽量覆盖±30度以上的变化范围。每组数据之间还应该留出足够大的位置间隔避免组与组之间过于接近。采集时要注意几点每拍一张照片前确保机械臂已经完全停止运动不要边动边拍否则图像会有运动模糊而且记录到的位姿和图像时刻不匹配。光照要均匀稳定避免强光在标定板上产生高光或阴影。标定板尽量放在视野中央区域不要贴边因为镜头边缘畸变最大。每次采集时同步记录机械臂位姿和拍摄的图像文件名建议在代码里用一个数组保存两者的对应关系不要手动记录手动记录早晚会出错。最后采集完成后要做一次简单的数据审核。看一下所有图像的角点检测是否都成功检测成功率是100%吗有没有哪张图角点明显错乱把明显有问题的数据直接剔除不要觉得可惜一条坏数据会把整个标定结果带偏。4.3 求解过程与验证方法数据准备好以后求解本身其实是相对简单的。用OpenCV的calibrateHandEye函数可以一步拿到结果但你要搞清楚自己用的是哪种模式。eye-in-hand情况下传入的是机械臂末端位姿序列和标定板位姿序列eye-to-hand情况下参数含义不同务必核对官方文档。函数输出的是一个4x4的变换矩阵通常返回值是旋转和平移分开保存的。拿到矩阵之后最重要的是验证而不是直接拿到现场使用。验证方法我推荐两种。第一种是重投影验证。把标定板上的已知角点坐标通过标定板到相机的位姿变换、再通过手眼矩阵和机械臂位姿变换到基座坐标系理论上每个角点在不同机械臂位姿下都应该重合到同一个基座坐标点。计算所有角点的分布半径就能反映标定精度。简单说你那个“同一个点在不同视角和位姿下映射回机器人坐标系后是否落在同一个位置”的偏差就是标定精度的直接度量。第二种是实测验证。在标定板上选一个特征点用视觉系统算它的坐标再通过手眼矩阵转换到机械臂坐标系然后驱动机械臂末端移动到该点测量实际误差。这个验证最直观也最有说服力。如果实测误差在可接受范围内标定才算真正完成。5. 常见问题与排查技巧实录5.1 为什么标定结果总是不对在实际项目里标定结果不对是常态关键是你要懂得怎么排查。我把自己踩过和见过别人踩的坑整理成一个速查表现象可能原因排查方向标定结果偏移很大几十毫米级内参未标定或标定不准重新做相机内参标定特定方向存在系统性误差TCP不准或运动学参数有误重新标定工具坐标系结果随数据集变化剧烈数据姿态多样性不足补充大角度旋转数据重投影误差大且分散图像角点提取错误检查标定板质量与光照标定精度“偏左偏右”不一致机械臂位姿和图像未同步检查记录逻辑确认静止后拍摄相机与末端间有移动相机固定螺钉松动或支架变形检查机械刚性这里面最常见的还是数据姿态多样性的问题。有些人为了省事控制机械臂在一个小范围内做微调拍了二十张图但角度几乎没变这样数据之间的约束极度软弱解算结果自然飘忽不定。另外一个高发问题就是位姿记录和图像不同步机械臂还在运动中就触发了拍照运动模糊和位姿错位叠加在一起标定结果完全不可用。5.2 打样排查清单与个人经验我在这里给一份我自己总结的排查清单按优先级排列。首先检查标定板角点检测结果是否全部正常这一步最简单也最省时间其次看机械臂位姿和图像时间戳是否严格对应再复查相机内参是否准确尤其是畸变系数最后检查TCP是否准确必要时重新标定一次如果以上都没问题再评估数据姿态覆盖是否充分。我以前遇到过一个典型案例某项目的视觉引导抓取精度始终在5毫米开外怎么调都达不到要求的1毫米。当时第一反应是重新做相机内参标定但结果没有明显改善。后来一条条排查才发现问题出在TCP标定上——机械臂末端装了一个异形夹爪但TCP只是用了一个默认值没有重新标定。工具坐标系本身的误差是毫米级的叠加到手眼标定里误差自然无法消除。重新标定TCP之后再对手眼标定做了一次修正精度立刻回到0.8毫米左右。这个案例说明手眼标定不是一个孤立的环节它依赖整个系统的精度链前端的任何一个误差都会传导到最终结果里。另外多说一句关于标定板和相机之间距离的建议。实际操作中标定板不要离相机太近也不要太远最好落在实际工作距离附近。如果你最终要用的工作距离是300毫米左右那标定数据也应该主要在这个距离附近采集这样标定出来的手眼矩阵在工作点附近最有代表性。5.3 我踩过几次坑之后的一些心得讲一个我反复遇到的情况明明数据采集很规范姿态覆盖也足够但标定结果在边缘区域总是不理想。后来发现这是因为我把标定板放在视野边缘附近采集了几组数据。镜头边缘的光学畸变和像差最严重虽然内参标定已经做了畸变校正但校正模型也不可能完美还原所有失真。从那以后我采集数据时严格把标定板控制在视野中央区域边缘数据宁可不采也不要硬凑。还有一个容易被忽略的细节标定板平面的平整度。某些中等价位的标定板特别是柔性的那种使用一段时间后会略微弯曲角点检测依然能通过但位姿解算出的角度就带上了未知偏差。这个偏差在标定数据里很难看出来但会实实在在降低标定精度。我现在用的标定板都是陶瓷或者硬化铝基材的使用时也特别注意避免磕碰和高温环境变形。说到底手眼标定是一个系统工程它考验的不是你调函数的水平而是你对整个坐标变换链条的理解程度。坐标系定义清晰、数学原理清楚、数据采集严谨、验证方法到位精度自然能上去。这个系列后续会继续展开具体的数据采集代码实现、标定算法细节以及不同相机2D、3D、结构光下手眼标定的差异。如果你正在做视觉引导项目建议先把这篇里的坐标系和数学模型彻底吃透再动手写代码相信我这样能少走很多弯路。