当前位置: 首页 > news >正文

ros2-tf树查看

首先先介绍一个在ubuntu20中能够使用的阿克曼转向的模型系统,car_demo,我们使用foxy,noetic,galactic三种都进行了测试,以下是最终的可用仿真环境。

git clone https://github.com/chan-yuu/car_demo.git -b foxy

编译即可

colcon build

在这里插入图片描述
同时也提供了其他ros版本的仿真环境,今天的主要目的是认识ros2的tf系统。
在这里插入图片描述
启动仿真环境

ros2 launch car_demo demo.launch.py

在这里插入图片描述
在这里插入图片描述
!!![debug]这个仿真环境比较占用资源,并且摩擦并没有调的很好,车辆会在静止时依然滑动都是常有的事。

由于ros2中rqt工具无法直接查看tf树,我们需要额外安装其他的工具实现可视化。

查看TF树结构

工具直接安装:

sudo apt install ros-$ROS_DISTRO-tf2-tools
ros2 run tf2_tools view_frames.py

生成了cv和pdf文件直接查看:
在这里插入图片描述

实时监控TF

更加推荐这种实时监控的方式,是rqt的独立的工具,没有进行集成,安装方式为:

sudo apt install ros-$ROS_DISTRO-rqt-tf-tree
ros2 run rqt_tf_tree rqt_tf_tree

在这里插入图片描述

查看特定变换

最后是通过tf2_ros 的tf2_echo 来直接打印坐标关系。借这个打印我们也来分析一下两个坐标的tf关系是怎么计算出来的。
ros2 run tf2_ros tf2_echo <源坐标系> <目标坐标系>
例如:ros2 run tf2_ros tf2_echo dummy base_link

cyun@cyun:~/Fork_RL/9.4/forklift_rl/car_demo$ ros2 run tf2_ros tf2_echo dummy base_link
[INFO] [1757851831.521967212] [tf2_echo]: Waiting for transform dummy ->  base_link: Invalid frame ID "dummy" passed to canTransform argument target_frame - frame does not exist
At time 0.0
- Translation: [0.000, 0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.000, 1.000]
At time 0.0
- Translation: [0.000, 0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.000, 1.000]
At time 0.0
- Translation: [0.000, 0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.000, 1.000]

ros2 run tf2_ros tf2_echo map base_link
旋转平移矩阵的转移关系分析:
在 ROS 2 中,tf2_echo输出的平移和旋转参数描述了源坐标系到目标坐标系的变换关系。这些参数在机器人系统中用于坐标转换、运动规划、传感器数据融合等关键任务。下面从数学角度详细说明其含义和用法:

假设存在两个坐标系:

源坐标系:A(如base_link)
目标坐标系:B(如odom)

tf2_echo A B输出的是从 A 到 B 的变换,即:如何将一个点在 A 中的坐标转换到 B 中。

变换公式的核心是齐次变换矩阵(4×4 矩阵),它同时包含平移和旋转信息:

plaintext
| R₁₁ R₁₂ R₁₃ tₓ |
| R₂₁ R₂₂ R₂₃ tᵧ |
| R₃₁ R₃₂ R₃₃ t_z |
| 0 0 0 1 |

其中:

R 是 3×3 旋转矩阵(由四元数转换而来)
tₓ, tᵧ, t_z 是平移向量(Translation输出值)
2. 平移向量(Translation)的使用
输出的[tₓ, tᵧ, t_z]表示:
目标坐标系 B 的原点在源坐标系 A 中的坐标(注意看,这里就是直接用源坐标系给出的目标坐标系的原点位置)。

若一个点P在 A 中的坐标为P_A = [x, y, z, 1](齐次坐标),经过平移后在 B 中的坐标计算为:

plaintext
P_B(平移) = P_A - [tₓ, tᵧ, t_z, 0]

(注:齐次坐标的最后一位为 1 时表示点,平移时加平移向量;为 0 时表示向量,平移不影响)
3. 旋转(Rotation)的表示与使用
ROS 2 中旋转默认用四元数[x, y, z, w]表示,其对应数学定义为:

plaintext
四元数 q = w + x·i + y·j + z·k

其中w是实部,x,y,z是虚部,满足x² + y² + z² + w² = 1。
3.1 四元数到旋转矩阵的转换
四元数需要先转换为旋转矩阵R才能用于坐标计算,转换公式为:

plaintext
R = [
[1-2y²-2z², 2xy-2zw, 2xz+2yw ],
[2xy+2zw, 1-2x²-2z², 2yz-2xw ],
[2xz-2yw, 2yz+2xw, 1-2x²-2y² ]
]
3.2 旋转的应用
若点P_A在 A 中经过旋转后到 B 中的坐标为P_B(旋转),则:

plaintext
P_B(旋转) = R × P_A

(× 表示矩阵乘法,P_A为 3×1 列向量)
4. 完整坐标变换公式
结合平移和旋转,从源坐标系 A 到目标坐标系 B 的完整变换为:

plaintext
P_B = R × P_A + t

用齐次矩阵表示则更简洁:

plaintext
[P_B] [R t] [P_A]
[ 1 ] = [0 1] [ 1 ]
5. 坐标系迁移的实际意义
例如tf2_echo base_link odom输出:

平移[0.1, 0.2, 0.3]:表示odom原点在base_link前方 0.1m、右侧 0.2m、上方 0.3m 处
四元数[0,0,sin0.5θ,cos0.5θ](绕 z 轴旋转 θ 角):表示odom相对base_link绕 z 轴旋转了 θ 角

当机器人移动时,这个变换会实时更新,通过上述公式可以:

将传感器数据(如激光雷达在base_link下的坐标)转换到世界坐标系(odom)中
反之,将世界坐标系中的目标点转换到机器人本地坐标系,用于路径规划
总结
Translation是目标坐标系原点在源坐标系中的位置
Quaternion通过转换为旋转矩阵描述两坐标系的姿态关系
完整变换公式:目标坐标 = 旋转矩阵 × 源坐标 + 平移向量

以下是一个用transformations实现的例子(ros2下)

import rclpy
from rclpy.node import Node
from tf2_ros import TransformBroad
caster
from geometry_msgs.msg import TransformStamped
import transformations
import mathclass GpsOdomTFPublisher(Node):def __init__(self):super().__init__('gps_odom_tf_publisher')# 创建TF广播器self.tf_broadcaster = TransformBroadcaster(self)# 设置定时器,定期发布TF变换self.timer = self.create_timer(0.1, self.broadcast_transform)# 定义odom在gps坐标系中的初始位姿self.odom_origin_in_gps = (580000.0, 4360000.0, 0.0)  # x, y, zself.odom_yaw_in_gps = math.radians(90)  # 90度转换为弧度self.get_logger().info('GPS-Odom TF发布器已启动')# 待转换的GPS点和航向self.gps_point = (580001.0, 4360001.0, 0.0)  # GPS坐标self.gps_heading = math.radians(50)  # GPS航向角(50度,弧度)# 执行转换self.convert_gps_to_odom()def broadcast_transform(self):# 创建变换消息t = TransformStamped()# 设置时间戳和坐标系t.header.stamp = self.get_clock().now().to_msg()t.header.frame_id = 'gps'  # 父坐标系t.child_frame_id = 'odom'  # 子坐标系# 设置平移参数 (odom原点在gps中的坐标)t.transform.translation.x = self.odom_origin_in_gps[0]t.transform.translation.y = self.odom_origin_in_gps[1]t.transform.translation.z = self.odom_origin_in_gps[2]# 将90度旋转转换为四元数,使用transformations库quat = transformations.quaternion_from_euler(0, 0, self.odom_yaw_in_gps)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)def convert_gps_to_odom(self):"""将GPS坐标和航向转换到odom坐标系"""# 1. 坐标点转换# 1.1 平移:减去odom原点在GPS中的坐标translated_x = self.gps_point[0] - self.odom_origin_in_gps[0]translated_y = self.gps_point[1] - self.odom_origin_in_gps[1]translated_z = self.gps_point[2] - self.odom_origin_in_gps[2]# 1.2 旋转:应用逆变换(-90度)将GPS点转换到odomtheta = -self.odom_yaw_in_gps  # 逆旋转角cos_theta = math.cos(theta)sin_theta = math.sin(theta)odom_x = translated_x * cos_theta + translated_y * sin_thetaodom_y = -translated_x * sin_theta + translated_y * cos_thetaodom_z = translated_z# 2. 航向角转换# 航向角 = GPS航向角 - odom相对gps的旋转角(90度)odom_heading = self.gps_heading - self.odom_yaw_in_gps# 归一化到[-π, π]odom_heading = (odom_heading + math.pi) % (2 * math.pi) - math.pi# 打印结果self.get_logger().info(f"GPS坐标: {self.gps_point}")self.get_logger().info(f"GPS航向角: {math.degrees(self.gps_heading):.1f}°")self.get_logger().info(f"Odom坐标: ({odom_x:.2f}, {odom_y:.2f}, {odom_z:.2f})")self.get_logger().info(f"Odom航向角: {math.degrees(odom_heading):.1f}°")# 验证旋转计算(90度逆旋转的特殊情况)verify_x = translated_yverify_y = -translated_xself.get_logger().info(f"旋转验证: ({verify_x:.2f}, {verify_y:.2f})")self.get_logger().info(f"航向验证: {math.degrees(self.gps_heading - math.pi/2):.1f}°")def main(args=None):rclpy.init(args=args)node = GpsOdomTFPublisher()rclpy.spin(node)node.destroy_node()rclpy.shutdown()if __name__ == '__main__':main()

在这里插入图片描述

cyun@cyun:~/Fork_RL/9.4/forklift_rl/autodrive/sim/src/Frenet_Planner_Simulation/Frenet_Planner_AGV/frenet_planner_agv/scripts$ python test_quat.py 
[INFO] [1757896880.219417976] [gps_odom_tf_publisher]: GPS-Odom TF发布器已启动
[INFO] [1757896880.220089600] [gps_odom_tf_publisher]: GPS坐标: (580001.0, 4360001.0, 0.0)
[INFO] [1757896880.220395713] [gps_odom_tf_publisher]: GPS航向角: 50.0°
[INFO] [1757896880.220799054] [gps_odom_tf_publisher]: Odom坐标: (-1.00, 1.00, 0.00)
[INFO] [1757896880.221059341] [gps_odom_tf_publisher]: Odom航向角: -40.0°
[INFO] [1757896880.221254674] [gps_odom_tf_publisher]: 旋转验证: (1.00, -1.00)
[INFO] [1757896880.221423286] [gps_odom_tf_publisher]: 航向验证: -40.0°

此时的tf关系为:

ros2 run tf2_ros tf2_echo  gps odom
cyun@cyun:~/Fork_RL/9.4/forklift_rl/autodrive/sim/src/Frenet_Planner_Simulation/Frenet_Planner_AGV/frenet_planner_agv/scripts$ ros2 run tf2_ros tf2_echo gps odom
[INFO] [1757896939.554518347] [tf2_echo]: Waiting for transform gps ->  odom: Invalid frame ID "gps" passed to canTransform argument target_frame - frame does not exist
At time 1757896940.409354626
- Translation: [580000.000, 4360000.000, 0.000]
- Rotation: in Quaternion [0.707, 0.000, 0.000, 0.707]
At time 1757896941.409428650
- Translation: [580000.000, 4360000.000, 0.000]
- Rotation: in Quaternion [0.707, 0.000, 0.000, 0.707]
At time 1757896942.409346144
- Translation: [580000.000, 4360000.000, 0.000]
- Rotation: in Quaternion [0.707, 0.000, 0.000, 0.707]
At time 1757896943.409368130
- Translation: [580000.000, 4360000.000, 0.000]
- Rotation: in Quaternion [0.707, 0.000, 0.000, 0.707]
At time 1757896944.409341732
- Translation: [580000.000, 4360000.000, 0.000]
- Rotation: in Quaternion [0.707, 0.000, 0.000, 0.707]

从这里的结果修正一下之前说的四元数的旋转关系,我们只需要知道绕着z轴旋转需要以下内容:

(sin0.5θ,0,0,cos0.5θ)

以上就是对于tf关系的解析的整个过程,理解旋转平移关系是关键。


文章转载自:

http://MwQxjYPE.rrcrs.cn
http://wV4c5EGJ.rrcrs.cn
http://gnfmYBUO.rrcrs.cn
http://1KR6g6EH.rrcrs.cn
http://4oyuragu.rrcrs.cn
http://tv6ADRKj.rrcrs.cn
http://KAjHmtfb.rrcrs.cn
http://tMmoe2Am.rrcrs.cn
http://02r9DQc9.rrcrs.cn
http://Q3igWKES.rrcrs.cn
http://g3AmdIAc.rrcrs.cn
http://IxDkrepp.rrcrs.cn
http://h9AcTc3H.rrcrs.cn
http://pcPvHXqB.rrcrs.cn
http://LScecy0p.rrcrs.cn
http://mbaKzo7K.rrcrs.cn
http://RHidmSF1.rrcrs.cn
http://bGbsdnzh.rrcrs.cn
http://7qTkWZFT.rrcrs.cn
http://o5ghXPgo.rrcrs.cn
http://hxOAvu6C.rrcrs.cn
http://y6yoVeqs.rrcrs.cn
http://Qyr9gdd4.rrcrs.cn
http://QhfKxnYD.rrcrs.cn
http://6mnQ2vEV.rrcrs.cn
http://GlEtPB6n.rrcrs.cn
http://rRTP9Tyf.rrcrs.cn
http://pR9zgBA4.rrcrs.cn
http://bRKqHkiw.rrcrs.cn
http://iMVVXn0p.rrcrs.cn
http://www.dtcms.com/a/383729.html

相关文章:

  • 速通ACM省铜第四天 赋源码(G-C-D, Unlucky!)
  • MFC仿真
  • Leetcode 19 java
  • Vue3 响应式核心 API
  • linux故障排查
  • 为什么哈希表能 O(1) 查找?——C++ 哈希基础入门
  • [CISCN2019 华北赛区 Day1 Web2]ikun
  • 算法——递推求解
  • stm32之点灯
  • Qt---内存管理 对象树(Object Tree)机制
  • 人工智能通识与实践 - 计算机视觉技术
  • GAMES101:现代计算机图形学入门(Chapter1 计算机图形学概述)学习笔记
  • MATLAB 常用函数汇总大全和高级应用总结
  • Knockout.js 任务调度模块详解
  • LeetCode 2414.最长的字母续连续子字符串的长度
  • 当环保遇上大数据:生态环境大数据技术专业的课程侧重哪些领域?
  • 【Ansible】使用角色和Ansible内容集合简化Playbook知识点
  • init / record / required:让 C# 对象一次成型
  • BigemapPro快速添加历史影像(Arcgis卫星地图历史地图)
  • 树莓派操作第一章常用指令
  • Altium Designer(AD24)工作面板的切换与定制
  • 【WebSocket✨】入门之旅(七):WebSocket 的未来发展趋势
  • MySQL——库的操作
  • Redis缓存的常见问题及其解决方案
  • Conda 安装 CUDA Toolkit 解决 nvcc 找不到的问题
  • (二)Django框架常用配置
  • Android开发-数据库SQLite
  • (附源码)基于springboot的幼儿园管理系统
  • 【从零到公网】本地电脑部署服务并实现公网访问(IPv4/IPv6/DDNS 全攻略)
  • VTK基础(01):VTK中的基本概念