ROS的TF坐标变换

TF 坐标变换
坐标转换是一个坐标在不同坐标系下的表示,而坐标系转换是不同坐标系的相对位姿关系
ROS中机器人模型包含大量的部件,每一个部件统称之为link(比如手部、头部、某个关节、某个连杆),每一个link上面对应着一个frame(坐标系),
用frame表示该部件的坐标系,frame和link是绑定在一起的。
 
TF是一个通俗的名称,实际上它有很多含义
1、可以被当做是一种标准规范,这套标准定义了坐标转换的数据格式和数据结构.tf本质是树状的数据结构,即"tf tree"。
2、tf也可以看成是一个话题 /tf,话题中的消息保存的就是tf tree的数据结构格式。维护了整个机器人的甚至是地图的坐标转换关系。维持并更新机器人整个坐标系的话题是/tf,
/tf话题表示的内容是整个机器人的tf树,而非仅仅是某两个坐标系的转换关系,这样的话,/tf话题是需要很多的节点来维护的,每一个节点维护两个frame之间的关系。
3、tf还可以看成是一个package,它当中包含了很多的工具.比如可视化,查看关节间的tf,debug tf等等
4、tf含有一部分的API接口,用来节点程序中的编程。TF对发布器与订阅器进行了封装,使开发者通过TF的接口更加简单地建立对TF树中某些坐标系转换关系的维护与订阅
 
tf是一个树状结构,维护坐标系之间的关系,靠话题通信机制来持续地发布不同link之间的坐标关系。
作为树状结构,要保证父子坐标系都有某个节点在持续地发布他们之间的位姿关系,才能使树状结构保持完整。
只有父子坐标系的位姿关系能被正确的发布,才能保证任意两个frame之间的连通。
 
如果出现某一环节的断裂,就会引发error系统报错.所以完整的tf tree不能有任何断层的地方,这样我们才能查清楚任意两个frame之间的关系。
每两个相邻frame之间靠节点发布它们之间的位姿关系,这种节点称为broadcaster。
broadcaster就是一个发布器publisher,如果两个frame之间发生了相对运动,broadcaster就会发布相关消息。
 
 
使用TF主要两个部分
1、监听TF变换
接受并缓存系统中发布的所有坐标变换数据,并从中查询所需要的坐标变换关系
 
2、广播TF变换
向系统中广播坐标系之间的坐标变换关系,系统中可能会存在多个不同部分的TF变换广播,每个广播可以直接将坐标变换关系插入TF树中,不需要再进行同步
 
工具:
tf_monitor :打印TF树中所有坐标系的发布状态,也可以通过输入参数来查看指定坐标系之间的发布状态
rosrun tf tf_monitor #显示当前坐标变换树的信息,主要是名称和实时的时间延时

 

tf_echo :查看指定坐标系之间的变换关系
rosrun tf tf_echo source_frame target_frame
 
view_frames : 可视化调试工具,生成PDF文件,显示整个TF树信息
rosrun tf view_frames
这个工具首先订阅/tf,订阅5秒钟,根据这段时间接受到的tf信息,绘制成一张tf tree,然后创建成一个pdf图。
将会以图形的形式显示出TF树中所有的frame和两个frame 的父子关系及其Broadcaster、Average rate等
 
基于TF的乌龟运动跟随
roslaunch turtle_tf turtle_tf_demo.launch
运行后可以看到两只小乌龟,其中一直在朝着另一只移动
 

 

打开键盘控制节点,控制原本静止的乌龟进行移动
rosrun turtlesim turtle_teleop_key

 

查看此时的TF树
rosrun tf view_frames

 

可以看到当前系统存在三个坐标系world 、turtle1 、turtle2
world是世界坐标系,作为系统的基础坐标系,其他坐标系都相对于该坐标系建立。所以world坐标系是TF树的根节点,相对于world坐标系,两只乌龟分别建立了自己的坐标系,
这两个坐标系的原点就是乌龟在世界坐标系下的坐标位置

 

要让turtle2跟随turtle1移动,等价于让坐标系turtle2跟随 坐标系turtle1移动,这就需要知道turtle2于turtle1之间的坐标变换。
 
三者的坐标变换关系可用如下公式表示
Tturtle1_turtle2 = Tturtle1_world X Tworld_turtle2
使用tf_echo可以查看当前状态下两乌龟坐标系之间的变换关系

 

也可以通过rviz 图形界面更加形象看到这三者之间的坐标关系
rosrun rviz rviz -d rospack find turtle_tf /rviz/turtle_rviz.rviz

 

能够随时知道turtle2与turtle1之间的坐标变换后就能计算两乌龟之间的距离和角度,就能控制turtle2向turtle1移动
 
1、首先创建一个发布乌龟坐标系与世界坐标系之间TF变换的节点,实现源码code_learning/learning_tf/src/turtle_tf_broadcaster.cpp
transformBroadcaster()类就是一个publisher,而sendTransform的作用是来封装publish的函数。
在实际的使用中,我们需要在某个Node中构建tf::TransformBroadcaster类,然后调用sendTransform(),将transform发布到/tf的一段transform上。
#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include <turtlesim/Pose.h>

std::string turtle_name;

void poseCallback(const turtlesim::PoseConstPtr& msg)
{
    // tf广播器
    static tf::TransformBroadcaster br;

    // 根据乌龟当前的位姿,设置相对于世界坐标系的坐标变换
    tf::Transform transform;
    transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );    //设置平移变换
    tf::Quaternion q;
    q.setRPY(0, 0, msg->theta);
    transform.setRotation(q);                 //设置旋转变换

    // 发布坐标变换
    br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));   //利用广播将坐标变换插入tf树并进行发布
}

int main(int argc, char** argv)
{
    // 初始化节点
    ros::init(argc, argv, "my_tf_broadcaster");
    if (argc != 2)
    {
        ROS_ERROR("need turtle name as argument"); 
        return -1;
    };
    turtle_name = argv[1];

    // 订阅乌龟的pose信息
    ros::NodeHandle node;
    ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);     //处理乌龟pose消息的回调函数 poseCallback

    ros::spin();

    return 0;
};

 

2、创建TF监听器
TF消息广播后,其节点就能监听到该消息,目前已经将乌龟于world的TF变换进行了广播,接下来就需要监听TF消息,从中获取turtle1与turtle2之间的坐标系变换,从而控制turtle2移动
code_learning/learning_tf/src/turtle_tf_listener.cpp
#include <ros/ros.h>
#include <tf/transform_listener.h>
#include <geometry_msgs/Twist.h>
#include <turtlesim/Spawn.h>

int main(int argc, char** argv)
{
    // 初始化节点
    ros::init(argc, argv, "my_tf_listener");

    ros::NodeHandle node;

    // 通过服务调用,产生第二只乌龟turtle2
    ros::service::waitForService("spawn");
    ros::ServiceClient add_turtle =
    node.serviceClient<turtlesim::Spawn>("spawn");
    turtlesim::Spawn srv;
    add_turtle.call(srv);

    // 定义turtle2的速度控制发布器
    ros::Publisher turtle_vel =
    node.advertise<geometry_msgs::Twist>("turtle2/cmd_vel", 10);

    // tf监听器
    tf::TransformListener listener;

    ros::Rate rate(10.0);
    while (node.ok())
    {
        tf::StampedTransform transform;
        try
        {
            // 查找turtle2与turtle1的坐标变换
            listener.waitForTransform("/turtle2", "/turtle1", ros::Time(0), ros::Duration(3.0));
            listener.lookupTransform("/turtle2", "/turtle1", ros::Time(0), transform);
        }
        catch (tf::TransformException &ex) 
        {
            ROS_ERROR("%s",ex.what());
            ros::Duration(1.0).sleep();
            continue;
        }

        // 根据turtle1和turtle2之间的坐标变换,计算turtle2需要运动的线速度和角速度
        // 并发布速度控制指令,使turtle2向turtle1移动
        geometry_msgs::Twist vel_msg;
        vel_msg.angular.z = 4.0 * atan2(transform.getOrigin().y(),     //设置turtle2速度控制msg的角速度
                                        transform.getOrigin().x());
        vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +     //设置turtle2速度控制msg的线速度
                                      pow(transform.getOrigin().y(), 2));
        turtle_vel.publish(vel_msg);

        rate.sleep();
    }
    return 0;
};

 

首先通过服务调用产生乌龟turtle2,然后申明控制turtle2速度的publisher,在监听TF消息前,需要先创建一个 tf::TransformListener 监听器,创建成功后监听器会自动接受TF树消息,并且缓存10秒
然后在循环中就可以实时查找TF树中的坐标变换了
两个重要接口:
waitForTransform 给定源坐标系和目标坐标系,等待两个坐标系指定时间(time)的变换关系,该函数会阻塞程序运行,所以需要设置超时时间(timeout)
lookupTransform 给定源坐标系和目标坐标系,得到两个坐标系指定时间(time)的变换(tramsform),ros::Time(0)代表我们需要的是最新一次的坐标变换
 
launch文件 将所有节点配置运行起来,包括小海龟节点,键盘控制节点,两个小海龟的广播节点(分别广播自己与world坐标系之间的坐标变换),监听节点(通过服务调用第二个海龟,并控制该海龟的运动行为)
 <launch>
    <!-- 海龟仿真器 -->
    <node pkg="turtlesim" type="turtlesim_node" name="sim"/>

    <!-- 键盘控制 -->
    <node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen"/>

    <!-- 两只海龟的tf广播 -->
    <node pkg="learning_tf" type="turtle_tf_broadcaster"
          args="/turtle1" name="turtle1_tf_broadcaster" />
    <node pkg="learning_tf" type="turtle_tf_broadcaster"
          args="/turtle2" name="turtle2_tf_broadcaster" />

    <!-- 监听tf广播,并且控制turtle2移动 -->
    <node pkg="learning_tf" type="turtle_tf_listener"
          name="listener" />

 </launch>

 

个人理解:TF提供给用户一个平台,只需要每个个体的坐标系在整个TF树中能连贯起来,那么就能实现TF树中任意两个体之间的坐标变换

 

posted @ 2022-02-09 23:36  victorywr  阅读(411)  评论(0编辑  收藏  举报