ROS 2边学边练(39)-- 调试tf2

本文详细介绍了在使用TF2进行小海龟机器人调试时,如何正确设置source_frame和target_frame,以及如何通过tf2相关工具(如tf2_echo,view_frames,tf2_monitor)诊断和解决转换错误。重点讲解了坐标系转换的概念和时间戳在transform操作中的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

        这节还是围绕tf2来进行,只不过针对调试相关,把之前有过一面之缘的问题再次拿出来重点说明一下,此过程中我们会碰到之前几期中认识但还不怎么熟络的朋友比如tf2_echo、tf2_monitor、view_frames。

动动手

        我们会利用一个有不少问题的例子来开展演练,通过分析问题解决问题的思路熟悉下tf2调试的大体流程。

修改例程

        我们继续利用learning_tf2_cpp包,拷贝一份turtle_tf2_listener.cpp为turtle_tf2_listener_debug.cpp,将原句:

std::string toFrameRel = "turtle2";

改为

std::string toFrameRel = "turtle3";

将原句

try {
  t = tf_buffer_->lookupTransform(
    toFrameRel, fromFrameRel,
    tf2::TimePointZero);
} catch (const tf2::TransformException & ex) {

改为

try {
  t = tf_buffer_->lookupTransform(
    toFrameRel, fromFrameRel,
    this->now());
} catch (const tf2::TransformException & ex) {

        我们再来编写一个启动文件start_tf2_debug_demo_launch.py到launch文件夹中:

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration

from launch_ros.actions import Node

def generate_launch_description():
   return LaunchDescription([
      DeclareLaunchArgument(
         'target_frame', default_value='turtle1',
         description='Target frame name.'
      ),
      Node(
         package='turtlesim',
         executable='turtlesim_node',
         name='sim',
         output='screen'
      ),
      Node(
         package='learning_tf2_cpp',
         executable='turtle_tf2_broadcaster',
         name='broadcaster1',
         parameters=[
               {'turtlename': 'turtle1'}
         ]
      ),
      Node(
         package='learning_tf2_cpp',
         executable='turtle_tf2_broadcaster',
         name='broadcaster2',
         parameters=[
               {'turtlename': 'turtle2'}
         ]
      ),
      Node(
         package='learning_tf2_cpp',
         executable='turtle_tf2_listener_debug',
         name='listener_debug',
         parameters=[
               {'target_frame': LaunchConfiguration('target_frame')}
         ]
      ),
   ])

        将turtle_tf2_listener_debug可执行文件相关内容添加到CMakeLists.txt中:

add_executable(turtle_tf2_listener_debug src/turtle_tf2_listener_debug.cpp)
ament_target_dependencies(
    turtle_tf2_listener_debug
    geometry_msgs
    rclcpp
    tf2
    tf2_ros
    turtlesim
)

install(TARGETS
    turtle_tf2_listener_debug
    DESTINATION lib/${PROJECT_NAME})

再构建包,我们运行下这个debug例子看看。

$ros2 launch learning_tf2_cpp start_tf2_debug_demo_launch.py

        这会启动一只小海龟turtle1,同时在窗口的左下方,也会出现第二只小海龟turtle3,我们再在另外一个终端启动turtle_teleop_key控制turtle1的游动。

$ros2 run turtlesim turtle_teleop_key

        正常情况下,turtle3是会随着turtle1的步伐节奏游动的,但是实际情况是没有,并且报出如下的信息(Could not transform turtle3 to turtle1,目标帧turtle3不存在)。

        此处有个很容易搞懵逼的概念需要解释清楚,不知道大家注意到没有,我们明明是指定从源帧turtle1到目标帧turtle3的转换,怎么提示成了turle3->turtle1,turtle3到turtle1的转换呢?其实输出的提示并没有错,提示的原意是:turtle3转换到turtle1帧的视角(坐标系)。

        lookupTransform(target_frame, source_frame, ...),target_frame = turtle3,source_frame = turtle1,target_frame意为我们需要转换的坐标系(坐标框架或帧),source_frame意为数据来源的坐标系,也就是我们最终要落地的坐标系,target_frame统一到source_frame。

        首先,turtle1是第一只小海龟的坐标系,它游动时产生了位姿数据(turtle1坐标系中),其次,我们希望第二只小海龟(turtle3坐标系中)能追随第一只小海龟的运动,也就是如何将turtle3的数据转换体现到turtle1坐标系中。跟随的前提是这俩海龟得统一到同一个坐标系下才有意义,既然让turtle3跟随turtle1,那么就得turtle3转换到turtle1中,所以我们就得需要获取turtle3到turtle1的转换,才能让turtle3跑到turtle1的坐标系中一起遨游哇。

        再举个例子。

        假设你有一个移动机器人,它有一个激光雷达(LiDAR)传感器,该传感器安装在机器人的顶部,并且有一个固定的偏移量。激光雷达的数据是在它自己的坐标系(我们称之为lidar_frame)中获取的,但你可能想要将这些数据转换到机器人的基座坐标系(我们称之为base_link)中,以便进行导航或其他处理。

        在这个例子中,base_link就是source_frame,而lidar_frame就是target_frame。为了查看从lidar_framebase_link的变换(即如何将LiDAR数据从LiDAR的坐标系转换到机器人的基座坐标系),可以使用以下命令:ros2 run tf2_ros tf2_echo base_link lidar_frame   

        这个命令会不断地输出从lidar_framebase_link的变换,包括平移(translation)和旋转(rotation)。这个变换告诉你如何将LiDAR数据从lidar_frame的坐标系转换到base_link的坐标系。

        注意,虽然我们说“从lidar_framebase_link的变换”,但实际上这个变换是描述了如何将base_link中的数据(或坐标)转换到lidar_frame的视角,但这并不意味着你不能使用它来转换lidar_frame中的数据到base_link。在tftf2中,变换总是从一个父坐标系(通常是固定的或全局的坐标系)到一个子坐标系(通常是移动的或局部的坐标系),但你可以使用这个变换的逆来执行相反的操作。

确认我们对tf2的请求

        先看看我们的请求是否合适,打开turtle_tf2_listener_debug.cpp源文件,找到如下几行内容:

std::string to_frame_rel = "turtle3";
try {
  t = tf_buffer_->lookupTransform(
    toFrameRel, fromFrameRel,
    this->now());
} catch (const tf2::TransformException & ex) {

        从上面可以看出,我们提供给了lookupTransform函数3个参数,其中源坐标系为turtle1,目标坐标系为turtle3,特定时间为现在,意思是向tf2请求获取从turtle3到turtle1的当前的位姿转换数据(解释见上面的块引用),我们不就是要turtle3实时追随turtle1的步伐吗。看起来都挺正常,那问题出在哪呢?

抓帧检查

        同网络编程调试的抓包分析一样,我们也需要对帧进行捕获分析。

        首先利用tf2_echo工具看看turtle3与turtle1之间的转换情况。

$ros2 run tf2_ros tf2_echo turtle3 turtle1

 

        提示turtle3不存在(在我们之前的一篇博文中首次碰到这个问题,当时不清楚原因),明明代码里面都设置好了啊,怎么会不存在turtle3,搞笑呢。我们利用view_frames工具来瞅瞅情况。

$ros2 run tf2_tools view_frames

        在当前路径下找到刚生成的frames_2024-04-29_21.29.12.pdf(一般文件名带日期),打开,情况如下:

        从上面可以很清楚的看到,我们的ROS中确实没有turtle3(可能你会奇怪,我们不是明明在代码里指定了turtle3吗,怎么就没有了呢,我们还是需要再看一遍learning_tf2_listener_debug.cpp的内容,里面确实没有turtle3的孵化生成),只有通过服务方式孵化的turtle2。而且这样才能解释的通上面的target_frame(turtle2)是有自己的数据进行转换的,turtle3可没有任何数据啊,如何转换。

        我们需要再次修改下代码,将turtle3改为turtle2。重新构建后(记得source环境)再来启动看看。

$ros2 launch turtle_tf2 start_debug_demo.launch.py

 

        提示我们熟悉的时间问题了,Could not transform turtle2 to turtle1:Lookup would require extrapolation into the future. 

检查时间戳

        现在我们解决了帧名字不存在的问题,是时候看看时间戳了。我们现在尝试获取当前turtle1与turtle2之间的转换数据,为了捕获实时的数据,我们需要使用tf2_monitor工具来监视对应的帧情况。

$ros2 run tf2_ros tf2_monitor turtle2 turtle1

(返回信息开头又提示target_frame turtle2不存在,说实话我也有点茫然了,再次通过view_frames工具查看turtle2是存在的,有了解的同学可以评论区告诉一下啊)

        我们先来看看上面的其他信息。这里的关键部分是turtle2到turtle1的变换链的延迟(上篇有解释过)。输出显示平均延迟大约为3毫秒。这意味着tf2只能在过去3毫秒后才能在这两个海龟之间进行变换。所以,如果我们要求tf2提供3毫秒前的海龟之间的变换,而不是现在的变换,tf2有时候能够给我们一个答案。

        我们来修改下时间来获取100ms之前(时间足够长了,实际情况不需要这么长)的转换,如下:

try {
  t = tf_buffer_->lookupTransform(
    toFrameRel, fromFrameRel,
    this->now() - rclcpp::Duration::from_seconds(0.1));
} catch (const tf2::TransformException & ex) {

        再次恢复正常。但我们修改时间的方法不是太推荐,往往我们会使用下面的写法:

try {
  t = tf_buffer_->lookupTransform(
    toFrameRel, fromFrameRel,
    tf2::TimePointZero);
} catch (const tf2::TransformException & ex) {

 或

try {
  t = tf_buffer_->lookupTransform(
    toFrameRel, fromFrameRel,
    tf2::TimePoint());
} catch (const tf2::TransformException & ex) {

或者下面这种带超时参数写法(我们在使用时间参数里用过的):

try {
  t = tf_buffer_->lookupTransform(
    toFrameRel, fromFrameRel,
    this->now(),
    rclcpp::Duration::from_seconds(0.05));
} catch (const tf2::TransformException & ex) {

        以上就是今天的主要内容,重点是对于source_frame、target_frame转换的理解以及如何通过tf2_echo、view_frames以及tf2_monitor工具来帮助我们分析问题所在。

本篇完。

<think>好的,用户的问题是关于在ROS2中安装和使用tf2的教程或指导。我需要先整理已有的信息,然后根据提供的引用内容给出合适的回答。 首先,用户提供的引用中提到了安装依赖包的指令,包括ros-humble-turtle-tf2-py、ros-humble-tf2-ros等,这应该是安装tf2相关包的关键步骤。需要确认这些包是否适用于用户的ROS2版本,比如Humble。用户可能需要先通过apt-get安装这些包,然后再进行配置和使用。 接下来,引用[1]提到需要编写监听器,并继续下一教程,但用户主要关注安装和使用方法。因此,回答应分步骤,先安装必要的包,然后介绍如何广播和监听变换,可能还需要提到使用tf2_echo工具进行验证。 需要确保回答结构清晰,步骤明确。安装部分可以直接引用提供的apt-get命令,然后说明如何编写广播器和监听器的示例,可能参考turtle_tf2_py的示例代码。同时,提醒用户可能需要使用rviz2tf2_tools来可视化或调试变换。 另外,用户可能需要生成相关问题,比如安装依赖项失败怎么办,如何验证安装成功,或者如何创建自定义的tf2广播器和监听器。这些问题需要与安装和使用过程相关,帮助用户进一步解决问题。 最后,确保所有代码块使用正确的格式,行内数学公式用$...$,但这里可能不需要数学公式,所以重点放在代码和命令行指令的正确呈现上。同时,引用标识要自然地放在引用内容的末尾,如[^1][^2]。 总结回答步骤: 1. 安装依赖包,使用提供的sudo apt-get命令。 2. 说明安装后的验证方法,如运行示例或使用tf2_echo。 3. 提供编写广播器和监听器的基本指导,参考ROS2官方教程。 4. 生成相关问题,涵盖安装、验证、使用中的常见问题。 需要确保所有信息准确,特别是包名称是否与用户ROS2版本对应,比如Humble使用ros-humble-*,其他版本可能需要调整。如果用户未提及ROS2版本,可能需要提醒他们根据版本选择合适的包,但根据引用内容,这里假设是Humble。</think>在ROS2中安装和使用`tf2`的步骤如下: --- ### 一、安装依赖包 1. 执行以下命令安装`tf2`相关功能包及工具: ```bash sudo apt-get install ros-humble-rviz2 ros-humble-turtle-tf2-py ros-humble-tf2-ros ros-humble-tf2-tools ros-humble-turtlesim ``` 此命令会安装: - `ros-humble-tf2-ros`: tf2ROS2接口库 - `ros-humble-turtle-tf2-py`: 基于乌龟示例的tf2演示(Python版) - `ros-humble-tf2-tools`: tf2调试工具(如`tf2_echo`) --- ### 二、验证安装 1. 运行乌龟示例: ```bash ros2 launch turtle_tf2_py turtle_tf2_demo.launch.py ``` 此时会弹出两个窗口:乌龟仿真界面(`turtlesim`)和键盘控制节点。通过方向键控制乌龟移动,可观察到另一只乌龟跟随运动,这是通过`tf2`广播和监听坐标变换实现的。 2. 使用`tf2_echo`查看坐标变换: ```bash ros2 run tf2_ros tf2_echo world turtle1 ``` 此命令会实时输出从`world`到`turtle1`的位姿变换矩阵。 --- ### 三、编写自定义广播器与监听器 1. **广播器**(发布坐标变换): ```python # 示例:广播乌龟的位姿 from geometry_msgs.msg import TransformStamped import rclpy from rclpy.node import Node from tf2_ros import TransformBroadcaster class TurtleTf2Broadcaster(Node): def __init__(self): super().__init__(&#39;turtle_tf2_broadcaster&#39;) self.tf_broadcaster = TransformBroadcaster(self) # 此处需订阅乌龟位姿并调用广播函数 ``` 2. **监听器**(获取坐标变换): ```python # 示例:监听两个坐标系间的变换 from tf2_ros import TransformListener, Buffer class TurtleTf2Listener(Node): def __init__(self): super().__init__(&#39;turtle_tf2_listener&#39;) self.tf_buffer = Buffer() self.tf_listener = TransformListener(self.tf_buffer, self) # 使用self.tf_buffer.lookup_transform()获取变换 ``` 完整代码可参考官方`turtle_tf2_py`示例[^1]。 --- ### 四、调试工具 1. **RViz2可视化**: ```bash ros2 run rviz2 rviz2 -d $(ros2 pkg prefix turtle_tf2_py)/share/turtle_tf2_py/rviz/turtle_rviz.rviz ``` 在RViz中可直观查看坐标系关系。 2. **查看坐标系树**: ```bash ros2 run tf2_tools view_frames.py ``` 生成`frames.pdf`文件描述坐标系拓扑结构。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值