TF
ROS2 中的几大核心坐标系
坐标系名称 | 英文名 | 作用 | 常见来源 |
---|---|---|---|
map | Map Frame | 表示世界或全局地图坐标系,固定不动 | SLAM、AMCL、定位系统 |
odom | Odometry Frame | 由轮式里程计或视觉里程计建立,短期连续但可能漂移 | 里程计节点(odom publisher) |
base_link | Base Link Frame | 机器人本体中心坐标系,所有传感器相对于此定义 | 机器人底盘(底盘驱动节点) |
laser / camera_link | Sensor Frame | 各类传感器的坐标系(相机、激光雷达、IMU等) | 驱动或URDF定义 |
map→odom→base_link | 坐标链(Transform Tree) | 描述世界到机器人到传感器的连续变换 | 由 tf2 广播与 |
其对应的层次结构
map
└── odom
└── base_link
├── laser_link
└── camera_link
ROS2中TF对应的消息接口
消息类型 | 所属包 | 作用说明 |
---|---|---|
geometry_msgs/msg/TransformStamped |
geometry_msgs | 表示一个时间戳的坐标变换(最核心的单位) |
tf2_msgs/msg/TFMessage |
tf2_msgs | 表示一组变换(TransformStamped 数组),用于 TF 话题传输 |
geometry_msgs/msg/Transform |
geometry_msgs | 表示平移 + 旋转的基本坐标变换 |
geometry_msgs/msg/Vector3 |
geometry_msgs | 表示 3D 向量(用于平移部分) |
geometry_msgs/msg/Quaternion |
geometry_msgs | 表示四元数(用于旋转部分) |
其中第一个消息接口是TF 系统的核心单元,下面是此消息接口的详细介绍
std_msgs/Header header # 消息头部(包含时间戳和父坐标系信息)
uint32 seq # 序列号(自动递增,用于标识消息顺序,可忽略)
builtin_interfaces/Time stamp # 时间戳(当前变换的时间点)
int32 sec # 时间(秒)
uint32 nanosec # 时间(纳秒),与 sec 组合成完整时间
string frame_id # 父坐标系名称(Parent Frame),即该变换相对于谁string child_frame_id # 子坐标系名称(Child Frame),即被描述的目标坐标系
geometry_msgs/Transform transform # 坐标变换(包括平移 + 旋转)
geometry_msgs/Vector3 translation # 平移分量(位置)
float64 x # 在父坐标系 X 方向的平移量(单位:米)
float64 y # 在父坐标系 Y 方向的平移量(单位:米)
float64 z # 在父坐标系 Z 方向的平移量(单位:米)geometry_msgs/Quaternion rotation # 旋转分量(姿态),使用四元数表示,得到一个旋转轴向量轴和w为旋转的角度
float64 x # 四元数的 X 分量
float64 y # 四元数的 Y 分量
float64 z # 四元数的 Z 分量
float64 w # 四元数的 W 分量(标量部分)
坐标变换包括静态和动态的
静态 TF 表示两个坐标系之间固定不动的空间关系,比如摄像头相对于机器人底盘的位置永远固定而动态 TF 用于描述随时间变化的坐标变换关系,比如机器人在地图中不断移动
发布一个base_link
→ camera_link
的静态坐标转换话题
通过ros2 pkg create demo_python_tf --build-type ament_python --dependencies rclpy geometry_msgs tf_ros tf_transformations --license Apache-2.0创建功能包在工作区tf_ws下
import rclpy
from rclpy.node import Node
from tf2_ros import StaticTransformBroadcaster #静态坐标发布器
from geometry_msgs.msg import TransformStamped #消息接口
from tf_transformations import quaternion_from_euler#欧拉角转四元数函数
import math #角度转弧度函数class StaticTFBroadcaster(Node):def __init__(self):super().__init__('static_tf_broadcaster')self.static_broadcaster_= StaticTransformBroadcaster(self)self.publish_static_tf()def publish_static_tf(self):'''发布静态TF从base_link到camera_link之间的坐标关系'''transform= TransformStamped()transform.header.frame_id = 'base_link'transform.child_frame_id = 'camera_link'transform.header.stamp = self.get_clock().now().to_msg()transform.transform.translation.x = 0.5transform.transform.translation.y = 0.3transform.transform.translation.z = 0.6#欧拉角转四元数q=x,y,z,Wq = quaternion_from_euler(math.radians(180),0,0)#旋转部分进行赋值transform.transform.rotation.x = q[0]transform.transform.rotation.y = q[1]transform.transform.rotation.z = q[2]transform.transform.rotation.w = q[3]#静态坐标关系发布出去self.static_broadcaster_.sendTransform(transform)self.get_logger().info(f'发布静态TF:{transform}')
def main():rclpy.init()node = StaticTFBroadcaster()rclpy.spin(node)rclpy.shutdown()
发布一个bottle_link
→ camera_link
的动态坐标转换话题
import rclpy
from rclpy.node import Node
from tf2_ros import StaticTransformBroadcaster #静态坐标发布器
from geometry_msgs.msg import TransformStamped #消息接口
from tf_transformations import quaternion_from_euler#欧拉角转四元数函数
import math #角度转弧度函数class DynamicTFBroadcaster(Node):def __init__(self):super().__init__('dynamic_tf_broadcaster')self.static_broadcaster_= StaticTransformBroadcaster(self)# 没0.01秒触发发布一个瓶子相对于相机的坐标变换self.timer = self.create_timer(0.01,self.publish_dynamic_tf)def publish_dynamic_tf(self):'''发布动态TF从camera_link到bottle_link之间的坐标关系'''transform= TransformStamped()transform.header.frame_id = 'camera_link'transform.child_frame_id = 'bottle_link'transform.header.stamp = self.get_clock().now().to_msg()transform.transform.translation.x = 0.5transform.transform.translation.y = 0.3transform.transform.translation.z = 0.6#欧拉角转四元数q=x,y,z,Wq = quaternion_from_euler(math.radians(180),0,0)#旋转部分进行赋值transform.transform.rotation.x = q[0]transform.transform.rotation.y = q[1]transform.transform.rotation.z = q[2]transform.transform.rotation.w = q[3]#静态坐标关系发布出去self.static_broadcaster_.sendTransform(transform)self.get_logger().info(f'发布动态TF:{transform}')
def main():rclpy.init()node = DynamicTFBroadcaster()rclpy.spin(node)rclpy.shutdown()
创建订阅监听并获取得到base_link
→ bottle_link
的转换关系,实时监听两个坐标系(base_link
和 bottle_link
)之间的空间变换关系(平移 + 旋转),并将结果打印出来
import rclpy
from rclpy.node import Node
from rclpy.time import Time,Duration
from tf2_ros import TransformListener,Buffer
from geometry_msgs.msg import TransformStamped #消息接口
from tf_transformations import euler_from_quaternion #四元数函数转欧拉角
import math #角度转弧度函数class TFListener(Node):def __init__(self):super().__init__('tf_listener')self.buffer =Buffer()self.listener =TransformListener(self.buffer,self)self.timer = self.create_timer(1.0,self.get_transform)def get_transform(self):'''实时查询坐标关系buffer'''try:# Time(seconds=0.0)为最新时间,Duration(seconds=1.0)为最大等待时间result = self.buffer.lookup_transform('base_link','bottle_link',Time(seconds=0.0),Duration(seconds=1.0))transform= result.transformself.get_logger().info(f'平移:{transform.translation}')self.get_logger().info(f'旋转:{transform.rotation}')rotation_euler = euler_from_quaternion([transform.rotation.x,transform.rotation.y,transform.rotation.z,transform.rotation.w])self.get_logger().info(f'旋转RPY:{rotation_euler}')except Exception as e:self.get_logger().warn(f'获取坐标变换失败:原因{str(e)}')
def main():rclpy.init()node = TFListener()rclpy.spin(node)rclpy.shutdown()
C++实现查询静动态坐标系并查询
有地图坐标系,机器人坐标系和目标点,其中机器人是会移动的而其余两个则是静止的
#include <iostream>
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/transform_stamped.hpp> //提供消息接口
#include <tf2/LinearMath/Quaternion.h> //提供tf2::Quaternion类
#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp> //消息类型转换函数
#include <tf2_ros/static_transform_broadcaster.h> //静态坐标广播器类
using namespace std;
using namespace rclcpp;class StaticTFBroadcaster: public Node
{private:shared_ptr<tf2_ros::StaticTransformBroadcaster> broadcaster;public:StaticTFBroadcaster(): Node("static_tf_broadcaster"){this->broadcaster = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);this->publish_tf();}void publish_tf(){geometry_msgs::msg::TransformStamped transform;transform.header.stamp = this->get_clock()->now();transform.header.frame_id="map";transform.child_frame_id="target_point";transform.transform.translation.x = 5.0;transform.transform.translation.y= 3.0;transform.transform.translation.z=0.0;tf2::Quaternion q;q.setRPY(0.0,0.0,60*M_PI/180.0);transform.transform.rotation =tf2::toMsg(q);this->broadcaster->sendTransform(transform);}};int main(int argc,char* argv[]){init(argc,argv);auto node = make_shared<StaticTFBroadcaster>();spin(node);shutdown();}
#include <iostream>
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/transform_stamped.hpp> //提供消息接口
#include <tf2/LinearMath/Quaternion.h> //提供tf2::Quaternion类
#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp> //消息类型转换函数
#include <tf2_ros/static_transform_broadcaster.h> //静态坐标广播器类
using namespace std;
using namespace rclcpp;class DynamicTFBroadcaster: public Node
{private:shared_ptr<tf2_ros::StaticTransformBroadcaster> broadcaster;shared_ptr<TimerBase> timer;public:DynamicTFBroadcaster(): Node("dynamic_tf_broadcaster"){this->broadcaster = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);timer = this->create_wall_timer(100ms,bind(&DynamicTFBroadcaster::publish_tf,this));}void publish_tf(){geometry_msgs::msg::TransformStamped transform;transform.header.stamp = this->get_clock()->now();transform.header.frame_id="map";transform.child_frame_id="base_link";transform.transform.translation.x = 2.0;transform.transform.translation.y= 3.0;transform.transform.translation.z=0.0;tf2::Quaternion q;q.setRPY(0.0,0.0,60*M_PI/180.0);transform.transform.rotation =tf2::toMsg(q);this->broadcaster->sendTransform(transform);}};int main(int argc,char* argv[]){init(argc,argv);auto node = make_shared<DynamicTFBroadcaster>();spin(node);shutdown();}
#include <iostream>
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/transform_stamped.hpp> //提供消息接口
#include <tf2/LinearMath/Quaternion.h> //提供tf2::Quaternion类
#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp> //消息类型转换函数
#include <tf2_ros/transform_listener.h> //监听器类
#include <tf2_ros/buffer.h>
#include <tf2/utils.h>
using namespace std;
using namespace rclcpp;class TFListener: public Node
{private:shared_ptr<tf2_ros::TransformListener> listener;shared_ptr<tf2_ros::Buffer> buffer;shared_ptr<TimerBase> timer;public:TFListener(): Node("tf_listener"){this->buffer = std::make_shared<tf2_ros::Buffer>(this->get_clock());this->listener = std::make_shared<tf2_ros::TransformListener>(*buffer,this);timer = this->create_wall_timer(100ms,bind(&TFListener::getTransfrom,this));}void getTransfrom(){try{//查询坐标关系const auto tranform = buffer->lookupTransform("base_link","target_point",this->get_clock()->now(),rclcpp::Duration::from_seconds(1.0f));//获取查询结果auto translation =tranform.transform.translation;auto rotation= tranform.transform.rotation;double y,p,r;tf2::getEulerYPR(rotation,y,p,r);RCLCPP_INFO(get_logger(),"平移:%f,%f,%f",translation.x,translation.y,translation.z);RCLCPP_INFO(get_logger(),"旋转:%f,%f,%f",y,p,r);}catch(const std::exception&e){RCLCPP_WARN(get_logger(),"%s",e.what());}}};int main(int argc,char* argv[]){init(argc,argv);auto node = make_shared<TFListener>();spin(node);shutdown();}
可视化工具
Rqt:rqt 是一个基于 Qt 框架的可视化工具框架,用于创建和集成各种 ROS 相关的图形化界面工具,rqt关联许多插件通过下面命令进行安装sudo apt install ros-$ROS_DISTRO-rqt* # 安装所有rqt插件(以ROS2 Humble为例)
例如sudo apt install ros-$ROS_DISTRO-rqt-tf-tree -y
安装之后需要更新配置环境
sudo rm -rf ~/.config/ros.org/rqt_gui.ini
然后启动rqt之后会自动生成一个带有新插件rqt_gui.ini
Rviz2:rviz2是 ROS2中最核心的 3D 可视化工具,用于实时显示机器人的传感器数据、状态信息、环境地图等,是机器人开发、调试和演示的必备工具。
通过add按钮可增加插件,FixedFrame为固定坐标系,其余坐标系的坐标都是以这个为参考,一般为世界坐标系
启动话题之后显示出三个坐标系
数据记录工具Ros2 bag
Ros2Bag:ros2 bag
是 ROS 2 中用于记录和回放话题数据的核心工具,类似于 ROS 1 中的 rosbag
。它能将 ROS 2 系统中流转的话题数据(如传感器数据、机器人状态等)存储到 .bag
文件中,便于离线分析、场景复现或数据共享。
bag 文件:存储 ROS 2 话题数据的二进制文件,包含话题名称、消息类型、时间戳及具体数据,后缀为 .bag
(ROS 2 中默认使用 SQLite 格式,与 ROS 1 的格式不同)。
核心功能:记录(record
)、回放(play
)、查看信息(info
)、验证(check
)等。