小猫

版主
  • 主题:39
  • 回复:39
  • 金钱:132
  • 积分:181
在机器人的控制中,坐标系统是非常重要的,在ROS使用tf软件库进行坐标转换。
一、tf简介
        我们通过一个小小的实例来介绍tf的作用。
1、安装turtle
  1. [plain] view plaincopy
  2. [        DISCUZ_CODE_380        ]nbsp;rosdep install turtle_tf rviz  
  3. [        DISCUZ_CODE_380        ]nbsp;rosmake turtle_tf rviz  
复制代码
2、运行demo
        运行简单的demo
  1. [plain] view plaincopy
  2. [        DISCUZ_CODE_381        ]nbsp;roslaunch turtle_tf turtle_tf_demo.launch  
复制代码
然后就会看到两只小乌龟了。

该例程中带有turtlesim仿真,可以在终端激活的情况下进行键盘控制。

  可以发现,第二只乌龟会跟随你移动的乌龟进行移动。
3demo分析
        接下来我们就来看一看到底ROS做了什么事情。
        这个例程使用tf建立了三个坐标帧:a world frame, a turtle1 frame, and a turtle2 frame。然后使用tf broadcaster发布乌龟的坐标帧,并且使用tf listener计算乌龟坐标帧之间的差异,使得第二只乌龟跟随第一只乌龟。
        我们可以使用tf工具来具体研究。

  1. [plain] view plaincopy
  2. [        DISCUZ_CODE_382        ]nbsp;rosrun tf view_frames  
复制代码
然后会看到一些提示,并且生成了一个frames.pdf文件。

      该文件描述了坐标系之间的联系。三个节点分别是三个坐标帧,而/world是其他两个乌龟坐标帧的父坐标帧。还包含一些调试需要的发送频率、最近时间等信息。
       tf还提供了一个tf_echo工具来查看两个广播帧之间的关系。我们可以看一下第二只得乌龟坐标是怎么根据第一只乌龟得出来的。


  1. [plain] view plaincopy
  2. [        DISCUZ_CODE_383        ]nbsp;rosrun tf tf_echo turtle1 turtle2  
复制代码
  控制一只乌龟,在终端中会看到第二只乌龟的坐标转换关系。

我们也可以通过rviz的图形界面更加形象的看到这三者之间的关系。
[plain] view plaincopy
$ rosrun rviz rviz -d `rospack find turtle_tf`/rviz/turtle_rviz.vcg  
   移动乌龟,可以看到在rviz中的坐标会跟随变化。其中左下角的是/world,其他两个是乌龟的坐标帧。
       下面我们就来详细分析这个实例。
二、Writing a tf broadcaster1、创建包
  1. [plain] view plaincopy
  2. [        DISCUZ_CODE_384        ]nbsp;roscd tutorials  
  3. [        DISCUZ_CODE_384        ]nbsp;roscreate-pkg learning_tf tf roscpp rospy turtlesim  
  4. [        DISCUZ_CODE_384        ]nbsp;rosmake learning_tf  
复制代码
2broadcast transforms
        我们首先看一下如何把坐标帧发布到tf
        代码文件:/nodes/turtle_tf_broadcaster.py
  1. [python] view plaincopy
  2. #!/usr/bin/env python   
  3. import roslib  
  4. roslib.load_manifest('learning_tf')  
  5. import rospy  
  6.   
  7.   
  8. import tf  
  9. import turtlesim.msg  
  10.   
  11.   
  12. def handle_turtle_pose(msg, turtlename):  
  13.     br = tf.TransformBroadcaster()  
  14.     br.sendTransform((msg.x, msg.y, 0),  
  15.                      tf.transformations.quaternion_from_euler(0, 0, msg.theta),  
  16.                      rospy.Time.now(),  
  17.                      turtlename,  
  18.                      "world")  #发布乌龟的平移和翻转  
  19.   
  20.   
  21. if __name__ == '__main__':  
  22.     rospy.init_node('turtle_tf_broadcaster')  
  23.     turtlename = rospy.get_param('~turtle')   #获取海龟的名字(turtle1,turtle2)  
  24.     rospy.Subscriber('/%s/pose' % turtlename,  
  25.                      turtlesim.msg.Pose,  
  26.                      handle_turtle_pose,  
  27.                      turtlename)   #订阅 topic "turtleX/pose"  
  28.     rospy.spin()  
复制代码
  创建launch文件start_demo.launch
  1. [plain] view plaincopy
  2. <launch>  
  3.     <!-- Turtlesim Node-->  
  4.     <node pkg="turtlesim" type="turtlesim_node" name="sim"/>  
  5.     <node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen"/>  
  6.   
  7.   
  8.     <node name="turtle1_tf_broadcaster" pkg="learning_tf" type="turtle_tf_broadcaster.py" respawn="false" output="screen" >  
  9.       <param name="turtle" type="string" value="turtle1" />  
  10.     </node>  
  11.     <node name="turtle2_tf_broadcaster" pkg="learning_tf" type="turtle_tf_broadcaster.py" respawn="false" output="screen" >  
  12.       <param name="turtle" type="string" value="turtle2" />   
  13.     </node>  
  14.   
  15.   
  16.   </launch>  
复制代码
运行:
  1. [plain] view plaincopy
  2. [        DISCUZ_CODE_387        ]nbsp;roslaunch learning_tf start_demo.launch  
复制代码
可以看到界面中只有移植乌龟了,打开tf_echo的信息窗口:
  1. [plain] view plaincopy
  2. [        DISCUZ_CODE_388        ]nbsp;rosrun tf tf_echo /world /turtle1
复制代码

world坐标系的原点在最下角,对于turtle1的转换关系,其实就是turtle1world坐标系中所在的坐标位置以及旋转角度。
三、Writing a tf listener
             这一步,我们将看到如何使用tf进行帧转换。首先写一个tf listenernodes/turtle_tf_listener.py):
  1. [python] view plaincopy
  2. #!/usr/bin/env python   
  3. import roslib  
  4. roslib.load_manifest('learning_tf')  
  5. import rospy  
  6. import math  
  7. import tf  
  8. import turtlesim.msg  
  9. import turtlesim.srv  
  10.   
  11. if __name__ == '__main__':  
  12.     rospy.init_node('tf_turtle')  
  13.   
  14.     listener = tf.TransformListener() #TransformListener创建后就开始接受tf广播信息,最多可以缓存10s  
  15.   
  16.     rospy.wait_for_service('spawn')  
  17.     spawner = rospy.ServiceProxy('spawn', turtlesim.srv.Spawn)  
  18.     spawner(4, 2, 0, 'turtle2')  
  19.   
  20.     turtle_vel = rospy.Publisher('turtle2/command_velocity', turtlesim.msg.Velocity)  
  21.   
  22.     rate = rospy.Rate(10.0)  
  23.     while not rospy.is_shutdown():  
  24.         try:  
  25.             (trans,rot) = listener.lookupTransform('/turtle2', '/turtle1', rospy.Time(0))  
  26.         except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):  
  27.             continue  
  28.   
  29.         angular = 4 * math.atan2(trans[1], trans[0])  
  30.         linear = 0.5 * math.sqrt(trans[0] ** 2 + trans[1] ** 2)  
  31.         turtle_vel.publish(turtlesim.msg.Velocity(linear, angular))  
  32.   
  33.         rate.sleep()  
复制代码
launch文件中添加下面的节点:
  1. [plain] view plaincopy
  2. <launch>  
  3.     ...  
  4.     <node pkg="learning_tf" type="turtle_tf_listener.py"   
  5.           name="listener" />  
  6. </launch>  
复制代码
    然后在运行,就可以看到两只turtle了,也就是我们在最开始见到的那种跟随效果。
四、Adding a frame
        在很多应用中,添加一个帧是很有必要的,比如在一个world坐标系下,有很一个激光扫描节点,tf可以帮助我们将激光扫描的信息坐标装换成全局坐标。
1、帧结构
        tf中的坐标帧是一个树状的结构,world坐标系统是最顶端的父坐标帧,其他的坐标帧都需要向下延伸。如果我们在上文的基础上添加一个帧,就需要让这个新的帧成为已有三个帧中的一个的子帧。

2、建立固定帧(fixed frame
        我们以turtle1作为父帧,建立一个新的帧“carrot1”。代码如下(nodes/fixed_tf_broadcaster.py):
  1. [plain] view plaincopy
  2. #!/usr/bin/env python   
  3. import roslib  
  4. roslib.load_manifest('learning_tf')  
  5.   
  6. import rospy  
  7. import tf  
  8.   
  9. if __name__ == '__main__':  
  10.     rospy.init_node('my_tf_broadcaster')  
  11.     br = tf.TransformBroadcaster()  
  12.     rate = rospy.Rate(10.0)  
  13.     while not rospy.is_shutdown():  
  14.         br.sendTransform((0.0, 2.0, 0.0),  
  15.                          (0.0, 0.0, 0.0, 1.0),  
  16.                          rospy.Time.now(),  
  17.                          "carrot1",  
  18.                          "turtle1") #建立一个新的帧,父帧为turtle1,并且距离父帧2米  
  19.         rate.sleep()  
复制代码
launch文件中添加节点:
  1. [plain] view plaincopy
  2. <launch>  
  3.   ...  
  4.   <node pkg="learning_tf" type="fixed_tf_broadcaster.py"  
  5.         name="broadcaster_fixed" />  
  6. </launch>  
复制代码


    运行,还是看到两只乌龟和之前的效果一样。新添加的帧并没有对其他帧产生什么影响。打开nodes/turtle_tf_listener.py文件,将turtle1改成carrot1
  1. [python] view plaincopy
  2. (trans,rot) = self.tf.lookupTransform("/turtle2", "/carrot1", rospy.Time(0))
复制代码

重新运行,现在乌龟之间的跟随关系就改变了:
3、建立移动帧(moving frame
        我们建立的新帧是一个固定的帧,在仿真过程中不会改变,如果我们要把carrot1帧和turtle1帧之间的关系设置可变的,可以修改代码如下:
  1. [python] view plaincopy
  2. #!/usr/bin/env python   
  3. import roslib  
  4. roslib.load_manifest('learning_tf')  
  5.   
  6. import rospy  
  7. import tf  
  8. import math  
  9.   
  10. if __name__ == '__main__':  
  11.     rospy.init_node('my_tf_broadcaster')  
  12.     br = tf.TransformBroadcaster()  
  13.     rate = rospy.Rate(10.0)  
  14.     while not rospy.is_shutdown():  
  15.         t = rospy.Time.now().to_sec() * math.pi  
  16.         br.sendTransform((2.0 * math.sin(t), 2.0 * math.cos(t), 0.0),  
  17.                          (0.0, 0.0, 0.0, 1.0),  
  18.                          rospy.Time.now(),  
  19.                          "carrot1",  
  20.                          "turtle1")  
  21.         rate.sleep()  
复制代码
  这次carrot1的位置现对于turtle1来说是一个三角函数关系了。