2、TF树的基本概念:坐标系、父子关系、坐标变换矩阵

说实话,刚接触TF的时候,我也被那一堆坐标系绕得头晕。但后来我发现,只要抓住三个核心——坐标系、父子关系、变换矩阵——整个TF树就清晰了。这一章,我就带你把这几个基础啃透。

2.1 坐标系 —— 机器人的“世界观”

每个坐标系就是一个参考点,带着自己的X、Y、Z轴。你可以把它想象成机器人身上的“标签”:激光雷达有一个坐标系,底盘有一个,摄像头也有一个。我习惯把坐标系看作“独立的观察者”,每个传感器都在自己的小世界里测量数据。

举个例子:激光雷达检测到前方1米有障碍物,这个“前方1米”是相对于激光雷达自己的坐标系说的。但机器人底盘想知道这个障碍物在它的坐标系里在哪,就得靠TF来转换。

🔑 关键点: 坐标系之间没有的对错,只有相对的关系。ROS里常见的坐标系包括 base_link(机器人本体)、odom(里程计)、map(地图)等。

2.2 父子关系 —— 谁是谁的“爹”

TF树里,坐标系不是平级的,而是构成一棵树。每个坐标系有且只有一个父坐标系,但可以有多个子坐标系。这种父子关系定义了变换的传递路径。

我记得有一次调试AGV,发现激光数据总是跳变。查了半天,原来是有人把 laser 的父坐标系设成了 map,而不是 base_link。这一下子就破坏了树的结构,导致变换链混乱。

  • 父坐标系:提供全局或局部的参考基准,比如 base_link 通常是机器人的根节点。
  • 子坐标系:依附于父坐标系,比如 laser_frame 是 base_link 的子坐标系。
  • 变换方向:从子到父,或者从父到子,通过变换矩阵描述。
💡 我的习惯: 画TF树的时候,我总爱把父坐标系画在上面,子坐标系画在下面。这样一眼就能看出依赖关系。你可以在终端用 view_frames 或 tf2_tools 生成PDF图,非常直观。

2.3 坐标变换矩阵 —— 数学翻译官

说白了,变换矩阵就是一个4x4的齐次矩阵,它把点从一个坐标系“翻译”到另一个坐标系。矩阵里包含了旋转(3x3)和平移(3x1)。

你想想看,如果激光雷达测到一个点 (0.5, 0, 0.3),而雷达安装在底盘前方0.2米、偏左0.1米,那这个点在底盘坐标系里是多少?变换矩阵就是干这个的。

# 一个典型的变换矩阵 (4x4 齐次)
[ R11  R12  R13  Tx ]
[ R21  R22  R23  Ty ]
[ R31  R32  R33  Tz ]
[  0    0    0    1  ]

在ROS里,我们通常用 geometry_msgs/TransformStamped 消息来传递变换。我个人觉得,理解变换矩阵的关键是分清“旋转”和“平移”两部分。旋转用四元数或欧拉角表示,平移就是三个轴的偏移量。

⚠️ 我曾经踩过的坑: 有一次我直接用手算欧拉角合成旋转矩阵,结果顺序搞错了(ZYX vs XYZ),导致机械臂抓取偏差了5厘米。后来我老老实实用 tf2::Quaternion 或者 tf2::Matrix3x3,再也没出过这种低级错误。
map (父) odom base_link laser_frame camera_frame 父坐标系 子坐标系 变换方向 (父→子)

▲ 典型的TF树结构:map → odom → base_link → 传感器子坐标系

2.4 变换矩阵的“链式法则”

TF树最妙的地方在于:你可以通过连续乘变换矩阵,把任意一个坐标系里的点转换到另一个坐标系。比如从 laser_frame 到 map,只需要沿着树往上走到根节点,把路径上的变换矩阵乘起来。

我经常跟团队里的人说:“TF树就是一张关系网,矩阵乘法就是你的导航。” 实际写代码时,你不需要手动乘矩阵,直接用 tf2::Buffer::transform() 就行。但理解背后的原理,能帮你快速定位问题。

📐 链式变换示例:
T_map_laser = T_map_odom * T_odom_base * T_base_laser
其中 T_a_b 表示从 b 坐标系到 a 坐标系的变换矩阵。

2.5 实战中常见的坐标系设计模式

我参与过几个移动机器人项目,总结下来,大部分机器人的TF树都长得很像:

坐标系父坐标系说明
map无 (根)全局地图坐标系,通常固定
odommap里程计坐标系,随机器人移动漂移
base_linkodom机器人本体中心,通常位于底盘
laser_framebase_link激光雷达安装位置
camera_framebase_link视觉传感器安装位置

当然,你也可以根据需求调整。比如有的机器人把 odom 作为根节点,不连接 map。但记住:树不能有环,否则TF会报错。

🧠 避坑指南: 我曾经在调试多机器人时,不小心把两个机器人的 base_link 设成了同一个父坐标系,导致TF树出现两个同名坐标系。ROS虽然能容忍,但变换结果完全错了。所以命名一定要唯一,尤其是多机器人场景。

好了,这一章的内容就是这些。坐标系、父子关系、变换矩阵,这三者构成了TF树的骨架。