第2章 TF树与坐标帧
说到 TF,很多人第一反应就是「坐标变换」。但在我看来,TF 树才是整个机器人空间认知的骨架。没有这棵树,你连激光雷达的数据该往哪儿投影都搞不清。今天我们就来把这棵树掰开揉碎,聊聊父帧、子帧,以及静态帧和动态帧到底怎么用。
2.1 TF树的概念
TF 树本质上是一个有向无环图,每个节点代表一个坐标帧,每条边代表一个变换关系。说白了,它就是机器人世界里所有「我在哪、你又在哪」的家族谱系。
我习惯把 TF 树想象成家谱:map 是老祖宗,odom 是儿子,base_link 是孙子,laser_frame 是重孙子。每个节点只能有一个父帧,但可以有多个子帧。嗯,这里要注意——不能出现环路,否则 TF 会直接罢工。
上面这张图就是一个典型的机器人 TF 树。你会发现 map 是根,下面挂着 odom,再往下才是真正的机器人本体和传感器。我个人习惯把这种结构叫做「三层架构」——全局、里程计、本体。你想想看,如果没有这棵树,导航的时候你连「激光扫描的点相对于谁」都说不清楚。
2.2 父帧与子帧
在 TF 树里,父帧是参考的基准,子帧是相对于父帧定义的。比如 base_link 相对于 odom 的变换,就描述了机器人在地图上的位置。
我记得有一次调试多传感器融合,发现激光雷达的点云总是歪的。查了半天,原来是 laser_frame 的父帧设成了 map,而不是 base_link。这就是典型的父帧选错——激光雷达是固定在机器人身上的,它的父帧应该是 base_link,而不是直接连到地图。
map 帧,局部路径用 odom 或 base_link 帧。
来看一个简单的 TF 广播代码片段,感受一下父子关系:
// 静态TF广播:base_link → laser_frame
geometry_msgs::TransformStamped t;
t.header.frame_id = "base_link"; // 父帧
t.child_frame_id = "laser_frame"; // 子帧
t.transform.translation.x = 0.2;
t.transform.translation.y = 0.0;
t.transform.translation.z = 0.4;
// ... 四元数设置
static_broadcaster.sendTransform(t);
你看,父帧 base_link 是车体中心,子帧 laser_frame 是激光雷达的安装位置。路径规划里,代价地图需要把激光数据从 laser_frame 变换到 map,中间就要经过 base_link 和 odom 的层层变换。这就是 TF 树存在的意义。
2.3 静态坐标帧 vs 动态坐标帧
搞清楚了父子关系,接下来要区分「动」与「静」。这俩概念在路径规划里特别重要,搞混了轻则警告刷屏,重则导航直接崩溃。
| 类型 | 特点 | 典型帧 | 更新频率 |
|---|---|---|---|
| 静态坐标帧 | 相对父帧固定不变,通常只发布一次 | laser_frame, camera_link, base_footprint | 仅启动时发布,或极低频率 |
| 动态坐标帧 | 随时间变化,需要高频更新 | odom → base_link, base_link → gripper | 10~100 Hz |
静态帧说白了就是「焊死」的关系。比如激光雷达装在机器人前方 0.2 米,这个偏移量不会变。我一般用 static_transform_publisher 或者写一个节点只发布一次。动态帧则相反,比如里程计到机器人本体的变换,每时每刻都在变。
base_link 到 laser_frame 的变换做成了动态发布,每 10ms 发布一次完全相同的变换。结果 TF 缓存里塞满了冗余数据,导致路径规划延迟飙升。后来改成静态广播,CPU 占用直接降了 30%。记住:能静态就别动态。
你可能会问:路径规划里怎么用?举个例子,全局规划器在 map 帧下规划路径,但机器人实际控制需要 base_link 帧下的速度指令。这中间的变换链就依赖 TF 树——map → odom (动态) → base_link (动态) → laser_frame (静态)。静态帧只参与一次变换,动态帧每周期都要重新计算。
arg 传参。这样换传感器时只需要改参数,不用动代码。比如:<node pkg="tf2_ros" type="static_transform_publisher" args="0.2 0 0.4 0 0 0 base_link laser_frame" />
2.4 为什么路径规划离不开TF树?
说白了,路径规划的本质是「在某个参考系下找一条无碰撞的路径」。但机器人身上的传感器、执行器、规划器各自使用不同的坐标系。没有 TF 树,你连「障碍物在机器人左边还是右边」都说不准。
我记得之前做差速小车导航,局部规划器总是往墙上撞。用 tf_echo 一看,原来是 base_link 到 odom 的变换延迟了 200ms,导致规划器以为机器人还在原地。这就是动态帧不及时更新的典型问题。
所以,理解 TF 树、选对父帧、区分静动态,是路径规划稳定的地基。