2. 坐标系基础
说实话,刚接触ROS2的TF2时,我也被那一堆包名绕晕过。tf2_ros、tf2、tf2_msgs……到底谁管谁?这一章我就带你拆开TF2的骨架,看看里面到底怎么运转的。你想想看,搞懂架构之后,写广播器、监听器就跟搭积木一样自然。
2.1 tf2_ros 包结构
tf2_ros 是 ROS2 里跟 TF 打交道的核心库。它把底层 tf2 的数学计算包装成了 ROS 节点能用的 API。我个人习惯把 tf2_ros 看作「翻译官」——它负责把坐标变换塞进 ROS 话题,也负责从话题里把变换捞出来。
- tf2_ros::TransformBroadcaster —— 动态广播器,一帧一帧地发
/tf消息。 - tf2_ros::StaticTransformBroadcaster —— 静态广播器,适合发布固定不变的变换(比如激光雷达装在车体哪个位置)。
- tf2_ros::Buffer —— 数据缓存区,存着所有收到的变换,支持时间回溯。
- tf2_ros::TransformListener —— 监听器,内部绑定 Buffer,自动订阅
/tf和/tf_static。
我记得第一次用的时候,以为 TransformListener 是个单独的类,后来才发现它其实就是 Buffer 的搭档——你创建一个 Buffer,再丢给 TransformListener,它就开始帮你收数据了。
2.2 Broadcaster 与 Listener 的工作原理
说白了,广播器就是「喊话」的。你每调用一次 sendTransform,它就往 /tf 话题里丢一条变换消息。监听器呢?它一直在听,把听到的变换存进 Buffer。
⚡ 核心流程:
- 广播器构造
geometry_msgs::msg::TransformStamped,填上父坐标系、子坐标系、平移旋转。 - 调用
broadcaster->sendTransform(msg),消息发布到/tf。 - 监听器(Buffer)收到消息,更新内部缓存。
- 你的节点调用
buffer->lookupTransform(...)获取任意时刻的变换。
你可能会问:那静态广播器呢?嗯,它走的是 /tf_static 话题,而且 Buffer 会把这些变换当成「永远有效」,除非你主动更新。我在项目里经常用静态广播器固定传感器外参,省心。
2.3 Buffer 与 TransformListener 的核心机制
Buffer 本质上是一个带时间戳的变换树数据库。它内部维护了一张图,节点是坐标系,边是变换。每次收到新变换,它都会更新图结构。
Buffer 的三大能力:
- canTransform —— 先问一声「能查到变换吗?」避免异常。
- lookupTransform —— 查两个坐标系在某个时间点的相对位姿。
- transform —— 直接把一个位姿从源坐标系变换到目标坐标系。
💡 避坑指南: 我曾经在仿真里查不到变换,折腾了半天才发现是时间戳没对上。Buffer 默认缓存 10 秒的数据,如果你查的时间戳太老,或者广播器的时间戳是 0,Buffer 会直接抛异常。记得用 tf2::TimePointZero 或者当前时间。
2.4 代码骨架一览
下面是一个最简的 C++ 广播器 + 监听器片段,帮你把概念落地。
// 广播器 (C++)
tf2_ros::TransformBroadcaster broadcaster(this);
geometry_msgs::msg::TransformStamped t;
t.header.frame_id = "world";
t.child_frame_id = "base_link";
t.transform.translation.x = 1.0;
t.transform.rotation.w = 1.0;
broadcaster.sendTransform(t);
// 监听器 (C++)
tf2_ros::Buffer buffer(this->get_clock());
tf2_ros::TransformListener listener(buffer);
// 在回调或循环里查询
geometry_msgs::msg::TransformStamped tf;
tf = buffer.lookupTransform("base_link", "laser", tf2::TimePointZero);
你看,代码量很少。但背后 Buffer 做了大量工作:维护变换树、处理时间插值、检测坐标系循环依赖……
2.5 一张表总结关键类
| 类 / 组件 | 所属包 | 职责 |
|---|---|---|
| TransformBroadcaster | tf2_ros | 动态发布 /tf 消息 |
| StaticTransformBroadcaster | tf2_ros | 发布 /tf_static 消息(持久) |
| Buffer | tf2_ros | 缓存变换,提供查询接口 |
| TransformListener | tf2_ros | 订阅话题,填充 Buffer |
| tf2::Transform | tf2 | 底层数学运算(矩阵、四元数) |
⚠️ 注意: 别把 tf2_ros::Buffer 和 tf2::BufferCore 搞混。前者是 ROS 感知的(带时钟、节点接口),后者是纯数学缓存。我们平时用的都是 tf2_ros::Buffer。
好了,这一章我们把 TF2 的骨架摸了一遍。从 tf2_ros 的包结构,到广播器怎么喊、监听器怎么听,再到 Buffer 这个「记忆中枢」。