第3章 · 坐标变换基础
TF树概念、坐标系定义、平移与旋转、四元数与欧拉角
好,咱们进入第三章节。如果你已经搭过几个 URDF 模型,肯定遇到过这样的问题:轮子到底在底盘的哪个位置? 激光雷达的数据是在哪个坐标系下发布的?这些全靠坐标变换来理清。说白了,TF 就是机器人的“空间关系网”。
1. TF 树概念 —— 一张家族谱
我习惯把 TF 树想象成一本族谱。每个坐标系就是一个“人”,有父有子。比如机器人最顶层的 base_link 是老祖宗,下面的 laser_frame、camera_frame 都是它的子孙。TF 树里不能有环,否则 ROS 会直接报错——嗯,我刚开始就吃过这个亏。
你想想看,如果底盘移动了,所有子坐标系(比如轮子、雷达)都得跟着动。TF 树自动帮你维护这种“连带关系”。
2. 坐标系定义 —— 右手定则别搞反
ROS 里统一用右手坐标系。X 轴向前,Y 轴向左,Z 轴向上。我见过有人把 Z 轴朝下,结果雷达数据全反了…… 避坑指南:定义新坐标系时,先比划一下右手。
- 世界坐标系(
map/odom):固定不动,作为全局参考。 - 机器人基坐标系(
base_link):通常位于机器人底盘中心,地面投影点。 - 传感器坐标系(
laser,camera_depth_frame):相对 base_link 的偏移。
我个人习惯在 URDF 里给每个 visible 的 link 都配一个坐标系,哪怕它和父 link 完全重合——方便以后调试。
3. 平移与旋转 —— 6 个数字搞定
两个坐标系之间的关系,用 6 个参数描述:平移 (x, y, z) + 旋转 (roll, pitch, yaw)。平移就是“从父原点走到子原点”,旋转就是“把父坐标轴掰到子坐标轴”。
tf2_echo 查看两个坐标系之间的变换。比如 rosrun tf2_ros tf2_echo base_link laser_frame,直接输出平移和四元数,省得自己算。
举个例子:激光雷达安装在底盘前方 0.3 米,高 0.2 米。那么平移就是 (0.3, 0.0, 0.2)。如果它朝下倾斜 10 度,旋转就是 (0.0, 0.1745, 0.0)(弧度)。
4. 四元数与欧拉角 —— 姿态的两种“方言”
欧拉角直观,但有个大坑:万向锁。我曾经在调机械臂时,俯仰角到了 ±90°,突然翻滚和偏航就分不清了…… 从那以后,我内部存储一律用四元数。
| 表示方式 | 优点 | 缺点 |
|---|---|---|
| 欧拉角 (rpy) | 人类友好,直观 | 万向锁;插值不平滑 |
| 四元数 (x,y,z,w) | 无奇点;插值平滑 | 不直观,四个数难读懂 |
ROS 里消息用四元数。但你可以在代码里用 tf2::getYaw() 转成偏航角来显示。我个人习惯:写 launch 文件时用欧拉角,写 C++ 节点时用四元数。
// 欧拉角 → 四元数 (C++)
tf2::Quaternion q;
q.setRPY(0.0, 0.1745, 0.0); // roll=0, pitch=10°, yaw=0
// 四元数 → 欧拉角
double roll, pitch, yaw;
tf2::Matrix3x3(q).getRPY(roll, pitch, yaw);
5. 把它们串起来 —— 一个简单例子
假设你要发布从 base_link 到 laser_frame 的静态变换。平移 (0.3, 0, 0.2),旋转 0 度。用 static_transform_publisher 最省事:
<node pkg="tf2_ros" type="static_transform_publisher" name="base_to_laser"
args="0.3 0 0.2 0 0 0 base_link laser_frame" />
六个数字分别是 x y z yaw pitch roll(注意顺序!)。我刚开始总写成 roll pitch yaw,结果雷达转了个奇怪的角度…… 后来我养成了习惯:先写平移,再写旋转,旋转用 RPY 顺序。
好了,这一章的核心就是:TF 树是坐标系的家谱,平移旋转是空间关系的“语言”,四元数保你远离万向锁。 下一章咱们会动手发布静态 TF,到时候这些概念全都会用上。