ROS2之TF
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.5
transform.transform.translation.y = 0.3
transform.transform.translation.z = 0.6
#欧拉角转四元数q=x,y,z,W
q = 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.5
transform.transform.translation.y = 0.3
transform.transform.translation.z = 0.6
#欧拉角转四元数q=x,y,z,W
q = 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.transform
self.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)等。

浙公网安备 33010602011771号