什么是Navigation2 ?
Navigation2(Nav2)是ROS 2机器人平台生态下官方开发与维护的自主导航(移动机器人定位与路径规划)系统。它让移动机器人可以实现类似“给定目标点,自动避障、去往指定位置”的能力。
简而言之,这是适配ROS2的一个导航框架,可以参考框架进行自定义开发,部署自己的导航算法。
环境要求:
- Ubuntu 22.04
- ROS2 Humble
- Navigation 2
- Gazebo
实现流程
我在尝试自定义Planner的过程中,尝试将自己的全局路径规划算法部署到Navigation2 中并在Gazebo中配合机器人仿真展示出来。
根据官方文档(地址:)
一共需要继承五个纯虚函数。如下图所示[1]

但是在实现的开发过程中,主要关注在configure()函数和createPlan()函数的实现上。官方存在一个demo(地址:https://github.com/ros-navigation/navigation2_tutorials/blob/rolling/nav2_straightline_planner/src/straight_line_planner.cpp)
此外,给出以我自己实现的代码设置为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #ifndef NAV2_SELF_PLANNER__NAV2_SELF_PLANNER_HPP_ #define NAV2_SELF_PLANNER__NAV2_SELF_PLANNER_HPP_ #include <memory> #include <string>
#include "geometry_msgs/msg/point.hpp" #include "geometry_msgs/msg/pose_stamped.hpp" #include "nav2_costmap_2d/costmap_2d_ros.hpp" #include "nav_msgs/msg/path.hpp"
#include "rclcpp/rclcpp.hpp" #include "nav2_core/global_planner.hpp" #include "nav2_util/lifecycle_node.hpp" #include "nav2_util/robot_utils.hpp"
namespace nav2_self_planner {
class CustomAstarPlanner : public nav2_core::GlobalPlanner { public: CustomPlanner() = default; ~CustomPlanner() = default; void configure( const rclcpp_lifecycle::LifecycleNode::WeakPtr &parent, std::string name, std::shared_ptr<tf2_ros::Buffer> tf, std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros) override; void cleanup() override; void activate() override; void deactivate() override; nav_msgs::msg::Path createPlan( const geometry_msgs::msg::PoseStamped &start, const geometry_msgs::msg::PoseStamped &goal) override;
private: std::shared_ptr<tf2_ros::Buffer> tf_; nav2_util::LifecycleNode::SharedPtr node_; nav2_costmap_2d::Costmap2D *costmap_; std::string global_frame_, name_; double parameter1; double parameter2; };
}
#endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #include "nav2_self_planner/nav2_self_planner.hpp" #include <queue> #include <unordered_map> #include <unordered_set> #include <cmath> #include <algorithm> #include <vector>
#include "nav2_util/node_utils.hpp"
void CustomAstarPlanner::configure( const rclcpp_lifecycle::LifecycleNode::WeakPtr &parent, std::string name, std::shared_ptr<tf2_ros::Buffer> tf, std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros) { node_ = parent.lock(); name_ = name; tf_ = tf; costmap_ = costmap_ros->getCostmap(); global_frame_ = costmap_ros->getGlobalFrameID(); nav2_util::declare_parameter_if_not_declared( node_, name_ + ".parameter1", rclcpp::ParameterValue(0.1)); nav2_util::declare_parameter_if_not_declared( node_, name_ + ".parameter2", rclcpp::ParameterValue(0.5)); node_->;get_parameter(name_ + ".parameter1", interpolation_resolution_); node_->;get_parameter(name_ + ".parameter2", safety_distance); }
void CustomAstarPlanner::cleanup() { RCLCPP_INFO(node_->;get_logger(), "正在清理类型为 CustomPlanner 的插件 %s",name_.c_str()); }
void CustomAstarPlanner::activate() { RCLCPP_INFO(node_->;get_logger(), "正在激活类型为 CustomPlanner 的插件 %s",name_.c_str()); }
void CustomAstarPlanner::deactivate() { RCLCPP_INFO(node_->;get_logger(), "正在停用类型为 CustomPlanner 的插件 %s",name_.c_str()); }
nav_msgs::msg::Path CustomAstarPlanner::createPlan( const geometry_msgs::msg::PoseStamped &start, const geometry_msgs::msg::PoseStamped &goal) { 自定义全局规划算法的实现代码... }
#include "pluginlib/class_list_macros.hpp" PLUGINLIB_EXPORT_CLASS(nav2_self_planner::CustomPlanner, nav2_core::GlobalPlanner)
|
其中:
1 2
| #include "pluginlib/class_list_macros.hpp" PLUGINLIB_EXPORT_CLASS(nav2_self_planner::CustomPlanner, nav2_core::GlobalPlanner)
|
完成算法对应的.hpp
和.cpp
文件之后,下一步需要在xml文件里面注册。主要包含三个文件,如下所示:
补充文件:
1 2 3 4 5
| <library path="nav2_self_planner_plugin"> <class name="nav2_self_planner/CustomAstarPlanner" type="nav2_self_planner::CustomAstarPlanner" base_class_type="nav2_core::GlobalPlanner"> <description>自定义示例插件,用于生成自定义路径。</description> </class> </library>
|
1 2 3
| <export> <nav2_core plugin="${prefix}/self_planner_plugin.xml"/> <export>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| cmake_minimum_required(VERSION 3.8) project(nav2_self_planner)
# find dependencies find_package(ament_cmake REQUIRED) find_package(pluginlib REQUIRED) find_package(nav2_core REQUIRED)
# 包含头文件目录 include_directories(include) # 定义库名称 set(library_name ${PROJECT_NAME}_plugin) # 创建共享库 加了SHARED 就是动态的库 add_library(${library_name} SHARED src/nav2_self_planner.cpp) # 指定库的依赖关系 ament_target_dependencies(${library_name} nav2_core pluginlib) # 安装库文件到指定目录 install(TARGETS ${library_name} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION lib/${PROJECT_NAME} ) # 安装头文件到指定目录 install(DIRECTORY include/ DESTINATION include/ ) # 导出插件描述文件(这里使用的是动态功能包+描述文件)而不是(当前功能包+描述文件) pluginlib_export_plugin_description_file(nav2_core self_planner_plugin.xml)
ament_package()
|
剩下的就是按照官方文档,在navigation2里面的参数设置里替换规划器为自己的即可。
项目感悟和心得
在完成整个项目的过程中,对于小白来说,我认为难的点在于:1.不知道需要哪些第三方库/包。2.此外用到的第三方库/包怎么去查看?
对于前者,建议参考别人实现算法用了什么工具库。你自己大概率也都是使用这些工具库。
比如常使用的库为:
<queue>:std::priority_queue (优先队列)
<unordered_map>、<unordered_set>:哈希表
<vector>:动态数组
<cmath>、<algorithm>:数学运算和常用算法
ROS/Navigation2 API:在规划器里要用参数声明、Costmap、TF 变换等,就需要包含它们提供的头文件,如
1 2
| #include "nav2_util/node_utils.hpp" (参数声明工具) #include "nav2_costmap_2d/costmap_2d_ros.hpp"
|
对于第二个问题,比如nav2_utils.hpp
,查看里面的函数。有三种方式:
- 直接
ctrl
点进去查看函数(最方便)。一般 VSCode、CLion 等 IDE,把光标放在类型或函数上,按 “Go to Definition”或者点击(转到定义)立刻能看到声明、参数和注释。
- 官网查看
开发技巧
从零去写一个工程很复杂,可以在一些示例工程的基础上进行二次开发。(示例工程地址:https://docs.nav2.org/plugins/index.html)
Navigation2 源码下有其他已有的 planner(如 SmacPlanner、GridBasedPlanner),直接参考它们的实现,复制和改造它们声明的参数和调用方式。