第3章 静态TF变换
静态变换的概念 · 发布器编写 · 机器人底盘实战
3.1 静态变换到底是个啥?
说白了,静态变换就是两个坐标系之间永远不变的位姿关系。你想想看,机器人底盘上固定了一个激光雷达,螺丝拧紧之后,雷达相对于底盘中心的位置和朝向就不会再动了——除非你拆了重装。这种“装好就不动”的关系,就是典型的静态变换。
我个人习惯把静态变换叫作“一次发布,终身有效”。在ROS里,你只需要在启动时发布一次,TF树就会记住这个变换,之后任何节点查询都不会再变化。我记得刚接触TF时,总以为所有变换都得实时更新,后来才发现静态变换省掉了大量不必要的计算。
你可能会问:“那动态变换呢?” 动态变换是随时间变化的,比如机器人的关节角度、里程计位姿。而静态变换就是那个“万年不变”的基准。我在项目中遇到过有人把激光雷达外参写成动态发布,结果每秒钟刷几千次同样的数据,白白浪费CPU——其实用静态变换一行命令就搞定了。
3.2 怎么写一个静态变换发布器?
ROS里提供了两种方式:命令行工具 和 C++/Python节点。我个人更推荐用命令行做快速验证,用代码做工程集成。
方式一:命令行(最常用)
rosrun tf2_ros static_transform_publisher 0.2 0.0 0.15 0 0 0 base_link laser_frame
这行命令表示:激光雷达在base_link坐标系下,x方向偏移0.2米,z方向偏移0.15米,无旋转。嗯,这里要注意——顺序是 x y z yaw pitch roll,我刚开始总搞反,后来干脆写了个小脚本记参数。
方式二:C++ 节点示例
#include <ros/ros.h>
#include <tf2_ros/static_transform_broadcaster.h>
#include <geometry_msgs/TransformStamped.h>
int main(int argc, char** argv) {
ros::init(argc, argv, "static_tf_broadcaster");
tf2_ros::StaticTransformBroadcaster broadcaster;
geometry_msgs::TransformStamped ts;
ts.header.stamp = ros::Time::now();
ts.header.frame_id = "base_link";
ts.child_frame_id = "laser_frame";
ts.transform.translation.x = 0.2;
ts.transform.translation.y = 0.0;
ts.transform.translation.z = 0.15;
ts.transform.rotation.w = 1.0; // 无旋转
broadcaster.sendTransform(ts);
ros::spin(); // 保持节点存活
return 0;
}
ros::spin()),否则TF树会丢失这个变换。我曾经在调试时忘了加spin,结果监听器一直报“找不到变换”,排查了半天才发现节点退出了。
Python版本也类似,用StaticTransformBroadcaster,代码更短。我个人习惯在launch文件中用node标签启动命令行工具,这样不用编译,改参数也快。
3.3 底盘实战:激光雷达与IMU的静态变换
拿一个典型的差速底盘举例:底盘中心是base_link,激光雷达装在正前方,IMU装在底盘中心偏右。这些传感器相对于底盘的位姿都是固定的,所以全部用静态变换。
| 传感器 | 父坐标系 | 子坐标系 | 平移 (x,y,z) m | 旋转 (yaw,pitch,roll) rad |
|---|---|---|---|---|
| 激光雷达 | base_link | laser_frame | 0.20, 0.00, 0.15 | 0, 0, 0 |
| IMU | base_link | imu_frame | 0.00, -0.05, 0.10 | 0, 0, 0 |
| 深度相机 | base_link | camera_frame | 0.10, 0.00, 0.25 | 0, 0.2, 0 |
你想想看,如果这些变换用动态发布,每秒钟重复广播同样的数据,不仅浪费带宽,还容易引入时间戳不一致的问题。静态变换发布一次,TF树就记住了,后续所有节点都能直接查询。
parent: imu_frame, child: base_link,方向搞反了。结果整个导航堆栈的航向角都是反的,机器人原地转圈。后来我养成了一个习惯——每次写完静态变换,先用tf2_echo base_link imu_frame看一眼平移和旋转对不对。
另外,在多传感器融合的底盘上,静态变换还承担着“外参标定”的角色。比如你通过标定得到了激光雷达相对于底盘的精确位姿,直接更新静态变换的数值就行,不需要改任何算法代码。这就是静态变换的魅力——把固定参数和逻辑解耦。