目前用的相机是 D405 和 D435,简单整理下机械臂标定里的几个坐标关系。先从 TCP 开始,再看眼在手上、眼在手外,最后记录下深度相机的数据怎么接到机械臂上。
坐标怎么记
先统一记号,不然后面很容易把矩阵乘反。
b:机械臂基座 base。f:法兰 flange。c:相机 camera,具体是哪一路图像对应的光学坐标系,要写清楚。t:标定板 target。tcp:工具实际使用的工作坐标系。
这里用 \({}^{a}T_b\) 表示把 b 系的坐标转换到 a 系:
\[ p_a={}^{a}T_b p_b \]
所以连续变换可以直接把中间的坐标系消掉:
\[ {}^{a}T_c={}^{a}T_b\,{}^{b}T_c \]
齐次矩阵的逆为:
\[ T=\begin{bmatrix}R&t\\0&1\end{bmatrix},\qquad T^{-1}=\begin{bmatrix}R^T&-R^Tt\\0&1\end{bmatrix} \]
注意平移部分是 \(-R^Tt\),不是直接把 \(t\) 取负。
TCP
TCP 标定求的是工具相对于法兰的变换 \({}^{f}T_{tcp}\)。比如探针的尖端,或者夹爪实际夹持中心。
常见的固定点法,就是让工具尖端从不同姿态接触同一个参考点。第 \(i\) 次记录到的法兰位姿为 \((R_i,t_i)\),工具尖端在法兰系的位置为 \(p_f\),固定点在基座系的位置为 \(q_b\),那么:
\[ R_i p_f+t_i=q_b \]
整理一下:
\[ \begin{bmatrix}R_i&-I\end{bmatrix} \begin{bmatrix}p_f\\q_b\end{bmatrix}=-t_i \]
把多次记录堆叠起来,就可以用最小二乘解出 \(p_f\) 和 \(q_b\)。
这里主要求出的是 TCP 的位置。工具坐标系的方向还要单独定义,比如夹爪朝前的方向、开合方向,不能因为尖端位置算出来了,就认为完整 TCP 已经确定。
示教姿态要有明显的角度变化。反复采集差不多的姿态,数据虽然多,提供的约束却没有增加多少。求完以后,换几组没有参与计算的姿态,检查尖端能否继续对准参考点。具体示教方式和点数以机器人厂商为准,可以参考 UR 的 TCP 示教说明。
眼在手上
眼在手上就是相机装在末端,标定板固定不动。要求的是相机到法兰的变换:
\[ X={}^{f}T_c \]
设第 \(i\) 次法兰位姿为 \(G_i={}^{b}T_{f,i}\),PnP 算出的标定板位姿为 \(C_i={}^{c}T_{t,i}\)。因为标定板相对于基座不动:
\[ G_iXC_i=G_jXC_j \]
整理成:
\[ (G_j^{-1}G_i)X=X(C_jC_i^{-1}) \]
也就是常见的 \(AX=XB\)。
求出 \(X\) 后,相机里的点转换到基座,需要使用拍摄这一帧时的机械臂位姿:
\[ p_b={}^{b}T_f\,{}^{f}T_c\,p_c \]
相机跟着末端运动,\({}^{b}T_f\) 就不能一直拿某个固定姿态的值。
眼在手外
眼在手外是相机固定,标定板跟着末端走。要求的是:
\[ X={}^{b}T_c \]
仍然记 \(G_i={}^{b}T_{f,i}\)、\(C_i={}^{c}T_{t,i}\)。标定板相对于法兰的变换虽然不知道,但固定不变,记为 \(Y={}^{f}T_t\):
\[ G_iY=XC_i \]
左右两条路径表达的是同一件事:把标定板坐标转换到基座。
消去 \(Y\) 后:
\[ (G_jG_i^{-1})X=X(C_jC_i^{-1}) \]
眼在手外同样能整理成 \(AX=XB\),但机器人侧的变换方向和眼在手上不同。
用 OpenCV 的 calibrateHandEye 时,如果保存的是 \({}^{b}T_f\),眼在手外需要先取逆,输入 \({}^{f}T_b\);相机侧输入 \({}^{c}T_t\),输出按这里的坐标定义解释为 \({}^{b}T_c\)。函数参数名是按眼在手上的情况起的,不能只看名字就直接传数据。OpenCV 文档
另外,手眼求解不一定要先标定 TCP。如果标定板固定在法兰上,机器人侧一直使用法兰位姿就能建立上面的约束。不过后面要让实际工具到达目标,TCP 还是要校准。
数据怎么采
先按眼在手外记录一套流程:
- 固定相机和标定板,确认支架、连接件不会在采集时松动。
- 读取实际使用图像流的内参、畸变、分辨率,测量并确认标定板尺寸。
- 机械臂停稳,保存图像和对应的实际法兰位姿。
- 检测标定板角点,用 PnP 得到 \({}^{c}T_t\)。
- 采集不同位置、距离、倾角的数据,剔除检测错误的图像,再求解手眼关系。
- 留出一部分姿态,单独验证,不全部拿去拟合。
可以先从十几组姿态开始,但不能把组数当成质量保证。只做平移,或者始终绕同一个轴旋转,都会缺少有效约束。OpenCV 对可解性的要求涉及绕非平行轴的相对旋转,实际使用时也建议采集更多姿态。
PnP 用的是标定板上已知的几何尺寸和图像角点,未必需要深度图参与。因此手眼关系看起来正常,深度抓取仍然偏,也不矛盾:深度测量是后面另外一段误差来源。
采集时最好顺便保存设备序列号、图像流配置、时间戳、单位和位姿表示方式。机器人返回的是法兰还是 TCP、角度是欧拉角还是旋转向量,都要确认。图像和位姿对应错了,后面换求解方法通常也救不回来。
D405、D435 要注意什么
官方给 D405 的理想工作范围是 7~50 cm,D435 的比较页面列出 0.3~3 m。这些是工作范围参考,不能当成整个范围内都能达到同一精度的承诺,具体还要看分辨率、模式、材质和现场环境。D405 产品资料;型号比较
D405 的 RGB 来自左侧深度成像器,经 ISP 输出,没有独立 RGB 相机。这和 D435 的彩色、深度成像结构不同,不能把两款的数据处理流程当成完全一样。无论用哪一款,内参和图像流之间的关系都应该从当前配置读取,而不是写死一套参数。
如果手眼标定使用的是彩色图像,最后反投影得到的点却在深度坐标系中,还需要把这两个坐标系接起来:
\[ p_b={}^{b}T_{c_{rgb}}\, {}^{c_{rgb}}T_{c_{depth}}\,p_{c_{depth}} \]
不能把深度坐标系里的点直接乘“彩色相机到基座”的矩阵。
RealSense 这里还有几个小地方:
- 原始 Z16 数值要乘运行时读取的
depth_scale;get_distance(u, v)已经返回米,不要再乘一次。 - 深度为 0 表示没有有效深度,不能当成正常测量。
- 像素坐标是
(u, v),数组通常按[v, u]索引。 - 彩色像素不能直接索引未对齐的深度图。要用 SDK 的投影、反投影和流间外参建立对应关系。
- 用实际深度帧对应的内参与畸变模型反投影,不能随手拿另一种分辨率的参数。
这些约定可以直接对照 RealSense 的投影说明。
取深度时,也尽量不要只读目标轮廓边缘的一个像素。边缘、遮挡和无效深度都会影响结果,可以在确认属于目标的区域里做有效值统计或几何拟合。滤波后的图更完整,并不代表每个补出来的深度都是真实观测。
接到机械臂上
眼在手外得到物体位姿 \({}^{c}T_o\) 后:
\[ {}^{b}T_o={}^{b}T_c\,{}^{c}T_o \]
再根据抓取策略定义物体到期望 TCP 的关系:
\[ {}^{b}T_{tcp}^{*} ={}^{b}T_c\,{}^{c}T_o\,{}^{o}T_{tcp}^{*} \]
如果机器人接口要求的是法兰位姿,再转换:
\[ {}^{b}T_f^{*} ={}^{b}T_{tcp}^{*}({}^{f}T_{tcp})^{-1} \]
如果接口本来就是按当前 TCP 接收目标,就不要再手动扣一遍工具长度,否则会重复补偿。
还有一点,像素加深度只确定一个三维点,不会自动得到完整的六自由度抓取位姿。夹爪朝哪个方向、是否需要沿物体边缘对齐,还需要目标几何或抓取规则。
怎么验收
标定软件显示成功,只能说明它算出了结果。验收可以拆成三层。
图像层:标定板尺寸是否正确,角点是否稳定,重投影误差是否合理。
几何层:用没有参与求解的姿态检查坐标链。眼在手外时计算:
\[ Y_i=G_i^{-1}XC_i \]
标定板相对于法兰固定,所以这些 \(Y_i\) 应该接近同一个变换。位置和角度误差分开统计,不要把两者混成一个没有单位说明的分数。
任务层:用独立参考,在不同位置、距离和画面区域检查 TCP 实际落点。先确认变换方向与工作空间,在安全距离、低速和既有防护措施下逐步接近,不要把第一次验证直接变成自动碰触。
角度误差也不能忽略。小角度下,距离为 \(L\) 时的横向误差大约是 \(L\delta\theta\)。例如 0.1° 在 500 mm 距离上,大约对应 0.87 mm 偏差。这只是几何估算,不是相机或机械臂的精度指标。
总结一下就是:先把坐标方向和单位弄清楚,再看采集数据是否有足够变化,最后用独立数据和实际任务验证。相机的标称精度、手眼残差、机械臂重复定位精度、最终抓取误差是几件不同的事,不能拿其中一个直接代替其他几个。