2、TF树的基本概念:坐标系、父子关系、坐标变换矩阵
说实话,刚接触TF的时候,我也被那一堆坐标系绕得头晕。但后来我发现,只要抓住三个核心——坐标系、父子关系、变换矩阵——整个TF树就清晰了。这一章,我就带你把这几个基础啃透。
2.1 坐标系 —— 机器人的“世界观”
每个坐标系就是一个参考点,带着自己的X、Y、Z轴。你可以把它想象成机器人身上的“标签”:激光雷达有一个坐标系,底盘有一个,摄像头也有一个。我习惯把坐标系看作“独立的观察者”,每个传感器都在自己的小世界里测量数据。
举个例子:激光雷达检测到前方1米有障碍物,这个“前方1米”是相对于激光雷达自己的坐标系说的。但机器人底盘想知道这个障碍物在它的坐标系里在哪,就得靠TF来转换。
base_link(机器人本体)、odom(里程计)、map(地图)等。
2.2 父子关系 —— 谁是谁的“爹”
TF树里,坐标系不是平级的,而是构成一棵树。每个坐标系有且只有一个父坐标系,但可以有多个子坐标系。这种父子关系定义了变换的传递路径。
我记得有一次调试AGV,发现激光数据总是跳变。查了半天,原来是有人把 laser 的父坐标系设成了 map,而不是 base_link。这一下子就破坏了树的结构,导致变换链混乱。
- 父坐标系:提供全局或局部的参考基准,比如
base_link通常是机器人的根节点。 - 子坐标系:依附于父坐标系,比如
laser_frame是base_link的子坐标系。 - 变换方向:从子到父,或者从父到子,通过变换矩阵描述。
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 消息来传递变换。我个人觉得,理解变换矩阵的关键是分清“旋转”和“平移”两部分。旋转用四元数或欧拉角表示,平移就是三个轴的偏移量。
tf2::Quaternion 或者 tf2::Matrix3x3,再也没出过这种低级错误。
▲ 典型的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 | 无 (根) | 全局地图坐标系,通常固定 |
| odom | map | 里程计坐标系,随机器人移动漂移 |
| base_link | odom | 机器人本体中心,通常位于底盘 |
| laser_frame | base_link | 激光雷达安装位置 |
| camera_frame | base_link | 视觉传感器安装位置 |
当然,你也可以根据需求调整。比如有的机器人把 odom 作为根节点,不连接 map。但记住:树不能有环,否则TF会报错。
base_link 设成了同一个父坐标系,导致TF树出现两个同名坐标系。ROS虽然能容忍,但变换结果完全错了。所以命名一定要唯一,尤其是多机器人场景。
好了,这一章的内容就是这些。坐标系、父子关系、变换矩阵,这三者构成了TF树的骨架。