ROS探索总结(十二)—— 坐标系统

29717
18
2016年2月19日 22时58分

        在机器人的控制中,坐标系统是非常重要的,在ROS使用tf软件库进行坐标转换。

        相关链接:http://www.ros.org/wiki/tf/Tutorials#Learning_tf

一、tf简介

        我们通过一个小小的实例来介绍tf的作用。

1、安装turtle包

 

$ rosdep install turtle_tf rviz  
$ rosmake turtle_tf rviz

 

2、运行demo

        运行简单的demo:
        $ roslaunch turtle_tf turtle_tf_demo.launch 
        然后就会看到两只小乌龟了。

 

ROS探索总结(十二)—— 坐标系统插图
        该例程中带有turtlesim仿真,可以在终端激活的情况下进行键盘控制。
ROS探索总结(十二)—— 坐标系统插图(1)

        可以发现,第二只乌龟会跟随你移动的乌龟进行移动。

3、demo分析

        接下来我们就来看一看到底ROS做了什么事情。
这个例程使用tf建立了三个参考系:a world frame, a turtle1 frame, and a turtle2 frame。然后使用tf broadcaster发布乌龟的参考系,并且使用tf listener计算乌龟参考系之间的差异,使得第二只乌龟跟随第一只乌龟。
我们可以使用tf工具来具体研究。
       $ rosrun tf view_frames 
        然后会看到一些提示,并且生成了一个frames.pdf文件。

 

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

 

          $ rosrun tf tf_echo turtle1 turtle2 
        控制一只乌龟,在终端中会看到第二只乌龟的坐标转换关系。

 

ROS探索总结(十二)—— 坐标系统插图(4)
        我们也可以通过rviz的图形界面更加形象的看到这三者之间的关系。
        $ rosrun rviz rviz -d `rospack find turtle_tf`/rviz/turtle_rviz.vcg 
 
ROS探索总结(十二)—— 坐标系统插图(5)
        移动乌龟,可以看到在rviz中的坐标会跟随变化。其中左下角的是/world,其他两个是乌龟的参考系。
下面我们就来详细分析这个实例。

二、Writing a tf broadcaster

1、创建包

$ roscd tutorials  
$ roscreate-pkg learning_tf tf roscpp rospy turtlesim  
$ rosmake learning_tf

2、broadcast transforms

        我们首先看一下如何把参考系发布到tf。
代码文件:/nodes/turtle_tf_broadcaster.py
#!/usr/bin/env python    
import roslib  
roslib.load_manifest('learning_tf')  
import rospy  
  
  
import tf  
import turtlesim.msg  
  
  
def handle_turtle_pose(msg, turtlename):  
    br = tf.TransformBroadcaster()  
    br.sendTransform((msg.x, msg.y, 0),  
                     tf.transformations.quaternion_from_euler(0, 0, msg.theta),  
                     rospy.Time.now(),  
                     turtlename,  
                     "world")  #发布乌龟的平移和翻转  
  
  
if __name__ == '__main__':  
    rospy.init_node('turtle_tf_broadcaster')  
    turtlename = rospy.get_param('~turtle')   #获取海龟的名字(turtle1,turtle2)  
    rospy.Subscriber('/%s/pose' % turtlename,  
                     turtlesim.msg.Pose,  
                     handle_turtle_pose,  
                     turtlename)   #订阅 topic "turtleX/pose"  
    rospy.spin()

 

        创建launch文件start_demo.launch: 
<launch>  
    <!-- Turtlesim Node-->  
    <node pkg="turtlesim" type="turtlesim_node" name="sim"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen"/>  
  
  
    <node name="turtle1_tf_broadcaster" pkg="learning_tf" type="turtle_tf_broadcaster.py" respawn="false" output="screen" >  
      <param name="turtle" type="string" value="turtle1" />  
    </node>  
    <node name="turtle2_tf_broadcaster" pkg="learning_tf" type="turtle_tf_broadcaster.py" respawn="false" output="screen" >  
      <param name="turtle" type="string" value="turtle2" />   
    </node>  
  
  
  </launch>
        运行:
       $ roslaunch learning_tf start_demo.launch 
        可以看到界面中只有移植乌龟了,打开tf_echo的信息窗口:
       $ rosrun tf tf_echo /world /turtle1  

 

ROS探索总结(十二)—— 坐标系统插图(6)
        world参考系的原点在最下角,对于turtle1的转换关系,其实就是turtle1在world参考系中所在的坐标位置以及旋转角度。

三、Writing a tf listener

        这一步,我们将看到如何使用tf进行参考系转换。首先写一个tf listener(nodes/turtle_tf_listener.py):

 

#!/usr/bin/env python    
import roslib  
roslib.load_manifest('learning_tf')  
import rospy  
import math  
import tf  
import turtlesim.msg  
import turtlesim.srv  
  
if __name__ == '__main__':  
    rospy.init_node('tf_turtle')  
  
    listener = tf.TransformListener() #TransformListener创建后就开始接受tf广播信息,最多可以缓存10s  
  
    rospy.wait_for_service('spawn')  
    spawner = rospy.ServiceProxy('spawn', turtlesim.srv.Spawn)  
    spawner(4, 2, 0, 'turtle2')  
  
    turtle_vel = rospy.Publisher('turtle2/command_velocity', turtlesim.msg.Velocity)  
  
    rate = rospy.Rate(10.0)  
    while not rospy.is_shutdown():  
        try:  
            (trans,rot) = listener.lookupTransform('/turtle2', '/turtle1', rospy.Time(0))  
        except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):  
            continue  
  
        angular = 4 * math.atan2(trans[1], trans[0])  
        linear = 0.5 * math.sqrt(trans[0] ** 2 + trans[1] ** 2)  
        turtle_vel.publish(turtlesim.msg.Velocity(linear, angular))  
  
        rate.sleep()

 

 

        在launch文件中添加下面的节点:
<launch>  
    ...  
    <node pkg="learning_tf" type="turtle_tf_listener.py"   
          name="listener" />  
</launch>

        然后在运行,就可以看到两只turtle了,也就是我们在最开始见到的那种跟随效果。

四、Adding a frame

        在很多应用中,添加一个参考系是很有必要的,比如在一个world参考系下,有很一个激光扫描节点,tf可以帮助我们将激光扫描的信息坐标装换成全局坐标。

1、tf消息结构

        tf中的信息是一个树状的结构,world参考系是最顶端的父参考系,其他的参考系都需要向下延伸。如果我们在上文的基础上添加一个参考系,就需要让这个新的参考系成为已有三个参考系中的一个的子参考系。
ROS探索总结(十二)—— 坐标系统插图(7)

2、建立固定参考系(fixed frame)

        我们以turtle1作为父参考系,建立一个新的参考系“carrot1”。代码如下(nodes/fixed_tf_broadcaster.py):

 

#!/usr/bin/env python    
import roslib  
roslib.load_manifest('learning_tf')  
  
import rospy  
import tf  
  
if __name__ == '__main__':  
    rospy.init_node('my_tf_broadcaster')  
    br = tf.TransformBroadcaster()  
    rate = rospy.Rate(10.0)  
    while not rospy.is_shutdown():  
        br.sendTransform((0.0, 2.0, 0.0),  
                         (0.0, 0.0, 0.0, 1.0),  
                         rospy.Time.now(),  
                         "carrot1",  
                         "turtle1") #建立一个新的参考系,父参考系为turtle1,并且距离父参考系2米  
        rate.sleep()

 

 

       在launch文件中添加节点:
<launch>  
  ...  
  <node pkg="learning_tf" type="fixed_tf_broadcaster.py"  
        name="broadcaster_fixed" />  
</launch>

        运行,还是看到两只乌龟和之前的效果一样。新添加的参考系并没有对其他参考系产生什么影响。打开nodes/turtle_tf_listener.py文件,将turtle1改成carrot1:
        (trans,rot) = self.tf.lookupTransform(“/turtle2”, “/carrot1”, rospy.Time(0)) 

 

        重新运行,现在乌龟之间的跟随关系就改变了:
ROS探索总结(十二)—— 坐标系统插图(8)

3、建立移动参考系(moving frame)

        我们建立的新参考系是一个固定的参考系,在仿真过程中不会改变,如果我们要把carrot1参考系和turtle1参考系之间的关系设置可变的,可以修改代码如下:
#!/usr/bin/env python    
import roslib  
roslib.load_manifest('learning_tf')  
  
import rospy  
import tf  
import math  
  
if __name__ == '__main__':  
    rospy.init_node('my_tf_broadcaster')  
    br = tf.TransformBroadcaster()  
    rate = rospy.Rate(10.0)  
    while not rospy.is_shutdown():  
        t = rospy.Time.now().to_sec() * math.pi  
        br.sendTransform((2.0 * math.sin(t), 2.0 * math.cos(t), 0.0),  
                         (0.0, 0.0, 0.0, 1.0),  
                         rospy.Time.now(),  
                         "carrot1",  
                         "turtle1")  
        rate.sleep()
        这次carrot1的位置现对于turtle1来说是一个三角函数关系了。
ROS探索总结(十二)—— 坐标系统插图(9)

 

发表评论

后才能评论

评论列表(18条)

  • zzz 2019年9月18日 上午10:00

    Traceback (most recent call last):
    File “/home/zyf/catkin_ws/src/learning_tf/scripts/turtle_tf_broadcaster.py”, line 22, in
    turtlename = rospy.get_param(‘~turtle’)
    File “/opt/ros/melodic/lib/python2.7/dist-packages/rospy/client.py”, line 465, in get_param
    return _param_server[param_name] #MasterProxy does all the magic for us
    File “/opt/ros/melodic/lib/python2.7/dist-packages/rospy/msproxy.py”, line 123, in __getitem__
    raise KeyError(key)
    KeyError: ‘~turtle’
    古月老师 我也遇到了这个情况 并且Python文件有可执行权限 这是怎么会事啊

  • 冯冬冬 2018年11月22日 下午5:42

    ,你好,我想问一下我在添加nodes/turtle_tf_listener.py之后开始运行,为什么只有一只小乌龟呀?

    • 古月 回复 冯冬冬 2018年11月24日 下午5:08

      除了turtle_tf_listener.py,其他内容启动了么

    • 冯冬冬 回复 古月 2018年11月25日 下午10:09

      我最后发现,就是里面的注释代码不能出现,在ubuntu18.04的环境下貌似不能编码,最后我把那些注释代码删掉就可以正常的跑了

    • 古月 回复 冯冬冬 2018年11月27日 下午5:56

      是的,python中出现中文需要在程序头部声明一下编码

  • smartcong 2018年10月8日 下午3:54

    月哥,乘数为什么是4和0.5,这个数据是哪里来的?是哪个公式?

    • 古月 回复 smartcong 2018年10月9日 下午7:15

      这个是一个人为设定的系数,可以自主调整

  • 枫飞 2018年7月19日 下午3:24

    月哥,我在执行roslaunch learning_tf start_demo.launch时,报错:
    File “/home/keal/catkin_ws/src/learning_tf/nodes/turtle_tf_listener.py”, line 19, in
    turtle_vel = rospy.Publisher(‘turtle2/command_velocity’, turtlesim.msg.Velocity)
    AttributeError: ‘module’ object has no attribute ‘Velocity’

    这是怎么回事?

    • 古月 回复 枫飞 2018年7月22日 下午4:00

      依赖的相关功能包没有在cmakelist里边设置好吧

  • alan 2018年7月18日 下午3:26

    你好,我在运行start_demo.launch的时候,报错:
    ERROR: cannot launch node of type [learning_tf/turtle_tf_broadcaster.py]: can’t locate node [turtle_tf_broadcaster.py] in package [learning_tf]
    ERROR: cannot launch node of type [learning_tf/turtle_tf_broadcaster.py]: can’t locate node [turtle_tf_broadcaster.py] in package [learning_tf]
    而我单独运行rosrun learning_tf tuetle_tf_broadcaster.py 时,报错:
    Traceback (most recent call last):
    File “/home/kinetic/catkin_ws/src/learning_tf/nodes/tuetle_tf_broadcaster.py”, line 22, in
    turtlename = rospy.get_param(‘~turtle’)
    File “/opt/ros/kinetic/lib/python2.7/dist-packages/rospy/client.py”, line 465, in get_param
    return _param_server[param_name] #MasterProxy does all the magic for us
    File “/opt/ros/kinetic/lib/python2.7/dist-packages/rospy/msproxy.py”, line 123, in __getitem__
    raise KeyError(key)
    KeyError: ‘~turtle’
    请问大神这是怎么回事??

    • 古月 回复 alan 2018年7月19日 上午12:16

      请确定一下python是否添加了可执行权限

    • zzz 回复 alan 2019年9月17日 下午9:42

      您好 请问您解决这个问题了吗 我也遇到了不知道怎么办

  • 杨辉 2018年1月4日 上午11:38

    您好,我的激光数据异常之后,在恢复正常,怎么不能正常去读取数据了呢?
    错误信息:Lookup would require extrapolation into the past. Requested time 1515037009.633734941 but the earliest data is at time 1515037014.678430796, when looking up transform from frame [odom] to frame [laser]

    以下代码:
    br = tf.TransformListener()
    rospy.Subscriber(“tf”, Twist)
    while not rospy.is_shutdown():
    try:
    (trans,rot) = br.lookupTransform(‘/laser’, ‘/odom’, rospy.Time(0))
    print trans
    time.sleep(0.1)
    except Exception,e:
    print e
    time.sleep(5)

    • 古月 回复 杨辉 2018年1月9日 上午9:49

      从这里提示的错误信息看,应该是两者的时间戳不匹配了,可以参考这篇文章中的解决方法,增加延时,或者采用阻塞模式:http://blog.hi58.net/?p=33766

  • 杨辉 2018年1月4日 上午11:12

    您好,我的激光odom服务抛异常的时候会执行异常处理,但是当激光恢复正常之后,他会一直抛异常,这个怎么搞呢?
    错误信息:Lookup would require extrapolation into the past. Requested time 1515035495.513136864 but the earliest data is at time 1515035495.532359362, when looking up transform from frame [odom] to frame [laser]

    以下是代码:
    br = tf.TransformListener()
    rospy.Subscriber(“tf”, Twist)
    while not rospy.is_shutdown():
    try:
    (trans,rot) = br.lookupTransform(‘/laser’, ‘/odom’, rospy.Time(0))
    print trans
    time.sleep(0.1)
    except Exception,e:
    print e
    time.sleep(5)

  • 2017年5月8日 下午7:25

    月哥这句话是什么意思啊roslib.load_manifest(‘learning_tf’)

    • 古月 回复 2017年5月9日 上午9:15

      这个是用来读取manifest文件,获得功能包依赖的,在groovy版本之前需要用到,现在的catkin已经不需要了