【ROS】使用pluginlib自定义costmap地图层

news2024/11/28 20:50:49

文章目录

文章目录

前言

一、重写地图层

1.包含头文件 

2.onInitialize()

3.updateBounds()

4.updateCosts()

二、向ROS注册

1.插件描述文件

2.向ROS注册插件

3.在costmap配置文件中使用

总结


前言

pluginlib是一个 C++ 库,用于从 ROS 包中加载和卸载插件。插件是从运行时库(即共享对象、动态链接库)加载的动态可加载类。使用 pluginlib,人们不必将他们的应用程序显式链接到包含类的库,相反,pluginlib 可以在任何时候打开一个包含导出类的库,而无需应用程序事先知道该库或包含类定义的头文件. 插件可用于在不需要应用程序源代码的情况下扩展/修改应用程序行为。

在costmap中就为用户提供了便捷丰富的地图层接口,用户可以使用C++中的继承操作,继承costmap_2d中的类,然后重写其中的虚函数,以实现自己的地图层功能,本文以添加禁用区域为例说明如何实现一个自定义的costmap地图层。


一、重写地图层

1.包含头文件 

为了允许动态加载类,必须将其标记为导出类。这是通过特殊宏PLUGINLIB_EXPORT_CLASS完成的。一般PLUGINLIB_EXPORT_CLASS都写在文件的开头,以虚拟墙地图层为例:

#include <costmap_prohibition_layer/costmap_prohibition_layer.h>
#include <pluginlib/class_list_macros.h>

PLUGINLIB_EXPORT_CLASS(costmap_prohibition_layer_namespace::CostmapProhibitionLayer, costmap_2d::Layer)

如果想实现基本的地图层插件至少要重写这几个函数

  • void onInitialize():在costmap执行初始化initialize后会执行这个函数,相当于为用户提供的初始化接口。
  • void updateBounds(double robot_x, double robot_y, double robot_yaw, double *min_x, double *min_y, double *max_x, double *max_y):计算插件图层要更新到主图层区域的大小,每个图层都可以增加这个尺寸,如下图(b)所示
  • void updateCosts(costmap_2d::Costmap2D& master_grid, int min_i, int min_j, int max_i, int max_j):将每个图层的代价值更新到主图层,如下图(c)(d)(e)所示

2.onInitialize()

 onInitialize()函数主要执行一些初始化工作,如下面代码所示,下面进行逐行讲解。

void CostmapProhibitionLayer::onInitialize()

{
  ros::NodeHandle nh("~/" + name_);
  current_ = true;

  _dsrv = new dynamic_reconfigure::Server<CostmapProhibitionLayerConfig>(nh);
  dynamic_reconfigure::Server<CostmapProhibitionLayerConfig>::CallbackType cb =
      boost::bind(&CostmapProhibitionLayer::reconfigureCB, this, _1, _2);
  _dsrv->setCallback(cb);

  // get a pointer to the layered costmap and save resolution
  costmap_2d::Costmap2D *costmap = layered_costmap_->getCostmap();
  _costmap_resolution = costmap->getResolution();

  // set initial bounds
  _min_x = _min_y = _max_x = _max_y = 0;
  
  // reading the prohibition areas out of the namespace of this plugin!
  // e.g.: "move_base/global_costmap/prohibition_layer/prohibition_areas"
  std::string params = "prohibition_areas";
  if (!parseProhibitionListFromYaml(&nh, params))
    ROS_ERROR_STREAM("Reading prohibition areas from '" << nh.getNamespace() << "/" << params << "' failed!");
  
  _fill_polygons = true;
  nh.param("fill_polygons", _fill_polygons, _fill_polygons);
  
  // compute map bounds for the current set of prohibition areas.
  computeMapBounds();
  
  ROS_INFO("CostmapProhibitionLayer initialized.");
}

创建ros节点句柄:

ros::NodeHandle nh("~/" + name_);
current_ = true;

创建动态配置的服务器和回调函数,并为服务器配置回调函数,这部分在我的其他文章进行过详细说明。

_dsrv = new dynamic_reconfigure::Server<CostmapProhibitionLayerConfig>(nh);
dynamic_reconfigure::Server<CostmapProhibitionLayerConfig>::CallbackType cb =
      boost::bind(&CostmapProhibitionLayer::reconfigureCB, this, _1, _2);
_dsrv->setCallback(cb);

获得地图数据的指针和分辨率参数:

// get a pointer to the layered costmap and save resolution
  costmap_2d::Costmap2D *costmap = layered_costmap_->getCostmap();
  _costmap_resolution = costmap->getResolution();

从yaml文件中读取虚拟墙区域:

// reading the prohibition areas out of the namespace of this plugin!
  // e.g.: "move_base/global_costmap/prohibition_layer/prohibition_areas"
  std::string params = "prohibition_areas";
  if (!parseProhibitionListFromYaml(&nh, params))
    ROS_ERROR_STREAM("Reading prohibition areas from '" << nh.getNamespace() << "/" << params << "' failed!");

从launch文件中读取参数,是否完全更新多边形区域,然后computeMapBounds()函数用来计算更新区域的最大最小值。

_fill_polygons = true;
nh.param("fill_polygons", _fill_polygons, _fill_polygons);
  
// compute map bounds for the current set of prohibition areas.
computeMapBounds();
  
ROS_INFO("CostmapProhibitionLayer initialized.");

3.updateBounds()

updateBounds()函数用来根据刚才computeMapBounds()计算得到的最大最小区域更新costmap中定义的最大最小区域变量double *min_x, double *min_y, double *max_x, double *max_y:

void CostmapProhibitionLayer::updateBounds(double robot_x, double robot_y, double robot_yaw, 
                                           double *min_x, double *min_y, double *max_x, double *max_y)
{
    if (!enabled_)
        return;
    
    std::lock_guard<std::mutex> l(_data_mutex);
    
    if (_prohibition_points.empty() && _prohibition_polygons.empty())
        return;

    *min_x = std::min(*min_x, _min_x);
    *min_y = std::min(*min_y, _min_y);
    *max_x = std::max(*max_x, _max_x);
    *max_y = std::max(*max_y, _max_y);

}

这里面主要就是进行了一个赋值操作

4.updateCosts()

updateCosts()是最主要的功能,用来将地图层的代价更新到主图层,这里面主要有两个部分,第一个循环是更新禁止通行的区域,第二个循环是更新禁止通行的点(因为这个图层约定的yaml文件格式可以选择禁用点或直线或区域,所以这里分开处理)

void CostmapProhibitionLayer::updateCosts(costmap_2d::Costmap2D &master_grid, int min_i, int min_j, int max_i, int max_j)
{
  if (!enabled_)
    return;

  std::lock_guard<std::mutex> l(_data_mutex);
  
  // set costs of polygons
  for (int i = 0; i < _prohibition_polygons.size(); ++i)
  {
      setPolygonCost(master_grid, _prohibition_polygons[i], LETHAL_OBSTACLE, min_i, min_j, max_i, max_j, _fill_polygons);
  }
      
  // set cost of points
  for (int i = 0; i < _prohibition_points.size(); ++i)
  {
    unsigned int mx;
    unsigned int my;
    if (master_grid.worldToMap(_prohibition_points[i].x, _prohibition_points[i].y, mx, my))
    {
      master_grid.setCost(mx, my, LETHAL_OBSTACLE);
    }
  }
}

先调用了setPolygonCost()函数将禁用区域的代价值设置为“致命障碍”(LETHAL_OBSTACLE)

  // set costs of polygons
  for (int i = 0; i < _prohibition_polygons.size(); ++i)
  {
      setPolygonCost(master_grid, _prohibition_polygons[i], LETHAL_OBSTACLE, min_i, min_j, max_i, max_j, _fill_polygons);
  }

setPolygonCost()函数如下:

void CostmapProhibitionLayer::setPolygonCost(costmap_2d::Costmap2D &master_grid, const std::vector<geometry_msgs::Point>& polygon, unsigned char cost,
                                             int min_i, int min_j, int max_i, int max_j, bool fill_polygon)
{
    std::vector<PointInt> map_polygon;
    for (unsigned int i = 0; i < polygon.size(); ++i)
    {
        PointInt loc;
        master_grid.worldToMapNoBounds(polygon[i].x, polygon[i].y, loc.x, loc.y);
        map_polygon.push_back(loc);
    }

    std::vector<PointInt> polygon_cells;

    // get the cells that fill the polygon
    rasterizePolygon(map_polygon, polygon_cells, fill_polygon);

    // set the cost of those cells
    for (unsigned int i = 0; i < polygon_cells.size(); ++i)
    {
        int mx = polygon_cells[i].x;
        int my = polygon_cells[i].y;
        // check if point is outside bounds
        if (mx < min_i || mx >= max_i)
            continue;
        if (my < min_j || my >= max_j)
            continue;
        master_grid.setCost(mx, my, cost);
    }
}

先通过这几行代码,将多边形区域的世界坐标转化为地图边界,存储在loc变量中(作者定义的结构体,有两个变量int x和int y),然后通过rasterizePolygon函数获得这个区域内所有单元格的地图坐标

std::vector<PointInt> map_polygon;
for (unsigned int i = 0; i < polygon.size(); ++i)
{
    PointInt loc;
    master_grid.worldToMapNoBounds(polygon[i].x, polygon[i].y, loc.x, loc.y);
    map_polygon.push_back(loc);
}

std::vector<PointInt> polygon_cells;

// get the cells that fill the polygon
rasterizePolygon(map_polygon, polygon_cells, fill_polygon);

这个循环是根据获得的序号设置代价值,关键函数是setCost函数,根据每一个单元格的地图坐标,将其代价值设置为“致命障碍”

// set the cost of those cells
for (unsigned int i = 0; i < polygon_cells.size(); ++i)
{
    int mx = polygon_cells[i].x;
    int my = polygon_cells[i].y;
    // check if point is outside bounds
    if (mx < min_i || mx >= max_i)
        continue;
    if (my < min_j || my >= max_j)
        continue;
    master_grid.setCost(mx, my, cost);
}

然后这个循环就更简单了,直接使用worldToMap()函数将世界坐标转化为地图坐标,然后根据获得的地图坐标使用setCost()函数设置代价值。

  // set cost of points
  for (int i = 0; i < _prohibition_points.size(); ++i)
  {
    unsigned int mx;
    unsigned int my;
    if (master_grid.worldToMap(_prohibition_points[i].x, _prohibition_points[i].y, mx, my))
    {
      master_grid.setCost(mx, my, LETHAL_OBSTACLE);
    }
  }

 

二、向ROS注册

1.插件描述文件

插件描述文件是一个 XML 文件,用于以机器可读格式存储有关插件的所有重要信息。它包含有关插件所在的库、插件名称、插件类型等的信息。对于虚拟墙禁用图层,它的描述性文件是这样的:

<library path="lib/libcostmap_prohibition_layer">
  <class type="costmap_prohibition_layer_namespace::CostmapProhibitionLayer" base_class_type="costmap_2d::Layer">
    <description>ROS-Package that implements a costmap layer to add prohibited areas to the costmap-2D by a user configuration.</description>
  </class>
</library>

library标签定义插件类所在的库。一个库可能包含多个不同类类型的插件。

class标签描述了库提供的类。

属性:

  • name :类的查找名称。由pluginlib工具用作插件的标识符。

  • type :完全限定的类的类型。
  • base_class_type :基类的完全限定类型
  • description :类及其作用的描述。

更详细的描述可以查看这个文档。

2.向ROS注册插件

为了让 pluginlib 查询系统上所有 ROS 包中的所有可用插件,每个包必须明确指定它导出的插件以及哪些包库包含这些插件。插件提供者必须在其导出标记块内的package.xml中指向其插件描述文件,需要在package.xml文件最后添加这样一个标签:

  <export>
    <costmap_2d plugin="${prefix}/costmap_plugins.xml" />
  </export>

当然需要在前面添加对基类(costmap_2d)的依赖:

<depend>costmap_2d</depend>

然后在工作空间中使用catkin_make或catkin_make_isolated编译即可,然后使用以下命令查看:

rospack plugins --attrib=plugin costmap_2d

如果出现以下结果,则说明插件可用:

costmap_prohibition_layer /home/lyh/catkin_acad/src/costmap_prohibition_layer-repush3/costmap_plugins.xml
costmap_2d /opt/ros/kinetic/share/costmap_2d/costmap_plugins.xml

3.在costmap配置文件中使用

在参数配置文件夹中找到 global_costmap_params.yaml 和/或 local_costmap_params.yaml,在末尾添加或修改:

 plugins:
      - {name: static_map,       type: "costmap_2d::StaticLayer"}
      - {name: obstacles,        type: "costmap_2d::VoxelLayer"}
      - {name: inflation_layer,        type: "costmap_2d::InflationLayer"}
      - {name: costmap_prohibition_layer,        type: "costmap_prohibition_layer_namespace::CostmapProhibitionLayer"}    

对于虚拟墙地图层,还需要在param文件夹中自己配置一个设置禁止区域的参数文件,在参数配置文件夹(就是和 global_costmap_params.yaml 以及 local_costmap_params.yaml 相同位置的文件夹)中创建新的文档,命名为 "prohibition_areas.yaml",然后在prohibition_areas.yaml文档中输入:

prohibition_areas:
#定义一个禁止点
 - [17.09, -6.388]
# 定义一个禁止通行的线
 - [[8.33, 2.11],
    [8.26, 5.11]]
# 定义一个禁止通行的区域
 - [[-11.15, -15.614],
    [-12.35, -13.89],
    [-10.05, -12.218]]

注意事项:
1.一定要严格按照上述格式来设置坐标,可能出现情况:
  (1)坐标前的短横线没对齐
  (2)定义禁止区域或者禁止线,两坐标之间缺少了逗号
2.你可以同时定义多个禁止点/多个禁止线/多个禁止区域,或者混合定义多个点/线/区域.


总结

本文以虚拟墙禁用层为例,详细介绍了如何实现一个自定义的costmap地图层,以及如何在ROS中使用pluginlib制作一个插件,插件机制应用广泛,使用C++的类继承为用户提供了极大的便利。

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

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

相关文章

SpringBoot实战(十一)集成RebbitMQ

目录1.工作原理图1.1 配置阶段1.2 生产者1.3 消费者2.Maven依赖3.常用交换机类型3.1 direct 直连交换机3.2 fanout 广播交换机3.3 topic 主题交换机4.Direct 直连交换机4.1 yml配置4.2 配置类4.3 消息推送类4.4 消息监听类4.5 测试5.Fanout 广播交换机5.1 配置类5.2 消息推送类…

javascript画全年日历

前些日子闲聊群里有人提了用js画全年日历的需求&#xff0c;趁闲暇时间画了个小demo&#xff0c;下面还是先上效果图吧。 高亮显示的是今天的日期和标记要高亮显示的日期&#xff0c;也添加了点击事件的钩子&#xff0c;自己可以实现钩子函数&#xff0c;从而操作点击的日期值。…

综述 | 深度强化学习在自动驾驶中的应用

本文是2020年的综述论文《Deep Reinforcement Learning for Autonomous Driving: A Survey》的部分内容节选。翻译稿全文共2万6千字&#xff0c;本文略掉了第3、4节强化学习理论的介绍及扩展部分。摘要随着深度表征学习(deep representation learning)的发展&#xff0c;强化学…

【8】SCI易中期刊推荐——图像处理领域(中科院4区)

🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…

windows下Docker部署Flask的教程

Docker默认安装路径是在C盘&#xff0c;Windows中修改Docker**默认安装****路径方法&#xff1a; 1.先创建 D:\Program Files\Docker 目录 2.运行命令&#xff0c;创建链接 mklink /J "C:\Program Files\Docker" "D:\codeSoftware\Docker"3.点击exe安装…

logstash 向多目标输出多份日志输出syslog

logstash默认情况下是内置了输入syslog日志的&#xff0c;但是不支持输出syslog&#xff0c;需要输出syslog的情况下&#xff0c;就需要手动安装logstash-output-syslog插件。安装方法如下&#xff1a;下载logstash-output-syslog插件&#xff0c;https://rubygems.org/downloa…

SpringBoot 注册自己的Servlet(三种方式)

SpringBoot 注册自己的Servlet&#xff08;三种方式&#xff09; 目录SpringBoot 注册自己的Servlet&#xff08;三种方式&#xff09;方法1:使用servlet3.0规范提供的注解方式注册Servlet1,声明servlet及映射2&#xff0c;加上ServletComponentScan 才会扫描加了这个注解运行结…

LeetCode 62. 不同路径

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 62. 不同路径&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetCode 62. …

蚂蚁智能内容合规产品,提供一站式营销合规管控解决方案

随着互联网服务的不断深化&#xff0c;产品营销的形式从传统文本、长图文&#xff0c;增加到短视频、直播等新媒介形态&#xff0c;展现形式愈加丰富的同时&#xff0c;也为营销宣传内容合规审核带来了诸多难题。如何解决与日俱增的审核量与合规审核人员有限之间的矛盾&#xf…

旧手机闲置?教你用Termux搭建个移动服务器

目录 前言 准备工作 实践 安装Termux&#xff1a; 运行Termux&#xff1a; 环境配置&#xff1a; 效果展示 写在最后 前言 最近偶然看到网上有人用KSWEB搭建本地服务器&#xff0c;于是突发奇想也想在手机中搭建一个node环境试试&#xff0c;趁着周末有空&#xff0c;…

Vue3商店后台管理系统设计文稿篇(五)

记录使用vscode构建Vue3商店后台管理系统&#xff0c;这是第五篇&#xff0c;主要记录Vue3项目路由知识 文章目录一、Vue3路由二、安装Element Plus三、NPM设置淘宝镜像四、Yarn 设置淘宝镜像正文内容&#xff1a; 一、Vue3路由 路由用于设定访问路径, 将路径和组件映射起来&…

【vue系列-06】vue的组件化编程

深入理解vue的组件一&#xff0c;vue组件1&#xff0c;什么是vue组件2&#xff0c;单文件组件和非单文件组件3&#xff0c;非单组件的基本使用4&#xff0c;vue组件命名规范4.1&#xff0c;一个单词组成4.2&#xff0c;多个单词组成5&#xff0c;组件与组件间的嵌套6&#xff0…

Tomcat结构体系

总体结构Tomcat中最顶层的容器是Server&#xff0c;代表着整个服务器&#xff0c;从上图中可以看出&#xff0c;一个Server可以包含至少一个Service&#xff0c;用于具体提供服务。Service主要包含两个部分&#xff1a;Connector和Container。从上图可以看出 Tomcat 的心脏就是…

opencv的mat openvino的tensor libtorch的tensor

opencv的mat 对于矩阵数据&#xff0c;在opencv里面是通过使用mat这个数据结构来实现的&#xff0c;我觉得这个数据结构本身设计是用来做图片的存储&#xff0c;所以很多的教程都是关于三维矩阵的&#xff08;其中一个维度是channel&#xff09;&#xff0c;关于三维矩阵的定义…

通讯录小练习:柔性数组和文件操作实现

目录 一.程序功能 二.定义关键类型的头文件与枚举的应用 三.封装柔性数组的增容函数与缩容函数 四.添加联系人功能模块 五 .联系人信息打印模块 六. 查找指定联系人的模块 七.删除指定联系人模块 八.修改指定联系人信息模块 九.排序模块 九.文件操作模块 十.通讯录初…

如何实现外网远程登录访问jupyter notebook?

Jupyter Notebook是一个交互式笔记本&#xff0c;本质是一个 Web 应用程序&#xff0c;支持运行 40 多种编程语言&#xff0c;此前被称为 IPython notebook。Jupyter Notebook 便于创建和共享程序文档、支持实时代码、数学方程、可视化和 markdown&#xff0c;应用场景有数据清…

机器学习基础——k-近邻算法概述和简单实现

本章内容 k-近邻分类算法 从文本文件中解析数据 前言 众所周知&#xff0c;电影可以按照题材分类&#xff0c;然而题材本身是如何定义的?由谁来判定某部电影属于哪个题材?也就是说同一题材的电影具有哪些公共特征?这些都是在进行电影分类时必须要考虑的问题。没有哪个电影人…

Revit问题:降板表面填充图案和构件上色

一、Revit中如何为降板表面填充不同的图案 在平面图中该如何利用填充图案来区别降板跟楼板&#xff1f; 1、中间的楼板为降板(120)/-150mm,下面我们通过“过滤器”来为其填充表面图案。 2、通过快捷键VV打开“可见性/图形替换”对话框&#xff0c;单击选择“过滤器”一项。 3、…

2023/1 寒假期间自学c++计划安排

寒假一期学习总结 寒假一期学习是在线下进行的&#xff0c;总的来说&#xff0c;非常充实&#xff0c;也很有收获&#xff0c;成体系的学习了 二分&#xff0c;高精度&#xff0c;函数&#xff0c;结构体&#xff0c;STL 等等内容&#xff0c;既开心有学到了知识。 在这7天的集…

最新ios证书申请流程

苹果官方申请ios证书的方法&#xff0c;需要mac电脑&#xff0c;需要使用钥匙串管理先生成csr文件&#xff0c;然后去苹果开发者中心生成证书&#xff0c;然后再用mac电脑导出p12证书。假如我们没有mac电脑&#xff0c;又如何申请证书呢&#xff1f;这个教程我将教会大家如何使…