ROS学习笔记(十四):Setting up your robot with tf

了解如何让机器人状态发布者为您完成所有的tf发布:在自己的机器人上使用机器人状态发布者

1.Transform Configuration

许多ROS软件包要求使用tf软件库发布机器人的变换树。在抽象级别上,变换树根据不同坐标系之间的平移和旋转来定义偏移量。为了更加具体,请考虑一个简单的机器人示例,该机器人具有一个可移动的基座,并在其上方安装了一个激光器。关于机器人,我们定义两个坐标系:一个对应于机器人基座的中心点,另一个对应于安装在基座顶部的激光器的中心点。让我们也给他们起个名字以便于参考。我们将附着在移动基座上的坐标系称为“ base_link”(对于导航,将其放置在机器人的旋转中心很重要),我们将附着在激光器上的坐标系称为“ base_laser”。有关框架命名约定,请参阅REP 105

在这一点上,让我们假设我们有一些来自激光器的数据,其形式是到激光器中心点的距离。换句话说,我们在“ base_laser”坐标系中有一些数据。现在假设我们要获取这些数据并将其用于帮助移动基地避免世界上的障碍。为了成功做到这一点,我们需要一种将已从“ base_laser”帧接收到的激光扫描转换为“ base_link”帧的方法。本质上,我们需要定义“ base_laser”和“ base_link”坐标系之间的关系。
在这里插入图片描述
在定义这种关系时,假设我们知道激光器被安装在向前10厘米,可移动基座中心点上方20厘米的位置。这为我们提供了一个平移偏移量,该偏移量将“ base_link”框架与“ base_laser”框架相关联。具体来说,我们知道,要从“ base_link”框架获取数据到“ base_laser”框架,我们必须应用(x:0.1m,y:0.0m,z:0.2m)的转换,并从“ “ base_laser”帧到“ base_link”帧,我们必须应用相反的转换(x:-0.1m,y:0.0m,z:-0.20m)。

我们可以选择自己管理这种关系,这意味着在必要时在框架之间存储和应用适当的转换,但是随着坐标框架数量的增加,这变得非常痛苦。幸运的是,我们不必自己做这项工作。相反,我们将使用tf一次定义“ base_link”和“ base_laser”之间的关系,并让它为我们管理两个坐标系之间的转换。

要使用tf定义和存储“ base_link”和“ base_laser”帧之间的关系,我们需要将它们添加到转换树中。从概念上讲,变换树中的每个节点都对应于一个坐标系,每个边缘对应于需要应用以从当前节点移动到其子节点的变换。 Tf使用树结构来确保只有一个遍历将任意两个坐标系链接在一起,并假定树中的所有边都从父节点指向子节点。

在这里插入图片描述

2.Writing Code

希望以上示例有助于从概念上理解tf。 现在,我们必须使用转换树并使用代码创建它。 对于此示例,假定您熟悉ROS,因此请确保在不熟悉任何术语或概念的情况下查看ROS文档。

假设我们有上面描述的高级任务,即在“ base_laser”帧中获取点并将其转换为“ base_link”帧。 我们需要做的第一件事是创建一个节点,该节点将负责在我们的系统中发布转换。 接下来,我们必须创建一个节点来侦听通过ROS发布的转换数据,并将其应用于转换点。 我们将首先创建一个供源代码使用的包,然后给它一个简单的名称,例如“ robot_setup_tf”。我们将依赖于roscpp,tf和geometry_msgs。

$ cd %TOP_DIR_YOUR_CATKIN_WS%/src
$ catkin_create_pkg robot_setup_tf roscpp tf geometry_msgs

请注意,您必须在具有权限的位置上方运行命令(例如,可能为先前教程创建的位置〜/ ros)。

在fuerte,groovy和Hydro中的替代方案:navigation_tutorials堆栈中有一个标准的robot_setup_tf_tutorial包。 您可能需要按照以下说明进行安装(%YOUR_ROS_DISTRO%可以是{fuerte,groovy}等):

$ sudo apt-get install ros-%YOUR_ROS_DISTRO%-navigation-tutorials 

3.Broadcasting a Transform

现在我们有了包,我们需要创建一个节点来完成通过ROS广播base_laser→base_link转换的工作。 在您刚创建的robot_setup_tf程序包中,启动您喜欢的编辑器,并将以下代码粘贴到src / tf_broadcaster.cpp文件中。

扫描二维码关注公众号,回复: 11529275 查看本文章
   1 #include <ros/ros.h>
   2 #include <tf/transform_broadcaster.h>
   3 
   4 int main(int argc, char** argv){
   5   ros::init(argc, argv, "robot_tf_publisher");
   6   ros::NodeHandle n;
   7 
   8   ros::Rate r(100);
   9 
  10   tf::TransformBroadcaster broadcaster;
  11 
  12   while(n.ok()){
  13     broadcaster.sendTransform(
  14       tf::StampedTransform(
  15         tf::Transform(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.1, 0.0, 0.2)),
  16         ros::Time::now(),"base_link", "base_laser"));
  17     r.sleep();
  18   }
  19 }

现在,让我们看一下与更详细地发布base_link→base_laser转换有关的代码。

 #include <tf/transform_broadcaster.h>

tf包提供了tf :: TransformBroadcaster的实现,以帮助简化发布转换的任务。 要使用TransformBroadcaster,我们需要包含tf / transform_broadcaster.h头文件。

 tf::TransformBroadcaster broadcaster;

在这里,我们创建一个TransformBroadcaster对象,稍后将使用它通过网络发送base_link→base_laser转换。

 broadcaster.sendTransform(
  tf::StampedTransform(
    tf::Transform(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.1, 0.0, 0.2)),
    ros::Time::now(),"base_link", "base_laser"));

这是真正的工作完成的地方。 使用TransformBroadcaster发送转换需要五个参数。 首先,我们传递旋转变换,旋转变换由btQuaternion指定,用于在两个坐标系之间进行任何旋转。 在这种情况下,我们不希望应用任何旋转,因此我们将发送一个由等于零的俯仰,侧倾和偏航值构造的btQuaternion。 其次,为我们要应用的任何翻译提供一个btVector3。 但是,我们确实要应用平移,因此我们创建了一个btVector3,它对应于激光距机器人基座的10厘米的x偏移和20厘米的z偏移。 第三,我们需要给转换发布一个时间戳,我们只需要用ros :: Time :: now()标记它。 第四,我们需要传递正在创建的链接的父节点的名称,在本例中为“ base_link”。 第五,我们需要传递正在创建的链接的子节点的名称,在本例中为“ base_laser”。

4.Using a Transform

上面,我们创建了一个节点,该节点通过ROS发布base_laser→base_link转换。 现在,我们将编写一个节点,该节点将使用该转换在“ base_laser”框架中获取一个点,并将其转换为在“ base_link”框架中的一个点。 再一次,我们将从将下面的代码粘贴到文件中开始,然后进行更详细的说明。 在robot_setup_tf软件包中,创建一个名为src / tf_listener.cpp的文件并粘贴以下内容:

   1 #include <ros/ros.h>
   2 #include <geometry_msgs/PointStamped.h>
   3 #include <tf/transform_listener.h>
   4 
   5 void transformPoint(const tf::TransformListener& listener){
   6   //we'll create a point in the base_laser frame that we'd like to transform to the base_link frame
   7   geometry_msgs::PointStamped laser_point;
   8   laser_point.header.frame_id = "base_laser";
   9 
  10   //we'll just use the most recent transform available for our simple example
  11   laser_point.header.stamp = ros::Time();
  12 
  13   //just an arbitrary point in space
  14   laser_point.point.x = 1.0;
  15   laser_point.point.y = 0.2;
  16   laser_point.point.z = 0.0;
  17 
  18   try{
  19     geometry_msgs::PointStamped base_point;
  20     listener.transformPoint("base_link", laser_point, base_point);
  21 
  22     ROS_INFO("base_laser: (%.2f, %.2f. %.2f) -----> base_link: (%.2f, %.2f, %.2f) at time %.2f",
  23         laser_point.point.x, laser_point.point.y, laser_point.point.z,
  24         base_point.point.x, base_point.point.y, base_point.point.z, base_point.header.stamp.toSec());
  25   }
  26   catch(tf::TransformException& ex){
  27     ROS_ERROR("Received an exception trying to transform a point from \"base_laser\" to \"base_link\": %s", ex.what());
  28   }
  29 }
  30 
  31 int main(int argc, char** argv){
  32   ros::init(argc, argv, "robot_tf_listener");
  33   ros::NodeHandle n;
  34 
  35   tf::TransformListener listener(ros::Duration(10));
  36 
  37   //we'll transform a point once every second
  38   ros::Timer timer = n.createTimer(ros::Duration(1.0), boost::bind(&transformPoint, boost::ref(listener)));
  39 
  40   ros::spin();
  41 
  42 }

代码解释:

#include <tf/transform_listener.h>

在这里,我们包含创建tf :: TransformListener所需的tf / transform_listener.h头文件。 一个TransformListener对象自动通过ROS订阅转换消息主题,并管理通过网络传入的所有转换数据。

void transformPoint(const tf::TransformListener& listener){

我们将创建一个函数,在给定TransformListener的情况下,该函数在“ base_laser”框架中获取一个点并将其转换为“ base_link”框架。 此函数将用作在程序的main()中创建的ros :: Timer的回调,并将每秒触发一次。

 //we'll create a point in the base_laser frame that we'd like to transform to the base_link frame
  geometry_msgs::PointStamped laser_point;
  laser_point.header.frame_id = "base_laser";
//we'll just use the most recent transform available for our simple example
  laser_point.header.stamp = ros::Time();
 
  //just an arbitrary point in space
   laser_point.point.x = 1.0;
   laser_point.point.y = 0.2;
   laser_point.point.z = 0.0;

在这里,我们将点创建为geometry_msgs :: PointStamped。 消息名称末尾的“已标记”仅表示它包含一个标头,使我们可以将时间戳和frame_id与消息相关联。 我们将laser_point消息的标记字段设置为ros :: Time(),这是一个特殊的时间值,允许我们向TransformListener询问最新的可用转换。 至于标题的frame_id字段,我们将其设置为“ base_laser”,因为我们正在“ base_laser”框架中创建一个点。 最后,我们将为该点设置一些数据。…选取x:1.0,y:0.2和z:0.0的值。

  try{
     geometry_msgs::PointStamped base_point;
     listener.transformPoint("base_link", laser_point, base_point);
 
     ROS_INFO("base_laser: (%.2f, %.2f. %.2f) -----> base_link: (%.2f, %.2f, %.2f) at time %.2f",
         laser_point.point.x, laser_point.point.y, laser_point.point.z,
         base_point.point.x, base_point.point.y, base_point.point.z, base_point.header.stamp.toSec());
 }

现在我们在“ base_laser”框架中有了该点,我们希望将其转换为“ base_link”框架。 为此,我们将使用TransformListener对象,并使用三个参数调用transformPoint():将点转换为的帧的名称(在本例中为“ base_link”),要转换的点以及 存储转换点。 因此,在对transformPoint()进行调用之后,base_point与“ laser_point”保持的信息相同,直到现在才在“ base_link”帧中。

 catch(tf::TransformException& ex){
    ROS_ERROR("Received an exception trying to transform a point from \"base_laser\" to \"base_link\": %s", ex.what());
   }

如果由于某种原因base_laser→base_link转换不可用(也许tf_broadcaster未运行),则当我们调用transformPoint()时,TransformListener可能会引发异常。 为了确保我们能够正常处理,我们将捕获异常并为用户打印错误。

5.Building the Code

现在我们已经编写了一个小示例,我们需要构建它。 打开由roscreate-pkg自动生成的CMakeLists.txt文件,并在文件底部添加以下几行。

add_executable(tf_broadcaster src/tf_broadcaster.cpp)
add_executable(tf_listener src/tf_listener.cpp)
target_link_libraries(tf_broadcaster ${catkin_LIBRARIES})
target_link_libraries(tf_listener ${catkin_LIBRARIES})

接下来,确保保存文件并生成程序包。

$ cd %TOP_DIR_YOUR_CATKIN_WS%
$ catkin_make

6.Running the Code

好的…我们都建成了。 让我们尝试一下,看看ROS上到底发生了什么。 对于此部分,您需要打开三个终端。

在第一个终端中,运行一个核心。

roscore

在第二个终端中,我们将运行tf_broadcaster。

rosrun robot_setup_tf tf_broadcaster

现在,我们将使用第三个终端来运行tf_listener,以将模拟点从“ base_laser”框架转换为“ base_link”框架。

rosrun robot_setup_tf tf_listener

如果一切顺利,您应该看到以下输出,该输出每秒显示一次将点从“ base_laser”帧转换为“ base_link”帧。

[ INFO] [1593485056.509939923]: base_laser: (1.00, 0.20, 0.00) ----->base_link:(1.10, 0.20, 0.20) at time 1593485056.50
[ INFO] [1593485057.509723041]: base_laser: (1.00, 0.20, 0.00) ----->base_link:(1.10, 0.20, 0.20) at time 1593485057.50
[ INFO] [1593485058.509675262]: base_laser: (1.00, 0.20, 0.00) ----->base_link:(1.10, 0.20, 0.20) at time 1593485058.50

猜你喜欢

转载自blog.csdn.net/qq_42910179/article/details/107005259