4. 静态TF发布
static_transform_publisher、命令行工具、launch文件配置、可视化验证。
静态TF,说白了就是那些一辈子都不动的坐标关系。比如激光雷达装在机器人底盘正中央,偏移量是固定的,旋转也是死的。你想想看,这种关系要是每帧都发一遍,多浪费带宽。ROS里专门给了个工具叫 static_transform_publisher,就干这一件事。
我个人习惯,只要两个link之间没有相对运动,一律用静态TF。省心,而且不会因为时间戳抖动出bug。我记得刚带团队时,有个同事非要用动态TF发一个固定支架的位姿,结果每次回放bag文件坐标就飘……后来改成静态,世界清净了。
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
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>
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里转一圈确认。
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定义,永远不会变。
base_link 或 base_footprint,不要出现A依赖B、B依赖C的静态链,除非你真的需要。否则调试时容易晕。
好了,静态TF就是这么简单。没有复杂的算法,没有时间同步的烦恼。你只要把安装尺寸量准,参数写对,剩下的交给 static_transform_publisher。下一章我们会聊动态TF——那些会动的关节,比如机械臂、舵机轮。但今天先到这里,把静态TF玩熟,后面才不慌。