2. ROS2 TF基础回顾:TF2库的核心API

这一章我们聚焦 TF2 库最常用的两个 API —— lookupTransform 和 sendTransform,以及静态与动态广播器的区别。说实话,TF 是 ROS2 机器人开发的“任督二脉”,搞懂它,MoveIt 里的坐标变换你基本就通了。

TF2 核心 API lookupTransform sendTransform StaticBroadcaster TransformBroadcaster 查询变换 · 广播变换 · 静态/动态场景

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 动态广播器

说白了,静态广播器适合那些“一辈子不变”的关系,比如激光雷达固定在机器人底盘上。动态广播器则用于关节角度、移动物体等实时变化的变换。

特性StaticBroadcasterTransformBroadcaster
发布频率通常只发一次(或极低频率)高频发布(10-100 Hz)
适用场景固定传感器、静态支架关节状态、移动物体
资源占用极低,内部缓存优化随频率线性增加
典型APIStaticTransformBroadcasterTransformBroadcaster

💡 我的小经验: 静态变换最好用 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 你几乎每天都会用。