【ROS2】中级:tf2-编写监听器(Python)

目标:学习如何使用 tf2 获取帧变换。

教程级别:中级

 时间:10 分钟

 目录

  •  背景

  •  先决条件

  •  任务

    • 1. 编写监听节点

    • 2. 更新启动文件

    • 3. 构建

    • 4. 运行

  •  摘要

 背景

在之前的教程中,我们创建了一个 tf2 广播器来发布乌龟的姿态到 tf2

在本教程中,我们将创建一个 tf2 监听器以开始使用 tf2

 先决条件

本教程假设您已经完成了 tf2 静态广播器教程(Python)和 tf2 广播器教程(Python)。在上一个教程中,我们创建了一个 learning_tf2_py 包,我们将继续在此基础上进行工作。

 任务

1. 编写监听节点

让我们首先创建源文件。转到我们在上一个教程中创建的 learning_tf2_py 包。在 src/learning_tf2_py/learning_tf2_py 目录中,通过输入以下命令下载示例监听器代码:

wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_py/turtle_tf2_py/turtle_tf2_listener.py

现在使用您喜欢的文本编辑器打开名为 turtle_tf2_listener.py 的文件。

# 版权 2021 开源机器人基金会,公司。
#
# 根据Apache许可证版本2.0(“许可证”)许可;
# 除非符合许可证,否则不得使用此文件。
# 您可以在以下位置获取许可证副本:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,否则在许可证下分发的软件
# 将按“原样”基础分发,无任何形式的明示或暗示
# 保证或条件。有关在许可证下管理权限和
# 限制的语言,请参阅许可证。


import math  # 导入数学库


from geometry_msgs.msg import Twist  # 从geometry_msgs包导入Twist消息


import rclpy  # 导入ROS客户端库
from rclpy.node import Node  # 从rclpy.node导入Node类


from tf2_ros import TransformException  # 从tf2_ros导入TransformException异常
from tf2_ros.buffer import Buffer  # 从tf2_ros.buffer导入Buffer类
from tf2_ros.transform_listener import TransformListener  # 从tf2_ros.transform_listener导入TransformListener类


from turtlesim.srv import Spawn  # 从turtlesim.srv导入Spawn服务




class FrameListener(Node):  # 定义FrameListener类,继承自Node类


    def __init__(self):  # 定义初始化函数
        super().__init__('turtle_tf2_frame_listener')  # 调用父类初始化函数,设置节点名为'turtle_tf2_frame_listener'


        # 声明并获取`target_frame`参数
        self.target_frame = self.declare_parameter(
            'target_frame', 'turtle1').get_parameter_value().string_value  # 声明并获取'target_frame'参数,默认值为'turtle1'


        self.tf_buffer = Buffer()  # 创建tf缓冲区
        self.tf_listener = TransformListener(self.tf_buffer, self)  # 创建tf监听器


        # 创建一个客户端来生成一个turtle # 在ROS中创建了一个客户端,用于调用名为 'spawn' 的服务。
        self.spawner = self.create_client(Spawn, 'spawn')  # 创建一个Spawn服务的客户端,服务名为'spawn'
        # 布尔值用于存储信息
        # 如果生成turtle的服务可用
        self.turtle_spawning_service_ready = False  # 初始化turtle生成服务状态为不可用
        # 如果turtle成功生成
        self.turtle_spawned = False  # 初始化turtle生成状态为未生成


        # 创建turtle2速度发布器
        self.publisher = self.create_publisher(Twist, 'turtle2/cmd_vel', 1)  # 创建一个Twist消息的发布器,话题名为'turtle2/cmd_vel',QoS设置为1


        # 每秒调用一次on_timer函数
        self.timer = self.create_timer(1.0, self.on_timer)  # 创建一个定时器,每秒调用一次on_timer函数


    def on_timer(self):  # 定义定时器回调函数
        # 将帧名存储在变量中,这些变量将用于
        # 计算变换
        from_frame_rel = self.target_frame  # 设置源帧为目标帧
        to_frame_rel = 'turtle2'  # 设置目标帧为'turtle2'


        if self.turtle_spawning_service_ready:  # 如果turtle生成服务可用
            if self.turtle_spawned:  # 如果turtle已经生成
                # 查找目标帧和turtle2帧之间的变换
                # 并发送速度命令使turtle2达到目标帧
                try:
                    t = self.tf_buffer.lookup_transform(
                        to_frame_rel,
                        from_frame_rel,
                        rclpy.time.Time())  # 尝试查找目标帧和turtle2帧之间的变换
                except TransformException as ex:  # 如果查找失败,抛出TransformException异常
                    self.get_logger().info(
                        f'无法将 {to_frame_rel} 变换到 {from_frame_rel}: {ex}')  # 记录无法进行变换的信息
                    return  # 返回,不执行后续代码


                msg = Twist()  # 创建一个Twist消息
                scale_rotation_rate = 1.0  # 设置旋转速度比例因子
                msg.angular.z = scale_rotation_rate * math.atan2(
                    t.transform.translation.y,
                    t.transform.translation.x)  # 计算并设置z轴的角速度


                scale_forward_speed = 0.5  # 设置前进速度比例因子
                msg.linear.x = scale_forward_speed * math.sqrt(
                    t.transform.translation.x ** 2 +
                    t.transform.translation.y ** 2)  # 计算并设置x轴的线速度


                self.publisher.publish(msg)  # 发布Twist消息
            else:  # 如果turtle未生成
                if self.result.done():  # 如果服务调用完成
                    self.get_logger().info(
                        f'成功生成 {self.result.result().name}')  # 记录成功生成turtle的信息
                    self.turtle_spawned = True  # 设置turtle生成状态为已生成
                else:  # 如果服务调用未完成
                    self.get_logger().info('生成未完成')  # 记录生成未完成的信息
        else:  # 如果turtle生成服务不可用
            if self.spawner.service_is_ready():  # 如果服务可用
                # 使用turtle名和坐标初始化请求
                # 注意在turtlesim/srv/Spawn中,x、y和theta被定义为浮点数
                request = Spawn.Request()  # 创建一个Spawn请求
                request.name = 'turtle2'  # 设置turtle名为'turtle2'
                request.x = 4.0  # 设置turtle的x坐标为4.0
                request.y = 2.0  # 设置turtle的y坐标为2.0
                request.theta = 0.0  # 设置turtle的theta值为0.0
                # 调用请求
                self.result = self.spawner.call_async(request)  # 异步调用服务,发送请求
                self.turtle_spawning_service_ready = True  # 设置turtle生成服务状态为可用
            else:  # 如果服务不可用
                # 检查服务是否准备好
                self.get_logger().info('服务未准备好')  # 记录服务未准备好的信息




def main():  # 定义主函数
    rclpy.init()  # 初始化ROS客户端库
    node = FrameListener()  # 创建一个FrameListener节点
    try:
        rclpy.spin(node)  # 让节点保持运行,处理回调函数
    except KeyboardInterrupt:  # 如果收到键盘中断信号
        pass  # 不做任何操作


    rclpy.shutdown()  # 关闭ROS客户端库

2cff245540fea0346817250072279e7d.png

d9cdb03d0feee99bbb6c3fd753529dcb.png

1.1 检查代码

要了解生成海龟服务背后的工作原理,请参阅编写一个简单的服务和客户端(Python)教程 https://docs.ros.org/en/jazzy/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Service-And-Client.html 。

现在,让我们来看一下与获取帧转换相关的代码。 tf2_ros 包提供了一个 TransformListener 的实现,以帮助简化接收转换的任务。

from tf2_ros.transform_listener import TransformListener

在这里,我们创建一个 TransformListener 对象。监听器创建后,它开始通过网络接收 tf2 变换,并将它们缓冲最多 10 秒。

self.tf_listener = TransformListener(self.tf_buffer, self)

最后,我们查询监听器以获取特定转换。我们调用 lookup_transform 方法,传入以下参数:

  1.  目标框架

  2.  来源框架

  3. 我们想要转变的时间

提供 rclpy.time.Time() 将只会得到最新可用的转换。所有这些都包装在一个 try-except 块中,以处理可能的异常。

t = self.tf_buffer.lookup_transform(
    to_frame_rel,
    from_frame_rel,
    rclpy.time.Time())
1.2 添加一个入口点 

要允许 ros2 run 命令运行您的节点,您必须将入口点添加到 setup.py (位于 src/learning_tf2_py 目录中)。

在 'console_scripts': 括号之间添加以下行:

'turtle_tf2_listener = learning_tf2_py.turtle_tf2_listener:main',

2. 更新启动文件

打开位于 src/learning_tf2_py/launch 目录中名为 turtle_tf2_demo_launch.py 的启动文件,用你的文本编辑器,向启动描述中添加两个新节点,添加一个启动参数,并添加导入。结果文件应该看起来像:

from launch import LaunchDescription  # 从launch包导入LaunchDescription类
from launch.actions import DeclareLaunchArgument  # 从launch.actions导入DeclareLaunchArgument类
from launch.substitutions import LaunchConfiguration  # 从launch.substitutions导入LaunchConfiguration类


from launch_ros.actions import Node  # 从launch_ros.actions导入Node类




def generate_launch_description():  # 定义generate_launch_description函数
    return LaunchDescription([  # 返回一个LaunchDescription对象
        Node(  # 创建一个Node对象
            package='turtlesim',  # 设置包名为'turtlesim'
            executable='turtlesim_node',  # 设置可执行文件名为'turtlesim_node'
            name='sim'  # 设置节点名为'sim'
        ),
        Node(  # 创建一个Node对象
            package='learning_tf2_py',  # 设置包名为'learning_tf2_py'
            executable='turtle_tf2_broadcaster',  # 设置可执行文件名为'turtle_tf2_broadcaster'
            name='broadcaster1',  # 设置节点名为'broadcaster1'
            parameters=[  # 设置参数
                {'turtlename': 'turtle1'}  # 设置'turtlename'参数为'turtle1'
            ]
        ),
        DeclareLaunchArgument(  # 创建一个DeclareLaunchArgument对象
            'target_frame', default_value='turtle1',  # 设置参数名为'target_frame',默认值为'turtle1'
            description='Target frame name.'  # 设置参数描述为'Target frame name.'
        ),
        Node(  # 创建一个Node对象
            package='learning_tf2_py',  # 设置包名为'learning_tf2_py'
            executable='turtle_tf2_broadcaster',  # 设置可执行文件名为'turtle_tf2_broadcaster'
            name='broadcaster2',  # 设置节点名为'broadcaster2'
            parameters=[  # 设置参数
                {'turtlename': 'turtle2'}  # 设置'turtlename'参数为'turtle2'
            ]
        ),
        Node(  # 创建一个Node对象
            package='learning_tf2_py',  # 设置包名为'learning_tf2_py'
            executable='turtle_tf2_listener',  # 设置可执行文件名为'turtle_tf2_listener'
            name='listener',  # 设置节点名为'listener'
            parameters=[  # 设置参数
                {'target_frame': LaunchConfiguration('target_frame')}  # 设置'target_frame'参数为LaunchConfiguration对象,参数名为'target_frame'
            ]
        ),
    ])

这将声明一个 target_frame 启动参数,启动第二只乌龟的广播器,我们将生成这只乌龟,并启动一个监听器来订阅那些变换。

 3.构建

在工作区的根目录运行 rosdep 以检查缺失的依赖项。

rosdep install -i --from-path src --rosdistro jazzy -y

仍在工作区的根目录下,构建您的包:

colcon build --packages-select learning_tf2_py

打开一个新的终端,导航到工作区的根目录,然后加载设置文件:

. install/setup.bash

4. 运行

现在您已准备好开始完整的海龟演示:

ros2 launch learning_tf2_py turtle_tf2_demo_launch.py

897099ee0990886e7fbfc9a1a25c898b.png

您应该看到有两只乌龟的 turtle sim。在第二个终端窗口中输入以下命令:

ros2 run turtlesim turtle_teleop_key

要查看事物是否正常工作,只需使用箭头键驾驶第一只乌龟(确保您的终端窗口处于活动状态,而不是您的模拟器窗口),您会看到第二只乌龟跟随第一只乌龟!

b7b30ad2a5752b99b92253e8fc9df841.png

 摘要

在本教程中,您学习了如何使用 tf2 来获取帧变换的访问权限。您还完成了编写您在 tf2 教程https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Tf2/Introduction-To-Tf2.html 介绍中首次尝试的 turtlesim 演示。

<think>我们是在ROS 2环境中使用MoveIt 2,并且需要监听一个TF变换。在ROS 2中,可以使用tf2_ros包中的TransformListener来获取两个坐标系之间的变换。 我们将创建一个简单的函数,该函数会监听指定的目标坐标系(child_frame)相对于参考坐标系(parent_frame)的变换,并返回最新的变换信息。 注意:由于TF变换是动态的,我们可能需要等待变换可用,因此函数中会包含等待变换可用的逻辑。 步骤: 1. 导入必要的模块:tf2_ros, geometry_msgs, rclpy等。 2. 创建一个tf2_ros.Buffer和tf2_ros.TransformListener。 3. 使用Buffer的lookup_transform方法来获取变换。为了处理可能出现的异常(如变换不存在),我们会进行尝试并设置等待时间。 由于我们希望在函数中简便地获取变换,我们可以设计一个函数,它接受节点对象、目标坐标系和源坐标系以及超时时间作为参数。 注意:在ROS 2中,我们通常会在节点中创建Buffer和Listener,并重复使用它们。因此,我们可以假设Buffer和Listener已经存在,或者每次调用时创建(不推荐,因为创建Listener多次会浪费资源)。为了简便,我们可以在函数外部创建一次Buffer和Listener,然后在函数内部使用这个Buffer。 但是,为了函数使用的简便性,我们可以设计一个类来管理Buffer和Listener,或者设计一个函数,它接受Buffer作为参数。 这里我们提供一个简便函数,它需要传入一个已经创建好的Buffer对象,以及两个坐标系名称,还有可选的超时时间。 然而,如果用户希望一个完全独立的函数,我们可以尝试在函数内部创建Buffer和Listener,但要注意:TransformListener需要节点作为构造参数,并且需要spin节点来接收消息。因此,我们不推荐在函数内部创建,因为这样每次调用都会创建新的Buffer和Listener,而且需要保证节点被spin。 因此,更合理的做法是让用户提前创建好Buffer和Listener,并作为参数传入。 以下是一个示例函数,它使用已经存在的Buffer来获取变换: 函数:get_transform(buffer, target_frame, source_frame, timeout_sec=5.0) 如果获取变换成功,返回变换(TransformStamped);如果超时或出错,则返回None。 但是,我们也可以设计一个不需要预先创建Buffer的方式,通过节点内已经存在的Buffer(比如节点本身可能已经有一个Buffer)来简化使用。 另一种简便方法是:在节点类中创建Buffer和Listener,然后提供一个方法。但这里要求的是一个简便函数,所以我们提供两种方式: 方式1(推荐在节点中使用):在节点初始化时创建Buffer和Listener,然后提供一个成员函数来获取变换。 方式2:独立函数,但需要传入节点对象(因为Buffer的创建需要节点上下文)。 这里我们按照要求写一个独立的简便函数,它需要节点对象作为参数,然后在函数内部利用节点的Buffer(如果节点没有,我们就创建一个,并附加Listener)。但注意,这样可能会在多次调用时创建多个Listener,所以我们建议节点自己管理Buffer。 因此,我们设计一个函数,它使用节点中已经存在的Buffer(如果节点没有,我们可以为节点添加一个Buffer属性)。但是,为了不改变节点结构,我们可以检查节点是否有一个Buffer,如果没有则创建一个并附加Listener。但是,这样会导致每次调用可能都创建一个新的Listener,不好。 所以,我们约定:节点应该自己管理Buffer和Listener。我们提供一个函数,它需要节点和两个坐标系,并假设节点已经有一个tf_buffer属性(类型为tf2_ros.Buffer)。如果节点没有,我们可以抛出一个异常。 为了简便,我们设计一个函数,它接受节点、目标坐标系、源坐标系和超时时间,并在函数内部使用节点内的tf_buffer(如果存在)或者使用一个全局的Buffer(不推荐,因为全局Buffer需要全局Listener,而Listener依赖于节点)。所以,我们要求节点必须有一个tf_buffer属性。 那么,我们可以在节点初始化时这样创建: self.tf_buffer = tf2_ros.Buffer() self.tf_listener = tf2_ros.TransformListener(self.tf_buffer, self) 然后,我们可以这样调用函数:get_transform(node, 'target_frame', 'source_frame') 但是,为了函数的独立性,我们也可以不依赖节点属性,而是让用户传入Buffer对象。 综合考虑,我们定义两个版本: 版本1(推荐,传入Buffer): def get_transform_simple(tf_buffer, target_frame, source_frame, timeout_sec=5.0): try: # 等待变换,并获取最新的变换 transform = tf_buffer.lookup_transform( target_frame, # 目标坐标系(例如:机械臂末端执行器坐标系) source_frame, # 源坐标系(例如:基坐标系) rclpy.time.Time(), # 获取最近时间的变换 timeout=rclpy.duration.Duration(seconds=timeout_sec) ) return transform except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException) as e: # 异常处理 node.get_logger().error(f'获取变换失败: {e}') return None 版本2(依赖节点属性,节点需要有tf_buffer属性): def get_transform_simple(node, target_frame, source_frame, timeout_sec=5.0): if not hasattr(node, 'tf_buffer'): # 如果节点没有tf_buffer,则创建一个并附加Listener(注意:这里我们只在第一次调用时创建) node.tf_buffer = tf2_ros.Buffer() node.tf_listener = tf2_ros.TransformListener(node.tf_buffer, node) tf_buffer = node.tf_buffer try: transform = tf_buffer.lookup_transform( target_frame, source_frame, rclpy.time.Time(), timeout=rclpy.duration.Duration(seconds=timeout_sec) ) return transform except Exception as e: node.get_logger().error(f'获取变换失败: {e}') return None 这里我们选择版本1,因为它更清晰,不依赖节点内部结构,而是由调用者传入Buffer。 但是,注意:在版本1中,我们需要在调用此函数之前确保Buffer已经被创建并且Listener已经附加(这样Buffer才能接收变换数据)。所以,使用这个函数的上下文必须已经有一个Buffer,并且这个Buffer正在被Listener更新。 因此,我们提供一个完整的节点示例,展示如何创建Buffer和Listener,然后使用这个函数。 完整示例代码: ```python import rclpy from rclpy.node import Node import tf2_ros from tf2_ros import TransformException from geometry_msgs.msg import TransformStamped def get_transform_simple(tf_buffer, target_frame, source_frame, timeout_sec=5.0): """ 获取两个坐标系之间的变换 :param tf_buffer: tf2_ros.Buffer对象 :param target_frame: 目标坐标系(字符串) :param source_frame: 源坐标系(字符串) :param timeout_sec: 超时时间(秒) :return: TransformStamped对象,如果失败则返回None """ try: # 等待变换,并获取最新的变换 transform = tf_buffer.lookup_transform( target_frame, source_frame, rclpy.time.Time(), # 获取最近时间的变换 timeout=rclpy.duration.Duration(seconds=timeout_sec) return transform except (TransformException, Exception) as e: # 可以在这里打印日志,但为了不依赖具体节点,我们不打印(或者可以通过传入logger?) # 或者我们抛出异常,让调用者处理 # 这里我们选择返回None,调用者应该检查返回值 return None class TFListenerNode(Node): def __init__(self): super().__init__('tf_listener_node') # 创建Buffer和Listener self.tf_buffer = tf2_ros.Buffer() self.tf_listener = tf2_ros.TransformListener(self.tf_buffer, self) # 创建一个定时器,定期获取变换 self.timer = self.create_timer(1.0, self.timer_callback) self.target_frame = 'tool0' # 目标坐标系(例如机械臂末端) self.source_frame = 'base_link' # 源坐标系(例如基座) def timer_callback(self): transform = get_transform_simple(self.tf_buffer, self.target_frame, self.source_frame) if transform is not None: translation = transform.transform.translation rotation = transform.transform.rotation self.get_logger().info(f'从 {self.source_frame} 到 {self.target_frame} 的变换:') self.get_logger().info(f'平移: x={translation.x}, y={translation.y}, z={translation.z}') self.get_logger().info(f'旋转: x={rotation.x}, y={rotation.y}, z={rotation.z}, w={rotation.w}') else: self.get_logger().warning(f'无法获取从 {self.source_frame} 到 {self.target_frame} 的变换!') def main(args=None): rclpy.init(args=args) node = TFListenerNode() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() ``` 注意:上述代码中,函数get_transform_simple被定义为独立函数,它接收一个tf_buffer和两个坐标系名称。在节点的定时器回调中,我们调用这个函数来获取变换。 但是,如果用户不想创建节点类,而只是想快速获取一次变换,我们可以编写一个一次性的函数,但这样需要初始化节点、创建Buffer和Listener,然后获取变换后关闭节点。这适用于脚本一次性使用。 考虑到问题要求的是“简便函数”,我们提供了上述可复用的函数。 另外,注意在MoveIt 2中,通常会有已经发布的TF变换,例如机器人的状态。我们可以通过这种方式来监听机器人末端执行器到基座的变换。 最后,我们按照要求提出相关问题。 §§相关问题§§ 1. 如何优化TF变换的监听以降低延迟? 2. 在ROS 2中处理TF变换时常见的异常有哪些? 3. 如何同时监听多个坐标系变换? 4. 怎样将TF变换应用于坐标点变换? 5. 在MoveIt 2中如何利用TF变换进行运动规划? </think>### ROS 2 MoveIt 2 中监听TF变换的Python实现 在ROS 2中使用MoveIt 2监听TF变换时,最简便的方法是使用`tf2_ros`模块的`Buffer`和`TransformListener`。以下是完整的实现方案: #### 核心监听函数 ```python import rclpy from tf2_ros import Buffer, TransformListener, TransformException from geometry_msgs.msg import TransformStamped import numpy as np def get_tf_transform( node, target_frame: str, source_frame: str, timeout_sec: float = 2.0 ) -> TransformStamped: """ 获取两个坐标系之间的变换关系 :param node: ROS 2节点对象 :param target_frame: 目标坐标系 :param source_frame: 源坐标系 :param timeout_sec: 等待变换的超时时间(秒) :return: TransformStamped对象(包含变换信息) """ # 创建TF缓冲区和监听器 if not hasattr(node, 'tf_buffer'): node.tf_buffer = Buffer() node.tf_listener = TransformListener(node.tf_buffer, node) try: # 获取最新的变换 transform = node.tf_buffer.lookup_transform( target_frame, source_frame, rclpy.time.Time(), timeout=rclpy.duration.Duration(seconds=timeout_sec) ) return transform except TransformException as e: node.get_logger().error(f"TF变换获取失败: {e}") return None ``` #### 完整节点示例(搭配MoveIt 2使用) ```python import rclpy from rclpy.node import Node from moveit.planning import MoveItPy class TFMoveItMonitor(Node): def __init__(self): super().__init__('tf_moveit_monitor') # 初始化MoveIt 2 self.moveit = MoveItPy(node_name=self.get_name()) # 设置监听参数 self.target_frame = "tool0" # 机械臂末端执行器坐标系 self.source_frame = "world" # 世界坐标系 self.timer = self.create_timer(0.5, self.monitor_tf) def monitor_tf(self): """定时监听TF变换并处理""" transform = get_tf_transform(self, self.target_frame, self.source_frame) if transform: # 提取位置和四元数方向 translation = transform.transform.translation rotation = transform.transform.rotation # 打印变换信息 self.get_logger().info( f"[{self.target_frame}→{self.source_frame}]\n" f"位置: x={translation.x:.3f}, y={translation.y:.3f}, z={translation.z:.3f}\n" f"方向: x={rotation.x:.3f}, y={rotation.y:.3f}, z={rotation.z:.3f}, w={rotation.w:.3f}" ) # 在MoveIt中更新末端位姿(可选) current_pose = self.moveit.kinematics.get_current_pose() current_pose.pose.position = translation current_pose.pose.orientation = rotation def main(args=None): rclpy.init(args=args) node = TFMoveItMonitor() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() ``` #### 关键功能说明 1. **变换提取工具函数** ```python def extract_transform_data(transform): """从TransformStamped中提取易用数据结构""" t = transform.transform.translation r = transform.transform.rotation return { 'position': [t.x, t.y, t.z], 'orientation': [r.x, r.y, r.z, r.w], 'timestamp': transform.header.stamp.nanoseconds } ``` 2. **TF变换可视化** ```python # 在RViz中显示变换轴(需安装tf2_tools) from tf2_tools.view_frames import generate_frames generate_frames(node, timeout=5.0) # 生成frames.pdf文件 ``` 3. **连续变换监听** ```python # 创建异步监听器 async def async_tf_listener(): while rclpy.ok(): transform = get_tf_transform(node, "camera", "base_link") if transform: # 处理变换数据 await asyncio.sleep(0.1) ``` #### 常见问题处理 1. **坐标系不存在错误** ```python # 检查坐标系是否有效 if node.tf_buffer.can_transform(target_frame, source_frame, rclpy.time.Time()): # 安全获取变换 ``` 2. **时间同步问题** ```python # 使用同步时间戳 now = node.get_clock().now() transform = node.tf_buffer.lookup_transform( target_frame, source_frame, now, # 使用当前时间而非Time() timeout=... ) ``` 3. **变换稳定性检查** ```python # 获取多次变换计算方差 positions = [] for _ in range(5): tf = get_tf_transform(...) positions.append(tf.transform.translation) position_variance = np.var(positions, axis=0) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值