ROS2--8章 机器人坐标系 tf2库

目录

++[目录... 1](#目录... 1)++

++[一、tf2基础理解... 2](#一、tf2基础理解... 2)++

++[1.1 tf2 解决什么问题?... 2](#1.1 tf2 解决什么问题?... 2)++

++[1.2 tf2 核心架构... 2](#1.2 tf2 核心架构... 2)++

++[二、tf2 (Transform Library 2) 3](#二、tf2 (Transform Library 2) 3)++

++[2.1. 核心数据结构:TF Tree(坐标树)... 3](#2.1. 核心数据结构:TF Tree(坐标树)... 3)++

++[2.2. 两种变换类型... 3](#2.2. 两种变换类型... 3)++

++[2.3. 三大核心角色... 3](#2.3. 三大核心角色... 3)++

++[三、坐标系的变换原理... 4](#三、坐标系的变换原理... 4)++

++[3.1. 旋转... 4](#3.1. 旋转... 4)++

++[3.2. 平移... 4](#3.2. 平移... 4)++

++[3.3. 坐标点变换原理... 4](#3.3. 坐标点变换原理... 4)++

++[四、坐标系的使用... 5](#四、坐标系的使用... 5)++

++[4.1 坐标系启动... 5](#4.1 坐标系启动... 5)++

++[4.2 可视化坐标系Rviz. 6](#4.2 可视化坐标系Rviz. 6)++

++[第五章 发布静态坐标系... 10](#第五章 发布静态坐标系... 10)++

++[5.1 创建工作空间,建立坐标系功能包,... 10](#5.1 创建工作空间,建立坐标系功能包,... 10)++

++[5.1 编译,加载,运行,验证... 13](#5.1 编译,加载,运行,验证... 13)++

++[第六章 发布动态坐标系... 16](#第六章 发布动态坐标系... 16)++

++[6.1 什么是动态坐标系... 16](#6.1 什么是动态坐标系... 16)++

++[6.2创建动态坐标系文件framepub.py. 17](#6.2创建动态坐标系文件framepub.py. 17)++

++[6.3 添加环境变量,编译,加载,运行,验证... 19](#6.3 添加环境变量,编译,加载,运行,验证... 19)++

++[第7章 坐标系的查询... 22](#第7章 坐标系的查询... 22)++

++[7.1 接收坐标系变化的代码... 22](#7.1 接收坐标系变化的代码... 22)++

++[7.2 添加环境变量,编译,加载,运行... 24](#7.2 添加环境变量,编译,加载,运行... 24)++

++[7.3运行结果... 26](#7.3运行结果... 26)++

一、tf2基础理解

tf2 是 ROS 2 中负责**坐标变换(Transform)**的核心功能包。

它的主要作用是维护机器人各坐标系之间的空间关系树(TF Tree),并允许节点在任意两个坐标系之间进行实时查询和转换。

1.1 tf2 解决什么问题?

在机器人系统中,传感器数据通常分散在不同的坐标系下:

  • 激光雷达点云 → lidar_link 坐标系
  • 相机图像 → camera_link 坐标系
  • 里程计位置 → odom 坐标系
  • 地图定位 → map 坐标系

tf2 让你无需手动计算矩阵乘法,只需告诉系统"把 A 坐标系下的某点转换到 B 坐标系",它会自动沿着 TF Tree 查找路径并完成变换。

1.2 tf2 核心架构

表格

组件 说明
TF Broadcaster 发布坐标变换关系(如 base_link → lidar_link)
TF Listener / Buffer 缓存历史变换,提供查询接口
TF Tree 所有坐标系构成的有向树结构(每个子帧只有一个父帧)
Static Transform 固定不变的变换(如传感器安装位置),只需发布一次

在 ROS 2 中,参数(Parameter) 是专门用于**外部配置节点(Node)**的一种机制。你可以把它理解为节点的"全局设置"或"运行时开关"。

二、tf2 (Transform Library 2)

在 ROS 2 中,tf2 (Transform Library 2) 是核心的坐标系管理框架。它负责维护机器人系统中所有坐标系(Frames)之间的相对位置和姿态关系,并支持随时间变化的动态查询。

简单来说,tf2 回答了机器人学中最基本的问题:"在时刻 T,坐标系 A 相对于坐标系 B 在哪里、朝向哪里?"

以下是 ROS 2 tf2 的核心体系简介:

2.1. 核心数据结构:TF Tree(坐标树)

tf2 将所有坐标系组织成一棵有向树状结构,而非图或网状结构。

  • 节点 (Node):代表一个坐标系(如 base_link, lidar_link, map)。
  • 边 (Edge):代表两个坐标系之间的刚体变换(3D 平移 + 旋转),具有方向性(父 → 子)。
  • 唯一父节点原则:每个坐标系只能有一个父节点,但可以有多个子节点。这保证了任意两帧之间的变换路径是唯一且确定的,避免了多路径冲突和歧义。

2.2. 两种变换类型

表格

类型 说明 典型示例 发布方式
Static Transform 不随时间变化的固定安装关系 雷达安装在底盘上、相机与IMU的相对位姿 static_transform_publisher 或 URDF/Xacro
Dynamic Transform 随时间实时变化的运动关系 里程计(odom)→底盘(base_link)、机械臂关节运动 节点代码实时广播 (Broadcaster)

⚠️ 关键区别:静态变换只需发布一次,tf2 会永久缓存;动态变换需要持续高频发布(通常 50Hz+),否则查询时会因超时失败。

2.3. 三大核心角色

📡 Broadcaster(广播者)

  • 负责发布坐标变换到 /tf 或 /tf_static 话题。
  • 动态广播使用 TransformBroadcaster,静态广播使用 StaticTransformBroadcaster。
  • 数据格式为 geometry_msgs/msg/TransformStamped。

🔍 Listener(监听者)

  • 负责查询任意两个坐标系之间的变换。
  • 使用 Buffer + TransformListener 组合。
  • 核心 API:lookup_transform(target_frame, source_frame, time)。
  • 支持时间旅行:可查询历史时刻的变换(受限于 Buffer 缓存时长,默认 10s)。

🛠️ Tools(调试工具)

  • ros2 run tf2_ros static_transform_publisher ...:命令行快速发布静态变换。
  • ros2 run tf2_tools view_frames:生成当前 TF Tree 的 PDF/SVG 可视化图。
  • ros2 run tf2_ros tf2_echo <target> <source>:实时打印两帧间的变换数值。
  • RViz2 TF Display:图形化查看各坐标系的位置、方向和命名。

三、坐标系的变换原理

一个机器人上可以建立很多个坐标系;

一个不同的坐标系之间的关系;

一个物体在指定坐标系中,经过旋转和运动后新的位置和姿态;

空间不同的坐标系,关系可以分解为 旋转和平移;

基本旋转矩阵(遵循右手螺旋定则)

3.1. 旋转

坐标系顶点位置一样,但是 xyz 轴不一样,旋转关系;

3.2. 平移

坐标系顶点位置不一样,但是 xyz 轴平行,平移;

3.3. 坐标点变换原理

先求坐标系B单位向量(xyz三轴)到坐标系A的 旋转 矩阵;在求出到A坐标系的平移矩阵;

在带入坐标的点,即可把B坐标系上的点移动到A坐标系中的坐标; 系顶点位置一样,但是 xyz 轴不一样,平移;

四、坐标系的使用

4.1 坐标系启动

启动tf2的服务器

终端1

|-------------------------------------------------------|
| root@LHAYR:~/ros2_ws# ros2 run tf2_ros buffer_server |

发布两个世界坐标系 A 和B(3,4,5)

终端2 发布A坐标系

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| root@LHAYR:~/ros2_ws# ros2 run tf2_ros static_transform_publisher --frame-id World --child-frame-id A INFO 1783082895.914468358 static_transform_publisher_gzVwOOL4ooXikTYb: Spinning until stopped - publishing transform translation: ('0.000000', '0.000000', '0.000000') rotation: ('0.000000', '0.000000', '0.000000', '1.000000') from 'World' to 'A' |

终端3 发布B坐标系

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| root@LHAYR:~/ros2_ws# ros2 run tf2_ros static_transform_publisher --frame-id World --child-frame-id B --x 3 --y 4 --z 5 INFO 1783082930.165232844 static_transform_publisher_5lY2hfQ0IZBvkPw7: Spinning until stopped - publishing transform translation: ('3.000000', '4.000000', '5.000000') rotation: ('0.000000', '0.000000', '0.000000', '1.000000') from 'World' to 'B' |

终端4,检测 坐标系B到坐标系A的变换,

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| root@LHAYR:~/ros2_ws# ros2 run tf2_ros tf2_echo A B INFO 1783083178.193853145 tf2_echo: Waiting for transform A -> B: Invalid frame ID "A" passed to canTransform argument target_frame - frame does not exist At time 0.0 - Translation: 3.000, 4.000, 5.000 - Rotation: in Quaternion (xyzw) 0.000, 0.000, 0.000, 1.000 - Rotation: in RPY (radian) 0.000, -0.000, 0.000 - Rotation: in RPY (degree) 0.000, -0.000, 0.000 - Matrix: 1.000 0.000 0.000 3.000 0.000 1.000 0.000 4.000 0.000 0.000 1.000 5.000 0.000 0.000 0.000 1.000 At time 0.0 - Translation: 3.000, 4.000, 5.000 - Rotation: in Quaternion (xyzw) 0.000, 0.000, 0.000, 1.000 - Rotation: in RPY (radian) 0.000, -0.000, 0.000 - Rotation: in RPY (degree) 0.000, -0.000, 0.000 - Matrix: 1.000 0.000 0.000 3.000 0.000 1.000 0.000 4.000 0.000 0.000 1.000 5.000 0.000 0.000 0.000 1.000 At time 0.0 - Translation: 3.000, 4.000, 5.000 - Rotation: in Quaternion (xyzw) 0.000, 0.000, 0.000, 1.000 - Rotation: in RPY (radian) 0.000, -0.000, 0.000 - Rotation: in RPY (degree) 0.000, -0.000, 0.000 - Matrix: 1.000 0.000 0.000 3.000 0.000 1.000 0.000 4.000 0.000 0.000 1.000 5.000 0.000 0.000 0.000 1.000 At time 0.0 - Translation: 3.000, 4.000, 5.000 - Rotation: in Quaternion (xyzw) 0.000, 0.000, 0.000, 1.000 - Rotation: in RPY (radian) 0.000, -0.000, 0.000 - Rotation: in RPY (degree) 0.000, -0.000, 0.000 - Matrix: 1.000 0.000 0.000 3.000 0.000 1.000 0.000 4.000 0.000 0.000 1.000 5.000 0.000 0.000 0.000 1.000 |

4.2 可视化坐标系Rviz

启动Rviz 执行指令rviz2

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| root@LHAYR:~/ros2_ws# rviz2 INFO 1783083881.835511841 rviz2: Stereo is NOT SUPPORTED INFO 1783083881.835566972 rviz2: OpenGl version: 4.5 (GLSL 4.5) INFO 1783083881.866538282 rviz2: Stereo is NOT SUPPORTED |

将全局设置(Global Options)下的固定坐标系(Fixed Frame)设置为World。添加TF可视化功能后,在三维可视化系统中,三个坐标系 World、A、B;

①、设置固定坐标系(Fixed Frame)

在 RViz2 窗口的左侧有一个 Displays 面板。如果没看到,点击顶部菜单 Panels → 勾选 Displays。操作:

  1. 点击 Fixed Frame 旁边输入框中的 map(或当前显示的文本)
  2. 删除它,输入 World(注意大小写,必须和你发布时用的 --frame-id World 完全一致)
  3. 按 Enter 键确认

✅ 此时,RViz2 会以 World 坐标系为世界原点。

②、添加 TF 可视化

操作:

  1. 点击 Add 按钮
  2. 会弹出一个 Create visualization display 对话框
  3. 在左侧列表中找到 By display 标签页,或在搜索框中输入 tf
  4. 选择 TF(图标是一个坐标系样子)
  5. 点击右下角的 OK 按钮

三位坐标系就显示出来了,其中A和World 是在(0,0,0), B(3,4,5)

在发布一个C坐标(2,2,2)

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| root@LHAYR:~/ros2_ws# ros2 run tf2_ros static_transform_publisher --frame-id World --child-frame-id c --x 2 --y 2 --z 2 INFO 1783084990.914592740 static_transform_publisher_LwEhZguk3RX9Tpr8: Spinning until stopped - publishing transform translation: ('2.000000', '2.000000', '2.000000') rotation: ('0.000000', '0.000000', '0.000000', '1.000000') from 'World' to 'c' |

显示三维

也可以输出绘制的信息使用指令ros2 run tf2_tools view_frames ;可以通过查看输出的pdf文件查看绘制输出的图;

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| root@LHAYR:~/ros2_ws# ros2 run tf2_tools view_frames INFO 1783085279.141443082 view_frames: Listening to tf data for 5.0 seconds... INFO 1783085284.221835049 view_frames: Generating graph... INFO 1783085284.222800630 view_frames: Result:tf2_msgs.srv.FrameGraph_Response(frame_yaml="B: \n parent: 'World'\n broadcaster: 'default_authority'\n rate: 10000.000\n most_recent_transform: 0.000000\n oldest_transform: 0.000000\n buffer_length: 0.000\nc: \n parent: 'World'\n broadcaster: 'default_authority'\n rate: 10000.000\n most_recent_transform: 0.000000\n oldest_transform: 0.000000\n buffer_length: 0.000\n") INFO 1783085284.223514746 view_frames: Exporting graph in frames_2026-07-03_21.28.04.pdf file... root@LHAYR:~/ros2_ws# code . #// 打开vs code ,可以打开pdf 文件 root@LHAYR:~/ros2_ws# |

通过vs code 打开frames_2026-07-03_21.28.04.pdf 文件

第五章 发布静态坐标系

在机器人系统中,固定的障碍物或建筑物等坐标不会随时间发生变化。对于这类静态坐标系,只需向 tf2 注册并发布一次坐标信息即可。在 ROS2 中,tf2_ros 功能包提供的 StaticTransformBroadcaster 类专门用于发布静态坐标变换;同时,geometry_msgs 功能包中的 TransformStamped 消息类型则用于定义具体的坐标系变换数据。

5.1 创建工作空间,建立坐标系功能包,

创建工作空间,建立坐标系功能包,包的名称叫 tf_test,用于存放案例代码,(在src目录下,创建一个功能包名称叫tf_test,同时创建一个节点(static_frame_pub));打卡vs code(code .);

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| root@LHAYR:~/ros2_ws# cd src root@LHAYR:~/ros2_ws/src# ls action_test bag_files json_interface parameter_test serviceclient_test topic_test root@LHAYR:~/ros2_ws/src# ros2 pkg create --build-type ament_python --node-name static_frame_pub --dependencies geometry_msgs tf2_ros --license MIT tf_test going to create a new package package name: tf_test ...... creating ./tf_test/tf_test/static_frame_pub.py root@LHAYR:~/ros2_ws/src# ls action_test json_interface serviceclient_test topic_test bag_files parameter_test tf_test root@LHAYR:~/ros2_ws/src# code . root@LHAYR:~/ros2_ws/src# |

修改static_frame_pub.py 节点代码,如下

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import math import rclpy from rclpy.node import Node from geometry_msgs.msg import TransformStamped from tf2_ros.static_transform_broadcaster import StaticTransformBroadcaster def quaternion_from_euler(ai, aj, ak): """将欧拉角(roll, pitch, yaw)转换为四元数(x, y, z, w)""" ai /= 2.0 aj /= 2.0 ak /= 2.0 ci = math.cos(ai) si = math.sin(ai) cj = math.cos(aj) sj = math.sin(aj) ck = math.cos(ak) sk = math.sin(ak) cc = ci * ck cs = ci * sk sc = si * ck ss = si * sk q = 0.0 * 4 q0 = cj * sc - sj * cs q1 = cj * ss + sj * cc q2 = cj * cs - sj * sc q3 = cj * cc + sj * ss return q class StaticFramePublisher(Node): """静态坐标系发布器""" def init(self): super().init('static_frame_tf2_broadcaster') # 声明并获取参数 self.x = self.declare_parameter('x', 0.0).get_parameter_value().double_value self.y = self.declare_parameter('y', 0.0).get_parameter_value().double_value self.z = self.declare_parameter('z', 0.0).get_parameter_value().double_value self.roll = self.declare_parameter('roll', 0.0).get_parameter_value().double_value self.pitch = self.declare_parameter('pitch', 0.0).get_parameter_value().double_value self.yaw = self.declare_parameter('yaw', 0.0).get_parameter_value().double_value self.child_frame = self.declare_parameter('child_frame', 'frame_A').get_parameter_value().string_value self.parent_frame = self.declare_parameter('parent_frame', 'world').get_parameter_value().string_value # 初始化静态广播器和日志器 self.tf_static_broadcaster = StaticTransformBroadcaster(self) self.logger = self.get_logger() # 发布静态坐标变换 self.pub_transforms() def pub_transforms(self): """创建并发送静态坐标变换""" t = TransformStamped() t.header.stamp = self.get_clock().now().to_msg() t.header.frame_id = self.parent_frame t.child_frame_id = self.child_frame # 设置平移 t.transform.translation.x = float(self.x) t.transform.translation.y = float(self.y) t.transform.translation.z = float(self.z) # 将角度转换为弧度,再转为四元数 quat = quaternion_from_euler( self.roll / 180.0 * math.pi, self.pitch / 180.0 * math.pi, self.yaw / 180.0 * math.pi ) t.transform.rotation.x = quat0 t.transform.rotation.y = quat1 t.transform.rotation.z = quat2 t.transform.rotation.w = quat3 # 打印日志并发布变换 self.logger.info(f'Publish static tf: {self.parent_frame} -> {self.child_frame}') self.tf_static_broadcaster.sendTransform(t) def main(args=None): rclpy.init(args=args) node = StaticFramePublisher() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if name == 'main': main() |
| 这段代码实现了一个通用的 ROS2 静态坐标系发布节点(Static Frame Publisher)。它的核心功能是:通过读取用户传入的参数,计算并广播两个坐标系之间的静态相对位姿(平移和旋转)。 为了让你更清晰地理解,我将这段代码按功能模块进行详细拆解: 1. 欧拉角转四元数工具函数 (quaternion_from_euler) * 功能:在 ROS2 中,坐标系的旋转必须使用四元数(Quaternion)来表示。这个函数接收三个欧拉角参数(Roll, Pitch, Yaw),并通过三角函数公式将其转换为四元数 x y zw 。 * 意义:让开发者在输入参数时可以使用更符合人类直觉的"角度",而由程序自动完成底层的数学转换。 2. 核心节点类 (StaticFramePublisher) 这是整个程序的主体,继承自 ROS2 的 Node 类,主要包含以下逻辑: * 参数声明与获取: 在 init 中,节点声明了 8 个参数(平移量 x/y/z,旋转角 roll/pitch/yaw,以及父子坐标系名称 parent_frame/child_frame),并设置了默认值。这使得该节点非常灵活,无需修改代码即可通过命令行传参来发布任意坐标系。 * 初始化组件: 实例化了 StaticTransformBroadcaster(静态变换广播器)和日志器(Logger),用于发送数据和打印运行状态。 * 构建并发布变换 (pub_transforms): * 创建消息:实例化一个 TransformStamped 消息对象,并打上当前时间戳。 * 绑定坐标系:将参数中的父、子坐标系名称赋值给消息的 Header。 * 设置平移:将 x, y, z 参数填入消息的 translation 字段。 * 设置旋转:将 roll, pitch, yaw 参数除以 180 乘以 π (将角度转为弧度),然后调用 quaternion_from_euler 函数,将计算出的四元数填入 rotation 字段。 * 广播与日志:调用 sendTransform(t) 将变换数据发布到 ROS2 网络中,并通过 logger.info 打印发布成功的提示。 3. 程序入口与生命周期管理 (main 函数) * 初始化与实例化:调用 rclpy.init() 初始化 ROS2 客户端,并创建 StaticFramePublisher 节点实例。 * 保持节点运行:调用 rclpy.spin(node) 让节点进入事件循环。虽然静态坐标只需发布一次,但保持节点存活可以确保该节点能被 ROS2 系统正常管理和查询。 * 优雅退出:使用 try...except KeyboardInterrupt...finally 结构捕获用户的 Ctrl+C 中断信号,确保在程序退出时能正确销毁节点并关闭 ROS2 通信,防止资源泄露。 总结 简单来说,这段代码就是一个**"坐标翻译官"**。当你运行它并告诉它"A坐标系在B坐标系前方1米,且向上仰起90度"时,它就会把这个空间关系广播给整个机器人系统,让其他节点(如导航、机械臂控制)都能知道这两个坐标系的相对位置。 |

5.1 编译,加载,运行,验证

编译,在ros2_ws 目录下编译,

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #//编译 root@LHAYR:~/ros2_ws# colcon build --packages-select tf_test Starting >>> tf_test Finished <<< tf_test 0.64s Summary: 1 package finished 0.80s root@LHAYR:~/ros2_ws# #// 加载环境变量 root@LHAYR:~/ros2_ws# source install/setup.bash root@LHAYR:~/ros2_ws# |
| 在终端1. 发布坐标系 A (相对于 world) root@LHAYR:~/ros2_ws# ros2 run tf_test static_frame_pub --ros-args \ -p parent_frame:=world \ -p child_frame:=frame_A \ -p x:=1.0 -p y:=0.0 -p z:=0.0 \ -p roll:=0.0 -p pitch:=0.0 -p yaw:=90.0 INFO 1783329977.973951153 static_frame_tf2_broadcaster: Publish static tf: world -> frame_A |
| 在终端2. 发布坐标系 B (相对于 A); 先加载一下 root@LHAYR:~/ros2_ws# source install/setup.bash root@LHAYR:~/ros2_ws# ros2 run tf_test static_frame_pub --ros-args \ -p parent_frame:=frame_A \ -p child_frame:=frame_B \ -p x:=1.0 -p y:=0.0 -p z:=0.0 \ -p roll:=0.0 -p pitch:=0.0 -p yaw:=90.0 INFO 1783330133.656663598 static_frame_tf2_broadcaster: Publish static tf: frame_A -> frame_B |
| 这三个词代表的是三维空间中物体的欧拉角(Euler Angles),也就是飞行器或机器人最常用的旋转描述方式: * roll(横滚角):绕 X 轴旋转的角度。 * pitch(俯仰角):绕 Y 轴旋转的角度。 * yaw(偏航角):绕 Z 轴旋转的角度。 |

打开rviz

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 终端3,进入/ros2_ws目录 root@LHAYR:~/ros2_ws# rviz2 INFO 1783330204.748346811 rviz2: Stereo is NOT SUPPORTED INFO 1783330204.748403214 rviz2: OpenGl version: 4.5 (GLSL 4.5) INFO 1783330204.784237586 rviz2: Stereo is NOT SUPPORTED |

配置Rviz 终端 Fixed Frame 选中 world;在点击add,添加tf显示窗口;

坐标系统 三维显示出来了,

终端4,在加上一个坐标点C;先加载,在输入

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| root@LHAYR:~/ros2_ws# source install/setup.bash root@LHAYR:~/ros2_ws# ros2 run tf_test static_frame_pub --ros-args -p parent_frame:=frame_B -p child_frame:=frame_C -p x:=1.0 -p y:=0.0 -p z:=1.0 -p roll:=0.0 -p pitch:=0.0 -p yaw:=90.0 INFO 1783332358.006986734 static_frame_tf2_broadcaster: Publish static tf: frame_B -> frame_C |

坐标系统 三维显示出来了,

第六章 发布动态坐标系

6.1 什么是动态坐标系

1. 静态坐标系 (Static Transform)

  • 特点:一旦发布,相对位置就固定不变了。
  • 应用场景:机器人的底盘与底盘上的激光雷达、相机之间的相对位置。只要你不把相机拆下来换个位置,它们之间的相对关系就是永远固定的。
  • 广播器:StaticTransformBroadcaster(只需发送一次)。

2. 动态坐标系 (Dynamic Transform)

  • 特点:需要以一定的频率(比如 10Hz、50Hz)持续不断地更新和广播相对位姿。
  • 应用场景:机器人身上正在运动的部件。例如:
    • 机械臂的关节(大臂和小臂之间在转动)。
    • 机器人的轮子(轮子相对于车体在滚动)。
    • 无人机的云台(云台在实时追踪目标并转动)。
  • 广播器:TransformBroadcaster(普通的动态广播器,需要在定时器或循环中不断发送)。

3. 代码层面的区别

如果你要在 ROS2 中发布一个动态坐标系,代码逻辑会发生以下变化:

  1. 广播器改变:将 StaticTransformBroadcaster 替换为普通的 TransformBroadcaster。
  2. 增加定时器:不能只在 init 里发一次了,你需要创建一个定时器(Timer),比如每 50 毫秒(20Hz)触发一次发布函数。
  3. 数据源改变:平移和旋转的参数不能写死,通常需要从硬件传感器(如电机编码器、IMU)实时读取,或者从仿真软件中获取。

总结

简单来说:静态坐标系描述的是"死"的、固定的结构关系;动态坐标系描述的是"活"的、正在运动的部件关系。

6.2创建动态坐标系文件framepub.py

在tf_test工作空间,创建一动态坐标系文件framepub.py,用于存放动态坐标系发布的节点代码。

在vs code 的 src/tf_test/tf_test目录下,创建一个文件(framepub.py));

修改framepub.py节点代码,如下

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import math import rclpy from rclpy.node import Node from geometry_msgs.msg import TransformStamped from tf2_ros import TransformBroadcaster def quaternion_from_euler(ai, aj, ak): """将欧拉角(roll, pitch, yaw)转换为四元数(x, y, z, w)""" ai /= 2.0 aj /= 2.0 ak /= 2.0 ci = math.cos(ai) si = math.sin(ai) cj = math.cos(aj) sj = math.sin(aj) ck = math.cos(ak) sk = math.sin(ak) cc = ci * ck cs = ci * sk sc = si * ck ss = si * sk q = 0.0 * 4 q0 = cj * sc - sj * cs q1 = cj * ss + sj * cc q2 = cj * cs - sj * sc q3 = cj * cc + sj * ss return q class FramePublisher(Node): """动态坐标系发布器""" def init(self): # 修复了 super() 的调用语法 super().init('frame_tf2_publisher') # 初始化动态 TF 广播器 self.tf_broadcaster = TransformBroadcaster(self) # 角度计数器,用于模拟随时间变化的角度 self.num = 0 # 创建定时器,每 0.03 秒(约 33Hz)调用一次 pub_transforms 函数 self.create_timer(0.03, self.pub_transforms) def pub_transforms(self): """创建并发送动态坐标变换""" t = TransformStamped() t.header.stamp = self.get_clock().now().to_msg() t.header.frame_id = 'frame_A' t.child_frame_id = 'frame_B' # 将角度计数器转换为弧度 theta = self.num / 180.0 * math.pi # 1. 计算平移:让 frame_B 绕 frame_A 做半径为 2 米的圆周运动 t.transform.translation.x = math.cos(theta) * 2.0 t.transform.translation.y = math.sin(theta) * 2.0 t.transform.translation.z = 0.0 # 2. 计算旋转:让 frame_B 的 X 轴始终指向运动方向(切线方向) # 在圆周运动中,切线方向的角度比半径方向的角度大 90 度 (math.pi/2) quat = quaternion_from_euler(0.0, 0.0, theta + math.pi / 2.0) t.transform.rotation.x = quat0 t.transform.rotation.y = quat1 t.transform.rotation.z = quat2 t.transform.rotation.w = quat3 # 发送动态变换 self.tf_broadcaster.sendTransform(t) # 更新角度计数器,每次增加 1 度 self.num += 1 # 达到 360 度后重置为 0,防止数值无限增大 if self.num >= 360: self.num = 0 def main(args=None): rclpy.init(args=args) node = FramePublisher() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if name == 'main': main() |
| 这段代码实现了一个动态坐标系发布节点(Dynamic Frame Publisher) 。它的核心功能是:让坐标系 B 以 2 米为半径,围绕坐标系 A 做匀速的圆周运动,并且始终保持其 X 轴指向运动的前进方向(切线方向)核心代码逻辑解析: 1. 动态更新机制:使用了 self.create_timer(0.03, self.pub_transforms),这意味着程序每隔 0.03 秒就会重新计算并发布一次坐标系 B 的位置。这就是动态 TF 与静态 TF 最大的区别。 2. 圆周运动轨迹:通过三角函数 x = cos(θ) * 2 和 y = sin(θ) * 2,精确计算出了 B 在半径为 2 米的圆上的实时坐标。 3. 朝向跟随(运动学处理):代码中 theta + math.pi/2 是一个非常巧妙的处理。在圆周运动中,物体的前进方向(切线)永远比它所在的半径方向超前 90 度。加上这个偏移量,可以让 B 坐标系在绕圈时,X 轴始终指向前方,就像汽车转弯一样自然。 |

6.3 添加环境变量,编译,加载,运行,验证

在vs code的setup.py文件夹,里面添加

|-------------------------------------------------------------------------------------------------------------------------------------|
| entry_points={ 'console_scripts': 'static_frame_pub = tf_test.static_frame_pub:main', 'framepub = tf_test.framepub:main', , }, |

编译,在ros2_ws 目录下编译,

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #//编译 root@LHAYR:~/ros2_ws# colcon build --packages-select tf_test Starting >>> tf_test Finished <<< tf_test 0.58s Summary: 1 package finished 0.65s #// 加载环境变量 root@LHAYR:~/ros2_ws# source install/setup.bash root@LHAYR:~/ros2_ws# root@LHAYR:~/ros2_ws# ros2 run tf_test framepub |
| |

打开rviz

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 终端2,进入/ros2_ws目录 root@LHAYR:~/ros2_ws# rviz2 INFO 1783343952.920860794 rviz2: Stereo is NOT SUPPORTED INFO 1783343952.920917613 rviz2: OpenGl version: 4.5 (GLSL 4.5) INFO 1783343952.954833328 rviz2: Stereo is NOT SUPPORTED |

配置Rviz 终端 Fixed Frame 选中 frame_A;在点击add,添加tf显示窗口;

坐标系统 三维显示出来了,B坐标系围绕坐标系A在旋转;

第7章 坐标系的查询

7.1 接收坐标系变化的代码

tf2会把系统里所有的坐标系连成一棵树,只要查这棵树,就能知道任意两个坐标系是怎么变换的。在 tf2_ros 这个包里,有一个叫 TransformListener 的类,就是专门负责'监听'这些坐标系的。第一步,我们先在 tf_test 功能包里建一个 framesub.py 文件,用来写接收坐标系的节点代码。

在vs code 的 src/tf_test/tf_test目录下,创建一个文件(framesub.py));

修改framesub.py节点代码,如下

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import rclpy from rclpy.node import Node from tf2_ros import Buffer, TransformListener class FrameListener(Node): def init(self): # 修复了 super() 的调用语法 super().init('tf2_frame_listener') # 声明并获取源坐标系和目标坐标系参数,方便外部动态修改 self.target_frame = self.declare_parameter('target_frame', 'frame_B').get_parameter_value().string_value self.source_frame = self.declare_parameter('source_frame', 'frame_A').get_parameter_value().string_value # 创建 TF 缓存和监听器 self.tf_buffer = Buffer() self.tf_listener = TransformListener(self.tf_buffer, self) # 创建定时器,每 1.0 秒查询并打印一次坐标变换 self.timer = self.create_timer(1.0, self.show_tf) self.get_logger().info(f'正在监听 {self.source_frame} -> {self.target_frame} 的坐标变换...') def show_tf(self): """定时查询并打印坐标变换""" try: # 查询两个坐标系之间的最新变换 t = self.tf_buffer.lookup_transform( self.source_frame, self.target_frame, rclpy.time.Time() ) # 提取平移和旋转数据 trans = t.transform.translation rot = t.transform.rotation # 格式化打印结果 self.get_logger().info( f'获取到坐标变换 {self.source_frame} -> {self.target_frame}:\n' f' 平移 (x, y, z): {trans.x:.2f}, {trans.y:.2f}, {trans.z:.2f}\n' f' 旋转 (x, y, z, w): {rot.x:.2f}, {rot.y:.2f}, {rot.z:.2f}, {rot.w:.2f}' ) except Exception as e: # 捕获异常并打印警告(在 TF 还没完全建立时,这里会短暂报错,属于正常现象) self.get_logger().warn(f'获取坐标变换失败: {str(e)}') def main(args=None): rclpy.init(args=args) node = FrameListener() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if name == 'main': main() |
| 代码解析 * Node: ROS2 节点的基础类。 * Buffer 和 TransformListener: tf2_ros 包中的核心组件。Buffer 负责在内存中缓存整棵"TF树",而 TransformListener 负责在后台自动接收并更新这些缓存数据。 * 动态参数化:使用 declare_parameter 声明了 target_frame 和 source_frame。这意味着你不需要修改代码,就可以在启动节点时通过命令行指定要查询的坐标系(例如:ros2 run pkg node --ros-args -p source_frame:=map -p target_frame:=odom)。 * 监听器绑定:将 tf_buffer 和当前节点 self 传入 TransformListener,监听器就会自动在后台接收系统广播的所有 TF 消息并更新到 Buffer 中。 * 定时器:设置了一个 1.0 秒的定时器,每秒触发一次 show_tf 函数。 * lookup_transform:这是 TF2 最核心的 API。它根据传入的源坐标系和目标坐标系,在 TF 树中进行路径搜索,返回一个包含平移(x,y,z)和旋转(四元数 x,y,z,w)的 TransformStamped 消息。 * 时间参数 rclpy.time.Time():传入空的时间对象表示查询最新可用的坐标变换。 * 异常处理(非常重要):由于 ROS2 节点启动有先后顺序,监听节点启动时,发布节点可能还没开始发 TF 数据。此时查询会抛出 LookupException 等异常。使用 try...except 捕获异常并打印警告,可以防止节点因为找不到坐标系而崩溃。 * 标准的 ROS2 节点生命周期管理。rclpy.spin(node) 会让节点进入阻塞状态,持续响应定时器回调和 TF 监听器的后台数据接收。 * 使用 try...finally 确保在按下 Ctrl+C 退出时,节点资源能被正确销毁和释放。 |

7.2 添加环境变量,编译,加载,运行

在vs code的setup.py文件夹,里面添加

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| entry_points={ 'console_scripts': 'static_frame_pub = tf_test.static_frame_pub:main', 'framepub = tf_test.framepub:main', 'frame_sub = tf_test.framesub:main', #//注意事项,这个名称是frame_sub , }, |

编译,在ros2_ws 目录下编译,

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #//编译 root@LHAYR:~/ros2_ws# colcon build --packages-select tf_test Starting >>> tf_test Finished <<< tf_test 0.60s Summary: 1 package finished 0.68s root@LHAYR:~/ros2_ws# source install/setup.bash #// 加载环境变量 root@LHAYR:~/ros2_ws# source install/setup.bash |

运行 ros2 run tf_test frame_sub

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| root@LHAYR:~/ros2_ws# ros2 run tf_test frame_sub INFO 1783346898.496246288 tf2_frame_listener: 正在监听 frame_A -> frame_B 的坐标变换... INFO 1783346899.490170375 tf2_frame_listener: 获取到坐标变换 frame_A -> frame_B: 平移 (x, y, z): 1.91, 0.58, 0.00 旋转 (x, y, z, w): 0.00, 0.00, 0.80, 0.59 INFO 1783346900.490171243 tf2_frame_listener: 获取到坐标变换 frame_A -> frame_B: 平移 (x, y, z): 1.29, 1.53, 0.00 旋转 (x, y, z, w): 0.00, 0.00, 0.94, 0.34 INFO 1783346901.490044148 tf2_frame_listener: 获取到坐标变换 frame_A -> frame_B: 平移 (x, y, z): 0.21, 1.99, 0.00 旋转 (x, y, z, w): 0.00, 0.00, 1.00, 0.05 INFO 1783346902.490176833 tf2_frame_listener: 获取到坐标变换 frame_A -> frame_B: 平移 (x, y, z): -0.91, 1.78, 0.00 旋转 (x, y, z, w): 0.00, 0.00, 0.97, -0.23 INFO 1783346903.490082208 tf2_frame_listener: 获取到坐标变换 frame_A -> frame_B: 平移 (x, y, z): -1.73, 1.00, 0.00 旋转 (x, y, z, w): 0.00, 0.00, 0.87, -0.50 INFO 1783346904.490007240 tf2_frame_listener: 获取到坐标变换 frame_A -> frame_B: 平移 (x, y, z): -2.00, -0.14, 0.00 旋转 (x, y, z, w): 0.00, 0.00, -0.68, 0.73 INFO 1783346905.490036439 tf2_frame_listener: 获取到坐标变换 frame_A -> frame_B: 平移 (x, y, z): -1.60, -1.20, 0.00 旋转 (x, y, z, w): 0.00, 0.00, -0.45, 0.89 INFO 1783346906.490267941 tf2_frame_listener: 获取到坐标变换 frame_A -> frame_B: 平移 (x, y, z): -0.68, -1.88, 0.00 旋转 (x, y, z, w): 0.00, 0.00, -0.17, 0.98 INFO 1783346907.490098519 tf2_frame_listener: 获取到坐标变换 frame_A -> frame_B: 平移 (x, y, z): 0.48, -1.94, 0.00 旋转 (x, y, z, w): 0.00, 0.00, 0.12, 0.99 |

7.3运行结果

相关推荐
txg6661 小时前
机器人领域热点简报(2026-06-30 — 2026-07-07)
人工智能·搜索引擎·机器人
yonlin10 小时前
Telegram Rss Bot - RssStream 面向创作者和开发者的 Telegram 自动化机器人简介
机器人·telegram·电报
YMWM_14 小时前
企业级机器人数据平台开发
机器人·数据平台
某林21216 小时前
从硬件解耦、3D公差设计到ROS 2柔性导航
人工智能·3d·机器人·ros2·技术复盘
EAI-Robotics2 天前
机器人操作鲁棒性:当机械手遇上真实世界的不确定性
人工智能·算法·机器人
深圳多奥智能一卡(码、脸)通系统2 天前
机器人(狗)和AGV,AMR自动开门过闸配合使用的门控规格书
机器人·agv·机器狗·门禁·梯控·智能门禁·amr
2601_963671682 天前
机器人产业演进逻辑与商业化落地全景攻略
机器人
rosemary5122 天前
WSL2 ROS2 控制Issac sim中机器人
机器人·ros2·issac sim
陈天伟教授2 天前
FreeCAD 启动后小窗口闪现即退的解决思路
人工智能·机器人·工业设计