2. ROS2 TF基础回顾:TF2库的核心API
这一章我们聚焦 TF2 库最常用的两个 API —— lookupTransform 和 sendTransform,以及静态与动态广播器的区别。说实话,TF 是 ROS2 机器人开发的“任督二脉”,搞懂它,MoveIt 里的坐标变换你基本就通了。
2.1 两个最常用的 API
我个人习惯把 TF2 的 API 分成两组:“问” 和 “说”。
lookupTransform 就是去 TF 树里查坐标关系,sendTransform 则是告诉世界“我在这里”。
🔹 lookupTransform (查询变换)
原型(C++ 伪代码):
tf2::lookupTransform(target_frame, source_frame, time, timeout)
返回两个坐标系之间的平移与旋转。你想想看,机械臂要抓杯子,首先得知道“杯子”在“基座”下的位姿,这就是 lookupTransform 干的事。
🔹 sendTransform (广播变换)
原型:
broadcaster.sendTransform(transform_stamped)
把父子坐标系的变换关系发布出去。我在项目中遇到过,有人忘记广播 base_link 到 laser 的变换,结果导航直接“撞墙”——嗯,TF 树不完整,代价很大。
2.2 静态广播器 vs 动态广播器
说白了,静态广播器适合那些“一辈子不变”的关系,比如激光雷达固定在机器人底盘上。动态广播器则用于关节角度、移动物体等实时变化的变换。
| 特性 | StaticBroadcaster | TransformBroadcaster |
|---|---|---|
| 发布频率 | 通常只发一次(或极低频率) | 高频发布(10-100 Hz) |
| 适用场景 | 固定传感器、静态支架 | 关节状态、移动物体 |
| 资源占用 | 极低,内部缓存优化 | 随频率线性增加 |
| 典型API | StaticTransformBroadcaster | TransformBroadcaster |
💡 我的小经验: 静态变换最好用 tf2_ros::StaticTransformBroadcaster,它会在 TF 树里标记为“静态”,后续查询时延迟更低。我曾经把一个动态广播器用在了固定支架上,CPU 占用多了 5% —— 没必要。
2.3 代码实战:C++ 示例
下面是一个极简的广播 + 查询例子。注意看注释,我习惯把时间戳和父子顺序写清楚,避免“反着查”的坑。
// 动态广播器示例 (C++)
#include <tf2_ros/transform_broadcaster.h>
#include <geometry_msgs/msg/transform_stamped.hpp>
rclcpp::Node::SharedPtr node = ...;
auto broadcaster = std::make_shared<tf2_ros::TransformBroadcaster>(node);
geometry_msgs::msg::TransformStamped t;
t.header.stamp = node->now();
t.header.frame_id = "world"; // 父坐标系
t.child_frame_id = "base_link"; // 子坐标系
t.transform.translation.x = 1.0;
t.transform.translation.y = 0.0;
t.transform.translation.z = 0.5;
t.transform.rotation.w = 1.0; // 单位四元数
broadcaster->sendTransform(t);
// lookupTransform 查询 (C++)
#include <tf2_ros/buffer.h>
#include <tf2_ros/transform_listener.h>
tf2_ros::Buffer buffer(node->get_clock());
tf2_ros::TransformListener listener(buffer);
try {
auto transform = buffer->lookupTransform(
"base_link", // 目标坐标系
"tool0", // 源坐标系
tf2::TimePointZero, // 最近可用时间
2s // 超时
);
RCLCPP_INFO(node->get_logger(), "平移: %.2f %.2f %.2f",
transform.transform.translation.x,
transform.transform.translation.y,
transform.transform.translation.z);
} catch (tf2::TransformException &ex) {
RCLCPP_WARN(node->get_logger(), "查询失败: %s", ex.what());
}
⚠️ 我曾经踩过的坑: 调用 lookupTransform 时,参数顺序是 目标帧, 源帧。很多人搞反,结果得到的是逆变换。还有,时间戳用 tf2::TimePointZero 表示“最新”,如果你指定了历史时间但缓存里没有,就会抛异常。记得加 try-catch。
2.4 Python 快速上手
Python 版本更简洁,适合快速验证。我个人在调试时喜欢用 Python 写小脚本,直接看变换对不对。
# Python 动态广播
import rclpy
from tf2_ros import TransformBroadcaster
from geometry_msgs.msg import TransformStamped
node = rclpy.create_node('tf_broadcaster')
broadcaster = TransformBroadcaster(node)
t = TransformStamped()
t.header.stamp = node.get_clock().now().to_msg()
t.header.frame_id = 'world'
t.child_frame_id = 'base_link'
t.transform.translation.x = 1.0
t.transform.translation.y = 0.0
t.transform.translation.z = 0.5
t.transform.rotation.w = 1.0
broadcaster.sendTransform(t)
# Python 查询变换
from tf2_ros import Buffer, TransformListener
buffer = Buffer()
listener = TransformListener(buffer, node)
try:
trans = buffer.lookup_transform('base_link', 'tool0', rclpy.time.Time())
print(trans)
except Exception as e:
node.get_logger().warn(f'查询失败: {e}')
📌 核心要点:
- sendTransform 负责发布父子关系,静态用
StaticTransformBroadcaster,动态用TransformBroadcaster。 - lookupTransform 负责查询,注意参数顺序(目标帧, 源帧)和时间策略。
- TF 树必须连续,从根到叶子不能断。我见过最诡异的 bug 就是少了一个静态变换,导致整个规划失败。
嗯,这一章的内容其实不多,但 TF2 这两个 API 你几乎每天都会用。