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

news2024/12/23 9:42:10

前言

        这节还是围绕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工具来帮助我们分析问题所在。

本篇完。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1633703.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python-100-Days: Day06 Functions and Modules

函数的作用 编程大师Martin Fowler先生曾经说过:“代码有很多种坏味道,重复是最坏的一种!”,要写出高质量的代码首先要解决的就是重复代码的问题。可以将特定的功能封装到一个称之为“函数”的功能模块中,在需要的时候…

JavaScript代码挑战#4

// 编程挑战 #4 /* 朱莉亚和凯特仍在研究狗,这次她们研究的是狗是否吃得太多或太少。 吃得太多意味着狗当前的食物份量比推荐份量大,吃得太少则相反。 吃得适量意味着狗当前的食物份量在推荐份量的正负 10% 的范围内(参见提示)。 …

企业邮箱哪个性价比高?2024年国内五大企业邮箱功能、价格对比

对于企业来说,更换企业邮箱的成本很高,包括企业邮箱数据迁移,新的通讯录导入等,都会耗时耗力。那么选择能稳定且性价比高的企业邮箱就至关重要,国内的Zoho Mail企业邮箱、网易企业邮箱、阿里企业邮箱、腾讯企业邮箱和新…

2024年宠物行业未来发展趋势(宠物行业增长风口深度报告)

近期,小红书联合第一财经发布了宠物行业洞察报告。报告中指出了宠物行业的三大消费新趋势:科学养宠、专宠专用和双向奔赴。而实际上,这三大趋势在2024年已经有明显的凸显。 趋势一:科学养宠 科学养宠主要体现在宠物主粮的选择上&…

Linux进程概念(七):进程替换 自主shell的编写

目录 进程替换 六种替换函数 自主shell的编写 shell解析命令行字符串的过程 创建makefile 打印输出命令行 获取与分割命令行字符串 多次执行命令 完善工作 完整代码 进程替换 原因:fork创建子进程后,执行的是和父进程相同的代码,…

Spring6 当中 获取 Bean 的四种方式

1. Spring6 当中 获取 Bean 的四种方式 文章目录 1. Spring6 当中 获取 Bean 的四种方式每博一文案1.1 第一种方式:通过构造方法获取 Bean1.2 第二种方式:通过简单工厂模式获取 Bean1.3 第三种方式:通过 factory-bean 属性获取 Bean1.4 第四种…

一例MFC文件夹病毒的分析

概述 这是一个MFC写的文件夹病毒,通过感染USB设备传播,感染后,会向c2(fecure.info:443)请求指令来执行。 样本的基本信息 Verified: Unsigned Link date: 19:52 2007/7/5 MachineType: 32-bit MD5: 4B463901E5858ADA9FED28FC5…

基于SpringBoot+Vue笔记记录分享网站设计与实现

项目介绍: 信息数据从传统到当代,是一直在变革当中,突如其来的互联网让传统的信息管理看到了革命性的曙光,因为传统信息管理从时效性,还是安全性,还是可操作性等各个方面来讲,遇到了互联网时代…

Docker-Compose单机多容器应用编排与管理

前言 Docker Compose 作为 Docker 生态系统中的一个重要组件,为开发人员提供了一种简单而强大的方式来定义和运行多个容器化应用。本文将介绍 Docker Compose 的使用背景、优劣势以及利用 Docker Compose 简化应用程序的部署和管理。 目录 一、Docker Compose 简…

闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子

前言 一个在实际应用中 EF Core 集成 FluentValidation 进行数据校验的例子。 Step By Step 步骤 创建一个 Asp.Net Core WebApi 项目 引用以下 Nuget 包 FluentValidation.AspNetCore Microsoft.AspNetCore.Identity.EntityFrameworkCore Microsoft.EntityFrameworkCore.Re…

leetcode51.N皇后(困难)-回溯法

思路 都知道n皇后问题是回溯算法解决的经典问题,但是用回溯解决多了组合、切割、子集、排列问题之后,遇到这种二维矩阵还会有点不知所措。 首先来看一下皇后们的约束条件: 不能同行不能同列不能同斜线 确定完约束条件,来看看究…

【Linux】yum、vim

🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12625432.html 目录 Linux 软件包管理器 yum 什么是软件包 查看软件包 如何安装软件 如何卸载软…

Apache Seata基于改良版雪花算法的分布式UUID生成器分析1

title: Seata基于改良版雪花算法的分布式UUID生成器分析 author: selfishlover keywords: [Seata, snowflake, UUID] date: 2021/05/08 本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。 Seata基于改良版雪花算法的分布式UUID生成器分析…

浅析扩散模型与图像生成【应用篇】(十八)——ControlNet

18. Adding Conditional Control to Text-to-Image Diffusion Models 现有的文生图模型如Stable Diffusion通常需要人工输入非常准确的提示词,而且生成的结果还是完全随机不可控制的,只能通过生成多个结果,再从中选取最佳方案。而ControlNet的…

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器(Http测试板块)

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器(Http测试板块) 一、使用Http网页界面1、main.cc原码和index.html原码2、运行结果(1)测试结果1:用index.html内部的代码(2&#xf…

JSON.toJSONString() 输出 “$ref“:“$[0]“问题解决及原因分析

一、背景 在构建一个公共的批处理方法类的时候,在测试输出的时候,打印了" r e f " : " ref":" ref":"[0][0]"的内容,这让我比较疑惑。不由得继续了下去… 二、问题分析 首先,我们需要…

MySQL Binlog 闪回与分析

文章目录 前言1. 修改 event 实现闪回1.1 binlog 结构1.2 闪回案例1.3 方法总结 2. 解析文本闪回2.1 mysqlbinlog2.2 闪回案例2.3 方法总结 3. 在线订阅闪回3.1 mysql-replication3.2 binlog2sql3.3 方法总结 4. Binlog 分析方法4.1 分析场景4.2 辅助定位事务4.3 方法总结 5. 平…

性能监控之prometheus+grafana搭建

前言 Prometheus和Grafana是两个流行的开源工具,用于监控和可视化系统和应用程序的性能指标。它们通常一起使用,提供了强大的监控和数据可视化功能。 Prometheus Prometheus是一种开源的系统监控和警报工具包。它最初由SoundCloud开发,并于…

基于SSM+Jsp+Mysql的汽车租赁系统的设计与实现

开发语言:Java框架:ssm技术:JSPJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包…

Python爬虫(入门版)

1、爬虫是什么 简单的来说:就是用程序获取网络上数据。 2、爬虫的原理 如果要获取网络上数据,我们要给爬虫一个网址(程序中通常叫URL),爬虫发送一个HTTP请求给目标网页的服务器,服务器返回数据给客户端&am…