天天看點

agx 安裝ros opencv_ROS技術點滴 —— 海龜例程中的tf

agx 安裝ros opencv_ROS技術點滴 —— 海龜例程中的tf

~歡迎關注~

微信公衆号:古月居

新浪微網誌:古月春旭

知乎專欄:古月居

原文連結:ROS技術點滴 —— 海龜例程中的tf

先學知識: ROS技術點滴 —— tf坐标變換庫

代碼例程: https:// github.com/huchunxu/ros _exploring

本篇我們在海龜仿真器中,通過一個例程(turtle_tf)來了解TF的作用,并且熟悉之前學到的TF工具。該例程的功能包turtle_tf可以使用如下指令進行安裝:

$ sudo apt-get install ros-kinetic-turtle-tf
           

安裝完成後,就可以使用如下指令運作例程了:

$ roslaunch turtle_tf turtle_tf_demo.launch
           

海龜仿真器打開後會出現兩隻小海龜,并且

下方的小海龜自動向中心位置的小海龜移動

agx 安裝ros opencv_ROS技術點滴 —— 海龜例程中的tf

打開鍵盤控制節點,控制中心位置的小海龜運作:

$ rosrun turtlesim turtle_teleop_key
           
agx 安裝ros opencv_ROS技術點滴 —— 海龜例程中的tf

另外一隻海龜總是會跟随我們控制的那隻海龜運作。在這個例程中,TF是如何運用的呢?我們首先使用TF工具來看一下這個例程中的TF樹是什麼樣的:

$ rosrun tf view_frames
           
agx 安裝ros opencv_ROS技術點滴 —— 海龜例程中的tf

在目前系統中存在三個坐标系:world、turtle1、turtle2。world是世界坐标系,作為系統的基礎坐标系,其他坐标系都相對該坐标系建立,是以world是TF樹的根節點。相對于world坐标系,又分别針對兩隻海龜建立了兩個海龜坐标系,這兩個坐标系的原點就是海龜在世界坐标系下的坐标位置。

現在要讓turtle2跟随turtle1運動,相當于

turtle2坐标系向turtle1坐标系移動

,這就需要知道turtle2與turtle1之間的坐标變換。三個坐标系之間的變換關系可以使用如下公式描述:

agx 安裝ros opencv_ROS技術點滴 —— 海龜例程中的tf

使用tf_echo工具在TF樹中查找海龜坐标系之間的變換關系:

$ rosrun tf tf_echoturtle1 turtle2
           
agx 安裝ros opencv_ROS技術點滴 —— 海龜例程中的tf

也可以通過rviz的圖形界面更加形象的看到這三者之間的坐标關系:

$ rosrun rviz rviz -d `rospack find turtle_tf`/rviz/turtle_rviz.rviz
           

得到turtle2與turtle1之間的坐标變換後,就可以計算兩隻海龜間的

距離和角度

,即可控制turtle2向turtle1移動了。

接下來,我們以這個例程為目标,學習如何實作TF的廣播和監聽功能。

一、建立TF廣播器

首先,我們需要建立一個釋出海龜坐标系與世界坐标系之間TF變換的節點,實作源碼turtle_tf_broadcaster.cpp的具體内容如下:

#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));
}

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);

ros::spin();

return 0;
};
           

以上代碼的關鍵部分是處理海龜pose消息的回調函數poseCallback。在廣播TF消息之前需要定義tf::TransformBroadcaster廣播器,然後根據海龜目前的位姿,設定tf::Transform類型的坐标變換,包含setOrigin設定的

平移變換

以及setRotation設定的

旋轉變換

然後使用廣播器将坐标變換插入TF樹并且釋出,這裡釋出的TF消息類型是tf::StampedTransform,不僅包含tf::Transform類型的

坐标變換、時間戳

,而且還需要指定坐标變換的

源坐标系(parent)

目标坐标系(child)

二、建立TF監聽器

TF消息廣播之後,其他節點就可以監聽該TF消息,進而擷取需要的坐标變換了。

目前我們已經将海龜相對于world坐标系的TF變換廣播,接下來需要監聽TF消息,并從中擷取turtle2相對于turtle1坐标系的變換,進而控制turtle2移動。實作源碼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(),
transform.getOrigin().x());
vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +
pow(transform.getOrigin().y(), 2));
turtle_vel.publish(vel_msg);

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

該節點首先通過

服務調用

産生海龜turtle2,然後聲明控制turtle2速度的Publisher。在監聽TF消息之前,需要建立一個tf::TransformListener類型的監聽器,建立成功後監聽器會自動接收TF樹的消息,并且緩存10秒。

然後在循環中就可以實時查找TF樹中的坐标變換了,這裡需要調用的是tf::TransformListener中的兩個接口:

waitForTransform(const std::string &target_frame, const std::string&source_frame, const ros::Time&time, const ros::Duration &timeout)
           

給定的源坐标系(source_frame)和目标坐标系(target_frame),等待兩個坐标系之間指定時間(time)的變換關系,該函數會

阻塞

程式運作,是以需要設定逾時時間(timeout)。

lookupTransform(const std::string & target_frame,const std::string &source_frame, const ros::Time & time,StampedTransform & transform)
           

給定的源坐标系(source_frame)和目标坐标系(target_frame),得到兩個坐标系之間指定時間(time)的坐标變換(transform),ros::Time(0) 表示我們想要的是最新一次的坐标變換。

通過以上兩個接口的調用,就可以擷取turtle2相對于turtle1的坐标變換了。然後根據坐标系之間的位置關系,計算得到turtle2需要運動的

線速度和角速度

,并釋出速度控制指令,使turtle2向turtle1移動。

三、實作海龜跟随運動

現在小海龜跟随例程的所有代碼都已經完成,我們來編寫一個launch檔案,将所有節點運作起來,start_demo_with_listener.launch:

<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>
           

運作該launch檔案,就可以看到與之前例程類似的兩隻海龜的界面了,在終端中通過鍵盤控制turtle1移動,turtle2也跟随移動。

通過這個例程的實作,我們學習了TF廣播與監聽的實作方法,在實際應用中會産生更多坐标系,TF樹的結構也會更加複雜,但是基本的使用方法依然相同。

更多内容歡迎關注:

微信公衆号:

古月居

(guyue_home)

新浪微網誌:古月春旭

知乎專欄:古月居

或通路

古月居網站

古月居 - 怕什麼真理無窮,進一寸有一寸的歡喜​www.guyuehome.com