第1章 · TF坐标系统概述
TF,全称 Transform Library,是 ROS2 里最基础也最容易被忽视的模块。说白了,它就是一套帮你搞定「机器人身上所有零件之间位置关系」的工具。我刚开始接触 ROS 时,觉得 TF 不就是几个坐标嘛,后来在项目里被它坑过几次,才真正重视起来。
1.1 什么是 TF
TF 维护的是一个树状坐标系网络。每个坐标系都有一个名字,比如 map、odom、base_link、laser_frame。你只需要告诉 TF:「base_link 相对于 odom 的平移和旋转是多少」,系统就会自动帮你算出任意两个坐标系之间的关系。
嗯,这里要注意:TF 只管变换,不管这些变换是怎么来的。你可以从里程计发布,也可以从视觉定位发布,甚至手动写死——TF 都一视同仁。
1.2 TF 在 ROS2 中的作用
在 ROS2 导航栈里,TF 几乎是所有模块的粘合剂。代价地图需要知道激光雷达在机器人什么位置,全局规划器需要知道机器人在 map 下的位姿,行为树需要实时查询当前变换是否有效……
我印象很深的一个项目:调试多传感器融合时,因为 IMU 的坐标系和激光雷达差了 2 厘米没对齐,导致建图出现重影。后来查了三天,才发现是 TF 里一个静态变换写错了。所以,TF 的准确性直接决定了导航能不能用。
- 坐标变换:把激光点云从激光雷达坐标系转到机器人基座坐标系。
- 时间戳管理:支持查询历史变换,用于传感器数据的时间对齐。
- 多机器人支持:每个机器人有独立的 TF 树,互不干扰。
1.3 TF 树的概念
TF 树是一个有向无环图,根节点通常是 map 或 odom,叶子节点是各种传感器和关节。为什么是树?因为每个坐标系只能有一个父节点,这样变换关系才不会乱。
你想想看,如果 TF 不是树而是网,那查找变换时可能遇到环路,系统就不知道该信谁了。ROS2 的 TF2 库强制要求树状结构,这也是它稳定可靠的原因之一。
1.4 静态TF vs 动态TF
这是实际配置里最容易混淆的地方。我见过不少新手把动态变换写成静态,结果机器人一动,地图就飘。
| 特性 | 静态TF | 动态TF |
|---|---|---|
| 发布频率 | 一次发布,或低频(如 1Hz) | 高频(10~100Hz) |
| 变换是否变化 | 固定不变 | 随时间变化 |
| 典型用途 | 激光雷达相对于底盘的安装位置 | 里程计 odom → base_link |
| ROS2 工具 | static_transform_publisher | tf2_ros::TransformBroadcaster |
| 是否可缓存 | 可永久缓存 | 只能缓存短时间窗口 |
base_link 到 steering_frame 的变换设成了静态,结果转向时激光雷达数据一直对不上。后来改成动态发布,问题立刻解决。记住:只要两个物体之间有相对运动,就必须用动态TF。
静态 TF 一般写在 launch 文件里,用 static_transform_publisher 节点发布一次即可。动态 TF 则需要在代码里实时计算并广播,比如从轮子编码器推算出的位姿。
# 静态TF示例(launch文件中)
<node pkg="tf2_ros" exec="static_transform_publisher" args="0.2 0 0.1 0 0 0 base_link laser_frame"/>
# 动态TF示例(C++代码片段)
geometry_msgs::msg::TransformStamped t;
t.header.stamp = now();
t.header.frame_id = "odom";
t.child_frame_id = "base_link";
t.transform.translation.x = odom_x;
t.transform.translation.y = odom_y;
t.transform.rotation = odom_q;
broadcaster->sendTransform(t);
我个人习惯把静态 TF 统一放在一个 YAML 文件里管理,这样换传感器时改一个文件就行,不用翻 launch 文件。动态 TF 则一定要保证时间戳准确,否则 TF 缓存会报 extrapolation 错误。
总结一下:TF 是 ROS2 导航的骨架,静态 TF 描述固定安装关系,动态 TF 描述运动关系。理解这两者的区别,你就能避免 80% 的 TF 配置坑。