racecar仿真竞赛经验总结(五)- TF树

racecar仿真竞赛经验总结(五)- TF树

前文链接:

racecar仿真竞赛经验总结(一) - 前言

racecar仿真竞赛经验总结(二)- racecar仿真模型介绍

racecar仿真竞赛经验总结(三)- 激光SLAM建图

racecar仿真竞赛经验总结(四)- AMCL与小车定位

关于机器人TF变换的原理和解析网上有许多优质博文,后面会附上相应链接,这里博主就不再多做赘述了,这篇博文的内容主要围绕racecar竞速小车中涉及的一些比较重要的内容和踩过的一些坑

博文推荐

这里仅列出一些博主学习过程中参考过的博文,大体上能把东西讲明白,建议学习时先看ROS Wiki上的讲解,在实际过程中遇到问题时针对问题查询资料

官方教程

ROS Wiki上的tf官方教程:

http://wiki.ros.org/tf/Tutorials

http://wiki.ros.org/tf

相关概念

ROS坐标系统:蓝鲸ROS机器人论坛

ROS坐标转换讲解:https://www.jianshu.com/p/935e0e954026

实用讲解

关于tf的监听和发布:https://blog.csdn.net/Start_From_Scratch/article/details/50762293

ubuntu小乌龟实例分析:https://blog.csdn.net/hcx25909/article/details/9255001

古月居-设置机器人tf:https://www.guyuehome.com/355

tf工具

利用一些tf工具能够获知一些坐标系的关系情况
1、view_frames能够监听当前时刻所有通过ROS广播的tf坐标系,并绘制出树状图表示坐标系之间的连接关系保存到离线文件中:

rosrun tf view_frames

2、rqt_tf_tree工具
虽然view_frames能够将当前坐标系关系保存在离线文件中,但是无法实时反映坐标关系,所以可以用rqt_tf_tree实时刷新显示坐标系关系:

rosrun rqt_tf_tree rqt_tf_tree

直接在终端中显示不同坐标系之间的关系:

rosrun tf tf_echo [reference_frame] [target_frame]

3、tf_echo工具
使用tf_echo工具可以查看两个广播参考系之间的关系。

rosrun tf tf_echo turtle1 turtle2

racecar中的tf

竞速小车中的tf简单阐述

​ ROS中的很多软件包都需要机器人发布tf变换树,那么什么是tf变换树呢?抽象的来讲,一棵tf变换树定义了不同坐标系之间的平移与旋转变换关系。具体来说,我们假设有一个机器人,包括一个机器人移动平台和一个安装在平台之上的激光雷达,以这个机器人为例,定义两个坐标系,一个坐标系以机器人移动平台的中心为原点,称为base_link参考系,另一个坐标系以激光雷达的中心为原点,称为base_laser参考系

image-20200804113141687

​ 单拿激光雷达和小车模型之间的tf变换来说:假设在机器人运行过程中,激光雷达可以采集到距离前方障碍物的数据,这些数据当然是以激光雷达为原点的测量值,换句话说,也就是base_laser参考系下的测量值。现在,如果我们想使用这些数据帮助机器人完成避障功能,当然,由于激光雷达在机器人之上,直接使用这些数据不会产生太大的问题,但是激光雷达并不在机器人的中心之上,在极度要求较高的系统中,会始终存在一个雷达与机器人中心的偏差值。这个时候,如果我们采用一种坐标变换,将及激光数据从base_laser参考系变换到base_link参考下,问题不就解决了么。这里我们就需要定义这两个坐标系之间的变换关系。

​ 当然上述假设只列出了激光雷达和小车之间的坐标系差异,如果是一个复杂的系统呢?比如racecar小车中的四个车轮、电池模块以及车上搭载的摄像头和imu等外设都需要一个相应的坐标变换(如下图所示),那这个工程就比较繁琐了,ROS中的tf包就是为这类问题准备的。

小车中涉及的tf变换简要分析

​ 首先我们使用rqt_tf_tree查看整个racecar的tf树结构,结果如下:

image-20200803195009706

​ 可以直观的发现小车的tf树总架构并不是常见的map->odom->base_link的结构,而是map->odom_combined->base_footprint->base_link。其中odom环节被我们替换为了odom_combined,也就是ekf融合后的里程计坐标系。而odom->base_link之间又添加了一层base_footprint,其实base_footprint和base_link大体上可以认为是差不多的,只不过base_footprint可以认为是base_link消除z轴后小车投影的坐标系,其总架构还是和常见的小车tf树结构相似。常见的小车tf树结构应该如下图所示:

image-20200804114350145


  • base_link和base_footprint的区别

    • base_link

      与机器人中心重合,坐标系原点一般为机器人的旋转中心

    • base_footprint

      原点为base_link原点在地面的投影,有些许区别(z值不同)


​ 首先我们来看base_footprint及其底下的tf树结构,这一部分内容是官方封装好发给我们的材料,其中已经搭建好了小车机身各部分之间的tf变换且不允许更改,这一部分内容可以通过使用racecar_rviz.launch文件打开小车的rviz模型并观察其中的tf变换

roslaunch racecar_gazebo racecar_rviz.launch

​ 留给我们的主要工作也是最重要的工作是完成map->odom_combined->base_footprint之间的tf变换,其中map->odom_combined之间的tf变换通过amcl发布,odom_combined->base_footprint之间的tf变换通过robot_pose_ekf发布

  • map->odom_combined

    amcl原函数里已经编写了了tf发布的相关代码,通过设置amcl.launch文件中的接口可以自动完成tf的变换

    image-20200804152621688

    到这里amcl的tf树看上去已经没有问题了,但是!!!仅发布这样的tf变换只能显示一下机器人的位姿(in rviz),我们想要实现的利用amcl矫正定位的功能其实并未用上,博主也是在这个地方踩了坑,我们先来看看amcl到底是怎么实现定位矫正功能的

    image-20200804154014219

    通过原理图不难发现amcl是矫正/map_frame->/base_frame之间的偏差,如果不主动监听的话是没办法实现其矫正功能的,结果就是小车始终带着一个偏移量在跑,还傻乎乎的以为自己的位置是正确的(惨状可以参考上一篇博客)

    实现方法也很简单,主动监听后再发布就可以了:

    #include <nav_msgs/Odometry.h>
    #include <ros/ros.h>
    #include <std_msgs/String.h>
    #include <tf/transform_listener.h>
    
    int main(int argc, char** argv)
    {
      ros::init(argc, argv, "tf_to_odom_converter");
      std::string source_frame_id, target_frame_id, odom_frame_id;
    
      ros::NodeHandle node;
      ros::NodeHandle priv_node("~");
      // priv_node.param<std::string>("odom_frame", odom_frame_id, "odom");
      // priv_node.param<std::string>("source_frame", source_frame_id, "/base_footprint");
      // priv_node.param<std::string>("target_frame", target_frame_id, "/odom_combined");
    
      ros::Publisher odom_pub = node.advertise<nav_msgs::Odometry>("odom111", 1);
    
      tf::TransformListener listener;
    
      ros::Rate rate(25.0);
      while (node.ok())
      {
        tf::StampedTransform transform;
        try
        {
          listener.waitForTransform("map", "base_footprint", ros::Time(0), ros::Duration(20.0));
          // ros::Time::now()
          listener.lookupTransform("map", "base_footprint", ros::Time(0), transform);
        }
        catch (tf::TransformException ex)
        {
          ROS_ERROR("%s", ex.what());
        }
    
        nav_msgs::Odometry odom;
        // copy pose to odom msg
        odom.header.stamp = transform.stamp_;
        odom.header.frame_id = "map";
        odom.child_frame_id = "base_footprint";
        geometry_msgs::TransformStamped ts_msg;
        tf::transformStampedTFToMsg(transform, ts_msg);
        odom.pose.pose.position.x = ts_msg.transform.translation.x;
        odom.pose.pose.position.y = ts_msg.transform.translation.y;
        odom.pose.pose.position.z = ts_msg.transform.translation.z;
        odom.pose.pose.orientation = ts_msg.transform.rotation;
    
        odom_pub.publish(odom);
        rate.sleep();
      }
    
      return 0;
    }

    具体文件我放在了odom_tf_converter/src/tf_to_odom.cpp下面了

  • odom_combined->base_footprint

    同理robot_pose_ekf的原函数里已经编写了了tf发布的相关代码,通过设置odom_combined.launch文件中的接口可以自动完成tf的变换

    image-20200804152949504

    注意一定要将 rf2o 中的 tf 发布相关代码注释掉,否则构建出的 tf 树将出现问题,具体方法可以参考之前的博客内容

完成这两步内容后我们小车的tf树就没有问题了,接下来就是导航相关内容啦


racecar仿真竞赛经验总结(五)- TF树
http://example.com/2020/08/04/racecar仿真竞赛经验总结(五)- TF树/
Author
Zachary Deng
Posted on
August 4, 2020
Licensed under