3. 坐标系基础:世界坐标系、机器人坐标系、轨迹坐标系、坐标变换

做轨迹跟踪,第一个绕不开的就是坐标系。我见过不少新手工程师,算法写得挺漂亮,结果一跑就飞,最后发现是坐标系搞反了。说白了,坐标系就是你和机器人沟通的「共同语言」。语言不通,啥都白搭。

这一章,咱们就把世界坐标系、机器人坐标系、轨迹坐标系这三个核心概念掰开揉碎,再聊聊它们之间怎么互相转换。嗯,这部分搞明白了,后面的误差分析才有根基。

3.1 世界坐标系:参考系

世界坐标系,也叫全局坐标系。你可以把它想象成整个场景的「标尺」。不管机器人怎么动,轨迹怎么画,最终都要回到这个坐标系里来度量。

通常我们用 {W} 表示,原点一般设在场景的某个固定点,比如工位的角落、地面的中心。三个轴 XW、YW、ZW 遵循右手定则。

关键点:世界坐标系是静止的。所有其他坐标系都是相对于它定义的。你规划的目标轨迹,通常也是在世界坐标系下描述的。

我在项目里遇到过一个问题:一台AGV在仓库里跑,激光SLAM建图时用的坐标系原点在仓库门口。后来换了工位布局,原点没变,但所有路径点都要重新标定。这就是世界坐标系选得不合理带来的麻烦。我个人习惯,世界坐标系的原点尽量选在场景的对称中心或长期不变的基准点上。

3.2 机器人坐标系:随体参考系

机器人坐标系,也叫本体坐标系,用 {R} 表示。它牢牢地「长」在机器人身上。原点通常在机器人的质心或几何中心,XR 轴指向机器人前进方向,YR 轴指向左侧,ZR 轴垂直向上。

为什么要单独搞一个机器人坐标系?因为传感器(激光雷达、摄像头、IMU)都装在机器人上,它们测得的数据天然就是相对于机器人自身的。你想想看,激光雷达扫到一个障碍物,它报告的是「障碍物在我前方2米」,而不是「障碍物在世界坐标系的(10, 5)处」。

坐标系 符号 原点 特点
世界坐标系 {W} 场景固定点 静止,参考
机器人坐标系 {R} 机器人质心 随机器人移动、旋转
轨迹坐标系 {T} 轨迹上的当前点 沿轨迹移动,方向随路径变化

小技巧:调试时,我习惯在可视化工具里同时显示世界坐标系和机器人坐标系。如果机器人坐标系的原点没有跟着机器人模型走,那一定是坐标变换参数没设对。我曾经因为这个排查了整整一个下午,最后发现是TF树里少写了一帧。

3.3 轨迹坐标系:路径上的移动参考系

轨迹坐标系 {T} 是专门为轨迹跟踪引入的。它沿着目标轨迹移动,原点在轨迹上距离机器人最近的点(或者当前跟踪的目标点)。

它的定义方式很讲究:

  • XT 轴:沿轨迹的切线方向,指向轨迹前进方向。
  • YT 轴:垂直于切线,指向轨迹的左侧(法线方向)。
  • ZT 轴:垂直于轨迹平面(对于平面轨迹,就是垂直向上)。

为什么要引入它?因为在这个坐标系下看误差,最直观。机器人在轨迹坐标系下的 YT 方向偏差,就是横向误差;XT 方向偏差,就是纵向误差(或前向误差)。

说白了,轨迹坐标系把「机器人偏离轨迹多远」这个问题,变成了一个简单的二维坐标读数。

3.4 坐标变换:连接三个世界的桥梁

三个坐标系之间需要互相转换。最常用的变换是齐次变换矩阵 T,它包含旋转矩阵 R 和平移向量 t。

一个点 P 在坐标系 {A} 下的坐标 AP,转换到坐标系 {B} 下的坐标 BP,公式是:

B_P = R_A^B * A_P + t_A^B

用齐次形式写就是:

| B_P |   =   | R_A^B   t_A^B |   *   | A_P |
|  1  |       |   0       1   |       |  1  |

实际工程中,我们最关心三个变换:

  1. 世界 → 机器人:TWR,把全局路径点转换到机器人局部视角。
  2. 世界 → 轨迹:TWT,根据轨迹参数方程实时计算。
  3. 机器人 → 轨迹:TRT,这是误差计算的核心,直接给出机器人在轨迹坐标系下的位姿偏差。

注意:坐标变换的顺序不可交换。先旋转再平移,和先平移再旋转,结果完全不同。我见过有人把 R * P + t 写成了 R * (P + t),结果轨迹跟踪曲线像喝醉了酒一样。嗯,这种bug最难查。

3.5 知识体系总览

下面这张图,把三个坐标系和它们之间的变换关系串起来了。你可以把它当作本章的「地图」。

坐标系与坐标变换关系图 世界坐标系 {W} 参考,静止不动 机器人坐标系 {R} 随体,传感器数据源 轨迹坐标系 {T} 沿路径移动,误差基准 T_W^R T_W^T T_R^T 核心变换链 T_R^T = T_W^T · (T_W^R)^{-1} 误差 = 机器人在 {T} 下的 (x, y, θ) 偏差 应用:误差计算 → 控制律设计 → 轨迹修正 所有控制算法最终都在 {T} 下计算反馈量

3.6 实战:从传感器数据到误差计算

咱们走一遍完整的流程。假设机器人上有个激光雷达,测得一个障碍物在雷达坐标系 {L} 下的位置是 LP = (1.0, 0.5, 0)。

第一步:雷达坐标 → 机器人坐标

雷达装在机器人前方0.3米处,向右偏移0.1米。那么变换矩阵 TLR 的平移部分是 (0.3, -0.1, 0),旋转是单位矩阵(假设雷达和机器人朝向一致)。

R_P = T_L^R * L_P
    = (1.0 + 0.3, 0.5 - 0.1, 0)
    = (1.3, 0.4, 0)

第二步:机器人坐标 → 世界坐标

假设机器人当前在世界坐标系下的位姿是 (x=5.0, y=3.0, θ=30°)。我们需要用 TRW 来转换。

W_P = R(30°) * R_P + t
    = [cos30  -sin30  0]   [1.3]   [5.0]
      [sin30   cos30  0] * [0.4] + [3.0]
      [0       0      1]   [0  ]   [0  ]
    = [0.866 -0.5  0] [1.3]   [5.0]
      [0.5   0.866 0] [0.4] + [3.0]
      [0     0     1] [0  ]   [0  ]
    = [0.925, 0.996, 0] + [5.0, 3.0, 0]
    = (5.925, 3.996, 0)

第三步:世界坐标 → 轨迹坐标

找到轨迹上距离 WP 最近的点,计算该点的轨迹坐标系 {T},然后做变换。这一步通常由路径跟踪库(如纯追踪、MPC)自动完成。最终输出就是机器人在轨迹坐标系下的横向误差和纵向误差。

我的经验:在实际代码中,我建议把坐标变换封装成独立的函数或类。不要到处写矩阵乘法。ROS里的TF库就是个好例子,你只需要发布坐标系之间的静态变换和动态变换,剩下的查询和转换交给库去处理。这样代码干净,也不容易出错。

好了,坐标系这块地基算是打牢了。你想想看,有了这三个坐标系和它们之间的变换关系,我们就能精确地描述「机器人到底偏了多少」。下一层,咱们就要用这些工具去定义和计算具体的跟踪误差指标了。