TF变换
已知小车base_link中心点(0,0,0) 激光雷达base_laser相对base_link位置,求激光雷达检测到的物体相对于小车中心点位置
ROS中的坐标描述
TF 静态坐标变换
rosrun tf2_ros static_transform_publisher x偏移量 y偏移量 z偏移量 z偏航角度 y俯仰角度 x翻滚角度 父级坐标 子级坐标
机器人的base_link 与其所有传感器坐标相对关系适合用TF静态坐标变换描述并放置在launch中。
rosrun tf2_ros static_transform_publisher 0.1 0.0 0.2 0 0 0 /base_link /base_laser
该操作描述了激光雷达与base_link间的坐标关系
TF 动态坐标变换
TF常用的msg
geometry_msgs/TransformStamped 用于传输坐标系相关位置信息(某两个坐标系间的关系)
geometry_msgs/PointStamped 用于传输某个坐标系内坐标点信息
rosmsg info geometry_msgs/PointStamped
std_msgs/Header header
uint32 seq #序列号
time stamp #时间戳
string frame_id #所属坐标系ID
geometry_msgs/Point point
float64 x
float64 y
float64 z
rosmsg info geometry_msgs/TransformStamped
std_msgs/Header header
uint32 seq #序列号
time stamp #时间戳
string frame_id #被参考的坐标ID
string child_frame_id #子坐标系ID
geometry_msgs/Transform transform
geometry_msgs/Vector3 translation #child相对于frame_id的偏移
float64 x # X方向的偏移量
float64 y # Y方向的偏移量
float64 z # Z方向上的偏移量
geometry_msgs/Quaternion rotation
float64 x #四元数(描述相对的角度变化)
float64 y
float64 z
float64 w
创建测试功能包,该栗子描述了乌龟与世界坐标系的关系
catkin_create_pkg tf_test tf2 tf2_ros tf2_geometry_msgs roscpp rospy std_msgs geometry_msgs turtlesim
在新功能包里创建scripts目录并写入文件
#! /usr/bin/env python3
"""
动态的坐标系相对姿态发布(一个坐标系相对于另一个坐标系的相对姿态是不断变动的)
需求: 启动 turtlesim_node,该节点中窗体有一个世界坐标系(左下角为坐标系原点),乌龟是另一个坐标系,键盘
控制乌龟运动,将两个坐标系的相对位置动态发布
实现分析:
1.乌龟本身不但可以看作坐标系,也是世界坐标系中的一个坐标点
2.订阅 turtle1/pose,可以获取乌龟在世界坐标系的 x坐标、y坐标、偏移量以及线速度和角速度
3.将 pose 信息转换成 坐标系相对信息并发布
实现流程:
1.导包
2.初始化 ROS 节点
3.订阅 /turtle1/pose 话题消息
4.回调函数处理
4-1.创建 TF 广播器
4-2.创建 广播的数据(通过 pose 设置)
4-3.广播器发布数据
5.spin
"""
# 1.导包
import rospy
import tf2_ros
import tf
from turtlesim.msg import Pose
from geometry_msgs.msg import TransformStamped
# 4.回调函数处理
def doPose(pose):
# 4-1.创建 TF 广播器
broadcaster = tf2_ros.TransformBroadcaster()
# 4-2.创建 广播的数据(通过 pose 设置)
tfs = TransformStamped()
tfs.header.frame_id = "world"
tfs.header.stamp = rospy.Time.now()
tfs.child_frame_id = "turtle1"
tfs.transform.translation.x = pose.x
tfs.transform.translation.y = pose.y
tfs.transform.translation.z = 0.0
#欧拉角转换四元数 2d乌龟不沿x翻滚,也不存在y方向俯仰,只有一个z的偏行,如果想不明白就伸出右手
qtn = tf.transformations.quaternion_from_euler(0,0,pose.theta)
tfs.transform.rotation.x = qtn[0]
tfs.transform.rotation.y = qtn[1]
tfs.transform.rotation.z = qtn[2]
tfs.transform.rotation.w = qtn[3]
# 4-3.广播器发布数据
broadcaster.sendTransform(tfs)
if __name__ == "__main__":
# 2.初始化 ROS 节点
rospy.init_node("demo01_pub")
# 3.订阅 /turtle1/pose 话题消息
sub = rospy.Subscriber("/turtle1/pose",Pose,doPose)
# 4.回调函数处理
# 4-1.创建 TF 广播器
# 4-2.创建 广播的数据(通过 pose 设置)
# 4-3.广播器发布数据
# 5.spin
rospy.spin()
catkin_make
启动乌龟
roucore
rosrun turtlesim turtlesim_node
#启动乌龟键盘控制
rosrun turtlesim turtle_teleop_key
#启动编写好的关系
rosrun tf_test demo01_pub.py
#启动rviz
rviz
# TF 订阅
#! /usr/bin/env python3
"""
动态的坐标系相对姿态发布(一个坐标系相对于另一个坐标系的相对姿态是不断变动的)
需求: 启动 turtlesim_node,该节点中窗体有一个世界坐标系(左下角为坐标系原点),乌龟是另一个坐标系,键盘
控制乌龟运动,将两个坐标系的相对位置动态发布
实现分析:
1.乌龟本身不但可以看作坐标系,也是世界坐标系中的一个坐标点
2.订阅 turtle1/pose,可以获取乌龟在世界坐标系的 x坐标、y坐标、偏移量以及线速度和角速度
3.将 pose 信息转换成 坐标系相对信息并发布
实现流程:
1.导包
2.初始化 ROS 节点
3.创建 TF 订阅对象
4.处理订阅的数据
"""
# 1.导包
import rospy
import tf2_ros
# 不要使用 geometry_msgs,需要使用 tf2 内置的消息类型
from tf2_geometry_msgs import PointStamped
# from geometry_msgs.msg import PointStamped
if __name__ == "__main__":
# 2.初始化 ROS 节点
rospy.init_node("static_sub_tf_p")
# 3.创建 TF 订阅对象
buffer = tf2_ros.Buffer()
listener = tf2_ros.TransformListener(buffer)
rate = rospy.Rate(1)
while not rospy.is_shutdown():
# 4.创建一个 radar 坐标系中的坐标点
point_source = PointStamped()
point_source.header.frame_id = "turtle1"
point_source.header.stamp = rospy.Time.now()
point_source.point.x = 10
point_source.point.y = 2
point_source.point.z = 3
try:
# 5.调研订阅对象的 API 将 4 中的点坐标转换成相对于 world 的坐标
point_target = buffer.transform(point_source,"world",rospy.Duration(1))
rospy.loginfo("转换结果:x = %.2f, y = %.2f, z = %.2f",
point_target.point.x,
point_target.point.y,
point_target.point.z)
except Exception as e:
rospy.logerr("异常:%s",e)
# 6.spin
rate.sleep()
完成求解
有了以上的栗子,我们可以求解最初的问题(本文第一张图),该问题也可映射到一些有意思的场景,假设摄像头检测到水杯,获取的位置是相对于摄像头的,如果此时有机械臂想去抓水杯,可通过该栗子将水杯坐标点非常简单的转换为相对于机械臂坐标系上的点,这样就可操作机械臂干活了。
栗子
模拟两个坐标系
<launch>
<node pkg="tf2_ros" type="static_transform_publisher" name="son1" args="0.2 0.8 0.3 0 0 0 /world /son1" output="screen" />
<node pkg="tf2_ros" type="static_transform_publisher" name="son2" args="0.5 0 0 0 0 0 /world /son2" output="screen" />
</launch>
#!/usr/bin/env python3
"""
需求:
现有坐标系统,父级坐标系统 world,下有两子级系统 son1,son2,
son1 相对于 world,以及 son2 相对于 world 的关系是已知的,
求 son1 与 son2中的坐标关系,又已知在 son1中一点的坐标,要求求出该点在 son2 中的坐标
实现流程:
1.导包
2.初始化 ROS 节点
3.创建 TF 订阅对象
4.调用 API 求出 son1 相对于 son2 的坐标关系
5.创建一依赖于 son1 的坐标点,调用 API 求出该点在 son2 中的坐标
6.spin
"""
# 1.导包
import rospy
import tf2_ros
from geometry_msgs.msg import TransformStamped
from tf2_geometry_msgs import PointStamped
if __name__ == "__main__":
# 2.初始化 ROS 节点
rospy.init_node("frames_sub_p")
# 3.创建 TF 订阅对象
buffer = tf2_ros.Buffer()
listener = tf2_ros.TransformListener(buffer)
rate = rospy.Rate(1)
while not rospy.is_shutdown():
try:
# 4.调用 API 求出 son1 相对于 son2 的坐标关系
#lookup_transform(self, target_frame, source_frame, time, timeout=rospy.Duration(0.0)):
tfs = buffer.lookup_transform("son2","son1",rospy.Time(0))
rospy.loginfo("son1 与 son2 相对关系:")
rospy.loginfo("父级坐标系:%s",tfs.header.frame_id)
rospy.loginfo("子级坐标系:%s",tfs.child_frame_id)
rospy.loginfo("相对坐标:x=%.2f, y=%.2f, z=%.2f",
tfs.transform.translation.x,
tfs.transform.translation.y,
tfs.transform.translation.z,
)
# 5.创建一依赖于 son1 的坐标点,调用 API 求出该点在 son2 中的坐标
point_source = PointStamped()
point_source.header.frame_id = "son1"
point_source.header.stamp = rospy.Time.now()
point_source.point.x = 1
point_source.point.y = 1
point_source.point.z = 1
point_target = buffer.transform(point_source,"son2",rospy.Duration(0.5))
rospy.loginfo("point_target 所属的坐标系:%s",point_target.header.frame_id)
rospy.loginfo("坐标点相对于 son2 的坐标:(%.2f,%.2f,%.2f)",
point_target.point.x,
point_target.point.y,
point_target.point.z
)
except Exception as e:
rospy.logerr("错误提示:%s",e)
rate.sleep()
# 6.spin
# rospy.spin()