ROS2之TF

TF

TF(Transform Frame) 是 ROS2 中用于维护多个坐标系之间空间关系的坐标变换系统。它可以实时跟踪机器人各个部分(如底盘、传感器、地图等)在三维空间中的位置与姿态,通过建立一棵动态的 坐标变换树(TF Tree),实现不同坐标系间的转换,使得各模块在统一的空间基准下进行感知、定位与导航。

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 用于描述随时间变化的坐标变换关系,比如机器人在地图中不断移动

image 

发布一个base_linkcamera_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()

 image

 发布一个bottle_linkcamera_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()

image

创建订阅监听并获取得到base_linkbottle_link的转换关系,实时监听两个坐标系(base_linkbottle_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()

image

 C++实现查询静动态坐标系并查询

image

有地图坐标系,机器人坐标系和目标点,其中机器人是会移动的而其余两个则是静止的

#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();

 }

image

可视化工具

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

image

然后启动rqt之后会自动生成一个带有新插件rqt_gui.ini

Rviz2:rviz2是 ROS2中最核心的 3D 可视化工具,用于实时显示机器人的传感器数据、状态信息、环境地图等,是机器人开发、调试和演示的必备工具。

通过add按钮可增加插件,FixedFrame为固定坐标系,其余坐标系的坐标都是以这个为参考,一般为世界坐标系

image

image

启动话题之后显示出三个坐标系

image

数据记录工具Ros2 bag

Ros2Bagros2 bag 是 ROS 2 中用于记录和回放话题数据的核心工具,类似于 ROS 1 中的 rosbag。它能将 ROS 2 系统中流转的话题数据(如传感器数据、机器人状态等)存储到 .bag 文件中,便于离线分析、场景复现或数据共享。

bag 文件:存储 ROS 2 话题数据的二进制文件,包含话题名称、消息类型、时间戳及具体数据,后缀为 .bag(ROS 2 中默认使用 SQLite 格式,与 ROS 1 的格式不同)。

核心功能:记录(record)、回放(play)、查看信息(info)、验证(check)等。

posted @ 2025-10-12 22:24  突破铁皮  阅读(27)  评论(0)    收藏  举报