您正在阅读的是旧版本但仍受支持的 ROS 2 文档。 Jazzy.

添加框架 (C++)

目标 学习如何在 tf2 中添加额外帧。

辅导水平: 中级

时间 15 分钟

背景介绍

在之前的教程中,我们通过编写一个 tf2 广播公司 和一个 tf2 监听器.本教程将教您如何在变换树中添加额外的固定帧和动态帧。事实上,在 tf2 中添加帧与创建 tf2 广播器非常相似,但本示例将向您展示 tf2 的一些附加功能。

对于许多与变换相关的任务,在局部框架内进行思考更为容易。例如,在以激光扫描仪为中心的框架内推理激光扫描测量是最简单的。tf2 可以为系统中的每个传感器、链接或关节定义一个局部框架。当从一个框架转换到另一个框架时,tf2 会处理所有引入的隐藏中间框架转换。

tf2 树

tf2 采用树形框架结构,因此不允许在框架结构中形成闭环。这意味着一个框架只有一个父框架,但可以有多个子框架。目前,我们的 tf2 树包含三个帧: 世界, 乌龟1乌龟2.这两个乌龟框架是 世界 帧。如果我们想在 tf2 中添加一个新框架,现有的三个框架中必须有一个是父框架,而新框架将成为它的子框架。

././././_images/turtlesim_frames.png

任务

1 编写固定框架广播节目

在我们的乌龟示例中,我们将添加一个新框架 胡萝卜1的子代。 乌龟1.这个框架将作为第二只乌龟的目标。

首先创建源文件。转到 learning_tf2_cpp 软件包。在 来源 目录,输入以下命令下载固定框架广播代码:

wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_cpp/src/fixed_frame_tf2_broadcaster.cpp

现在打开名为 fixed_frame_tf2_broadcaster.cpp.

#include 时间顺序<chrono>;
#include 功能强大;
#include <内存>;

#include "geometry_msgs/msg/transform_stamped.hpp";
#include "rclcpp/rclcpp.hpp";
#include "tf2_ros/transform_broadcaster.h";

使用 命名空间 标准::计时器;

 固定框架广播 :  rclcpp::节点
{
:
  固定框架广播()
  : 节点("fixed_frame_tf2_broadcaster";)
  {
    tf_broadcaster_ = 标准::共享<;tf2_ros::改造播音员>;();
    定时器 = ->;创建隔离墙计时器(
      100毫秒, 标准::约束(及样品;固定框架广播::广播定时器回调, ));
  }

私人:
  空白 广播定时器回调()
  {
    几何参数::信息::TransformStamped t;

    t.页眉.盖章 = ->;获取时钟()->;现在();
    t.页眉.frame_id = "turtle1";;
    t.子帧标识 = "carrot1";;
    t..译文.x = 0.0;
    t..译文.y = 2.0;
    t..译文.z = 0.0;
    t..自转.x = 0.0;
    t..自转.y = 0.0;
    t..自转.z = 0.0;
    t..自转.w = 1.0;

    tf_broadcaster_->;发送变换(t);
  }

rclcpp::定时器基数::SharedPtr 定时器;
  标准::共享_ptr<;tf2_ros::改造播音员>; tf_broadcaster_;
};

int 主要(int 参数, 烧焦 * 参数[])
{
  rclcpp::启动(参数, 参数);
  rclcpp::后旋(标准::共享<;固定框架广播>;());
  rclcpp::关闭();
  返回 0;
}

代码与 tf2 广播员教程示例非常相似,唯一的区别是这里的变换不会随时间而改变。

1.1 检查代码

让我们来看看这段代码中的关键行。在这里,我们从父代的 乌龟1 给新孩子 胡萝卜1.......。 胡萝卜1 帧的 Y 轴偏移 2 米。 乌龟1 镜框

几何参数::信息::TransformStamped t;

t.页眉.盖章 = ->;获取时钟()->;现在();
t.页眉.frame_id = "turtle1";;
t.子帧标识 = "carrot1";;
t..译文.x = 0.0;
t..译文.y = 2.0;
t..译文.z = 0.0;

1.2 CMakeLists.txt

导航一级回到 learning_tf2_cpp 目录中的 CMakeLists.txtpackage.xml 文件的位置。

现在打开 CMakeLists.txt 添加可执行文件并命名 固定框架_TF2_播音员.

add_executable(fixed_frame_tf2_broadcaster src/fixed_frame_tf2_broadcaster.cpp)
ament_target_dependencies(
    固定框架_TF2_播音员
    几何参数
    rclcpp
    tf2_ros
)

最后,添加 install(TARGETS...) 因此 玫瑰2 运行 能找到你的可执行文件:

install(TARGETS
    固定框架_TF2_播音员
    DESTINATION lib/${PROJECT_NAME})

1.3 编写启动文件

现在,让我们为这个示例创建一个启动文件。用文本编辑器新建一个名为 turtle_tf2_fixed_frame_demo.launch.pysrc/learning_tf2_cpp/launch 目录,并添加以下几行:

舶来品 os

 ament_index_python.packages 舶来品 获取软件包共享目录

 启动 舶来品 启动说明
 launch.actions 舶来品 包含启动描述
 launch.launch_description_sources 舶来品 PythonLaunchDescriptionSource

 launch_ros.actions 舶来品 节点


捍卫 生成发射描述():
    演示节点 = 包含启动描述(
        PythonLaunchDescriptionSource([os..加入(
            获取软件包共享目录('learning_tf2_cpp'), 'launch';),
            '/turtle_tf2_demo.launch.py';]),
        )

    返回 启动说明([
        演示节点,
        节点(
            包装='learning_tf2_cpp',
            可执行='fixed_frame_tf2_broadcaster';,
            名字='fixed_broadcaster';,
        ),
    ])

这个启动文件会导入所需的软件包,然后创建一个 演示节点 变量,该变量将存储我们在上一教程的启动文件中创建的节点。

代码的最后一部分将添加我们固定的 胡萝卜1 使用我们的 固定框架_TF2_播音员 节点。

节点(
    包装='learning_tf2_cpp',
    可执行='fixed_frame_tf2_broadcaster';,
    名字='fixed_broadcaster';,
),

1.4 建设

运行 rosdep 在工作区的根目录下,检查是否存在缺失的依赖项。

rosdep install -i --from-path src --rosdistro humble -y

还是在工作区的根目录下,构建你的软件包:

colcon build --packages-select learning_tf2_cpp

打开一个新终端,导航到工作区的根目录,然后获取设置文件:

install/setup.bash

1.5 运行

现在可以开始海龟广播员演示了:

ros2 launch learning_tf2_cpp turtle_tf2_fixed_frame_demo.launch.py

您应该注意到,新的 胡萝卜1 帧出现在转换树中。

.././././_images/turtlesim_frames_carrot.png

如果你驾驶第一只乌龟四处走动,你应该会注意到,尽管我们添加了一个新的帧,但其行为与之前的教程相比并没有变化。这是因为增加一帧并不会影响其他帧,我们的监听器仍在使用之前定义的帧。

因此,如果我们想让第二只乌龟跟随胡萝卜而不是第一只乌龟,就需要更改 target_frame.这有两种方法。一种方法是通过 target_frame 参数直接从控制台调入启动文件:

ros2 launch learning_tf2_cpp turtle_tf2_fixed_frame_demo.launch.py target_frame:=carrot1

第二种方法是更新启动文件。为此,打开 turtle_tf2_fixed_frame_demo.launch.py 文件,并添加 target_frame': 胡萝卜1 参数通过 启动参数 争论。

捍卫 生成发射描述():
    演示节点 = 包含启动描述(
        ...,
        启动参数={'target_frame': 'carrot1';}.项目(),
        )

现在重建软件包,重启 turtle_tf2_fixed_frame_demo.launch.py然后,你会看到第二只乌龟跟着胡萝卜走,而不是第一只乌龟!

././././_images/carrot_static.png

2 编写动态帧广播器

我们在本教程中发布的额外帧是一个固定帧,与父帧相比不会随时间变化。不过,如果您想发布一个移动帧,可以编写代码使广播器随时间改变帧。让我们改变我们的 胡萝卜1 帧,使其相对于 乌龟1 帧。转到 learning_tf2_cpp 软件包。在 来源 输入以下命令,下载动态帧广播器代码:

wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_cpp/src/dynamic_frame_tf2_broadcaster.cpp

现在打开名为 dynamic_frame_tf2_broadcaster.cpp:

#include 时间顺序<chrono>;
#include 功能强大;
#include <内存>;

#include "geometry_msgs/msg/transform_stamped.hpp";
#include "rclcpp/rclcpp.hpp";
#include "tf2_ros/transform_broadcaster.h";

使用 命名空间 标准::计时器;

 双人 PI = 3.141592653589793238463;

 动态帧广播 :  rclcpp::节点
{
:
  动态帧广播()
  : 节点("dynamic_frame_tf2_broadcaster";)
  {
    tf_broadcaster_ = 标准::共享<;tf2_ros::改造播音员>;();
    定时器 = ->;创建隔离墙计时器(
      100毫秒, 标准::约束(及样品;动态帧广播::广播定时器回调, ));
  }

私人:
  空白 广播定时器回调()
  {
    rclcpp::时间 现在 = ->;获取时钟()->;现在();
    双人 x = 现在.秒钟() * PI;

    几何参数::信息::TransformStamped t;
    t.页眉.盖章 = 现在;
    t.页眉.frame_id = "turtle1";;
    t.子帧标识 = "carrot1";;
    t..译文.x = 10 * 罪过(x);
    t..译文.y = 10 * 系数(x);
    t..译文.z = 0.0;
    t..自转.x = 0.0;
    t..自转.y = 0.0;
    t..自转.z = 0.0;
    t..自转.w = 1.0;

    tf_broadcaster_->;发送变换(t);
  }

  rclcpp::定时器基数::SharedPtr 定时器;
  标准::共享_ptr<;tf2_ros::改造播音员>; tf_broadcaster_;
};

int 主要(int 参数, 烧焦 * 参数[])
{
  rclcpp::启动(参数, 参数);
  rclcpp::后旋(标准::共享<;动态帧广播>;());
  rclcpp::关闭();
  返回 0;
}

2.1 检查代码

我们没有固定定义 x 和 y 偏移量,而是使用了 sin()cos() 在当前时间上的功能,使 胡萝卜1 是不断变化的。

双人 x = 现在.秒钟() * PI;
...
t..译文.x = 10 * 罪过(x);
t..译文.y = 10 * 系数(x);

2.2 CMakeLists.txt

导航一级回到 learning_tf2_cpp 目录中的 CMakeLists.txtpackage.xml 文件的位置。

现在打开 CMakeLists.txt 添加可执行文件并命名 dynamic_frame_tf2_broadcaster.

add_executable(dynamic_frame_tf2_broadcaster src/dynamic_frame_tf2_broadcaster.cpp)
ament_target_dependencies(
    dynamic_frame_tf2_broadcaster
    几何参数
    rclcpp
    tf2_ros
)

最后,添加 install(TARGETS...) 因此 玫瑰2 运行 能找到你的可执行文件:

install(TARGETS
    dynamic_frame_tf2_broadcaster
    DESTINATION lib/${PROJECT_NAME})

2.3 编写启动文件

要测试这段代码,请创建一个新的启动文件 turtle_tf2_dynamic_frame_demo.launch.pysrc/learning_tf2_cpp/launch 目录,并粘贴以下代码:

舶来品 os

 ament_index_python.packages 舶来品 获取软件包共享目录

 启动 舶来品 启动说明
 launch.actions 舶来品 包含启动描述
 launch.launch_description_sources 舶来品 PythonLaunchDescriptionSource

 launch_ros.actions 舶来品 节点


捍卫 生成发射描述():
    演示节点 = 包含启动描述(
        PythonLaunchDescriptionSource([os..加入(
            获取软件包共享目录('learning_tf2_cpp'), 'launch';),
            '/turtle_tf2_demo.launch.py';]),
        启动参数={'target_frame': 'carrot1';}.项目(),
        )

    返回 启动说明([
        演示节点,
        节点(
            包装='learning_tf2_cpp',
            可执行='dynamic_frame_tf2_broadcaster';,
            名字='dynamic_broadcaster';,
        ),
    ])

2.4 建设

运行 rosdep 在工作区的根目录下,检查是否存在缺失的依赖项。

rosdep install -i --from-path src --rosdistro humble -y

还是在工作区的根目录下,构建你的软件包:

colcon build --packages-select learning_tf2_cpp

打开一个新终端,导航到工作区的根目录,然后获取设置文件:

install/setup.bash

2.5 运行

现在可以开始动态帧演示了:

ros2 launch learning_tf2_cpp turtle_tf2_dynamic_frame_demo.launch.py

你应该看到,第二只乌龟正跟着胡萝卜的位置不断变化。

.././././_images/carrot_dynamic.png

摘要

在本教程中,您了解了 tf2 变换树、其结构和功能。您还了解到在局部框架内思考是最简单的方法,并学会了为局部框架添加额外的固定框架和动态框架。