ROS从入门到精通5-5:局部路径规划插件开发案例(以DWA算法为例)

news2024/9/28 7:15:35

目录

  • 0 专栏介绍
  • 1 局部规划插件制作框架
  • 2 DWA算法源码分析
    • 2.1 全局路径裁剪
    • 2.2 更新局部代价
    • 2.3 运行DWA算法
      • 2.3.1 构造动态窗口
      • 2.3.2 生成最优轨迹
    • 2.4 终点规划
  • 3 算法测试

0 专栏介绍

本专栏旨在通过对ROS的系统学习,掌握ROS底层基本分布式原理,并具有机器人建模和应用ROS进行实际项目的开发和调试的工程能力。

🚀详情:《ROS从入门到精通》


1 局部规划插件制作框架

参考ROS从入门到精通5-3:插件库与开发+实例分析开发插件。首先创建功能包my_planner用于生成自定义全局路径规划插件

  1. 构造基类:由于局部路径规划插件全部继承于nav_core功能包的BaseLocalPlanner类,因此无需构造

  2. 构造插件类:在local_planner/include中新建local_planner.h,继承自基类nav_core::BaseLocalPlanner,与全局规划器不同,这里需要实现更多接口

    • initialize:规划器初始化接口
    • setPlan:设置全局规划器规划的路径
    • computeVelocityCommands:计算运动指令,驱动机器人实际运动,在全局规划中只是规划了路径点,并没有指导机器人应该如何运动
    • isGoalReached:判断机器人是否到达终点
    namespace local_planner{
    	class LocalPlanner : public nav_core::BaseLocalPlanner{
    	public:
    	
    	    LocalPlanner();
    	    LocalPlanner(std::string name, tf2_ros::Buffer* tf,
    	                 costmap_2d::Costmap2DROS* costmap_ros);
    	    ~LocalPlanner();
    	
    	    void initialize(std::string name, tf2_ros::Buffer* tf,
    	                    costmap_2d::Costmap2DROS* costmap_ros);
    	
    	    bool setPlan(const std::vector<geometry_msgs::PoseStamped>& orig_global_plan);
    	
    	    bool computeVelocityCommands(geometry_msgs::Twist& cmd_vel);
    	
    	    bool isGoalReached();
    	private:
    	    costmap_2d::Costmap2DROS* costmap_ros_;
    	    tf2_ros::Buffer* tf_;
    	    bool initialized_;
    	};
    };
    
  3. 注册插件:在local_planner/src中新建local_planner.cpp使用PLUGINLIB_EXPORT_CLASS宏注册插件,限于篇幅不列出完整代码。

    PLUGINLIB_EXPORT_CLASS(local_planner::LocalPlanner, nav_core::BaseLocalPlanner)
    
    namespace local_planner{
    	LocalPlanner::LocalPlanner() : costmap_ros_(NULL), tf_(NULL), initialized_(false) {}
    	LocalPlanner::LocalPlanner(std::string name, tf2_ros::Buffer* tf,
    	                           costmap_2d::Costmap2DROS* costmap_ros)
    	    : costmap_ros_(NULL), tf_(NULL), initialized_(false)
    	{
    	    initialize(name, tf, costmap_ros);
    	}
    	
    	LocalPlanner::~LocalPlanner() {}
    	
    	void LocalPlanner::initialize(std::string name, tf2_ros::Buffer* tf,
    	                              costmap_2d::Costmap2DROS* costmap_ros)
    	{
    	    if(!initialized_)
    	    {
    	        tf_ = tf;
    	        costmap_ros_ = costmap_ros;
    	        initialized_ = true;
    	    }
    	}
    	...
    }
    
  4. 构建插件库.so:编译此功能包local_planner将会在根目录devel/lib中生成插件liblocal_planner.so

  5. 集成插件库到ROS:在功能包local_planner下创建local_planner_plugin.xml描述插件信息和库路径

    <library path="lib/liblocal_planner">
    	<class name ="local_planner/LocalPlanner" type ="local_planner::LocalPlanner" base_class_type= "nav_core::BaseLocalPlanner">
        <description>
          My planner
        </description>
      </class>
    </library>
    

    在功能包local_plannerpackage.xml中导出local_planner_plugin.xml

     <depend>nav_core</depend><!-- 注意此依赖,否则找不到自定义规划器 -->
     <!-- The export tag contains other, unspecified, tags -->
     <export>
         <nav_core plugin="${prefix}/local_planner_plugin.xml" />
     </export>
    
  6. 使用插件:在turtlebots_navigation中的move_base.launch新增一行,声明使用自定义插件

    <node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen">
       <param name="base_local_planner" value="local_planner/LocalPlanner"/>
    	...
    </node>
    

所有局部规划插件的编写均遵从上述框架。

2 DWA算法源码分析

DWA算法的核心逻辑集中在computeVelocityCommands函数中,接下来重点分析

2.1 全局路径裁剪

std::vector<geometry_msgs::PoseStamped> transformed_plan;
if (!planner_util_.getLocalPlan(current_pose_, transformed_plan))
{
  ROS_ERROR("Could not get local plan");
  return false;
}

这里裁剪函数getLocalPlan来自base_local_planner提供的工具类LocalPlannerUtil,这个类封装了规划器常用的数据结构——例如地图、tf树,以及一些常用的工具函数

getLocalPlan中分为两步:

  • transformGlobalPlan():将全局规划的路径通过tf坐标变换到局部代价地图坐标系
  • prunePlan():实际的裁剪函数,将机器人背后的路径点裁掉,因为这部分路径在规划中不使用

在这里插入图片描述

2.2 更新局部代价

因为每时每刻机器人的局部代价地图和运行的全局路径点不同,因此要进行更新

dp_->updatePlanAndLocalCosts(current_pose_, transformed_plan, costmap_ros_->getRobotFootprint());

updatePlanAndLocalCosts()内部主要进行了代价函数的更新,这些代价函数将用于轨迹评价,具体地,DWA中包含以下代价函数

对象名称数据类型说明
oscillation_costs_OscillationCostFunction尽量降低机器人在原地晃动的情况
obstacle_costs_ObstacleCostFunction防止机器人撞到障碍物
path_costs_MapGridCostFunction使机器人尽可能的贴近全局轨迹
goal_costs_MapGridCostFunction更倾向于选择接近局部目标点的轨迹
goal_front_costs_MapGridCostFunction尽可能地让机器人朝向全局目标
alignment_costs_MapGridCostFunction尽可能地让机器人朝向全局轨迹
twirling_costs_TwirlingCostFunction尽量不让机器人原地打转

2.3 运行DWA算法

dp_->updatePlanAndLocalCosts(current_pose_, transformed_plan, costmap_ros_->getRobotFootprint());

这个函数内部运行逻辑包含以下几步

2.3.1 构造动态窗口

generator_.initialise(pos,
        vel,
        goal,
        &limits,
        vsamples_);

在这个函数内部构造了采样窗口

max_vel[0] = std::min(max_vel_x, vel[0] + acc_lim[0] * sim_period_);
max_vel[1] = std::min(max_vel_y, vel[1] + acc_lim[1] * sim_period_);
max_vel[2] = std::min(max_vel_th, vel[2] + acc_lim[2] * sim_period_);

min_vel[0] = std::max(min_vel_x, vel[0] - acc_lim[0] * sim_period_);
min_vel[1] = std::max(min_vel_y, vel[1] - acc_lim[1] * sim_period_);
min_vel[2] = std::max(min_vel_th, vel[2] - acc_lim[2] * sim_period_);

再通过迭代器

VelocityIterator x_it(min_vel[0], max_vel[0], vsamples[0]);
VelocityIterator y_it(min_vel[1], max_vel[1], vsamples[1]);
VelocityIterator th_it(min_vel[2], max_vel[2], vsamples[2]);

遍历所有可能的速度组合,存入sample_params_

2.3.2 生成最优轨迹

std::vector<base_local_planner::Trajectory> all_explored;
scored_sampling_planner_.findBestTrajectory(result_traj_, &all_explored);

findBestTrajectory中主要分为两步:

  • 轨迹生成
    gen_->nextTrajectory(loop_traj);
    
    这里的gen_就是2.3.1节的generator_,所以它会通过已构造的sample_params_遍历所有可能的速度组合,通过其generateTrajectory()函数生成轨迹返回
  • 轨迹评价
    loop_traj_cost = scoreTrajectory(loop_traj, best_traj_cost);
    
    这里实际上调用代价函数栈对轨迹进行逐一评价
    for (std::vector<TrajectoryCostFunction *>::iterator score_function = critics_.begin(); score_function != critics_.end(); ++score_function)
    {
      TrajectoryCostFunction *score_function_p = *score_function;
      double cost = score_function_p->scoreTrajectory(traj);
      ...
    }
    
    而这些代价函数就是2.2节所列出的,它们已经在上一步完成了更新。

最后选择代价最小的轨迹返回即可。

2.4 终点规划

在机器人靠近终点时,可以简化规划流程,直接朝着终点前进,LatchedStopRotateController就是base_local_planner提供用于执行该功能的工具类,其中包含若干工具函数,在编写自定义插件时很有用:

  • isPositionReached:判断机器人是否到达指定位置
  • isGoalReached:判断机器人是否到达指定位姿
  • stopWithAccLimits:机器人逐渐减速到指定位置
  • rotateToGoal:机器人旋转到指定位姿
  • computeVelocityCommandsStopRotate:机器人逐渐减速到指定位置,并旋转到指定位姿

在DWA中应用如下:

  1. 临近终点时的规划
    if (latchedStopRotateController_.isPositionReached(&planner_util_, current_pose_))
    {
      // publish an empty plan because we've reached our goal position
      std::vector<geometry_msgs::PoseStamped> local_plan;
      std::vector<geometry_msgs::PoseStamped> transformed_plan;
      publishGlobalPlan(transformed_plan);
      publishLocalPlan(local_plan);
      base_local_planner::LocalPlannerLimits limits = planner_util_.getCurrentLimits();
      return latchedStopRotateController_.computeVelocityCommandsStopRotate(
          cmd_vel,
          limits.getAccLimits(),
          dp_->getSimPeriod(),
          &planner_util_,
          odom_helper_,
          current_pose_,
          [this](auto pos, auto vel, auto vel_samples)
          { return dp_->checkTrajectory(pos, vel, vel_samples); });
    }
    
  2. 判断是否到达终点
    bool DWAPlannerROS::isGoalReached() {
       ...
        if (latchedStopRotateController_.isGoalReached(&planner_util_, odom_helper_, current_pose_))
        {
          ROS_INFO("Goal reached");
          return true;
        }
        else
        {
          return false;
        }
      }
    

3 算法测试

在这里插入图片描述
在这里插入图片描述

本文的完整工程代码联系下方博主名片获取


🔥 更多精彩专栏

  • 《ROS从入门到精通》
  • 《Pytorch深度学习实战》
  • 《机器学习强基计划》
  • 《运动规划实战精讲》

👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇

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

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

相关文章

区块链知识系列 - 系统学习EVM(四)-zkEVM

区块链知识系列 - 系统学习EVM(一) 区块链知识系列 - 系统学习EVM(二) 区块链知识系列 - 系统学习EVM(三) 今天我们来聊聊 zkEVM、EVM 兼容性 和 Rollup 是什么&#xff1f; 1. 什么是 Rollup rollup顾名思义&#xff0c;就是把一堆交易卷&#xff08;rollup&#xff09;起来…

oracle官方下载历史版本JDK版本

背景 日常工作中由于一些特殊原因&#xff0c;我们需要下载指定系统指定位数指定版本的jdk&#xff0c;这个时候去网上搜索下载就会遇到各种坑&#xff0c;病毒、诱导连接、诱导关注/注册、付费、错误版本等&#xff0c;所以最好的办法是去官网下载&#xff0c;下面列举两种方式…

Allegro中如何删除多余D码操作指导

Allegro中如何删除多余D码操作指导 用Allegro做PCB设计的时候,在最后输出生产文件的时候,必须清除多余的D码,不让多余的D码出现在D码文件中,类似下图 如何清除多余D码,具体操作如下 点击Tools点击Padstack

WIN11/win10+Azure Kinect DK详细驱动配置教程(亲测)

本人3000多大洋 买了一台 Azure Kinect DK设备&#xff0c;打算研究研究人体姿态。今天配置一下&#xff0c;网上的教程不少&#xff0c;有的过期教程&#xff0c;有的和我的不匹配&#xff0c;所以,只能参考他们的&#xff0c;取其精华 去其糟粕。下面 开始&#xff0c;这里先…

C#.Net正则表达式学习笔记

C#.Net正则表达式学习笔记 在处理字符串时&#xff0c;你会经常有查找符合特定条件的字符串的需求&#xff0c;比如判断一串电话号码是否符合格式、一个邮箱是否符合格式、一个密码是否包含了字母大小写等等。 正则表达式(Regular expressions)用于匹配文本&#xff0c;使用一…

[2023]自动化测试框架完整指南

所有软件在提供给用户之前都必须经过测试。软件测试是开发生命周期中必不可少的一步因为它确保用户必须收到符合其开发目的的高质量产品。每个企业都优先考虑测试;因此&#xff0c;大多数人更愿意从手动测试转向自动化。因此&#xff0c;自动化测试框架是任何软件测试过程的基础…

redis 分布式缓存、主从集群

目录分布式缓存1.Redis持久化1.1.RDB持久化RDB原理1.2.AOF持久化1.3.RDB与AOF对比2.Redis主从2.1.搭建主从架构2.2.主从数据同步原理2.2.1.全量同步2.2.2.增量同步2.2.3.repl_backlog原理2.3.主从同步优化方式2.4.全量同步和增量同步区别3. Redis哨兵3.1 集群监控原理3.2 集群故…

powerjob的worker启动,研究完了这块代码之后我发现了,代码就是现实中我们码农的真实写照

这是一篇让你受益匪浅的文章&#xff0c;代码即使人生。 worker启动比server启动要复杂一些&#xff0c;毕竟worker是要实际干活的&#xff0c;工欲善其事必先利其器&#xff0c;所以需要准备的工具还是不能少的&#xff0c;server对于powerjob来说&#xff0c;只是一个调度用的…

JVM详解

一&#xff0c;JVM 1&#xff0c;JVM区域划分 类装载器&#xff0c;运行时数据区&#xff0c;字节码执行引擎 2&#xff0c;JVM内存模型&#xff08;运行时数据区&#xff09; 由本地方法栈&#xff0c;虚拟机栈&#xff0c;堆&#xff0c;方法区&#xff0c;和程序计数器组成。…

C++类基础(十五)

类的继承——虚函数&#xff08;二&#xff09; ● 由虚函数所引入的动态绑定属于运行期行为&#xff0c;与编译期行为有所区别 虚函数与继承紧密相关 – 虚函数的缺省实参只会考虑静态类型 struct Base {virtual void fun(int x 3){std::cout << "virtual void f…

国产技术迎来突破,14nm芯片横空出世,低代码也有好消息

芯片&#xff0c;被称为工业时代的“粮食”&#xff0c;小到手机手环&#xff0c;大到飞机轮船&#xff0c;几乎各个行业都不离开芯片的支持&#xff0c;其重要性不言而喻。而我国在这一领域一直较为薄弱。 一、“芯片之路坎坷” 由于国内半导体芯片市场底子薄弱、没有主动权…

NetApp AFF A 系列全闪存存储阵列

NetApp AFF A 系列全闪存阵列是一款智能、至强、至信的解决方案&#xff0c;它可利用现代云技术为您的 Data Fabric 提供所需的速度、效率和安全性。 是时候实现数据现代化了 进行任何 IT 转型的基础性第一步是利用高性能全闪存存储打造现代化基础架构&#xff0c;提高关键业务…

【C++之容器适配器】反向迭代器的实现

目录前言一、反向迭代器的实现1. 底层2. 成员函数1. 构造函数2. operator*()3. operator->()4. 前置5. 后置6. 前置--7. 后置--8. operator!()9. operator()二、vector反向迭代器的实现1. vector的正向迭代器2. vector反向迭代器的实现3. 测试vector的反向迭代器三、list反向…

git提交

文章目录关于数据库&#xff1a;桌面/vue-admin/vue_shop_api 的 git 输入 打开 phpStudy ->mySQL管理器 导入文件同时输入密码&#xff0c;和文件名 node app.js 错误区&#xff1a; $ git branch // git branch 查看分支 只有一个main分支不见master解决&#xff1a; gi…

PyQt5保姆级入门教程——从安装到使用

目录 Part1&#xff1a;安装PyQt5 Part 2&#xff1a;PyCharm配置PyQt5 Part 3&#xff1a;PyQt5设计界面介绍 Part 4&#xff1a;PyQt5设计UI 今天看了多个大佬的教程&#xff0c;总算是把PyQt5开发弄好了&#xff0c;每个部分都要看几个人的十分不方便&#xff0c;我十分…

YOLOv3简介

YOLOv3 预测部分 Darknet-53 YOLOv3的主干提取网络为Darknet-53&#xff0c;相比于YOLOv2时期的Darknet-19&#xff0c;其加深了网络层数且引入了Residual残差结构。其通过不断的1X1卷积和3X3卷积以及残差边的叠加&#xff0c;大幅度的加深了网络。残差网络的特点是容易优化&a…

【Unity VR开发】结合VRTK4.0:将浮点数从交互器传递到可交互对象

语录&#xff1a; 愿你熬得过万丈孤独&#xff0c;藏得下星辰大海。 前言&#xff1a; 默认情况下&#xff0c;交互器只能将单个布尔操作传递给可交互对象&#xff0c;后者控制可交互对象上的抓取操作。在其他时候&#xff0c;交互器中的其他操作可能希望传递给可交互对象&…

leaflet 设置marker,并可以任意拖动每一个marker(071)

第071个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中通过L.marker来添加marker,通过设置其属性,可以让marker在地图上任意的拖动。 直接复制下面的 vue+leaflet源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共76行)相关API参…

实体店店铺管理软件怎么挑?看了排名就知道!

很多实体店店主在选择店铺管理软件时,不知道怎么选择,其实这个不难。一般根据市场上的排名也选择就ok了&#xff0c;因为一款被大家都认证过好用的软件&#xff0c;怎么都比盲选的或者名不见经传的软件好。选择一款适合的实体店店铺管理软件可以省很多事。而截止现在管理软件排…

linux将新加磁盘绑挂载到指定目录

查看当前挂载情况df -l此时可以看到sda和sdb两块磁盘已经被挂载&#xff0c;但实际上还有更多块磁盘未被挂载&#xff08;磁盘名称sda&#xff0c;结尾字母安顺递增&#xff09;查看一安装的所有磁盘fdisk -l此时我们可以看到还有很多未进行分区磁盘为磁盘添加分区fdisk /dev/s…