Skip to content

关于 TF2

tf2 是一个坐标变换库,用于随时间追踪多个坐标系。 它在按时间缓存的数据结构中维护坐标系之间的关系,并允许用户在任意时间点、在任意两个坐标系之间变换点、向量等数据。

ros2 tf2 frames

一个机器人系统通常有许多随时间变化的 3D 坐标系(frame),例如世界坐标系(world frame)、基座坐标系(base frame)、夹爪坐标系(gripper frame)、头部坐标系(head frame)等。 tf2 会随时间追踪所有这些坐标系,并允许你提出如下问题:

  • 5 秒前头部坐标系相对于世界坐标系的位置在哪里?
  • 夹爪中的物体相对于基座的位姿是什么?
  • 基座坐标系在地图坐标系中的当前位姿是什么?

tf2 可以在分布式系统中运行。 这意味着机器人所有坐标系的信息,对于系统中任何计算机上的所有 ROS 2 组件都是可用的。 tf2 既可以让分布式系统中的每个组件各自构建坐标变换数据库,也可以通过一个中心节点(node)来统一收集和存储所有坐标变换信息。

在发布坐标变换时,我们通常将变换视为从一个坐标系到另一个坐标系的映射。 语义上的区别在于:你是在变换某个坐标系中表示的数据,还是在变换坐标系本身。 这两者的值恰好互为逆变换。 以 geometry_msgs/msg/Transform 消息发布的变换,表示的是坐标系本身的变换。 在调试已发布的变换时请记住这一点——它们与你查找的值互为逆变换,具体取决于你遍历变换树的方向。

BTAdata=(BTAframe)−1_{B}T^{data}_{A} = (_{B}T^{frame}_{A})^{-1}

TF 库会根据你遍历变换树的方向自动处理这些求逆运算。 在本文档的其余部分,我们将只使用 TdataT^{data},其中 data 是隐含的。

如果车 AA 中的驾驶员观察到某个物体,而地面上的人想知道该物体相对于自己的位置,你需要将观测从源坐标系变换到目标坐标系。

ETA∗PAObs=PEObs_{E}T_{A} * P_{A}^{Obs} = P_{E}^{Obs}

现在如果车 B 中的人也想知道该物体的位置,你可以计算净变换。

BTE∗ETA∗PAObs=BTA∗PAObs=PBObs_{B}T_{E} * _{E}T_{A} * P_{A}^{Obs} = _{B}T_{A} * P_{A}^{Obs} = P_{B}^{Obs}

这正是 lookupTransform 所提供的功能,其中 A 是源 frame_id,B 是目标 frame_id。

建议在可能的情况下使用 transform<T>(target_frame, ...) 方法,因为它会从数据类型中读取源 frame_id,并在结果数据类型中写入目标 frame_id,数学计算在内部自动完成。

如果 PP 是一个 Stamped 数据类型,那么 A_A 就是它的 frame_id。

例如,如果根坐标系 A 在坐标系 B 下方一米处,那么从 A 到 B 的变换值为正。

然而,当将数据从坐标系 B 转换到坐标系 A 时,你必须使用该值的逆。 这可以这样理解——当你切换到更低的参考坐标系时,你需要增加高度值。 而如果你将数据从坐标系 A 变换到坐标系 B,高度会减小,因为新的参考点更高。

BTA=(BTfA)−1_{B}T_{A} = (_{B}{Tf}_{A})^{-1}

表示速度(Velocity)时,有三个信息要素。 Vobserving_framemoving_frame−reference_frameV^{moving\_frame - reference\_frame}_{observing\_frame} 这个速度表示运动坐标系与参考坐标系之间的速度。 并且它是在观测坐标系中表示的。

例如,车 A 中的驾驶员可以报告他们正在以 1m/s 的速度向前行驶(在 A 中观测,相对于地面),那么就是 VAA−E=(1,0,0)V_{A}^{A - E} = (1,0,0)。 而同一个速度可以从地面的视角来观测(假设车向东行驶,地面为 NED 坐标系),那就是 VEA−E=(0,1,0)V_{E}^{A - E} = (0, 1, 0)。

然而,变换可以表明它们实际上是相同的:

ETA∗VAA−E=VEA−E_{E}T_{A} * V_{A}^{A - E} = V_{E}^{A - E}

如果速度在同一个坐标系(这里是 Obs)中表示,则可以进行加减。

VObsA−C=VObsA−B+VObsD−CV_{Obs}^{A - C} = V_{Obs}^{A - B} + V_{Obs}^{D - C}

速度可以通过求逆来”反转”。

VObsA−C=−(VObsC−A)V_{Obs}^{A - C} = -(V_{Obs}^{C - A})

如果你想比较两个速度,必须首先将它们变换到同一个观测坐标系中。

我们创建了一套 教程,逐步引导你学习如何使用 tf2。 你可以从 tf2 简介教程开始。 有关所有 tf2 及相关教程的完整列表,请查看 教程 页面。

tf2 本质上有两个主要任务,任何用户都会用到:监听变换(listening for transforms)和广播变换(broadcasting transforms)。

如果你想使用 tf2 在坐标系之间进行变换,你的节点需要监听变换。 具体来说,你需要接收并缓存系统中广播的所有坐标系,然后查询坐标系之间的特定变换。 查看”编写监听器”教程 (Python) (C++)了解更多。

要扩展机器人的能力,你需要开始广播变换。 广播变换就是向系统的其余部分发送坐标系的相对位姿。 一个系统可以有多个广播器(broadcaster),每个广播器提供机器人不同部分的信息。 查看”编写广播器”教程 (Python) (C++)了解更多。

此外,tf2 可以广播不随时间变化的静态变换。 这主要可以节省存储和查找时间,同时也减少发布开销。 需要注意的是,静态变换只发布一次且假定不会改变,因此不存储历史记录。 如果你想在 tf2 树中定义静态变换,请查看”编写静态广播器”教程 (Python) (C++)。

你还可以在”添加坐标系”教程 (Python) (C++)中学习如何向 tf2 树添加固定和动态坐标系。

完成基础教程后,你可以继续学习 tf2 和时间的相关内容。 tf2 和时间教程 (C++)讲解了 tf2 和时间的基本原理。 关于 tf2 和时间的高级教程 (C++)讲解了使用 tf2 进行时间旅行(time travel)的原理。

在 TePRA 2013 上发表了一篇关于 tf2 的论文:tf: The transform library。