以下内容均来自b站这个老师的视频,建议大家给视频老师投币 b站视频在线参考文档当前TF转换文档页面 为帮助理解,我此基础,本博客中上加了部分辅助的理解的图片和部分代码注释
ros官方说明参考这里 ros的参考系 开始前注意以下代码处于ros工作区的位置,ros下的调用方式参考视频,以下为视频中基于ros tf2的使用记录
launch文件语法规范参考 tf中重要函数解析
1.ROS tf2 功能总结描述 2.参数与类型 geometry_msgs/TransformStamped.msg geometry_msgs/PointStamped Message
一.静态坐标变换
两个坐标的位置是固定的,如下图的雷达和雷达下方的小车 步骤流程如下 python参考静态坐标变换老师讲解视频
一.静态坐标转换问题如下:
注意里面的坐标关系,下方代码中默认主体原点为世界坐标系的原点
1.1静态发布端
距离作为主体原点为世界坐标系的原点 的偏移量 与 四元数(实际就是旋转的欧拉角) 准备参数: 偏移量 + 四元数(由于是静态这里设置为0如下 --p 注释处)
"""
静态坐标变换发布方:
发布关于 laser 坐标系的位置信息
实现流程:
1.导包
2.初始化 ROS 节点
3.创建 静态坐标广播器
4.创建并组织被广播的消息
5.广播器发送消息
6.spin
"""
import rospy
import tf2_ros
import tf
from geometry_msgs.msg import TransformStamped
if __name__ == "__main__":
rospy.init_node("static_tf_pub_p")
broadcaster = tf2_ros.StaticTransformBroadcaster()
tfs = TransformStamped()
tfs.header.frame_id = "world"
tfs.header.stamp = rospy.Time.now()
tfs.header.seq = 101
tfs.child_frame_id = "radar"
tfs.transform.translation.x = 0.2
tfs.transform.translation.y = 0.0
tfs.transform.translation.z = 0.5
qtn = tf.transformations.quaternion_from_euler(0,0,0)
tfs.transform.rotation.x = qtn[0]
tfs.transform.rotation.y = qtn[1]
tfs.transform.rotation.z = qtn[2]
tfs.transform.rotation.w = qtn[3]
broadcaster.sendTransform(tfs)
rospy.spin()
1.2静态订阅端
引入雷达坐标系中障碍物的坐标 准备参数: 雷达坐标系下检测到的物体的坐标点(如果是相机就是相机坐标系下) 返回:转换后的坐标点,即检测物体在主体原点为世界坐标系的原点,的世界坐标系下的坐标点
"""
订阅坐标系信息,生成一个相对于 子级坐标系的坐标点数据,
转换成父级坐标系中的坐标点
实现流程:
1.导包
2.初始化 ROS 节点
3.创建 TF 订阅对象
4.创建一个 radar 坐标系中的坐标点
5.调研订阅对象的 API 将 4 中的点坐标转换成相对于 world 的坐标
6.spin
"""
import rospy
import tf2_ros
from tf2_geometry_msgs import PointStamped
if __name__ == "__main__":
rospy.init_node("static_sub_tf_p")
buffer = tf2_ros.Buffer()
listener = tf2_ros.TransformListener(buffer)
rate = rospy.Rate(1)
while not rospy.is_shutdown():
point_source = PointStamped()
point_source.header.frame_id = "radar"
point_source.header.stamp = rospy.Time.now()
point_source.point.x = 2.0
point_source.point.y = 3.0
point_source.point.z = 5.0
try:
point_target = buffer.transform(point_source,"world")
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)
rate.sleep()
二.动态坐标变换
以下为ros中经典的小乌龟举例,注意坐标系是两个,一个是小乌龟本身的,一个是小乌龟在世界坐标系下的。 这里求解就是当小乌龟移动,使得小乌龟自身的坐标系变换,然后该坐标系在世界坐标系下又处于怎样的位置。(通俗来说,比如小乌龟向前走然后转个圈,那么小乌龟的头在世界坐标系下处于什么坐标)
准备参数: 小乌龟相对于世界坐标系下的偏移量 + 自身的欧拉角变换
详细说明链接 视频部分
2.1动态发布端
欧拉角示意图补充 下面代码中获得欧拉角的ros模块官方链接: turtlesim.msg 下方x,y 为小乌龟在世界坐标系下的偏移量二维,theta为绕z的那个欧拉角,这个话题一般用于小车底盘控制,因为都可以等同于二维的小乌龟。。。。。。
"""
动态的坐标系相对姿态发布(一个坐标系相对于另一个坐标系的相对姿态是不断变动的)
需求: 启动 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
"""
import rospy
import tf2_ros
import tf
from turtlesim.msg import Pose
from geometry_msgs.msg import TransformStamped
def doPose(pose):
broadcaster = tf2_ros.TransformBroadcaster()
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
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]
broadcaster.sendTransform(tfs)
if __name__ == "__main__":
rospy.init_node("dynamic_tf_pub_p")
sub = rospy.Subscriber("/turtle1/pose",Pose,doPose)
rospy.spin()
2.2动态订阅端
"""
动态的坐标系相对姿态发布(一个坐标系相对于另一个坐标系的相对姿态是不断变动的)
需求: 启动 turtlesim_node,该节点中窗体有一个世界坐标系(左下角为坐标系原点),乌龟是另一个坐标系,键盘
控制乌龟运动,将两个坐标系的相对位置动态发布
实现分析:
1.乌龟本身不但可以看作坐标系,也是世界坐标系中的一个坐标点
2.订阅 turtle1/pose,可以获取乌龟在世界坐标系的 x坐标、y坐标、偏移量以及线速度和角速度
3.将 pose 信息转换成 坐标系相对信息并发布
实现流程:
1.导包
2.初始化 ROS 节点
3.创建 TF 订阅对象
4.处理订阅的数据
"""
import rospy
import tf2_ros
from tf2_geometry_msgs import PointStamped
if __name__ == "__main__":
rospy.init_node("static_sub_tf_p")
buffer = tf2_ros.Buffer()
listener = tf2_ros.TransformListener(buffer)
rate = rospy.Rate(1)
while not rospy.is_shutdown():
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:
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)
rate.sleep()
三.多坐标变换
3.1多坐标变换发布端
创建项目功能包依赖于 tf2、tf2_ros、tf2_geometry_msgs、roscpp rospy std_msgs geometry_msgs、turtlesim 准备参数: 即使两组静态坐标变换发布,即对应的son1于son2,在世界坐标系下的 -》 偏移量 + 四元数
具体参考上文1.1静态坐标变换 以下为通过ros中的launch文件,调用的方法(和上面代码一样,不过直接怼接口了)具体参考视频这里
<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>
3.2多坐标变换订阅端
"""
需求:
现有坐标系统,父级坐标系统 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
"""
import rospy
import tf2_ros
from geometry_msgs.msg import TransformStamped
from tf2_geometry_msgs import PointStamped
if __name__ == "__main__":
rospy.init_node("frames_sub_p")
buffer = tf2_ros.Buffer()
listener = tf2_ros.TransformListener(buffer)
rate = rospy.Rate(1)
while not rospy.is_shutdown():
try:
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,
)
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()
|