base_lcoal_planner的LocalPlannerUtil类中getLocalPlan函数详解

news2024/11/16 22:28:38

   本文主要介绍base_lcoal_planner功能包中LocalPlannerUtil类的getLocalPlan函数,以及其调用的transformGlobalPlan函数、prunePlan函数的相关内容


   一、getLocalPlan函数

   getLocalPlan函数的源码如下:

bool LocalPlannerUtil::getLocalPlan(const geometry_msgs::PoseStamped& global_pose, std::vector<geometry_msgs::PoseStamped>& transformed_plan) {
  //get the global plan in our frame
  if(!base_local_planner::transformGlobalPlan(
      *tf_,
      global_plan_,
      global_pose,
      *costmap_,
      global_frame_,
      transformed_plan)) {
    ROS_WARN("Could not transform the global plan to the frame of the controller");
    return false;
  }

  //now we'll prune the plan based on the position of the robot
  if(limits_.prune_plan) {
    base_local_planner::prunePlan(global_pose, transformed_plan, global_plan_);
  }
  return true;
}

   下面对函数的主要功能和步骤进行介绍

   1. 函数签名:

bool LocalPlannerUtil::getLocalPlan(const geometry_msgs::PoseStamped& global_pose, std::vector<geometry_msgs::PoseStamped>& transformed_plan)

   2. 函数参数:

   - global_pose:机器人在全局坐标系中的当前位置,通常表示为位姿信息(包括位置和方向)。

   - transformed_plan:函数将转换后的局部路径规划存储在此参数中。

   3. 函数功能:

   这个函数的主要功能是获取机器人的局部路径规划的初始参考路径,并存储在 transformed_plan 中。函数执行以下步骤:

   - 首先,它调用名为 transformGlobalPlan 的函数来将全局路径规划 global_plan_ 转换为机器人的局部路径规划,结果存储在 transformed_plan 中。注意:transformed_plan中的坐标依然为全局坐标系

   - 如果转换失败(transformGlobalPlan 返回 false),函数打印警告消息并返回 false

   - 接下来,如果 limits_.prune_plan 为真,函数会调用名为 prunePlan 的函数,以根据机器人的当前位置修剪局部路径规划。这将确保局部路径规划与机器人的当前位置对齐。

   - 最后,函数返回 true,表示成功获取局部路径规划。

   4. 返回值:

   - 如果获取局部路径规划成功,函数返回 true

   - 如果获取失败,函数返回 false,并在需要时输出警告消息。

   5. 函数效果示意

   为了方便大家理解,我从实际运行过程中导出了转换前后的数据,绘制了以下图像,下图中的绿色虚线是一些列全局路径规划器规划的路径,蓝色实线是其对应的经过getLocalPlan函数后,得到的局部初始路径transformed_plan,从图中可以发现getLocalPlan函数对全局路径进行了裁剪,仅取与当前点距离小于局部地图一半尺寸的部分,在下图示例中,局部地图的设定尺寸为5x5m,下图中红色虚线圆圈的半径为5/2=2.5m。

   需要注意的是,尽管transformed_plan中存储着局部初始路径,但其坐标的坐标系依然为全局坐标系


   二、transformGlobalPlan 函数

   transformGlobalPlan 函数的源码如下:

bool transformGlobalPlan(
      const tf2_ros::Buffer& tf,
      const std::vector<geometry_msgs::PoseStamped>& global_plan,
      const geometry_msgs::PoseStamped& global_pose,
      const costmap_2d::Costmap2D& costmap,
      const std::string& global_frame,
      std::vector<geometry_msgs::PoseStamped>& transformed_plan){
    transformed_plan.clear();

    if (global_plan.empty()) {
      ROS_ERROR("Received plan with zero length");
      return false;
    }

    const geometry_msgs::PoseStamped& plan_pose = global_plan[0];
    try {
      // get plan_to_global_transform from plan frame to global_frame
      geometry_msgs::TransformStamped plan_to_global_transform = tf.lookupTransform(global_frame, ros::Time(),
          plan_pose.header.frame_id, plan_pose.header.stamp, plan_pose.header.frame_id, ros::Duration(0.5));

      //let's get the pose of the robot in the frame of the plan
      geometry_msgs::PoseStamped robot_pose;
      tf.transform(global_pose, robot_pose, plan_pose.header.frame_id);

      //we'll discard points on the plan that are outside the local costmap
      double dist_threshold = std::max(costmap.getSizeInCellsX() * costmap.getResolution() / 2.0,
                                       costmap.getSizeInCellsY() * costmap.getResolution() / 2.0);

      unsigned int i = 0;
      double sq_dist_threshold = dist_threshold * dist_threshold;
      double sq_dist = 0;

      //we need to loop to a point on the plan that is within a certain distance of the robot
      while(i < (unsigned int)global_plan.size()) {
        double x_diff = robot_pose.pose.position.x - global_plan[i].pose.position.x;
        double y_diff = robot_pose.pose.position.y - global_plan[i].pose.position.y;
        sq_dist = x_diff * x_diff + y_diff * y_diff;
        if (sq_dist <= sq_dist_threshold) {
          break;
        }
        ++i;
      }

      geometry_msgs::PoseStamped newer_pose;

      //now we'll transform until points are outside of our distance threshold
      while(i < (unsigned int)global_plan.size() && sq_dist <= sq_dist_threshold) {
        const geometry_msgs::PoseStamped& pose = global_plan[i];
        tf2::doTransform(pose, newer_pose, plan_to_global_transform);

        transformed_plan.push_back(newer_pose);

        double x_diff = robot_pose.pose.position.x - global_plan[i].pose.position.x;
        double y_diff = robot_pose.pose.position.y - global_plan[i].pose.position.y;
        sq_dist = x_diff * x_diff + y_diff * y_diff;

        ++i;
      }
    }
    catch(tf2::LookupException& ex) {
      ROS_ERROR("No Transform available Error: %s\n", ex.what());
      return false;
    }
    catch(tf2::ConnectivityException& ex) {
      ROS_ERROR("Connectivity Error: %s\n", ex.what());
      return false;
    }
    catch(tf2::ExtrapolationException& ex) {
      ROS_ERROR("Extrapolation Error: %s\n", ex.what());
      if (!global_plan.empty())
        ROS_ERROR("Global Frame: %s Plan Frame size %d: %s\n", global_frame.c_str(), (unsigned int)global_plan.size(), global_plan[0].header.frame_id.c_str());

      return false;
    }

    return true;
  }

  bool getGoalPose(const tf2_ros::Buffer& tf,
      const std::vector<geometry_msgs::PoseStamped>& global_plan,
      const std::string& global_frame, geometry_msgs::PoseStamped &goal_pose) {
    if (global_plan.empty())
    {
      ROS_ERROR("Received plan with zero length");
      return false;
    }

    const geometry_msgs::PoseStamped& plan_goal_pose = global_plan.back();
    try{
      geometry_msgs::TransformStamped transform = tf.lookupTransform(global_frame, ros::Time(),
                         plan_goal_pose.header.frame_id, plan_goal_pose.header.stamp,
                         plan_goal_pose.header.frame_id, ros::Duration(0.5));

      tf2::doTransform(plan_goal_pose, goal_pose, transform);
    }
    catch(tf2::LookupException& ex) {
      ROS_ERROR("No Transform available Error: %s\n", ex.what());
      return false;
    }
    catch(tf2::ConnectivityException& ex) {
      ROS_ERROR("Connectivity Error: %s\n", ex.what());
      return false;
    }
    catch(tf2::ExtrapolationException& ex) {
      ROS_ERROR("Extrapolation Error: %s\n", ex.what());
      if (global_plan.size() > 0)
        ROS_ERROR("Global Frame: %s Plan Frame size %d: %s\n", global_frame.c_str(), (unsigned int)global_plan.size(), global_plan[0].header.frame_id.c_str());

      return false;
    }
    return true;
  }

   下面对函数的主要功能和步骤进行介绍

   1. 函数签名:

bool transformGlobalPlan(
  const tf2_ros::Buffer& tf,
  const std::vector<geometry_msgs::PoseStamped>& global_plan,
  const geometry_msgs::PoseStamped& global_pose,
  const costmap_2d::Costmap2D& costmap,
  const std::string& global_frame,
  std::vector<geometry_msgs::PoseStamped>& transformed_plan
)

   2. 参数:

   - tf:tf2_ros::Buffer 对象,用于进行坐标变换(Transform)的查询。

   - global_plan:全局路径规划的一系列位姿(姿态信息),通常包含从机器人当前位置到目标点的路径。

   - global_pose:机器人在全局坐标系中的当前位置,通常表示为位姿信息(包括位置和方向)。

   - costmap:局部代价地图,用于检查路径规划中的点是否在局部地图中。

   - global_frame:全局坐标系的名称,通常是机器人的地图坐标系。

   - transformed_plan:函数将转换后的全局路径规划存储在此参数中。

   3. 函数功能:

   这个函数的主要功能是将全局路径规划 global_plan 转换为局部路径规划,并存储在 transformed_plan 中。函数执行以下步骤:

   - 首先,它清空 transformed_plan,以确保在填充之前不包含任何数据。

   - 如果 global_plan 为空,它将打印错误消息并返回 false,表示接收到零长度的路径规划。

   - 获取全局路径规划的第一个位姿 plan_pose,通常表示目标点。

   - 使用 tf 对象查询全局坐标系 global_frameplan_pose 所在坐标系的坐标变换。这里暂时没有看明白,貌似转换后还是全局坐标系,不清楚此操作的作用

   - 计算机器人当前位置 robot_pose 在路径规划坐标系中的姿态,以便确定机器人在哪个位置附近需要开始路径转换。

   - 计算一个距离阈值 dist_threshold,它通常是局部代价地图的一半尺寸,用于确定哪些点需要保留在局部路径规划中。

   - 使用一个循环,找到全局路径规划中距离机器人位置不超过 dist_threshold 的点,并将它们添加到 transformed_plan 中,同时计算距离并更新 i

   - 最后,函数处理可能的异常,如坐标变换失败,并返回 false,以指示转换失败。

   4. 返回值:

   - 如果路径转换成功,函数返回 true,并将转换后的路径存储在 transformed_plan 中。

   - 如果出现异常或全局路径规划为空,函数返回 false



   三、prunePlan函数

   prunePlan函数的源码如下:

  void prunePlan(const geometry_msgs::PoseStamped& global_pose, std::vector<geometry_msgs::PoseStamped>& plan, std::vector<geometry_msgs::PoseStamped>& global_plan){
    ROS_ASSERT(global_plan.size() >= plan.size());
    std::vector<geometry_msgs::PoseStamped>::iterator it = plan.begin();
    std::vector<geometry_msgs::PoseStamped>::iterator global_it = global_plan.begin();
    while(it != plan.end()){
      const geometry_msgs::PoseStamped& w = *it;
      // Fixed error bound of 2 meters for now. Can reduce to a portion of the map size or based on the resolution
      double x_diff = global_pose.pose.position.x - w.pose.position.x;
      double y_diff = global_pose.pose.position.y - w.pose.position.y;
      double distance_sq = x_diff * x_diff + y_diff * y_diff;
      if(distance_sq < 1){
        ROS_DEBUG("Nearest waypoint to <%f, %f> is <%f, %f>\n", global_pose.pose.position.x, global_pose.pose.position.y, w.pose.position.x, w.pose.position.y);
        break;
      }
      it = plan.erase(it);
      global_it = global_plan.erase(global_it);
    }
  }

   下面对函数的主要功能和步骤进行介绍

   1. 函数签名:

void prunePlan(const geometry_msgs::PoseStamped& global_pose, std::vector<geometry_msgs::PoseStamped>& plan, std::vector<geometry_msgs::PoseStamped>& global_plan)

   2. 参数:

   - global_pose:机器人在全局坐标系中的当前位置,通常表示为位姿信息(包括位置和方向)。

   - plan:一个存储局部路径规划的一系列位姿(姿态信息),这是局部路径规划器生成的候选路径。

   - global_plan:全局路径规划的一系列位姿,通常包含从起点到终点的路径。

   3. 函数功能:

   这个函数的主要目的是将planglobal_plan修剪,以便将全局路径规划和局部路径规划的起点对齐。它的实现逻辑如下:

   - 首先,函数使用断言 ROS_ASSERT 来确保global_plan的大小大于等于plan的大小,因为全局规划必须包括局部规划。

   - 接下来,函数使用迭代器遍历planglobal_plan中的位姿信息。

   - 对于每个位姿,它计算全局位置 global_pose 与该位姿的距离的平方,这是通过计算差值并计算欧几里德距离的平方来完成的。

   - 如果距离的平方小于1(可以根据需要更改阈值),则认为机器人已经接近路径的一部分,函数会停止修剪,这通常意味着机器人已经到达或非常接近路径上的某个点。

   - 如果距离的平方大于等于1,函数会将planglobal_plan中的当前位姿从列表中删除,以便继续修剪下一个位姿。

   4. 总结:

   该函数的目的是将局部路径规划器生成的位姿列表plan修剪到机器人当前位置附近的部分,并且同时将全局路径规划器生成的路径global_plan做相应的修剪,以确保局部路径规划与全局路径规划对齐。这是导航中的一个常见操作,以确保机器人沿着规划路径前进。如果机器人远离路径,函数将删除不再需要的位姿,以节省计算资源。


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

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

相关文章

网络协议--广播和多播

12.1 引言 在第1章中我们提到有三种IP地址&#xff1a;单播地址、广播地址和多播地址。本章将更详细地介绍广播和多播。 广播和多播仅应用于UDP&#xff0c;它们对需将报文同时传往多个接收者的应用来说十分重要。TCP是一个面向连接的协议&#xff0c;它意味着分别运行于两主…

用 Rust 和 cURL 库制作一个有趣的爬虫

目录 一、介绍 二、准备工作 三、代码实现 四、解析 HTML 并提取特定元素示例 总结 本文将介绍如何使用 Rust 编程语言和 cURL 库制作一个有趣的网络爬虫。我们将通过实例代码来展示如何抓取网页内容、处理数据和解析 HTML 结构。同时&#xff0c;还将探讨爬虫技术的原理、…

语义分割的常用方法和评价准则

常用方法 目前主流的语义分割网络一般是遵循下采样,上采样,特征融合,然后重复该过程,最后经过softmax像素分类。 评价准则 语义分割的评价准则为: 1.像素精度(pixel accuracy):每一类像素正确分类的个数/ 每一类像素的实际个数。

Linux C语言开发-D4数据类型

数据类型分类 bool类型&#xff1a;非零为真&#xff08;true&#xff09;&#xff0c;零为假&#xff08;false&#xff09;&#xff0c;其在<stdbool.h>头文件中 2552的8次方-1 1272的7次方-1 -128的补码是&#xff1a;10000000 一定要注意长度和范围&#xff0c;防…

【C语言】实现通讯录管理系统

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家实现通讯录&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 前言二. 通讯录的实现2.1 写出基本框架2.2 制作menu菜单2.3 创建联系人和通讯录结构体2.4 …

BOA服务器(一):简介

在嵌入式设备的管理与交互中&#xff0c;基于Web方式的应用成为目前的主流&#xff0c;这种程序结构也就是大家非常熟悉的B/S结构&#xff0c;即在嵌入式设备上运行一个支持脚本或CGI功能的Web服务器&#xff0c;能够生成动态页面&#xff0c;在用户端只需要通过Web浏览器就可以…

Itheima-Springboot2【二刷】

1. Springboot parent和starter区别 parent&#xff1a;开发Springboot项目需要继承spring-boot-starter-parent&#xff0c;其中定义了若干个依赖管理&#xff08;坐标版本号&#xff09;&#xff0c;避免依赖版本冲突&#xff1b;starter&#xff1a;开发Springboot项目需要…

请解释一下React中的条件渲染(conditional rendering)。

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

麒麟KYLINOS桌面操作系统2303上安装tigervnc

原文链接&#xff1a;麒麟KYLINOS桌面操作系统2303上安装tigervnc hello&#xff0c;大家好啊&#xff0c;今天给大家带来在麒麟桌面操作系统2303上安装tigervnc的文章&#xff0c;本篇文章给大家讲述如何安装并且远程连接使用&#xff0c;后面会给大家更新如何将tigervnc做成桌…

【ARM Cortex-M 系列 4 番外篇 -- 常用 benchmark 介绍】

文章目录 1.1 CPU 性能测试 MIPS 计算1.1.1 Cortex-M7 CPI 1.2 benchmark 小节1.3.1 Geekbenck 介绍 1.3 编译参数配置 1.1 CPU 性能测试 MIPS 计算 每秒百万指令数 (MIPS)&#xff1a;在数据压缩测试中&#xff0c;MIPS 每秒测量一次 CPU 执行的低级指令的数量。越高越好&…

二十、设计模式之迭代器模式

目录 二十、设计模式之迭代器模式能帮我们干什么&#xff1f;主要解决什么问题&#xff1f;优缺点优点缺点&#xff1a; 使用的场景角色 实现迭代器模式定义迭代器容器实现可迭代接口迭代器实现使用 总结 二十、设计模式之迭代器模式 所属类型定义行为型提供一种方法顺序访问一…

uni-app:解决异步请求返回值问题

可以使用 Promise 或者回调函数来处理异步请求的返回值。 方法一&#xff1a; Promise处理异步请求的返回值 使用 Promise 可以将异步请求的结果通过 resolve 和 reject 返回&#xff0c;然后通过 .then() 方法获取成功的结果&#xff0c;通过 .catch() 方法获取错误信息。 …

ES Nested解释

参考博客 参考博客 nested类型是对象数据类型的专用版本&#xff0c;它允许对象数组以可以彼此独立查询的方式进行索引

React之diff原理

一、是什么 跟Vue一致&#xff0c;React通过引入Virtual DOM的概念&#xff0c;极大地避免无效的Dom操作&#xff0c;使我们的页面的构建效率提到了极大的提升 而diff算法就是更高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处 传统diff算法通过循环递归对节点进行依…

为什么亚马逊卖家一定要有独立站?新手低成本快速搭建跨境电商独立站完整图文教程

效果展示 翻译助手 一、购买域名 二、购买主机托管 三、搭建独立网站 四、网站装修设计 五、网站迁移 六、补充 前言&#xff1a;为什么亚马逊卖家一定要有独立站&#xff1f; 先来谈谈为什么亚马逊卖家一定得有独立站&#xff0c;从我一些个人经历来看&#xff0c;有独…

C语言程序设计——题目:用*号输出字母C的图案。程序分析:可先用‘*‘号在纸上写出字母C,再分行输出。

题目&#xff1a;用*号输出字母C的图案。 程序分析&#xff1a;可先用*号在纸上写出字母C&#xff0c;再分行输出。 #include<stdio.h> int main() {printf(" *****\n");printf(" *\n");printf("*\n");printf("*\n");printf(&…

如何一键核实验证身份证的真伪?

据报道&#xff0c;今年10月10日&#xff0c;广东省佛山市朱某因生活琐事与丈夫发生争吵&#xff0c;民警发现她的身份证有问题。 在民警打算进一步了解情况&#xff0c;查看夫妻二人的身份证件时&#xff0c;朱某的身份证引起了民警的注意。这张身份证表面很光滑&#xff0c;…

【JAVA学习笔记】34 - 房屋出租系统(综合性强)

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_chapter09/src/com/yinhai/houserent 一、项目需求说明 实现基于文本界面的房屋出租系统&#xff0c;能够实现对房屋信息的添加、修改和删除&#xff08;用数组实现&#xff09;&#xff0c;并能够…

大学兼职教师管理系统 用JAVA语言开发

一、项目介绍 基于VueSpringBootMySQL的大学兼职教师管理系统包含学生管理、教师管理、课程档案管理、课程评价管理、课程考勤管理、授课管理、课程成绩管理教龄/薪资分析可视化图表&#xff0c;还包含系统自带的用户管理、部门管理、角色管理、菜单管理、日志管理、数据字典管…

canal五部曲-canal是如何处理insert幂等性的

canal使用了Rocketmq来接收mysql采集的binlog的事件&#xff0c;做到采集和处理的解耦。同时满足一次采集多方消费的需求。那么既然使用到Rocketmq就一定会存在MQ消费超时或是处理失败MQ重发的问题。 那么canal是如何处理MQ重复消费幂等性问题的呢 一般&#xff0c;在业务上我…