ROS 坐标变换实操(Python版)

一、建立launch文件 test_p.launch

<launch>
    <!-- 
        流程详解:
            1. 准备工作: 启动乌龟 GUI 节点与键盘控制节点
            2. 需要调用服务器生成一只新的乌龟
            3. 发布两只乌龟的坐标信息
            4. 订阅坐标信息,并且转换成 乌龟A 相对于 乌龟B 的坐标信息, 最后再生成控制乌龟B的速度信息
     -->
     <!-- 1. 准备工作: 启动乌龟 GUI 节点与键盘控制节点 -->
    <node pkg = "turtlesim" type = "turtlesim_node" name = "turtle1" output = "screen"/>
    <!-- 键盘控制-->
    <node pkg = "turtlesim" type = "turtle_teleop_key" name = "key" output = "screen" />

    <!-- 2. 需要调用服务器生成一只新的乌龟 -->
    <node pkg = "tf04_test" type = "test01_new_turtle_p.py" name = "turtle2" output = "screen" />

    <!-- 3. 发布两只乌龟的坐标信息:
            (1) 复用之前的乌龟坐标发布功能
            (2) 调用节点时,以参数的方式传递乌龟名称,解析参数置换: 订阅的话题消息 和 子级坐标系的名称
            args表示传参,args[0]表示函数名称,args[1]才表示传入的第一个参数
    -->
    <node pkg = "tf04_test" type = "test02_pub_turtle_p.py" name = "pub1" args = "turtle1" output = "screen" />
    <node pkg = "tf04_test" type = "test02_pub_turtle_p.py" name = "pub2" args = "turtle2" output = "screen" />

    <!-- 4. 订阅坐标信息,并且转换成 乌龟A 相对于 乌龟B 的坐标信息, 最后再生成控制乌龟B的速度信息 -->
    <node pkg = "tf04_test" type = "test03_control_turtle2_p.py" name = "control" output = "screen"/>

</launch>

二、生成一个新的乌龟节点 test01_new_turtle_p.py

launch文件

 <!-- 2. 需要调用服务器生成一只新的乌龟 -->
    <node pkg = "tf04_test" type = "test01_new_turtle_p.py" name = "turtle2" output = "screen" />
#! /usr/bin/env python


"""
    需求:向服务器发送请求生成一个乌龟
        话题:/spawn
        消息类型:turtlesim/Spawn
    1. 导包
    2. 初始化ROS节点
    3. 创建服务的客户端对象
    4. 组织数据并发送请求
    5. 处理响应结果

"""

import rospy
from turtlesim.srv import Spawn,SpawnRequest,SpawnResponse

if __name__ == "__main__":
    # 2. 初始化ROS节点
    rospy.init_node("service_call_p")
    # 3. 创建服务的客户端对象
    client = rospy.ServiceProxy("/spawn",Spawn) # /spawn是话题名称
    # 4. 组织数据并发送请求
    # 4.1 组织数据
    request = SpawnRequest()
    request.x = 4.5
    request.y = 2.0
    request.theta = -3 # 向右转3个rad
    request.name = "turtle2"
    # 4.2 判断服务器状态并发送
    client.wait_for_service() # 服务端开启则执行,否则挂起
    # 防止抛出异常
    try:
        response = client.call(request)
        # 5. 处理响应结果
        rospy.loginfo("生成乌龟的名字叫:%s",response.name)
    except Exception as e:
        rospy.logerr("请求处理异常")

rostopic list

/rosout
/rosout_agg
/turtle1/cmd_vel (目前只能控制)
/turtle1/color_sensor
/turtle1/pose
/turtle2/cmd_vel
/turtle2/color_sensor
/turtle2/pose

发布方(发布两只相对于世界坐标系的乌龟 )

test02_pub_turtle_p.py

乌龟的坐标信息是相对于世界坐标系而言的
launch 文件

<!-- 3. 发布两只乌龟的坐标信息:
            (1) 复用之前的乌龟坐标发布功能
            (2) 调用节点时,以参数的方式传递乌龟名称,解析参数置换: 订阅的话题消息 和 子级坐标系的名称
            args表示传参,args[0]表示函数名称,args[1]才表示传入的第一个参数
    -->
    <node pkg = "tf04_test" type = "test02_pub_turtle_p.py" name = "pub1" args = "turtle1" output = "screen" />
    <node pkg = "tf04_test" type = "test02_pub_turtle_p.py" name = "pub2" args = "turtle2" output = "screen" />
#! /usr/bin/env python

"""
    发布方: 需要订阅乌龟的位姿信息, 转换成相对于窗体的坐标关系,并发布
    准  备: 
        话题: /turtle1/pose
        消息: turtlesim/Pose

    流程:
        1. 包含头文件
        2. 初始化ROS节点
        3. 创建订阅对象,订阅/turtle1/pose
        4. 回调函数处理订阅信息: 将位姿信息转换成坐标相对关系并发布(关注)
        5. spin()
"""

import rospy
from turtlesim.msg import Pose
import tf2_ros
from geometry_msgs.msg import TransformStamped
import tf
import sys # 解析参数

# 接受乌龟名称的变量
turtle_name = " "


def doPose(pose):
    # 1. 创建发布坐标系相对关系的对象 头文件3
    pub = tf2_ros.TransformBroadcaster()

    # 2. 将 pose 转换成 坐标系相对关系消息 头文件4
    ts = TransformStamped()
    ts.header.frame_id = "world"
    ts.header.stamp = rospy.Time.now()

    # 修改1 --------------------------- 子级坐标系
    ts.child_frame_id = turtle_name
    
    # 2.1 子级坐标系相对于父级坐标系的偏移量
    ts.transform.translation.x = pose.x
    ts.transform.translation.y = pose.y
    ts.transform.translation.z = 0

    # 2.2 四元数 头文件5
    # 从欧拉角转换四元数
    """
        乌龟是2D,不存在 x 上的翻滚和 y 上的俯仰,只有 z 上的偏航
        0 0 pose.theta
    """
    qtn = tf.transformations.quaternion_from_euler(0,0,pose.theta)
    ts.transform.rotation.x = qtn[0]
    ts.transform.rotation.y = qtn[1]
    ts.transform.rotation.z = qtn[2]
    ts.transform.rotation.w = qtn[3]

    # 3. 发布
    pub.sendTransform(ts)

if __name__ == "__main__":

    # 2. 初始化ROS节点
    rospy.init_node("dynamic_pub_p")

    # 解析传入的参数(现在传入几个参数? 文件全路径 + 传入的参数 + 节点名称 + 日志文件路径)
    if len(sys.argv) != 4:
        rospy.loginfo("参数个数不对")
        sys.exit(1)
    else:
        turtle_name = sys.argv[1] # 这里argv[1]指的就是turtle1或者turtle2

    # 3. 创建订阅对象 头文件2

    # 修改2 -------------------- 乌龟的名称是通过参数传递进来的
    sub = rospy.Subscriber(turtle_name + "/pose",Pose,doPose,queue_size = 100) # /tur是话题名称

    # 4. 回调函数处理订阅信息
    # 5. spin()
    rospy.spin()

关键代码

# 解析传入的参数(现在传入几个参数? 文件全路径 + 传入的参数 + 节点名称 + 日志文件路径)
    if len(sys.argv) != 4:
        rospy.loginfo("参数个数不对")
        sys.exit(1)
    else:
        turtle_name = sys.argv[1] # 这里argv[1]指的就是turtle1或者turtle2

订阅方(解析坐标信息)

launch文件

<!-- 4. 订阅坐标信息,并且转换成 乌龟A 相对于 乌龟B 的坐标信息, 最后再生成控制乌龟B的速度信息 -->
    <node pkg = "tf04_test" type = "test03_control_turtle2_p.py" name = "control" output = "screen"/>
#! /usr/bin/env python

# 实现功能: 1 坐标1向坐标2转变 2 坐标1中点在坐标2的变换


import rospy
import tf2_ros
from tf2_geometry_msgs import tf2_geometry_msgs
from geometry_msgs.msg import TransformStamped

if __name__ == "__main__":

    # 2. 初始化
    rospy.init_node("tfs_sub_p")

    # 3. 创建订阅对象
    # 3.1 创建一个缓存对象 头文件2
    buffer = tf2_ros.Buffer()
    # 3.2 创建订阅对象(将缓存传入)
    sub = tf2_ros.TransformListener(buffer)

    # 4. 组织被转换的坐标点 头文件3(这里不需要)

    # 5. 转换逻辑实现,调用tf封装的算法
    rate = rospy.Rate(10)
    while not rospy.is_shutdown():
        # 使用try防止
        try:
            # ------ 需要计算 son1 相对于 son2 的坐标关系  头文件4
            """
                参数1: 目标坐标系
                参数2: 源坐标系
                参数3: rospy.Time(0) --- 取时间间隔最近的两个坐标系帧(son1相对world,与son2相对world)计算
                返回值: turtle1 与 turtle2坐标系关系
            """
            ts = buffer.lookup_transform("turtle2","turtle1",rospy.Time(0)) # 目标坐标系, 源坐标系
            rospy.loginfo("父级坐标系: %s,子级坐标系: %s, 偏移量(%.2f,%.2f,%.2f)",
                        ts.header.frame_id,
                        ts.child_frame_id,
                        ts.transform.translation.x,
                        ts.transform.translation.y,
                        ts.transform.translation.z
                        )

        except Exception as e:
            rospy.logwarn("错误提示:%s",e) 
        
        rate.sleep()

订阅方(生成速度信息并发布)

#! /usr/bin/env python

# 实现功能: 1 坐标1向坐标2转变 2 坐标1中点在坐标2的变换


import rospy
import tf2_ros
from tf2_geometry_msgs import tf2_geometry_msgs
from geometry_msgs.msg import TransformStamped,Twist
import math

if __name__ == "__main__":

    # 2. 初始化
    rospy.init_node("tfs_sub_p")

    # 3. 创建订阅对象
    # 3.1 创建一个缓存对象 头文件2
    buffer = tf2_ros.Buffer()
    # 3.2 创建订阅对象(将缓存传入)
    sub = tf2_ros.TransformListener(buffer)

    # 4. 创建速度消息发布对象
    pub = rospy.Publisher("/turtle2/cmd_vel",Twist,queue_size=100)

    # 5. 转换逻辑实现,调用tf封装的算法
    rate = rospy.Rate(10)
    while not rospy.is_shutdown():
        # 使用try防止
        try:
            # ------ 需要计算 son1 相对于 son2 的坐标关系  头文件4
            """
                参数1: 目标坐标系
                参数2: 源坐标系
                参数3: rospy.Time(0) --- 取时间间隔最近的两个坐标系帧(son1相对world,与son2相对world)计算
                返回值: turtle1 与 turtle2坐标系关系
            """
            ts = buffer.lookup_transform("turtle2","turtle1",rospy.Time(0)) # 目标坐标系, 源坐标系
            rospy.loginfo("父级坐标系: %s,子级坐标系: %s, 偏移量(%.2f,%.2f,%.2f)",
                        ts.header.frame_id,
                        ts.child_frame_id,
                        ts.transform.translation.x,
                        ts.transform.translation.y,
                        ts.transform.translation.z
                        )
            
            # 组织 Twist 消息
            # 组织速度,只需要设置线速度的 x 与 角速度的 z
            # x = 系数 * sqrt((y^2+x^2))
            # z = 系数 * atan2(对边/邻边) 反正切,以弧度为单位
            twist = Twist()
            twist.linear.x = 0.5 * math.sqrt(math.pow(ts.transform.translation.x,2) + math.pow(ts.transform.translation.y,2))
            twist.angular.z = 4 * math.atan2(ts.transform.translation.y,ts.transform.translation.x)
            
            # 发布消息
            pub.publish(twist)

        except Exception as e:
            rospy.logwarn("错误提示:%s",e) 
        
        rate.sleep()

重点

 # 组织 Twist 消息
            # 组织速度,只需要设置线速度的 x 与 角速度的 z
            # x = 系数 * sqrt((y^2+x^2))
            # z = 系数 * atan2(对边/邻边) 反正切,以弧度为单位
            twist = Twist()
            twist.linear.x = 0.5 * math.sqrt(math.pow(ts.transform.translation.x,2) + math.pow(ts.transform.translation.y,2))
            twist.angular.z = 4 * math.atan2(ts.transform.translation.y,ts.transform.translation.x)
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2021 Nqq

你的鼓励是我学习的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值