C++ / Robotics · ROS 2 工程 · LESSON 28

TF2、URDF 与 RViz

理解坐标系、机器人模型和可视化工具,建立传感器数据的空间直觉。

22 分钟tf2 · urdf · rviz

TF2、URDF 与 RViz

Web 页面显示 x/y 就足够做图,但机器人数据必须说明“在哪个坐标系、什么时刻、单位是什么”。TF2 维护坐标变换树,URDF 描述 link/joint 的机构关系,RViz 把消息和模型放在一起供人检查。三者不是独立工具:URDF 的 frame、传感器消息的 frame_id 和 TF 查询的目标必须一致。

学习目标

  • 能从 JS/TS 的二维对象模型迁移到带 frame_id、单位、四元数和时间戳的机器人空间数据。
  • 能发布和查询静态/动态 TF,理解 TF buffer、查询时刻和 ROS time 对结果的影响。
  • 能用 URDF、RViz、tf2_echo 和 bag 回放定位坐标系断链、时间超前与轴方向错误。

点的坐标和时间是一体的

TRANSLATION LENS 同一个意图,两种工程表达 窄屏可左右滑动查看完整代码
JS / TS
const point = { x: scan.x, y: scan.y, frame: "laser" };
const worldPoint = transform(point, "map");
C++ / ROS 2
geometry_msgs::msg::PointStamped point;
point.header.frame_id = "laser";
point.header.stamp = node->get_clock()->now();
auto base_point = tf_buffer_->transform(point, "base_link");

查询时使用消息原始时间戳通常比用“现在”更正确;静态传感器可按明确规则使用 latest。laserbase_link 的方向不能凭名字猜,要从 TF 树验证。若变换在缓存时间范围外,TF2 会报告 extrapolation;这不是简单的坐标数学错误,而是数据和变换的时间不重叠。

用 URDF 表达机构关系

<link name="base_link"/>
<link name="laser"/>
<joint name="laser_mount" type="fixed">
  <parent link="base_link"/>
  <child link="laser"/>
  <origin xyz="0.20 0.00 0.15" rpy="0 0 0"/>
</joint>

URDF 的 origin 只描述父子 link 关系,传感器消息仍要设置正确的 frame_id。旋转角、米和弧度要在团队中固定。轮式机器人还要检查关节轴方向、惯性和 collision 模型;RViz 显示的模型对了,不代表传感器时间戳或算法输出就对了。

用 RViz 做空间诊断

设置 Fixed Frame 为 mapbase_link,打开 TF、RobotModel、LaserScan/PointCloud2 和坐标轴,逐层确认树是否连通。点云整体偏移通常检查静态变换平移,左右镜像检查轴方向/四元数,随时间抖动检查时间戳、缓存和设备时钟。RViz 是人类可视化诊断,不应替代自动检查 TF 可用性、frame 名和单位。

常见编译、链接、运行时错误

头文件找不到检查 tf2_rosgeometry_msgs 的依赖声明和 target;链接缺少转换模板支持检查库链接。运行时 Lookup would require extrapolation 先比较消息 stamp 与 TF buffer 时间范围;frame does not exist 检查 namespace、拼写和 broadcaster 是否启动。模型不显示时查 URDF install、robot_state_publisher 和 Fixed Frame。不要用 sleep 粗暴等待 TF,应使用 timeout 和可诊断错误。

迁移练习

laser 中的点转换到 base_link,然后在 RViz 中显示;列出 TF 是否存在、时间戳是否匹配、方向是否正确、URDF 是否安装和单位是否一致五项检查。故意把 frame_id 写成相似拼写,观察命令和日志如何定位。

01
TRY IT YOURSELF

让一帧测距数据落在正确坐标系

准备 PointStamped、设置原始 stamp,查询 laser 到 base_link 的变换,并用 URDF/RViz 验证结果;为超时和缺 frame 设计错误处理。

给我一点提示

先用 tf2_echo 或 TF 图确认树,再在代码中使用有限 timeout;不要用当前时间替换历史消息时间。

查看参考答案
消息的 frame_id 设为 laser,stamp 保留采集时间,查询 base_link 并设置 timeout。若 lookup 失败记录 source/target/stamp 和错误,丢弃或进入安全策略;URDF 固定 joint 必须正确安装,RViz 用同一 Fixed Frame 检查方向与偏移。

静态变换和动态变换的职责

雷达安装在底盘上的外参通常是静态的,机器人底盘相对地图的位姿可能是动态的。静态发布器只应启动一次;把固定外参放进每帧回调会产生重复 TF 和难以解释的时间行为。

geometry_msgs::msg::TransformStamped t;
t.header.stamp = now();
t.header.frame_id = "base_link";
t.child_frame_id = "front_laser";
t.transform.translation.x = 0.18;
t.transform.rotation.w = 1.0;
static_broadcaster_->sendTransform(t);

必须补齐四元数和单位检查;没有有效旋转的默认零四元数不是“没有旋转”,而是非法姿态。用 ros2 run tf2_tools view_framesros2 run tf2_ros tf2_echo base_link front_laser 检查树是否连通。

查询变换时要同时考虑时间

lookupTransform("map", "laser", stamp) 查询的是指定时刻的关系,不是永远最新的关系。相机、IMU 和里程计有不同延迟时,强行用 TimePointZero 会掩盖同步问题;查询失败时要区分 frame 不存在、时间超出缓存、未来时间和连接断开。

try {
  auto tf = buffer_->lookupTransform(
    "map", "laser", tf2::TimePoint(std::chrono::nanoseconds(stamp_ns)),
    50ms);
  auto world = tf2::doTransform(point, tf);
} catch (const tf2::TransformException& e) {
  RCLCPP_WARN_THROTTLE(get_logger(), *get_clock(), 2000,
                       "transform unavailable: %s", e.what());
}

URDF、frame_id 与 RViz 必须一致

URDF 的 link 名称是机器人模型的语义边界,消息 header 的 frame_id 是数据来源,RViz 的 Fixed Frame 是显示参考系。比如 URDF 使用 laser_link 而驱动发布 front_laser,TF 树即使有其它节点也无法自动猜测这是同一坐标系。用 ros2 topic echo /scan --once 检查 header.frame_id,再在 RViz 选择正确 Fixed Frame。

典型错位如何定位

机器人在 RViz 中“跳动”,先检查时间戳是否使用设备启动时间、系统时间或仿真时间;点云镜像,检查轴方向和静态旋转;只在高速度时错位,检查传感器时间延迟和 TF 缓存;地图完全空白,检查 Fixed Frame 是否位于 TF 树根。将一段 bag 在固定 /clock 下回放,可以把坐标错误与真实设备噪声分开。

本节结论

TF2 的难点不是调用一个 transform,而是让消息时间、坐标树和模型描述相互一致。下一节会用 rosbag 把这些空间问题记录并离线重放。

FURTHER READING

延伸阅读

先完成本节练习,再用这些资料查阅完整 API 和真实项目组织方式。

当前学习阶段ROS 2 工程
0/9

阶段共 9 节课,按顺序完成更容易建立完整的迁移模型。