首页 > 其他分享 >move base全解

move base全解

时间:2024-05-27 16:33:15浏览次数:27  
标签:move planner costmap global base 全解 规划

0. 简介

之前我们专门有一节讲到了《move_base源码学习》。主要介绍了MoveBase基类中函数的大概意思以及调用的方式。move_base是ROS下关于机器人路径规划的中心枢纽。它通过订阅激光雷达、map地图、amcl的定位等数据,然后规划出全局和局部路径,再将路径转化为机器人的速度信息,最终实现机器人导航。下面是move_base的整个框架。

1. 代码详解

1.1 代码结构

$ tree .
.
├── cfg
│   └── MoveBase.cfg
├── CHANGELOG.rst
├── CMakeLists.txt
├── include
│   └── move_base
│       └── move_base.h
├── package.xml
├── planner_test.xml
└── src
    ├── move_base.cpp
    └── move_base_node.cpp

4 directories, 8 files

如右侧代码所示,我们通过Git工具把navigation从github上拉下来,并查看包move_base的目录结构。涉及到的文件和目录并没有多少。

其中子目录cfg中只有一个MoveBase.cfg的文件,实际上它是一个python的脚本,用于动态的修改运行节点的参数。与导航控制的实现无关,这里不再赘述。

子目录include中的move_base.h和src目录下的move_base.cpp一起定义和实现了我们要研究的导航框架类MoveBase。而源文件move_base_node.cpp是ROS系统的节点实现, 它实例化了MoveBase,并开启了ROS的消息循环。

CHANGELOG.rst是更新日志,记录了move_base的历次版本的修改内容。package.xml是ROS系统用于描述包的基本信息的文件,其中记录了包的名称、作者信息、以及依赖关系。

文件planner_test.xml实际上是一个launch文件,是一个以PR2机器人为平台测试规划器的demo,对于我们而言没有什么用处。

CMakeLists.txt是CMake的编译指导文件,描述了如何把源文件编译成实际运行的节点move_base。下面是从中截取的一段代码片段,我们可以看到move_base.cpp被编译成为一个库, 而实际的可执行文件move_base则是由move_base_node.cpp生成的。

        add_library(move_base src/move_base.cpp)
        target_link_libraries(move_base ${Boost_LIBRARIES} ${catkin_LIBRARIES})
        add_dependencies(move_base ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
        
        add_executable(move_base_node src/move_base_node.cpp)
        add_dependencies(move_base_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
        target_link_libraries(move_base_node move_base)
        set_target_properties(move_base_node PROPERTIES OUTPUT_NAME move_base)

1.2 代码解析

以下是MoveBase的构造函数,为了方便本文的写作,我将它的语句顺序做了适当的调整,不影响整个系统的运行。如下面的代码片段所示,在一开始对一系列的成员变量赋予了初值:

  • tf_是坐标变换TF2的接口对象;
  • as_则是Action服务器;
  • planner_costmap_ros_是用于全局规划器的代价地图对象;
  • controller_costmap_ros_则是局部规划器所用的代价地图对象;
  • bgp_loader_是装载全局规划器插件的工具;
  • blp_loader_是装载局部规划器插件的工具;
  • planner_plan_, latest_plan_ 和controller_plan_是三个记录规划结果的缓存;
  • runPlanner_, setup_, p_freq_change_, c_freq_change_, new_global_plan_则是一些控制和反映MoveBase系统状态的布尔变量。
        MoveBase::MoveBase(tf2_ros::Buffer& tf) :
            tf_(tf), as_(NULL), planner_costmap_ros_(NULL), controller_costmap_ros_(NULL),
            bgp_loader_("nav_core", "nav_core::BaseGlobalPlanner"),
            blp_loader_("nav_core", "nav_core::BaseLocalPlanner"), 
            recovery_loader_("nav_core", "nav_core::RecoveryBehavior"),
            planner_plan_(NULL), latest_plan_(NULL), controller_plan_(NULL),
            runPlanner_(false), setup_(false), p_freq_change_(false), c_freq_change_(false), new_global_plan_(false)
        {

在构造函数的一开始,定义了两个ROS的句柄,用于获取节点参数,订阅和发布主题。以下面的第13和14行为例,MoveBase从参数服务器中获取了全局规划器和局部规划器的名称, 如果系统中没有定义这些参数,将以默认值"navfn/NavfnROS"和"base_local_planner/TrajectoryPlannerROS"完成初始化工作。还有很多其它参数需要配置,这里不再一一介绍。

            ros::NodeHandle private_nh("~");
            ros::NodeHandle nh;
            std::string global_planner, local_planner;

            private_nh.param("base_global_planner", global_planner, std::string("navfn/NavfnROS"));
            private_nh.param("base_local_planner", local_planner, std::string("base_local_planner/TrajectoryPlannerROS"));
            // 省略其它加载参数的语句

通过在一开始获取的全局规划器名称global_planner构造全局规划器,并用刚刚构建的全局代价地图完成对其的初始化操作。整个过程在一个try-catch语句块中完成,如果出现异常将退出整个系统。

            // 全局规划器
            try {
                planner_ = bgp_loader_.createInstance(global_planner);
                planner_->initialize(bgp_loader_.getName(global_planner), planner_costmap_ros_);
            } catch (const pluginlib::PluginlibException& ex) {
                ROS_FATAL("Failed to create the %s planner, are you sure it is properly registered and that the containing library is built? Exception: %s", global_planner.c_str(), ex.what());
                exit(1);
            }

以类似的套路,MoveBase还构建了局部代价地图和局部规划器的对象。

            // 局部代价地图和局部规划器
            controller_costmap_ros_ = new costmap_2d::Costmap2DROS("local_costmap", tf_);
            controller_costmap_ros_->pause();
            try {
                tc_ = blp_loader_.createInstance(local_planner);
                tc_->initialize(blp_loader_.getName(local_planner), &tf_, controller_costmap_ros_);
            } catch (const pluginlib::PluginlibException& ex) {
                ROS_FATAL("Failed to create the %s planner, are you sure it is properly registered and that the containing library is built? Exception: %s", local_planner.c_str(), ex.what());
                exit(1);
            }

该规划方法具体实现在 navigation/navfn中,标志在文件 navigation/navfn/bgp_plugin.xml中。这也是每一个插件必须要有的

  <class name="navfn/NavfnROS" type="navfn::NavfnROS" base_class_type="nav_core::BaseGlobalPlanner">

该方法的核心调用在move_base.cpp下

// 函数 MoveBase::makePlan()下
planner_->makePlan(start, goal, plan)


详细的内容和结构可以参考:《move_base源码学习》以及《ROS DWA局部路径规划原理详解+源码分析

2. 参数配置

启动move_base的launch,包括解析map,move_base和amcl定位三个部分,这构成了一个完整的框架,下面我们主要来看move_base.launch里的配置。

<launch>
 
  <param name="use_sim_time" value="false" />
  
  <!-- EDIT THIS LINE TO REFLECT THE NAME OF YOUR OWN MAP FILE 
       Can also be overridden on the command line -->
  <arg name="map" default="test_map.yaml" />
 
  <!-- Run the map server with the desired map -->
  <node name="map_server" pkg="map_server" type="map_server" args="$(find dart_nav)/maps/dart.yaml"/> 
 
  <!-- Start move_base  -->
  <include file="$(find dart_nav)/launch/tb_move_base_test.launch" />
 
  <!-- Fire up AMCL -->
  <include file="$(find dart_nav)/launch/tb_amcl.launch" /> 
 
 
</launch>

下面是move_base.launch内的配置:

在这里插入图片描述

<launch>
  <node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen" clear_params="true">
    <rosparam file="$(find dart_nav)/config1/move_base_params.yaml" command="load" ns="global_costmap" />
    <rosparam file="$(find dart_nav)/config1/move_base_params.yaml" command="load" ns="local_costmap" />
    <rosparam file="$(find dart_nav)/config1/local_costmap_params.yaml" command="load" />
    <rosparam file="$(find dart_nav)/config1/global_costmap_params.yaml" command="load" />
    <rosparam file="$(find dart_nav)/config1/teb_local_planner_params.yaml" command="load" />
 
     <param name="base_global_planner" value="global_planner/GlobalPlanner"/> 
     <param name="planner_frequency" value="1.0" />
     <param name="planner_patience" value="5.0" />
 
     <param name="base_local_planner" value="teb_local_planner/TebLocalPlannerROS" />
     <param name="controller_frequency" value="15.0" />
     <param name="controller_patience" value="15.0" />
 <rosparam file="$(find dart_nav)/config1/costmap_conversion_params.yaml" command="load" />
 
  </node>
  
</launch>

如上所示,我使用的是global_planner这个包,它默认使用的是dijkstra,当然也可以使用A*全局路径规划,局部路径规划我使用的是teb,同样需要配置上面第3行到第7行的一些yaml,这些yaml是costmap和planner的一些配置文件。上图已经展示了对应部分的配置代表的含义,下面我们来主要看一下local_costmap_params.yaml和global_costmap_params.yaml。这里配置了上面提到的各个层(layers)的使用。

2.1 move_base_params.yaml

配置文件内容如下:

#FileName: move_base_params.yaml 
#Copyright: 2016-2018 ROS小课堂www.corvin.cn
#Author: corvin
#Description:
# move_base软件包的通用配置参数,现在依次解释每个参数意义:
#   shutdown_costmaps:当move_base在不活动状态时,是否关掉costmap.
#   controller_frequency:向底盘控制移动话题cmd_vel发送命令的频率.
#   controller_patience:在空间清理操作执行前,控制器花多长时间等有效控制下发.
#   planner_frequency:全局规划操作的执行频率.如果设置为0.0,则全局规划器仅
#       在接收到新的目标点或者局部规划器报告路径堵塞时才会重新执行规划操作.
#   planner_patience:在空间清理操作执行前,留给规划器多长时间来找出一条有效规划.
#   oscillation_timeout:执行修复机制前,允许振荡的时长.
#   oscillation_distance:来回运动在多大距离以上不会被认为是振荡.
#   base_local_planner:指定用于move_base的局部规划器插件名称.
#   base_global_planner:指定用于move_base的全局规划器插件名称.
#History:
# 20180726: initial this comment.
#

shutdown_costmaps: false

controller_frequency: 5.0
controller_patience: 3.0

planner_frequency: 1.0
planner_patience: 5.0

oscillation_timeout: 8.0
oscillation_distance: 0.3

base_local_planner: "dwa_local_planner/DWAPlannerROS"
base_global_planner: "global_planner/GlobalPlanner"

下面来依次解释下各参数的意义,其中base_local_planner和base_global_planner我们可以替换自己的算法:

  • shutdown_costmaps:当move_base在不活动状态时,是否关掉costmap.
  • controller_frequency:向底盘控制移动话题cmd_vel发送命令的频率.
  • controller_patience:在空间清理操作执行前,控制器花多长时间等有效控制下发.
  • planner_frequency:全局规划操作的执行频率.如果设置为0.0,则全局规划器仅在接收到新的目标点或者局部规划器报告路径堵塞时才会重新执行规划操作.
  • planner_patience:在空间清理操作执行前,留给规划器多长时间来找出一条有效规划.
  • oscillation_timeout:执行修复机制前,允许振荡的时长.
  • oscillation_distance:来回运动在多大距离以上不会被认为是振荡.
  • base_local_planner:指定用于move_base的局部规划器名称.
  • base_global_planner:指定用于move_base的全局规划器插件名称.

…详情请参照古月居

标签:move,planner,costmap,global,base,全解,规划
From: https://blog.csdn.net/lovely_yoshino/article/details/136872464

相关文章

  • C++程序分享--常见算法/编程面试题:(百度笔试题)用 C 语言实现函数 void * memmove
    关注我,持续分享逻辑思维&管理思维&面试题;可提供大厂面试辅导、及定制化求职/在职/管理/架构辅导;有意找工作的同学,请参考博主的原创:《面试官心得--面试前应该如何准备》,《面试官心得--面试时如何进行自我介绍》, 《做好面试准备,迎接2024金三银四》。【图解《程序员面试常见的......
  • Git之合并(merge rebase)
    合并(mergerebase)1、mergegitcheckoutfeaturegitmergemain说明:merge合并再feature分支中创建了一个合并提交(绿色带星提交)。合并操作没有破坏性。存在的分支历史不会发生改变。2、rebasegitcheckoutfeaturegitrebasemain说明:如同所示,把feature分支的......
  • PD还是QC?快充协议全解析
    摘自:https://zhuanlan.zhihu.com/p/646357568 快充技术在现代生活中的重要性不言而喻,它大大提高了充电效率,使人们可以在短时间内为设备充满电,从而提高生活和工作效率。同时,快速充电技术的发展也推动了电池技术、半导体材料技术等相关领域的进步,为科技创新提供了可能。然而,我们......
  • [论文笔记] The Fact Selection Problem in LLM-Based Program Repair
    Introduction:当bug发生时,我们会拿到很多信息:上下文、报错信息等等,文章把这些东西定义为facts,自然产生一个问题:“哪种facts应该被组织进prompt?”这篇文章就这一点做出了一些探讨。之前的工作研究了很多独立的信息,比如上下文、GitHubissue(这也行?)、栈跟踪信息;这篇文章将它......
  • Java中生成二维码转为base64存入MySQL数据库中
    1.引入架包<dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.4.1</version></dependency><dependency><groupId>com.google.zxing</groupId>......
  • 加装hbase2.6.0(等待重新安装)
    点击查看代码[root@master~]#tarzxfhbase-2.6.0[root@master~]#cdhbase-2.6.0/conf[root@masterconf]#cdhbase-2.6.0/conf[root@masterconf]#cphbase-env.shhbase-env.sh.bak[root@masterconf]#vimhbase-env.sh[root@masterconf]#cphbase-site.x......
  • 探索Go语言的原子操作秘籍:sync/atomic.Value全解析
    引言​在并发编程的世界里,数据的一致性和线程安全是永恒的话题。Go语言以其独特的并发模型——goroutine和channel,简化了并发编程的复杂性。然而,在某些场景下,我们仍然需要一种机制来保证操作的原子性。这就是sync/atomic.Value发挥作用的地方。原子性:并发编程的基石​......
  • LeetCode 283. Move Zeroes All In One
    LeetCode283.MoveZeroesAllInOnearrayin-placeswap/数组就地交换算法errorsfunctionmoveZeroes(nums:number[]):void{//in-place就地交换letindex=0;//letflag=false;for(leti=0;i<nums.length;i++){if(nums[i]===0){......
  • Mask DINO: Towards A Unified Transformer-based Framework for Object Detection an
    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布!ProceedingsoftheIEEE/CVFConferenceonComputerVisionandPatternRecognition.2023. Abstract在本文中,我们提出了一个统一的对象检测和分割框架MaskDINO。MaskDINO通过添加一个支持所有图像分割任务(例如......
  • Qt线程使用方法一:moveToThread
    在Qt中创建线程去执行耗时任务,并在任务完成后通知调用方(无论成功还是失败),可以通过使用QThread和信号槽机制来实现。以下是一个简单的示例,展示如何创建一个线程来执行任务,并在任务完成后发送信号。步骤 1: 定义工作类首先,定义一个工作类,该类将在单独的线程中执行任务。这个类......