ROS2--8章 机器人坐标系 tf2库
目录
一、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。操作:
- 点击 Fixed Frame 旁边输入框中的 map(或当前显示的文本)
- 删除它,输入 World(注意大小写,必须和你发布时用的 --frame-id World 完全一致)
- 按 Enter 键确认
✅ 此时,RViz2 会以 World 坐标系为世界原点。

②、添加 TF 可视化
操作:
- 点击 Add 按钮
- 会弹出一个 Create visualization display 对话框
- 在左侧列表中找到 By display 标签页,或在搜索框中输入 tf
- 选择 TF(图标是一个坐标系样子)
- 点击右下角的 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 q[0] = cj * sc - sj * cs q[1] = cj * ss + sj * cc q[2] = cj * cs - sj * sc q[3] = 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 = quat[0] t.transform.rotation.y = quat[1] t.transform.rotation.z = quat[2] t.transform.rotation.w = quat[3]
# 打印日志并发布变换 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)
2. 核心节点类 (StaticFramePublisher) 这是整个程序的主体,继承自 ROS2 的 Node 类,主要包含以下逻辑:
3. 程序入口与生命周期管理 (main 函数)
总结 简单来说,这段代码就是一个“坐标翻译官”。当你运行它并告诉它“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),也就是飞行器或机器人最常用的旋转描述方式:
|
打开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 中发布一个动态坐标系,代码逻辑会发生以下变化:
- 广播器改变:将 StaticTransformBroadcaster 替换为普通的 TransformBroadcaster。
- 增加定时器:不能只在 __init__ 里发一次了,你需要创建一个定时器(Timer),比如每 50 毫秒(20Hz)触发一次发布函数。
- 数据源改变:平移和旋转的参数不能写死,通常需要从硬件传感器(如电机编码器、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 q[0] = cj * sc - sj * cs q[1] = cj * ss + sj * cc q[2] = cj * cs - sj * sc q[3] = 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 = quat[0] t.transform.rotation.y = quat[1] t.transform.rotation.z = quat[2] t.transform.rotation.w = quat[3]
# 发送动态变换 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 轴指向运动的前进方向(切线方向)。 核心代码逻辑解析:
|
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() |
|
代码解析
|
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运行结果

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)