4. 静态TF发布

static_transform_publisher、命令行工具、launch文件配置、可视化验证。

静态TF,说白了就是那些一辈子都不动的坐标关系。比如激光雷达装在机器人底盘正中央,偏移量是固定的,旋转也是死的。你想想看,这种关系要是每帧都发一遍,多浪费带宽。ROS里专门给了个工具叫 static_transform_publisher,就干这一件事。

我个人习惯,只要两个link之间没有相对运动,一律用静态TF。省心,而且不会因为时间戳抖动出bug。我记得刚带团队时,有个同事非要用动态TF发一个固定支架的位姿,结果每次回放bag文件坐标就飘……后来改成静态,世界清净了。

静态TF发布 · 知识结构 static_transform_publisher 命令行工具 launch文件配置 可视化验证 固定坐标关系 · 零漂移 · 低开销 适用于雷达/相机/支架等静态传感器

4.1 static_transform_publisher 怎么用?

ROS里内置了这个节点,你不需要自己写代码。用法非常直接:

rosrun tf2_ros static_transform_publisher x y z yaw pitch roll frame_id child_frame_id

或者用四元数版本(我更喜欢这个,避免欧拉角歧义):

rosrun tf2_ros static_transform_publisher x y z qx qy qz qw frame_id child_frame_id

举个例子,我的机器人底盘叫 base_link,激光雷达叫 laser,装在正前方0.3米,高0.2米,朝前看(无旋转):

rosrun tf2_ros static_transform_publisher 0.3 0 0.2 0 0 0 1 base_link laser
💡 个人小技巧: 四元数 (0,0,0,1) 代表无旋转。如果你不确定怎么算,可以用 tf2_ros 里的 euler_to_quaternion 工具,或者直接写Python算一下。

4.2 命令行工具 — 快速验证

有时候我就想快速看看坐标对不对,懒得写launch。直接在终端敲上面的命令,然后另开一个窗口用 tf_echo 查看:

rosrun tf2_ros tf2_echo base_link laser

你会看到平移和旋转信息每秒刷一次。嗯,这里要注意:静态TF虽然只发一次,但节点会以固定间隔重发(默认100秒),所以 tf2_echo 能一直收到。

我曾经在调试多传感器融合时,发现雷达点云和视觉图像对不上。用 tf2_echo 一看,原来我把激光雷达的安装高度写成了0.2米,实际是0.25米。这种低级错误,命令行一查就露馅。

4.3 launch文件配置 — 工程化必备

你不可能每次启动机器人都在终端敲命令。正规做法是写进launch文件。ROS里提供了 static_transform_publisher 的launch语法:

<launch>
    <node pkg="tf2_ros" type="static_transform_publisher" name="laser_broadcaster"
          args="0.3 0 0.2 0 0 0 1 base_link laser" />
</launch>

我个人习惯把所有的静态TF单独放在一个 static_transforms.launch 里,这样一目了然。如果机器人有多个传感器,就堆多个node:

<launch>
    <node pkg="tf2_ros" type="static_transform_publisher" name="laser_static"
          args="0.3 0 0.2 0 0 0 1 base_link laser" />
    <node pkg="tf2_ros" type="static_transform_publisher" name="camera_static"
          args="0.1 -0.05 0.15 0 0 0 1 base_link camera_link" />
</launch>
⚠️ 命名要规范: 每个node的name不能重复,否则后面的会覆盖前面的。我见过有人把两个传感器都叫 static_broadcaster,结果只有一个生效。

4.4 可视化验证 — 眼见为实

光看数字不够直观。打开 rviz,添加TF显示,你就能看到坐标轴画出来。我一般会加一个 Grid 和 RobotModel,然后看激光雷达的坐标系是不是在正确的位置。

具体步骤:

  • 启动你的机器人URDF + 静态TF launch
  • 运行 rviz
  • 在Displays面板添加 TF,勾选 Show Names
  • 检查 laser 坐标系是否在 base_link 前方0.3米、高0.2米处

如果发现坐标轴方向不对,比如雷达应该朝前却朝上了,多半是旋转参数写错了。我曾经有一次把四元数的顺序搞反了(ROS里是x,y,z,w,我写成了w,x,y,z),结果雷达朝天看……嗯,从那以后我每次都会在rviz里转一圈确认。

🚨 避坑指南: 静态TF发布后,如果你修改了launch文件,一定要先杀掉原来的节点再重新启动。因为static_transform_publisher不会自动更新,你可能会看到两个同名的节点在打架。用 rosnode list 检查一下。

4.5 一个完整的例子

假设我们有一个差速底盘,上面装了激光雷达和深度相机。目录结构如下:

my_robot/
├── urdf/
│   └── my_robot.urdf
├── launch/
│   ├── robot.launch
│   └── static_transforms.launch
└── config/
    └── rviz_config.rviz

static_transforms.launch 内容:

<launch>
    <node pkg="tf2_ros" type="static_transform_publisher" name="lidar_tf"
          args="0.25 0 0.18 0 0 0 1 base_link laser" />
    <node pkg="tf2_ros" type="static_transform_publisher" name="camera_tf"
          args="0.05 -0.06 0.12 0 0 0 1 base_link camera_link" />
</launch>

然后在主launch里include它:

<launch>
    <include file="$(find my_robot)/launch/static_transforms.launch" />
    <node name="rviz" pkg="rviz" type="rviz" args="-d $(find my_robot)/config/rviz_config.rviz" />
</launch>

启动后,你会在rviz里看到三个坐标系:base_link、laser、camera_link。它们之间的相对关系完全由静态TF定义,永远不会变。

📌 我的习惯: 所有静态TF的父坐标系尽量统一用 base_link 或 base_footprint,不要出现A依赖B、B依赖C的静态链,除非你真的需要。否则调试时容易晕。

好了,静态TF就是这么简单。没有复杂的算法,没有时间同步的烦恼。你只要把安装尺寸量准,参数写对,剩下的交给 static_transform_publisher。下一章我们会聊动态TF——那些会动的关节,比如机械臂、舵机轮。但今天先到这里,把静态TF玩熟,后面才不慌。