简介
TF(transform)工具包用于追踪不同坐标系下随时间变化的帧位置。TF工具包以树结构保存不同时间、不同坐标系下的位置关系,并运行在求取任意两个坐标系在任意时间的坐标变换。ROS Hydro之后,tf工具包已经转为tf2工具包
参考链接
tf2工具包做什么?怎么用?
机器人开发中有多个坐标系随着时间变化,tf2可以跟踪这些变化,并可查询如如下内容:
- 5秒钟之前,头部坐标相对于时间坐标的值是多少?
- 手指中的物体坐标相对于手臂坐标是多少?
- 世界中标中当前位置坐标是多少?
内容
Static Broadcaster
广播一个坐标变换
12345678910111213141516171819202122232425262728293031323334#!/usr/bin/env pythonimport rospy# to get commandline argumentsimport sys#Because of transformationsimport tfimport tf2_rosimport geometry_msgs.msgrospy.init_node('my_static_tf2_broadcaster')# tf2中有StaticTransformBroadcaster类用于广播静态变换broadcaster = tf2_ros.StaticTransformBroadcaster()# static_transformStamped是需要发送的变换信息static_transformStamped = geometry_msgs.msg.TransformStamped()# 变换时间static_transformStamped.header.stamp = rospy.Time.now()# 变换父坐标系static_transformStamped.header.frame_id = "world"# 变换子坐标系static_transformStamped.child_frame_id = sys.argv[1]static_transformStamped.transform.translation.x = float(sys.argv[2])static_transformStamped.transform.translation.y = float(sys.argv[3])static_transformStamped.transform.translation.z = float(sys.argv[4])quat = tf.transformations.quaternion_from_euler(float(sys.argv[5]),float(sys.argv[6]),float(sys.argv[7]))static_transformStamped.transform.rotation.x = quat[0]static_transformStamped.transform.rotation.y = quat[1]static_transformStamped.transform.rotation.z = quat[2]static_transformStamped.transform.rotation.w = quat[3]# 以上几步后,设置了变换的6个自由度(平移+旋转)broadcaster.sendTransform(static_transformStamped)rospy.spin()执行以下命令运行Broadcaster(详细代码及过程见此),参数表示沿z轴平移1米
1$ rosrun learning_tf2 static_turtle_tf2_broadcaster.py mystaticturtle 0 0 1 0 0 0当Broadcaster运行后,会向名为tf_static的topic广播变换内容
1234567891011121314151617181920transforms:-header:seq: 0stamp:secs: 1459282870nsecs: 126883440frame_id: worldchild_frame_id: mystaticturtletransform:translation:x: 0.0y: 0.0z: 1.0rotation:x: 0.0y: 0.0z: 0.0w: 1.0---
Broadcaster
- 内容参考上一节Static Broadcaster,以及ROS官网Broadcaster
- 与Static Broadcaster的区别
- Static Broadcaster发送到tf_static主题
- Static Broadcaster只在需要的时候发送,通常只在启动的时候发送一次
Listener
Listener代码
123456789101112131415161718192021222324252627282930313233343536373839404142#!/usr/bin/env pythonimport rospyimport mathimport tf2_rosimport geometry_msgs.msgimport turtlesim.srvif __name__ == '__main__':rospy.init_node('tf2_turtle')tfBuffer = tf2_ros.Buffer()# 创建tf2_ros.TransformListener用于自动接收变换消息,并缓存10秒内的所有变换listener = tf2_ros.TransformListener(tfBuffer)rospy.wait_for_service('spawn')spawner = rospy.ServiceProxy('spawn', turtlesim.srv.Spawn)turtle_name = rospy.get_param('turtle', 'turtle2')spawner(4, 2, 0, turtle_name)turtle_vel = rospy.Publisher('%s/cmd_vel' % turtle_name, geometry_msgs.msg.Twist, queue_size=1)rate = rospy.Rate(10.0)while not rospy.is_shutdown():try:# 根据参数查询变换# 参数1:源坐标系# 参数2:目标坐标系# 查询的变换时间:rospy.Time(0)获取最新的可用变换trans = tfBuffer.lookup_transform(turtle_name, 'turtle1', rospy.Time())except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException):rate.sleep()continuemsg = geometry_msgs.msg.Twist()msg.angular.z = 4 * math.atan2(trans.transform.translation.y, trans.transform.translation.x)msg.linear.x = 0.5 * math.sqrt(trans.transform.translation.x ** 2 + trans.transform.translation.y ** 2)turtle_vel.publish(msg)rate.sleep()执行效果
当使用键盘移动turtle时,turtle2会跟着一起运动
创建一个坐标轴
创建坐标轴的原因
- 在应用过程中,创建一个本地内部的本地坐标轴通常更容易思考。
- 如果在处理传感器时,创建位于每个传感器中心的坐标轴,然后只需要按照新的坐标轴处理,tf2可以处理所有的变换
在哪儿创建坐标轴
- 在tf2中创建了一个树结构来保存所有的坐标轴
- 坐标轴树中不允许出现循环
- 任何一个坐标轴只有一个父坐标轴,但可以有多个子坐标轴
- 完成上文的Braodcaster和Listener后,树中有如下三个坐标轴,新建的坐标轴必须从这三个坐标轴中选择一个作为父坐标轴
如何创建坐标轴
代码如下
12345678910111213141516171819202122232425262728293031323334353637#!/usr/bin/env pythonimport rospyimport tf2_rosimport tf2_msgs.msgimport geometry_msgs.msgclass FixedTFBroadcaster:def __init__(self):self.pub_tf = rospy.Publisher("/tf", tf2_msgs.msg.TFMessage, queue_size=1)while not rospy.is_shutdown():# Run this loop at about 10Hzrospy.sleep(0.1)t = geometry_msgs.msg.TransformStamped()t.header.frame_id = "turtle1"t.header.stamp = rospy.Time.now()# 新建名为carrot1的坐标轴,其副坐标轴为turtle1t.child_frame_id = "carrot1"t.transform.translation.x = 0.0t.transform.translation.y = 2.0t.transform.translation.z = 0.0t.transform.rotation.x = 0.0t.transform.rotation.y = 0.0t.transform.rotation.z = 0.0t.transform.rotation.w = 1.0tfm = tf2_msgs.msg.TFMessage([t])self.pub_tf.publish(tfm)if __name__ == '__main__':rospy.init_node('my_tf2_broadcaster')tfb = FixedTFBroadcaster()rospy.spin()
基于时间的tf2
使用lookup_transform(),可以以阻塞的方式查询接下来一段时间内是否有两个坐标轴间的坐标变换
1tfBuffer.lookup_transform('turtle2', 'turtle1', rospy.Time.now(), rospy.Duration(1.0))
使用tf2进行延时坐标变换
- 目的:让turtle2跟随着5秒前的turtle1运动轨迹运动
错误写法:
12past = rospy.Time.now() - rospy.Duration(5.0)trans = tfBuffer.lookup_transform(turtle_name, 'carrot1', past, rospy.Duration(1.0))- 实际效果:查询carrot1 5秒前的状态,然后以turtle2 5秒前的状态进行坐标变换
- 想要的效果:查询carrot1 5秒前的状态,然后以turtle2当前状态进行坐标变换
正确写法:
123456789past = rospy.Time.now() - rospy.Duration(5.0)trans = tfBuffer.lookup_transform_full(target_frame=turtle_name,target_time=rospy.Time.now(),source_frame='carrot1',source_time=past,fixed_frame='world',timeout=rospy.Duration(1.0))lookupTransform()完整的6个参数含义:
- 变换的目标坐标系
- 目标坐标系的时间
- 变换的参考坐标系
- 参考坐标系时间
- 不随时间变换的时间坐标系
- time-out(查询等待超时时间)
坐标在不同数据结构间的转换
Create Data Conversion Package
其他
tf2设计思路
支持的数据类型
- tf2提供模块化的数据类型支持,可以与任何外部package合作,主要有:
- tf2_bullet
- tf2_eigen
- tf2_geometry_msgs
- tf2_kdl
- tf2_sensor_msgs