• 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树中任意两个体之间的坐标变换

  • 相关阅读:
    点击按钮生成遮罩层后这个按钮被遮住还可以点击解决办法
    关于jq的load不用回调获取其中dom元素方法
    移动端默认返回按键,使用h5+修改默认事件
    移动端解决input focus后键盘弹出,高度被挤压的问题
    模拟移动端上拉超过页面实际高度
    软工作业
    一周进度汇报
    alhpa阶段回顾
    一周进度汇报
    一周进度汇报
  • 原文地址:https://www.cnblogs.com/victorywr/p/15877389.html
Copyright © 2020-2023  润新知