ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

手眼标定全攻略:眼在手中与眼在手外的原理、实操与避坑指南

手眼标定全攻略:眼在手中与眼在手外的原理、实操与避坑指南

手眼标定,眼在手中,眼在手外——这四个字看起来简单,但凡是做过机器人视觉引导项目的人,都知道这是整个系统里最容易让人失眠的环节。你搭好了相机、装好了机械臂、写好了识别算法,结果机械臂怎么都抓不准,误差从几毫米到几厘米飘忽不定,最后排查下来,十有八九是手眼标定这一步出了问题。这篇文章我就把这块的内容完整拆开,从原理到实操,从眼在手中到眼在手外,把坐标变换的来龙去脉、标定步骤、常见坑点一次讲透,适合刚接触机器人视觉的工程师,也适合被标定精度折磨过一阵子的老手对照排查。

1. 手眼标定到底在解决什么问题

1.1 机器人的眼睛和手,各说各话

先说一个最直观的感受:机械臂能拿到一个物体,靠的是末端执行器精确移动到某个坐标位置。而相机能看到物体,输出的是像素坐标或者相机坐标系下的三维坐标。问题是,相机坐标系和机器人坐标系之间,没有任何天然的对应关系——你在图像里看到一个螺丝钉在画面正中央,但机械臂到底要往哪个方向伸、伸多远,完全取决于相机和机器人之间的空间关系。

这个关系不是靠眼睛看出来的,也不是靠尺子量出来的。相机装在机械臂旁边或者末端上,角度、距离、高度稍有偏差,机械臂的目标位置就会跟着偏。手眼标定要做的事情,就是通过一系列数学计算,把相机看到的物体坐标,换算成机械臂能执行的机器人坐标。这个换算关系一旦出错,后面所有的视觉识别精度都白搭。

有人可能会说,那我用尺子量一下相机相对机械臂的位置角度,手动换算不行吗?理论上是可行的,但实际几乎没人这么干。因为相机的镜头光轴、成像中心、安装倾角都不是肉眼能精确测量的,几度的角度误差在远距离上就是几十毫米的位置偏差。标定算法通过多组测量数据反解出变换关系,比手工测量可靠得多,这也是为什么手眼标定成了视觉引导项目里不可跳过的一步。

1.2 眼在手中和眼在手外的坐标系关系

眼在手中(eye-in-hand),指的是相机固定在机械臂末端法兰上,跟着机械臂一起运动。这种情况下,相机坐标系和机械臂末端坐标系是相对固定的,但机械臂在运动,所以相机在世界中的位置也在不断变化。标定要求解的,是相机坐标系到机械臂末端坐标系的齐次变换矩阵,记为X = T_cam_to_tool。

眼在手外(eye-to-hand),指的是相机固定在机械臂之外的某个位置,比如支架上、天花板上、工作台上方,相机不动,机械臂在它的视野里运动。这种情况下,相机坐标系和机器人基座坐标系才是相对固定的,标定要求解的是相机坐标系到机器人基坐标系的变换矩阵,记为X = T_cam_to_base。

这两种安装方式的求解目标不同,数据采集方式也不同,但底层数学都是同一个问题:AX = XB。这个方程看着抽象,实际上描述的是一个闭合的坐标变换链——机械臂带着末端动一圈,相机每次都记录下标定板在相机里的位姿,同时记录下机械臂末端的位姿,联立方程把未知的固定变换解出来。理解了这条坐标链,后面的所有操作步骤就都顺理成章了。

1.3 AX=XB的通俗理解

AX = XB 这个方程,说白了就是:我从两个不同路径走到同一个房间,路径不一样,但终点一样,于是两个路径的变换关系可以联立。具体到手眼标定,假设标定板固定不动,机械臂带着(或不带)相机运动到多个位置:

  • A 是机械臂末端在两个位姿之间的运动变换,由机器人示教器或控制接口直接读出。
  • B 是相机在两次拍照之间观察到的标定板位姿变化,由标定板识别得到。
  • X 是我们想求的固定变换,眼在手中时是相机到末端的变换,眼在手外时是相机到基座的变换。

多取几组位姿,就有了多个 AX = XB 方程,数值求解得到最优的 X。这些年 OpenCV 的calibrateHandEye函数已经把常用的经典算法(Tsai-Lenz、Park-Martin 等)封装好了,输入肉眼看不懂,输出就是一个 4×4 齐次变换矩阵。理解到这一层就够了,真正的工作量在数据采集和质量把控上,而不是在数学推导上。

2. 两种安装方式怎么选,别等装好了再后悔

2.1 眼在手中的优势与适用场景

眼在手中最直观的好处,是相机离作业目标近。视野小、分辨率高,螺丝孔、小零件、表面缺陷这些细节能看得很清楚,精度潜力比固定相机大得多。其次,机械臂动到哪,相机就跟到哪,总能找到合适的角度去看目标,不用担心被其他物体挡住视线。比如在狭窄的机箱内部插线、小零件抓取、焊缝跟踪这些需要贴近看、照着干的场景,眼在手中基本是默认选择。

但它也有明显的代价。相机跟着机械臂动,运动惯性会影响整机的动态精度,而且每次拍照时机械臂必须稳定到位,不然画面模糊直接导致识别失败。另外一个容易被忽视的问题是:眼在手中标定时,标定板必须固定在工作空间里,机械臂带着相机在不同角度拍照,这就对机械臂的工作空间有要求,活动范围太小会严重影响标定质量。我见过不少项目,机械臂工作空间本身就紧凑,还要腾出标定位置,最后只能把标定板往边缘放,标定出来的结果自然不理想。

2.2 眼在手外的优势与适用场景

眼在手外是另一种思路:相机固定在龙门架、支架或工作台上,俯视整个作业区域。最大的优势是视野覆盖大且稳定,适合大范围定位、物料来料检测、多工位共享一个相机。机械臂运动时相机不受任何振动影响,图像质量稳定,标定一次之后只要相机不碰,长时间不用重新标。对很多流水线场景来说,眼在手外更省心。

缺点也很现实。相机离得远,视野大但分辨率被摊薄,识别小物体时精度有限。俯视角度也意味着低矮物体、堆叠物体的侧面信息看不到,有些姿态识别做不了。还有一个比较大坑:相机和机械臂如果距离太远,标定板放在机械臂末端时,相机的安装角度和距离会导致标定板成像过小或过大,直接影响标定板角点检测的稳定性。这也是为什么眼在手外标定时,很多工程师倾向于选较大的标定板,而不能随手拿个小的凑合用。

2.3 选型决策要点

拿不准选哪种的时候,我从项目经验里总结了几个判断点:

判断维度眼在手中眼在手外
目标物大小小零件、细节特征中大物体、整面覆盖
视野需求局部高精度全局覆盖
遮挡情况机器人靠近观察、规避遮挡容易被机械臂自身遮挡
标定频率换末端夹具或相机松动时需要重标相机固定稳定,可长期复用
安装空间相机占用末端载荷和空间需要外部支架或龙门
运动影响图像受运动振动影响图像完全不受运动影响

如果项目里既有全局定位需求,又要有细节检测,还有些人会做成双相机方案:一个眼在手外负责找大范围目标,粗定位,另一个眼在手中跟着末端走,精定位。这种组合在不少复杂工位上是比较实用的做法,但前期调试量也翻倍,初学者不建议一上来就双相机。我的建议是:先根据主要作业目标确定主方案,复杂度控制在一个相机,稳定跑通之后再考虑加装。

3. 手眼标定的完整实操流程

3.1 标定板的选型与准备

标定板是手眼标定的基准参照物,质量直接决定标定成败。市面上常见的三类:棋盘格、圆点阵、二维码/ArUco 板。棋盘格最容易获得,纸质打印就能用,OpenCV 的findChessboardCorners成熟稳定,适合快速验证流程;但纸质板容易弯折、反光,精度天花板有限。圆点阵对畸变和模糊更鲁棒,圆心提取精度比角点高,适合高精度标定。ArUco / AprilTag 的优势在于单张图就能提供唯一的位姿信息,部分算法用它做实时标定更灵活,但对相机离焦比较敏感。

尺寸选择有一个很实用的经验法则:标定板边长至少占相机视野的 1/3 到 1/2。太小了角点检测不稳定,太大了在近距离下又拍不全。眼在手中因为相机跟得近,标定板比眼在手外可以小一些;眼在手外相机离得远,标定板尽量选大方形的。我自己的习惯是打印后贴在刚性背板上,比如铝板、亚克力板,用双面胶贴平后压重物静置一晚,确保板面平整。这一步看着不起眼,但板面不平造成的误差,后面靠算法怎么调都调不回来。

3.2 数据采集的关键姿势与数量控制

标定精度很大程度上由数据决定,而不是由算法决定。数据采集的核心要求是:姿态要多样,位置要分散,覆盖整个工作区域。

具体来说,至少采集 15 到 20 组有效图像,每组对应机械臂的一个位姿。每组之间要保证:

  • 机械臂末端的姿态要有明显变化,光平移不转或者光转不平移都容易让 AX=XB 方程退化,解出来的 X 精度很差。
  • 标定板成像在视野内尽量往四角和边缘靠,不要总在正中心徘徊。相机镜头的畸变在边缘区域表现得更明显,标定数据覆盖边缘有助于算法把畸变和手眼关系分开。
  • 机械臂的运动范围要覆盖后续作业会用到的区域,不能只在某个角落转小圈。

还有一个很多人会忽略的点:每组数据记录时,机械臂必须到达目标位姿并完全稳定之后再拍照。如果机械臂还在抖动或者移动中就触发拍照,标定板位姿识别会产生随机误差,每一组都带噪声,最终标定结果就是抖的。控制相机触发的信号我一般建议直接接机械臂的输出端口,到位后延时 300 到 500 毫秒再拍,比在工控机里靠线程延时靠谱得多。

3.3 标定求解:OpenCV工具链与核心代码

数据采集完之后,求解阶段用 OpenCV 的calibrateHandEye就够了。下面这段是标准流程,基于 Python 和 OpenCV,我实际项目里一直在用,直接套用即可。

import cv2 import numpy as np import glob # 读入所有标定板图像 images = sorted(glob.glob("calib_images/*.jpg")) pattern_size = (9, 6) # 棋盘格内角点数 square_size = 30.0 # 格边长,单位mm # 标定板在相机坐标系下的位姿,以及末端位姿的容器 rvecs_cam = [] tvecs_cam = [] rvecs_robot = [] tvecs_robot = [] # 1. 从图像中提取标定板角点并解算相机外参 objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objp *= square_size # 相机内参从单独的相机标定中获得,或先用近似值 camera_matrix = ... # 3x3内参矩阵 dist_coeffs = ... # 畸变系数 for fname in images: img = cv2.imread(fname) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners = cv2.findChessboardCorners(gray, pattern_size, None) if not ret: continue # 亚像素角点精化 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 50, 1e-6) corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) # 解算标定板相对相机的位姿 ret, rvec, tvec = cv2.solvePnP(objp, corners, camera_matrix, dist_coeffs) rvecs_cam.append(rvec) tvecs_cam.append(tvec) # 从机械臂控制接口读取当前末端位姿,转成旋转向量和平移向量 robot_pose = get_robot_pose() # 自行实现,返回4x4齐次矩阵或欧拉角 rvec_robot, tvec_robot = pose_to_rvec_tvec(robot_pose) rvecs_robot.append(rvec_robot) tvecs_robot.append(tvec_robot) # 2. 手眼标定求解 R_cam2tool, t_cam2tool = cv2.calibrateHandEye( rvecs_robot, tvecs_robot, # 机械臂末端运动 rvecs_cam, tvecs_cam, # 相机观测标定板运动 method=cv2.CALIB_HAND_EYE_TSAI ) # 3. 组装齐次变换矩阵 H_cam2tool = np.eye(4) H_cam2tool[:3, :3] = R_cam2tool H_cam2tool[:3, 3] = t_cam2tool.flatten() print(H_cam2tool)

眼在手外的求解过程几乎一样,区别只在读取机械臂位姿的时候用的是基坐标系下的机械臂末端位姿,而且标定板一般固定在机械臂末端法兰上,数据记录顺序和输入参数要对应好。很多人在这一步栽跟头,就是把两种安装方式的输入搞反了。

3.4 标定结果怎么验证才算通过

标定完不是看一眼矩阵就收工,必须做验证。最常用的方法有两种:重投影验证和实际点验证。

重投影验证的思路是:用标定得到的手眼矩阵,把标定板上的已知点反投到图像坐标中,和实际检测到的像素坐标对比,计算重投影误差。这个误差在亚像素到 1 到 2 个像素内,说明标定结果在图像层面是自洽的。但注意一点:重投影误差只反映数据拟合的残差,不代表机器人抓取的实际精度,因为它没法暴露机械臂自身绝对定位误差带来的影响。

所以更关键的是实际点验证:把标定板(或者一个特征明显的小工件)放在相机视野内的多个位置,用视觉检测出它的坐标,再通过手眼矩阵换算成机器人坐标,让机械臂末端移动到该坐标,看实际对准误差。一般来说,平移误差控制在 1 到 3 毫米内是及格线,配合高精度末端工具可以做到 0.5 毫米内;旋转误差最好在 0.1 度到 0.2 度以内。如果验证结果超出这个范围,先不要急着调算法,回头检查数据采集的质量,多数问题出在那里。

4. 实操中踩过的坑与排查技巧

4.1 标定板检测失败的常见原因

标定板检测失败是最容易让人沮丧的问题,因为报错往往不明确。我遇到的情况大致有几类:

第一类是光照问题。标定板表面反光,尤其是棋盘格的白格在强光下过曝,角点附近的高光会把角点吞掉。解决办法是调整光源角度,不要让光源对着标定板直射,漫射光或者角度低一点的侧光效果会好很多。这类问题用眼睛看图像无法发现,因为人眼能脑补出边界,但算法不行,一定要把图像放大逐块看。

第二类是标定板尺寸和视野的比例不合适。棋盘格在画面里占的像素太少,角点之间的间距只有几个像素,亚像素精化也没有意义。遇到这种情况,要么换更大号的标定板,要么让相机离标定板更近,保证角点间距至少在 10 个像素以上。

第三类是运动模糊。机械臂到位后没有稳定就触发拍照,图像边缘有轻微的拖影,角点检测就会识别出斜向误差。排查方法是拍照后立刻查看图像是否清晰,标定板边缘是否锐利,不行就在相机触发逻辑上加延时。

4.2 标定结果发散或误差大的排查思路

标定出来的矩阵如果明显不合理——比如平移分量有几十毫米的突变、旋转矩阵不正交、两次标定结果差得离谱——先别怀疑算法公式写错了,大概率是数据本身的病态问题。

最典型的是姿态多样性不够。如果你采集的 20 组数据里,机械臂末端姿态几乎没变,只是左右前后平移,那 AX=XB 方程就处于退化状态,解出来的旋转分量严重不可靠。这时候就算重采样,也要刻意让机械臂做俯仰、偏航、滚转的大角度运动,姿态角度差异最好在 30 度以上,才能把旋转自由度约束住。

另外一个常见的坑是坐标系混淆。机械臂接口返回的位姿,有的是末端坐标系相对基坐标系的,有的是工具坐标系相对基坐标系的,如果你的工具坐标系有偏移(比如装了吸盘、气爪),直接拿来标定就会引入一个固定偏差。排查方法很简单:手动把机械臂末端转个 90 度,看读取的坐标变化是否符合预期;也可以在示教器上单独查看工具坐标系的数值是否归零。

还有一个容易忽略的是机械臂的绝对定位精度。某些型号的机械臂重复定位精度很高,比如 ±0.02 毫米,但绝对定位精度可能只有 ±2 到 ±3 毫米,这种差异会被标定算法当成噪声吸收掉,导致视觉引导的最终精度上限受制于机械臂自身。这种情况不是标定能弥补的,要做的是在验证环节就把机械臂的绝对定位能力测一遍,心里有数。

4.3 手眼矩阵验证的独门方法

标定矩阵的验证环节,我有一个自己一直在用的“三点法”,实施起来很快,但很能说明问题:在相机视野内的左、中、右三个区域各放一个特征点,用视觉检测出坐标,换算成机器人坐标后控制机械臂去指。如果三个点的误差方向一致且大小相近,可能是手眼矩阵里某个平移分量有固定偏差;如果误差在不同区域差别很大,通常是旋转分量不对,因为旋转误差在视野边缘会被放大;如果误差随机跳动,优先怀疑机械臂定位稳定性或视觉识别本身的波动。

这个方法的优点是把误差来源做了粗分离,省得每次出问题都全链路排查。我做了这么多项目之后有个体会:手眼标定是一次性的活儿,但验证是每次换工装、换夹具、调相机之后都要做的安全网。很多现场故障看起来是识别算法的问题,追根溯源往往是手眼矩阵已经失效了(比如相机被碰歪了几毫米),提前做一个三点法验证,能省几个小时的无头绪排查。

4.4 提高标定精度的几个独家技巧

第一,采集数据时给机械臂“多转少移”。每次到位后让末端姿态多做一些变化,平移动量大但姿态变化小的数据,对求解的贡献远不如姿态变化大的数据。我一般会刻意规划几组“大角度低头看标定板”的姿态,比如 30 度、45 度、60 度俯仰角各几组,效果好很多。

第二,标定板不要放在工作空间正中心附近。很多人习惯把标定板放在机器人正前方,然后让机械臂带着相机原地转圈拍。这样采集的数据覆盖范围太集中,标定出来的手眼矩阵在中心附近拟合得很好,但到了工作区域边缘就明显偏。正确做法是让标定板覆盖整个工作平面的多个位置,移动标定板而不是只移动机械臂。

第三,多标定几次取平均。同一个数据集因为初值不同,算法可能收敛到略有差异的解,这不是算法不稳定,而是数值求解的正常现象。我通常会连续标定 5 次,把得到的五个矩阵两两比较,看平移分量波动多少。波动在 1 毫米以内算稳定,如果波动过大,说明数据质量还不够,加了再多数据也白搭。

5. 最后分享一点个人体会

做了这些年视觉引导项目,我个人的体会是:手眼标定这个东西,技术含量并不在数学和代码上,而在数据采集和现场判断上。OpenCV 给了一行函数,真正决定成败的是你有没有耐心把 20 组高质量数据采出来,有没有把标定板贴平、把光源调好、把机械臂的姿态覆盖到位。很多时候项目卡住,不是卡在算法上,而是卡在“差不多就行”的心态上。

另外,一个小建议:标定完的矩阵一定要做好归档和版本管理。换相机、换镜头、换末端工具、甚至只是相机支架被人碰了一下,手眼矩阵可能就失效了。我在现场的习惯是每次标定完,把矩阵、采集的图像编号、机械臂型号、工具坐标系的配置一起存成一个版本文件夹,现场出问题时可以快速回溯是哪一次标定出了问题。这个习惯看起来不起眼,但真正遇到半夜现场调试、设备被人动过又没人承认的场合,它比什么都管用。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进