ros2 从零开始29 添加帧变换C++

发布时间:2026/6/27 7:51:00
ros2 从零开始29 添加帧变换C++ ros2 从零开始29 添加帧变换C前言背景在之前的教程中我们通过写一个tf2广播和tf2监听。 本教程教会你如何在变换树中添加额外的固定帧和动态帧。 事实上在 tf2 中添加帧与创建 tf2 广播器非常相似但这个示例将展示 tf2 的一些额外功能。对于许多涉及变换相关的任务在局部框架内思考更容易。 例如在激光扫描仪中心的框架中推理激光扫描测量是最容易的。 TF2允许你为系统中的每个传感器、链路或关节定义本地帧。 在从一个帧转换到另一个帧时tf2 会处理所有引入的隐藏中间帧变换。本章学习如何添加额外帧。TF2树tf2 构建了一个树状结构的坐标系帧因此不允许在坐标系结构中出现闭环。这意味着一个坐标系只能有一个父坐标系但可以有多个子坐标系。在 ROS 的 tf2 系统中坐标系之间的关系必须是一个有向无环图DAG即树状结构。world 通常是根坐标系所有其他坐标系都是它的直接或间接子坐标系。这种设计保证了坐标系变换的唯一性和可计算性。‌典型结构示例‌一个典型的移动机器人TF2树可能如下所示world (或 map) └── odom (里程计坐标系) └── base_link (机器人底座/底盘) ├── laser (激光雷达) ├── camera (摄像头) └── imu (惯性测量单元)目前我们的 tf2 树包含三个坐标系world、turtle1 和 turtle2。这两个乌龟坐标系是 world 坐标系的子坐标系。如果我们想向 tf2 添加一个新的坐标系那么现有的三个坐标系中的一个需要成为父坐标系而新的坐标系将成为它的子坐标系。如何查看TF2树请看之前文章《ros2 从零开始25 介绍tf2》实践1.创建一个包本次不另外创建包我们使用《编写静态广播C》。我们创建一个新的帧carrot1是turtle1的子节点作为第二只乌龟的目标。2.编写广播C在learning_tf2_cpp/src目录下创建源文件fixed_frame_tf2_broadcaster.cpp, 其内容如下#include chrono #include functional #include memory #include geometry_msgs/msg/transform_stamped.hpp #include rclcpp/rclcpp.hpp #include tf2_ros/transform_broadcaster.h using namespace std::chrono_literals; class FixedFrameBroadcaster : public rclcpp::Node { public: FixedFrameBroadcaster() : Node(fixed_frame_tf2_broadcaster) { tf_broadcaster_ std::make_sharedtf2_ros::TransformBroadcaster(this); timer_ this-create_wall_timer( 100ms, std::bind(FixedFrameBroadcaster::broadcast_timer_callback, this)); } private: void broadcast_timer_callback() { geometry_msgs::msg::TransformStamped t; t.header.stamp this-get_clock()-now(); t.header.frame_id turtle1; t.child_frame_id carrot1; t.transform.translation.x 0.0; t.transform.translation.y 2.0; t.transform.translation.z 0.0; t.transform.rotation.x 0.0; t.transform.rotation.y 0.0; t.transform.rotation.z 0.0; t.transform.rotation.w 1.0; tf_broadcaster_-sendTransform(t); } rclcpp::TimerBase::SharedPtr timer_; std::shared_ptrtf2_ros::TransformBroadcaster tf_broadcaster_; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_sharedFixedFrameBroadcaster()); rclcpp::shutdown(); return 0; }代码和tf2广非常相似唯一的区别是这里的变换不会随时间变化。2.1 代码解析现在关键代码如下我们定义一个新的变换从父turtle1变换到子carrot1的变换。 carrot1在turtle1坐标第中纵轴y偏移2米。geometry_msgs::msg::TransformStamped t; t.header.stamp this-get_clock()-now(); t.header.frame_id turtle1; t.child_frame_id carrot1; t.transform.translation.x 0.0; t.transform.translation.y 2.0; t.transform.translation.z 0.0; t.transform.rotation.x 0.0; t.transform.rotation.y 0.0; t.transform.rotation.z 0.0; t.transform.rotation.w 1.0;2.3 更新CMakeLists.txt在CMakeLists.txt添加配置可执行程序fixed_frame_tf2_broadcaster, 如下add_executable(fixed_frame_tf2_broadcaster src/fixed_frame_tf2_broadcaster.cpp) ament_target_dependencies( fixed_frame_tf2_broadcaster geometry_msgs rclcpp tf2_ros )最后添加安装配置install(TARGETS…)install(TARGETS fixed_frame_tf2_broadcaster DESTINATION lib/${PROJECT_NAME})3 启动文件现在为此演示创建一个启动文件。在src/learning_tf2_cpp目录下创建一个launch文件夹。使用文本编辑器在launch文件夹中创建一个名为turtle_tf2_fixed_frame_demo_launch的新文件扩展名为 .py、.xml 或 .yaml随便选一个。并添加以下内容3.1.1 XML?xml version1.0 encodingUTF-8?launchincludefile$(find-pkg-share learning_tf2_cpp)/launch/turtle_tf2_demo_launch.xml/nodepkglearning_tf2_cppexecfixed_frame_tf2_broadcasternamefixed_broadcaster//launch3.1.2 YAML%YAML 1.2---launch:-include:file:$(find-pkg-share learning_tf2_cpp)/launch/turtle_tf2_demo_launch.yaml-node:pkg:learning_tf2_cppexec:fixed_frame_tf2_broadcastername:fixed_broadcaster3.1.3 PYTHONfromlaunchimportLaunchDescriptionfromlaunch.actionsimportIncludeLaunchDescriptionfromlaunch.substitutionsimportPathJoinSubstitutionfromlaunch_ros.actionsimportNodefromlaunch_ros.substitutionsimportFindPackageSharedefgenerate_launch_description():returnLaunchDescription([IncludeLaunchDescription(PathJoinSubstitution([FindPackageShare(learning_tf2_cpp),launch,turtle_tf2_demo.launch.py])),Node(packagelearning_tf2_cpp,executablefixed_frame_tf2_broadcaster,namefixed_broadcaster,),])注意到turtle_tf2_fixed_frame_demo_launch.*文件里面使用turtle_tf2_demo_launch.*例如xml里面includefile$(find-pkg-share learning_tf2_cpp)/launch/turtle_tf2_demo_launch.xml/在这种情况下会先启动turtle_tf2_demo_launch.*定义的4个node节点 再启动本节点fixed_broadcaster一定要保证这里的文件turtle_tf2_demo_launch.*是上一节创建的。4 编译和运行进入工作区用colcon build等待编译完成。rootbc2bf85b2e4a:/# cd ~/ros2_wsrootbc2bf85b2e4a:~/ros2_ws# colcon build --packages-select learning_tf2_cppStartinglearning_tf2_cpp Finishedlearning_tf2_cpp[5.52s]Summary:1package finished[5.87s]编译完成后我们需要安装后才能运行使用sourceinstall/setup.sh打开一个终端输入如下命令运行(这里取turtle_tf2_demo_launch.xml,如果你用yaml或python需要自己替换)rootbc2bf85b2e4a:~/ros2_ws# source install/setup.shrootbc2bf85b2e4a:~/ros2_ws# ros2 launch learning_tf2_cpp turtle_tf2_fixed_frame_demo_launch.xml查看TF2树命令ros2 run tf2_tools view_frames现在移动小乌龟turtle1turtle2仍然紧紧跟随turtle1跟上一章没有区别。这是因为目前我们还没有使用新的帧数据carrot1。现在我们把参数传过去令小乌龟使用帧数据carrot1。重新运行turtle_tf2_fixed_frame_demo_launch传入参数target_frame:carrot1rootbc2bf85b2e4a:~/ros2_ws# ros2 launch learning_tf2_cpp turtle_tf2_fixed_frame_demo_launch.xml target_frame:carrot1[INFO][launch]: All log files can be found below /root/.ros/log/2026-06-23-09-21-08-776721-bc2bf85b2e4a-25547[INFO][launch]: Default logging verbosity issetto INFO[INFO][turtlesim_node-1]: process started with pid[25551][INFO][turtle_tf2_broadcaster-2]: process started with pid[25553][INFO][turtle_tf2_broadcaster-3]: process started with pid[25555][INFO][turtle_tf2_listener-4]: process started with pid[25557][INFO][fixed_frame_tf2_broadcaster-5]: process started with pid[25559][turtlesim_node-1]QStandardPaths:XDG_RUNTIME_DIRnot set, defaulting to/tmp/runtime-root[turtlesim_node-1][INFO][1782206468.950885987][sim]: Starting turtlesim withnodename /sim[turtlesim_node-1][INFO][1782206468.956032375][sim]: Spawning turtle[turtle1]atx[5.544445],y[5.544445],theta[0.000000][turtlesim_node-1][INFO][1782206469.914678148][sim]: Spawning turtle[turtle2]atx[4.000000],y[2.000000],theta[0.000000][turtle_tf2_listener-4][INFO][1782206470.896408863][listener]: Successfully spawned启动时2只乌龟就有间隔了。跟随运动总结我们发布静态的位置数据carrot1 这个位置相对于turtle1是静止的。扩展练习发布一个随时间变化的位置数据carrot1把代码fixed_frame_tf2_broadcaster.cpp中的函数broadcast_timer_callback修改成随时间变化的值。如下void broadcast_timer_callback() { const double PI 3.141592653589793238463; rclcpp::Time now this-get_clock()-now(); double x now.seconds() * PI; geometry_msgs::msg::TransformStamped t; t.header.stamp now; t.header.frame_id turtle1; t.child_frame_id carrot1; t.transform.translation.x 10 * sin(x); t.transform.translation.y 10 * cos(x); t.transform.translation.z 0.0; t.transform.rotation.x 0.0; t.transform.rotation.y 0.0; t.transform.rotation.z 0.0; t.transform.rotation.w 1.0; tf_broadcaster_-sendTransform(t); }重新编译后运行可以看看效果。