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

9.ROS的TF坐标变换(三):坐标系关系查看与一个案例

1 查看目前的坐标系变化

        我们先安装功能包:

sudo apt install ros-melodic-tf2-tools

        安装成功!

        我们先启动上次的发布坐标变换的节点:

liuhongwei@liuhongwei-Legion-Y9000P-IRX8H:~/Desktop/final/my_catkin$ source devel/setup.bash 
liuhongwei@liuhongwei-Legion-Y9000P-IRX8H:~/Desktop/final/my_catkin$ roslaunch test tf_son1_son2.launch 
<launch>
    <node pkg="tf2_ros" type="static_transform_publisher" name="son1" args="0.2 0.8 0.3 0 0 0 /world /son1" output="screen" />
    <node pkg="tf2_ros" type="static_transform_publisher" name="son2" args="0.5 0 0 0 0 0 /world /son2" output="screen" />
</launch>

        目前坐标系的关系已经发布了。

        我们查看现在的坐标关系:       

rosrun tf2_tools view_frames.py
liuhongwei@liuhongwei-Legion-Y9000P-IRX8H:~$ rosrun tf2_tools view_frames.py
[INFO] [1701501936.157621]: Listening to tf data during 5 seconds...
[INFO] [1701501941.163282]: Generating graph in frames.pdf file...
liuhongwei@liuhongwei-Legion-Y9000P-IRX8H:~$ 

        生成了一个pdf文件。

        我们查看一下。      

2 案例:产生两只乌龟并让一只跟随另一只

2.1 需求

        程序启动之初: 产生两只乌龟,中间的乌龟(A) 和 左下乌龟(B), B 会自动运行至A的位置,并且键盘控制时,只是控制 A 的运动,但是 B 可以跟随 A 运行。

2.2 实现分析

        乌龟跟随实现的核心,是乌龟A和B都要发布相对世界坐标系的坐标信息,然后,订阅到该信息需要转换获取A相对于B坐标系的信息,最后,再生成速度信息,并控制B运动。

  1. 启动乌龟显示节点
  2. 在乌龟显示窗体中生成一只新的乌龟(需要使用服务)
  3. 编写两只乌龟发布坐标信息的节点
  4. 编写订阅节点订阅坐标信息并生成新的相对关系生成速度信息

实现流程:

  1. 新建功能包,添加依赖

  2. 编写服务客户端,用于生成一只新的乌龟

  3. 编写发布方,发布两只乌龟的坐标信息

  4. 编写订阅方,订阅两只乌龟信息,生成速度信息并发布

  5. 运行

2.3 实现

2.3.1 实现1--启动乌龟1,生成乌龟2:GUI

        建立gen_turtle.cpp创建第二只乌龟:

//
// Created by liuhongwei on 23-12-2.
//
/*
    创建第二只小乌龟
 */
#include "ros/ros.h"
#include "turtlesim/Spawn.h"

int main(int argc, char *argv[])
{

    setlocale(LC_ALL,"");

    //执行初始化
    ros::init(argc,argv,"create_turtle");
    //创建节点
    ros::NodeHandle nh;
    //创建服务客户端
    ros::ServiceClient client = nh.serviceClient<turtlesim::Spawn>("/spawn");

    ros::service::waitForService("/spawn");
    turtlesim::Spawn spawn;
    spawn.request.name = "turtle2";
    spawn.request.x = 1.0;
    spawn.request.y = 2.0;
    spawn.request.theta = 3.12415926;
    bool flag = client.call(spawn);
    if (flag)
    {
        ROS_INFO("乌龟%s创建成功!",spawn.response.name.c_str());
    }
    else
    {
        ROS_INFO("乌龟2创建失败!");
    }

    ros::spin();

    return 0;
}

        放入launch文件中:

<launch>
    <!-- 启动乌龟节点与键盘控制节点 -->
    <node pkg="turtlesim" type="turtlesim_node" name="turtle1" output="screen" />
    <node pkg="turtlesim" type="turtle_teleop_key" name="key_control" output="screen"/>
    <!-- 启动创建第二只乌龟的节点 -->
    <node pkg="test" type="generate_turtle" name="turtle2" output="screen" />

</launch>

        启动节点:roslaunch test generate_turtle.launch

        功能全部实现!

        那么如何控制第二只乌龟运动呢?大家想一下怎么做!

2.3.2 发布乌龟坐标系相关关系

        发布两只乌龟相对于世界坐标系之间的关系:

        和前面基本一样,这里我们复用了前面的代码:

1.TF2与TF比较_简介
  • TF2已经替换了TF,TF2是TF的超集,建议学习 TF2 而非 TF

  • TF2 功能包的增强了内聚性,TF 与 TF2 所依赖的功能包是不同的,TF 对应的是tf包,TF2 对应的是tf2tf2_ros包,在 TF2 中不同类型的 API 实现做了分包处理。

  • TF2 实现效率更高,比如在:TF2 的静态坐标实现、TF2 坐标变换监听器中的 Buffer 实现等

2.TF2与TF比较_静态坐标变换演示

接下来,我们通过静态坐标变换来演示TF2的实现效率。

2.1启动 TF2 与 TF 两个版本的静态坐标变换

TF2 版静态坐标变换:rosrun tf2_ros static_transform_publisher 0 0 0 0 0 0 /base_link /laser

TF 版静态坐标变换:rosrun tf static_transform_publisher 0 0 0 0 0 0 /base_link /laser 100

会发现,TF 版本的启动中最后多一个参数,该参数是指定发布频率

2.2运行结果比对

使用rostopic查看话题,包含/tf/tf_static, 前者是 TF 发布的话题,后者是 TF2 发布的话题,分别调用命令打印二者的话题消息

rostopic echo /tf: 当前会循环输出坐标系信息

rostopic echo /tf_static: 坐标系信息只有一次

2.3结论

如果是静态坐标转换,那么不同坐标系之间的相对状态是固定的,既然是固定的,那么没有必要重复发布坐标系的转换消息,很显然的,tf2 实现较之于 tf 更为高效

        我们把它写入launch文件。

<!--
    tf2 实现小乌龟跟随案例
-->
<launch>
    <!-- 启动乌龟节点与键盘控制节点 -->
    <node pkg="turtlesim" type="turtlesim_node" name="turtle1" output="screen" />
    <node pkg="turtlesim" type="turtle_teleop_key" name="key_control" output="screen"/>
    <!-- 启动创建第二只乌龟的节点 -->
    <node pkg="test" type="generate_turtle" name="turtle2" output="screen" />
    <!-- 启动两个坐标发布节点 -->
    <node pkg="test" type="generate_turtle_locate" name="caster1" output="screen" args="turtle1" />
    <node pkg="test" type="generate_turtle_locate" name="caster2" output="screen" args="turtle2" />

</launch>

        启动launch文件:roslaunch test generate_turtle.launch

        我们打开RVIZ可视化一下:

2.3.3 订阅坐标信息并生成速度信息

        订阅 turtle1 和 turtle2 的 TF 广播信息,查找并转换时间最近的 TF 信息
        将 turtle1 转换成相对 turtle2 的坐标,在计算线速度和角速度并发布

        实现流程:
        1.包含头文件
        2.初始化 ros 节点
        3.创建 ros 句柄
        4.创建 TF 订阅对象
        5.处理订阅到的 TF
        6.spin

        先来看t1怎么到t2:

        他们的直线距离为\sqrt{x^2+y^2},角度为atan(y,x),我们乘以一个参数就是速度、角速度。

            //5-1.先获取 turtle1 相对 turtle2 的坐标信息
            geometry_msgs::TransformStamped tfs = buffer.lookupTransform("turtle2","turtle1",ros::Time(0));

            //5-2.根据坐标信息生成速度信息 -- geometry_msgs/Twist.h
            geometry_msgs::Twist twist;
            twist.linear.x = 0.5 * sqrt(pow(tfs.transform.translation.x,2) + pow(tfs.transform.translation.y,2));
            twist.angular.z = 4 * atan2(tfs.transform.translation.y,tfs.transform.translation.x);

            //5-3.发布速度信息 -- 需要提前创建 publish 对象
            pub.publish(twist);

        因此我们得到了角速度、线速度并发布。

//
// Created by liuhongwei on 23-12-2.
//
/*
    订阅 turtle1 和 turtle2 的 TF 广播信息,查找并转换时间最近的 TF 信息
    将 turtle1 转换成相对 turtle2 的坐标,在计算线速度和角速度并发布

    实现流程:
        1.包含头文件
        2.初始化 ros 节点
        3.创建 ros 句柄
        4.创建 TF 订阅对象
        5.处理订阅到的 TF
        6.spin

*/
//1.包含头文件
#include "ros/ros.h"
#include "tf2_ros/transform_listener.h"
#include "geometry_msgs/TransformStamped.h"
#include "geometry_msgs/Twist.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ros 节点
    ros::init(argc,argv,"sub_TF");
    // 3.创建 ros 句柄
    ros::NodeHandle nh;
    // 4.创建 TF 订阅对象
    tf2_ros::Buffer buffer;
    tf2_ros::TransformListener listener(buffer);
    // 5.处理订阅到的 TF

    // 需要创建发布 /turtle2/cmd_vel 的 publisher 对象

    ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel",1000);

    ros::Rate rate(10);
    while (ros::ok())
    {
        try
        {
            //5-1.先获取 turtle1 相对 turtle2 的坐标信息
            geometry_msgs::TransformStamped tfs = buffer.lookupTransform("turtle2","turtle1",ros::Time(0));

            //5-2.根据坐标信息生成速度信息 -- geometry_msgs/Twist.h
            geometry_msgs::Twist twist;
            twist.linear.x = 0.5 * sqrt(pow(tfs.transform.translation.x,2) + pow(tfs.transform.translation.y,2));
            twist.angular.z = 4 * atan2(tfs.transform.translation.y,tfs.transform.translation.x);

            //5-3.发布速度信息 -- 需要提前创建 publish 对象
            pub.publish(twist);
        }
        catch(const std::exception& e)
        {
            // std::cerr << e.what() << '\n';
            ROS_INFO("错误提示:%s",e.what());
        }



        rate.sleep();
        // 6.spin
        ros::spinOnce();
    }

    return 0;
}

        我们执行launch文件。

        可以的。

3 小结

        坐标变换在机器人系统中是一个极其重要的组成模块,在 ROS 中 TF2 组件是专门用于实现坐标变换的,TF2 实现具体内容又主要介绍了如下几部分:

1.静态坐标变换广播器,可以编码方式或调用内置功能包来实现(建议后者),适用于相对固定的坐标系关系

2.动态坐标变换广播器,以编码的方式广播坐标系之间的相对关系,适用于易变的坐标系关系

3.坐标变换监听器,用于监听广播器广播的坐标系消息,可以实现不同坐标系之间或同一点在不同坐标系之间的变换

4.机器人系统中的坐标系关系是较为复杂的,还可以通过 tf2_tools 工具包来生成 ros 中的坐标系关系图

5.当前 TF2 已经替换了 TF,官网建议直接学习 TF2,并且 TF 与 TF2 的使用流程与实现 API 比较类似,只要有任意一方的使用经验,另一方也可以做到触类旁通。

1.TF2与TF比较_简介
  • TF2已经替换了TF,TF2是TF的超集,建议学习 TF2 而非 TF

  • TF2 功能包的增强了内聚性,TF 与 TF2 所依赖的功能包是不同的,TF 对应的是tf包,TF2 对应的是tf2tf2_ros包,在 TF2 中不同类型的 API 实现做了分包处理。

  • TF2 实现效率更高,比如在:TF2 的静态坐标实现、TF2 坐标变换监听器中的 Buffer 实现等

2.TF2与TF比较_静态坐标变换演示

接下来,我们通过静态坐标变换来演示TF2的实现效率。

2.1启动 TF2 与 TF 两个版本的静态坐标变换

TF2 版静态坐标变换:rosrun tf2_ros static_transform_publisher 0 0 0 0 0 0 /base_link /laser

TF 版静态坐标变换:rosrun tf static_transform_publisher 0 0 0 0 0 0 /base_link /laser 100

会发现,TF 版本的启动中最后多一个参数,该参数是指定发布频率

2.2运行结果比对

使用rostopic查看话题,包含/tf/tf_static, 前者是 TF 发布的话题,后者是 TF2 发布的话题,分别调用命令打印二者的话题消息

rostopic echo /tf: 当前会循环输出坐标系信息

rostopic echo /tf_static: 坐标系信息只有一次

2.3结论

如果是静态坐标转换,那么不同坐标系之间的相对状态是固定的,既然是固定的,那么没有必要重复发布坐标系的转换消息,很显然的,tf2 实现较之于 tf 更为高效


http://www.kler.cn/a/155183.html

相关文章:

  • 如何进行产线高阶能耗数据的计算和可视化?
  • makefile 设置动态库路径参数
  • 【数学二】线性代数-线性方程组-齐次线性方程组、非齐次线性方程组
  • 深度学习中的感受野:从基础概念到多层次特征提取
  • 2024 年 Apifox 和 Postman 对比介绍详细版
  • 06.VSCODE:备战大项目,CMake专项配置
  • Linux /etc/hosts文件
  • 【Spring】AOP实现公共字段填充
  • 19.C++ 中将一维数组转成多维的三种方式
  • CMake语法入门篇
  • 基于51单片机的交通灯_紧急开关+黄灯倒计时+可调时间
  • ASP.NET Core MVC过滤器
  • Spring如何解决循环依赖问题?
  • 【EasyExcel实践】导出多个sheet到多个excel文件,并压缩到一个zip文件
  • SmartSoftHelp8,数据库字段详细文档自动生成工具
  • LeetCode 每日一题 2023/11/27-2023/12/3
  • 【数电笔记】18-卡诺图化简
  • 【C++练级之路】【Lv.1】C++,启动!(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for,nullptr)
  • 锐捷RG-UAC应用网关 前台RCE漏洞复现
  • 说说你对Vue的理解
  • Pytorch中的Net.train()和 Net.eval()函数讲解
  • Java实战案例————ATM
  • 卫星影像数据查询网址(WORLDVIEW1/2/3/4、PLEIADES、SPOT系列、高景、高分1-7、资源系列、吉林一号等)
  • 【Unity动画】为一个动画片段添加事件Events
  • 深度学习——第03章 Python程序设计语言(3.1 Python语言基础)
  • 类和对象(上篇)