2. 坐标系基础:世界坐标系、机器人坐标系、传感器坐标系、坐标变换与齐次矩阵

说实话,坐标系这个东西,刚入行时我觉得太简单了。不就是三个轴嘛,谁不会?

直到我第一次做多传感器融合的项目——激光雷达和IMU对不上,GPS数据飘得离谱,机器人明明往前走,地图上却在后退。折腾了三天,最后发现是某个坐标系的旋转方向搞反了。

嗯,从那以后,我再也不敢小看坐标系了。

2.1 为什么需要多个坐标系?

你想想看,一个机器人要完成定位和建图,它得同时回答三个问题:

  • 我在哪? —— 这需要世界坐标系
  • 我的身体怎么动的? —— 这需要机器人坐标系
  • 传感器看到了什么? —— 这需要传感器坐标系

这三个问题,没法用同一个坐标系回答。因为每个坐标系都有自己的“原点”和“朝向”。

核心认知:SLAM的本质,就是不断在这三个坐标系之间来回切换,找到它们之间的变换关系。

2.2 世界坐标系(World Frame)

世界坐标系是绝对的、固定的。它不会动。

我习惯把世界坐标系的原点设在机器人启动的位置,或者地图的某个角落。比如,GPS的经纬度就是一种世界坐标系,但我们在室内SLAM中通常用笛卡尔坐标系。

  • 符号:通常记为 \( W \) 或 \( G \)
  • 特点:静止不动,是所有其他坐标系的参考基准
  • 用途:存储地图、规划全局路径、回环检测

2.3 机器人坐标系(Robot/Body Frame)

机器人坐标系是跟着机器人走的。它的原点在机器人的中心(通常是质心或底盘中心),x轴朝前,y轴朝左,z轴朝上。

我在项目中遇到过一个问题:用里程计推算位置时,如果直接用机器人坐标系下的速度积分,结果会越偏越远。为什么?因为机器人坐标系本身在旋转,速度方向一直在变。

  • 符号:通常记为 \( B \) 或 \( R \)
  • 特点:随机器人运动而运动,是控制指令的参考系
  • 用途:发布速度指令、处理IMU数据、局部避障

2.4 传感器坐标系(Sensor Frame)

每个传感器都有自己的小世界。激光雷达的坐标系原点在旋转中心,相机的坐标系原点在光心,IMU的坐标系在芯片内部。

我建议你拿到一个新传感器时,第一件事就是查它的坐标系定义。有些厂家把x轴朝前,有些朝右,搞混了就是灾难。

  • 符号:通常记为 \( S \) 或 \( C \)(Camera)、\( L \)(Lidar)
  • 特点:每个传感器独立,需要标定到机器人坐标系
  • 用途:原始数据采集、特征提取

个人习惯:我会在代码里用枚举类型明确定义每个坐标系,比如 Frame::WORLD、Frame::ROBOT、Frame::LIDAR。这样即使项目做大了,也不会搞混。

2.5 坐标变换:从传感器到世界

说白了,坐标变换就是“搬家”。把传感器看到的一个点,搬到世界坐标系里去。

举个例子:激光雷达检测到前方2米处有一个障碍物。这个“前方2米”是在激光雷达坐标系下的。但机器人是倾斜的,激光雷达是装在侧面的,所以这个点在世界坐标系下的位置完全不一样。

变换分为两种:

  1. 平移:坐标系原点之间的位移,用向量 \( t \) 表示
  2. 旋转:坐标系轴之间的朝向差异,用旋转矩阵 \( R \) 表示

完整的变换公式:

P_w = R * P_s + t

其中 \( P_w \) 是世界坐标,\( P_s \) 是传感器坐标。

2.6 齐次矩阵:把变换写成乘法

你发现没有?上面的公式里既有乘法又有加法,写起来麻烦,组合多个变换时更麻烦。

齐次矩阵就是来解决这个问题的。它把旋转和平并到一起,变成一个4x4的矩阵:

| R   t |
| 0   1 |

这样,变换就变成了纯粹的矩阵乘法:

P_w = T * P_s

其中 \( P_w \) 和 \( P_s \) 都是齐次坐标(4维向量,最后一维是1)。

为什么重要? 因为你可以把多个变换连乘起来:

T_world_to_sensor = T_world_to_robot * T_robot_to_sensor

这样,从传感器到世界的变换,就变成了两个矩阵的乘积。简洁、高效、不易出错。

2.7 知识体系图

下面这张图展示了本章的核心逻辑:

世界坐标系 绝对参考,静止不动 机器人坐标系 随机器人运动 传感器坐标系 每个传感器独立 T_w2b T_b2s 齐次变换矩阵 T | R(3x3) t(3x1) | | 0(1x3) 1 | 旋转 + 平移 = 一个矩阵搞定 核心公式:P_w = T_w2b * T_b2s * P_s

2.8 避坑指南

我曾经犯过的错:

  • 把旋转矩阵的转置和逆搞混了。记住:旋转矩阵是正交矩阵,逆等于转置,但平移部分不能这么干。
  • 齐次坐标最后一维忘了写1。结果矩阵乘法出来全是错的,查了半天。
  • 多个传感器标定时,变换顺序搞反了。先旋转后平移,和先平移后旋转,结果完全不同。

调试小技巧:写一个简单的单元测试,把一个已知点从传感器坐标系变换到世界坐标系,再变换回来。如果结果和原点一致,说明变换写对了。

2.9 代码示例:C++中的坐标变换

#include <Eigen/Dense>

// 定义齐次变换矩阵
Eigen::Matrix4d T_world_to_robot;
T_world_to_robot << 
  1, 0, 0, 1.0,   // x方向平移1米
  0, 1, 0, 0.5,   // y方向平移0.5米
  0, 0, 1, 0.0,   // z方向不变
  0, 0, 0, 1;

// 传感器坐标系下的点
Eigen::Vector4d p_sensor(0.5, 0.0, 0.0, 1.0);

// 变换到世界坐标系
Eigen::Vector4d p_world = T_world_to_robot * p_sensor;

std::cout << "世界坐标: " << p_world.transpose() << std::endl;
// 输出: 1.5, 0.5, 0.0, 1

嗯,代码很简单,但背后是整套坐标系理论在支撑。没有这个基础,你写再多代码也是空中楼阁。

可可精品资料库