ROS基础教程--CostMap_2D包的一些理解

2023-05-16

本文是在综合了多篇文章的基础之上进行的综合.

1.基本概念

Voxel:体素,即顾名思义是体积的像素。用来在三维空间中表示一个显示基本点的单位。类似于二维平面下的pixel(像素)。voxel是三维空间中定义一个点的图象信息的单位。在平面中定义一个点要两个坐标X和Y就够了,而在三维世界中还要有一个坐标。光有3维坐标位置还不行,还要有颜色等信息,这就是voxel的含义。

机器人在costmap_2D中的模型:两个同心圆,在图1里可以看到左下角有两个淡蓝色同心圆,一个机器人的轮廓外切圆和一个机器人内切圆,机器人在costmap里就能够简化成为这两个圆。根据机器人中心至边界或者障碍物的距离和两个同心圆半径比较来判断是否碰撞。

footprint:足迹,即机器人的轮廓。在ROS中,它由二维数组表示[x0,y0] ; [x1,y1] ; [x2,y2]……不需要重复第一个坐标。该占位面积将用于计算内切圆和外接圆的半径,用于以适合此机器人的方式对障碍物进行膨胀。为了安全起见,我们通常将足迹稍大于机器人的实际轮廓。要确定机器人的占地面积,最直接的方法是参考机器人的图纸。 此外,您可以手动拍摄其基座顶视图。 然后使用CAD软件(如Solidworks)适当缩放图像,并将鼠标移动到基座轮廓上并读取其坐标。 坐标的起点应该是机器人的中心。 或者,您可以将机器人移动到一张大纸上,然后绘制基座的轮廓。 然后选择一些顶点并使用标尺来确定它们的坐标。

cost:代价或者占用,0-255的取值,表示该机器人位于该网格点(grid cell)的代价,或者机器人的frootprint中心cell走到该网格点的代价(中心到达某个位置的代价与非中心部分到达某个位置付出的代价不同,如撞击造成的损伤程度等)。

cell:单元,网格,栅格

2. costmap的网格代价计算

无论是激光雷达还是如kinect 或xtion pro深度相机作为传感器跑出的2D或3D SLAM地图,都不能直接用于实际的导航,必须将地图转化为costmap(代价地图),ROS的costmap通常采用grid(网格)形式。以前一直没有搞明白每个栅格的概率是如何算出来的,原因是之前一直忽略了内存的存储结构,栅格地图一个栅格占1个字节,也就是八位,可以存0-255中数据,也就是每个cell cost(网格的值)从0~255我们只需要三种情况:Occupied被占用(有障碍), Free自由区域(无障碍),  Unknown Space未知区域。。

Costmap是机器人收集传感器信息建立和更新的二维或三维地图,可以从下图简要了解。

 

这里写图片描述

图0. 机器人和障碍物以及膨胀区域在2维地图上的footprint和投影表示

注意: 在上图中,红色cell(图中红色蓝色区域都是一系列cell堆叠出来的)代表的是代价地图中的障碍,蓝色cell代表的是通过机器人内切圆半径计算的障碍物膨胀,红色多边形代表的是机器人footprint(机器人轮廓的垂直投影)。 为了使机器人不碰到障碍物,机器人的footprint绝对不允许与红色cell相交,机器人的中心绝对不允许与蓝色cell相交。

    空间状态(Occupied, Free, and Unknown)

ROS的代价地图(costmap)采用网格(grid)形式,每个网格的值(cell cost)从0~255。分成三种状态:被占用(有障碍)、自由区域(无障碍)、未知区域;以激光雷达为传感器(或者kinect之类的深度相机的伪激光雷达),根据激光测量的障碍物距离机器人中心的距离,结合机器人的内切和外切半径,搞一个映射,利用bresenham算法(计算方法参考https://www.cnblogs.com/zjiaxing/p/5543386.html)可以填充由激光雷达的位置到障碍物之间的栅格概率了。

虽然代价地图中每个cell可用255个不同值中任何一个值,可是下层数据结构仅需要3个值。 具体来说在这种下层结构中,每个cell仅需要3个值来表示cell的3种状态:free,occupied,unknown。 当投影到代价地图时候,每种状态被赋一个特定的代价值,也就是说每个cell的cost值是由这个cell对应的各层中对应的cell的状态进行加权得到的。 如果列有一定量的占用就被赋代价值costmap_2d::LETHAL_OBSTACLE, 如果列有一定量的unknown cells 就被赋代价值costmap_2d::NO_INFORMATION, 剩余其它列赋代价值为costmap_2d::FREE_SPACE

在某时刻和机器人当前前进方向上的网格点的代价计算示意图如下(如果机器人的前进方向改变,则网格点的代价也会发生变化):

图1. 机器人模型以及在当前前进方向上各grid的代价分布示意图

图1. 机器人模型以及在当前前进方向上各grid的代价分布示意图

上图下面的红色五边形区域为机器人的轮廓。坐标系内的区域可分为五部分,(cost的计算是以cell为单位进行的,而不是以某个障碍物为单位进行的,也就是一次只计算一个cell的cost值)
(1) Lethal(致命的):机器人的中心(center cell)与该网格的中心重合,此时机器人必然与障碍物冲突。
(2) Inscribed(内切):网格中心位于机器人的内切轮廓内,此时机器人也必然与障碍物冲突。
(3) Possibly circumscribed(可能受限):网格中心位于机器人的外切圆与内切圆轮廓之间,此时机器人相当于靠在障碍物附近,所以不一定冲突,取决于机器人的方位或者说姿态。
(4) Freespace(自由空间):网格中心位于机器人外切圆之外,属于没有障碍物的空间。

(5) Unknown(未知):未知的空间。

如果按照cell的三种状态划分,我个人认为上述前3种状态,都属于“被占用”状态。

 

这里介绍一下costmap_2d中计算cost的方法。假设,机器人内切半径为0.5m,外切半径为0.7m,当激光返回障碍距离在机器人中心附近,叫致命障碍,机器人一定能碰到障碍物,比如说0m,直接贴着机器人,或会取小于栅格的边长,比如小于0.1m范围内,则这个栅格值就设为254;当返回来的数值在0.1-0.5m之间,就设253;当在0.5-0.7之间,则可以设128,或者在252-128找个比例值(程序中可以控制),属于受限区域,可能发生碰撞,是否碰撞,取决于机器人的姿态;当0.7-膨胀半径之间,设1-127之间的映射值,不会发生碰撞;当大于膨胀距离,则设为0,称为freespace。Unknown -- 意味着给定的单元没有相应的信息。我们看坐标系中较细的红色光滑曲线就是cost曲线,x是距离机器人footprint的圆心距离,而y是cost值,cost随着x的增大而减小距离,当x>=内切圆半径时开始有值;当x=0时,y=254;当x=resolution/2时,cost=253;(图中右上角的较粗的台阶状红线是单元格的边线,或者认为是障碍物(单元格化后)的边线)

3. Costmap的分层与更新

3.1 Costmap中的地图与分层

Costmap_2D提供了一种2D代价地图的实现方案,该方案利用输入传感器数据,构建数据2D或者3D(依赖于是否使用基于voxel的实现)代价地图。 此外,该包也支持利用map_server初始化代价地图,支持滚动窗口的代价地图,支持参数化订阅和配置传感器主题。

voxel占用栅格

图2. voxel代价地图示意图(将3维障碍物投影到2维平面,2维空间内不相撞的导航或者轨迹在三维空间内也肯定不相撞)

从Hydro发布版本开始, 用来写数据到代价地图的底层方法已经完全可配置了。 Costmap由多层组成,每种功能放置一层中。 例如图3所示,静态地图是一层,障碍物是另一层。 缺省情况下,障碍物层维护的是3D信息,3D障碍物数据可以让层更加灵活的标记和清除障碍物。例如在costmap_2d包中,StaticLayer(静态地图层)是第一层,ObstacleLayer(障碍物层)是第二层,InflationLayer(膨胀层)是第三层。这三层组合成了master map(最终的costmap),供给路线规划模块使用。

图3. costmap_2D中的4个分层(从Hydro版本之后采用这种分层结构)

我自己定义的障碍物也可以是一层(假如我不想让机器人通过一个freespace就可以自己插入个障碍物,主要的接口是costmap_2d::Costmap2DROS,在每一层中使用pluginlib实例化Costmap2DROS并将每一层添加到LayeredCostmap),各个层可以被独立的编译。如下图所示:

costmap_2d包提供了一种可配置框架来维护机器人在代价地图上应该如何导航的信息。 代价地图使用来自传感器的数据和来自静态地图中的信息,通过costmap_2d::Costmap2DROS来存储和更新现实世界中障碍物信息。costmap_2d::Costmap2DROS给用户提供了纯2D的接口,这意味着查询障碍只能在列上进行。例如,在XY平面上位于同一位置的桌子和鞋,虽然在Z方向上有差异但是它们在costmap_2d::Costmap2DROS对象代价地图中对应的cell上拥有相同的代价值。 这种设计对平面空间进行路径规划是有帮助的。

costmap_2D提供的ROS化功能接口主要就是costmap_2d::Costmap2DROS,它使用costmap_2d::LayeredCostmap 来跟踪每一层。 每一层在Costmap2DROS中以插件方式被实例化,并被添加到LayeredCostmap。 每一层可以独立编译,且可使用C++接口实现对代价地图的随意修改,即LayerdCostmap为Costmap2DROS(用户接口)提供了加载地图层的插件机制,每个插件(即地图层)都是Layer类型的。costmap_2d::Costmap2D 类中实现了用来存储和访问2D代价地图的的基本数据结构。

图4. costmap中的Layer的继承关系

costmap中各Layer之间的继承关系如图4所示,本文后面我们附的layeredcostmap的相关介绍还会用到这个图。

3.2 Costmap初始化流程

在navigation的主节点move_base中(costmap隶属于navigation包,或者说是navigation的一个子模块),建立了两个costmap。其中planner_costmap_ros_是用于全局导航的地图,controller_costmap_ros_是用于局部导航用的地图。图5为costmap的初始化流程。

这里写图片描述

图5. costmap的初始化流程

(1)Costmap初始化首先获得全局坐标系和机器人坐标系的转换
(2)加载各个Layer,例如StaticLayer,ObstacleLayer,InflationLayer。
(3)设置机器人的轮廓
(4)实例化了一个Costmap2DPublisher来发布可视化数据。
(5)通过一个movementCB函数不断检测机器人是否在运动
(6)开启动态参数配置服务,服务启动了更新map的线程。

3.3 costmap中各层的更新

简单整理了下costmap初始化过程中的各层加载的调用过程:

这里写图片描述

在move_base刚启动时就建立了两个costmap,而这两个costmap都加载了三个Layer插件,它们的初始化过程如上图所示。

StaticLayer主要为处理gmapping或者amcl等产生的静态地图。
ObstacLayer主要处理机器人移动过程中产生的障碍物信息。
InflationLayer主要处理机器人导航地图上的障碍物信息膨胀(让地图上的障碍物比实际障碍物的大小更大一些),尽可能使机器人更安全的移动。
costmap在mapUpdateLoop线程中执行更新地图的操作,每个层的工作流程如下:
(1)StaticLayer工作流程


这里写图片描述

上图是StaticLayer的工作流程,updateBounds阶段将更新的界限设置为整张地图,updateCosts阶段根据rolling参数(是否采用滚动窗口)设置的值,如果是,那静态地图会随着机器人移动而移动,则首先要获取静态地图坐标系到全局坐标系的转换,再更新静态地图层到master map里。
(2)ObstacleLayer工作流程


这里写图片描述

上图是ObstacleLayer的工作流程,updateBounds阶段将获取传感器传来的障碍物信息经过处理后放入一个观察队列中,updateCosts阶段则将障碍物的信息更新到master map。
(3)inflationLayer工作流程

这里写图片描述

上图是inflationLayer的工作流程,updateBounds阶段由于本层没有维护的map,所以维持上一层地图调用的Bounds值(处理区域)。updateCosts阶段用了一个CellData结构存储master map中每个grid点的信息,其中包括这个点的二维索引和这个点附近最近的障碍物的二维索引。改变每个障碍物CELL附近前后左右四个CELL的cost值,更新到master map就完成了障碍物的膨胀。

 

3.4 Costmap更新

前面说过,“Costmap2DROS使用costmap_2d::LayeredCostmap 来跟踪每一层”。这里我们要清楚每一层有什么操作。

    障碍物标记和清除

    代价地图自动订阅传感器发布的主题并基于数据进行相应自我更新。 对每个传感器来说,其可以用来执行mark(将障碍物信息插入到代价地图),也可以用来执行clear(从代价地图移除障碍物)或者二者都执行。marking操作就是索引到数组内修改cell的代价。然而对于clearing操作,每次观测报告都需要传感器源向外发射线,由射线穿过的珊格组成(是不是clear要清除该cell对应的诸多层中对应cell的状态)。 如果存储的障碍物信息是3D的,需要将每一列的障碍物信息投影成2D后才能放入到代价地图。

costmap以参数update_frequency 指定的周期进行costmap更新。每个周期传感器数据进来后,都要在代价地图底层占用结构上执行标记和清除障碍操作,并且这种结构会被投影到代价地图附上相应代价值。 这完成之后,对代价赋值为costmap_2d::LETHAL_OBSTACLE的每个cell执行障碍物的膨胀操作,即从每个代价cell向外传播代价值,直到用户定义的膨胀半径为止。这里确实只需要对状态为LETHAL_OBSTACLE的cell进行膨胀操作即可。

Costmap的更新在mapUpdateLoop线程中实现,此线程分为两个阶段:
(阶段一)UpdateBounds:这个阶段会更新每个Layer的更新区域(增量更新?只更新变化的部分而不是全部更新),这样在每个运行周期内减少了数据拷贝的操作时间。StaticLayer的Static map只在第一次做更新,Bounds 范围是整张Map的大小,而且在UpdateBounds过程中没有对Static Map层的数据做过任何的更新(静态地图,一次写入,后面读取,很少变更)。ObstacleLayer在这个阶段主要的操作是更新Obstacles Map层的数据,然后更新Bounds(清空了Master层对应的bounds内的数据)。InflationLayer则保持上一次的Bounds。
(阶段二)UpdateCosts:这个阶段将各层数据逐一拷贝到Master Map,可以通过下图观察Master Map的生成流程。(图来源于David Lu的《Layered Costmaps for Context-Sensitive Navigation》)


这里写图片描述

在(a)中,初始有三个Layer和Master costmap,Static Layer和Obstacles Layer维护它们自己的地图,而inflation Layer并没有。为了更新costmap,算法首先在各层上调用自己的UpdateBounds方法(b)。为了决定新的bounds,Obstacles Layer利用新的传感器数据更新它的costmap。然后每个层轮流用UpdateCosts方法更新Master costmap的某个区域,从Static Layer开始(c),然后是Obstacles Layer(d),最后是inflation Layer(e)。

如果上图不容易理解,可以参考下面这个PPT,里面有更新的动态过程:http://download.csdn.net/download/jinking01/10272584

4. LayeredCostmap代码分析

 

class uml
classes relation
在数据成员中,有两个重要的变量:Costmap2D costmap_;std::vector<boost::shared_ptr<Layer> > plugins_;
这个类相对比较简单,首先来看构造函数:


   
  1. LayeredCostmap::LayeredCostmap(std::string global_frame, bool rolling_window, bool track_unknown) :

  2. costmap_(), global_frame_(global_frame), rolling_window_(rolling_window), initialized_(false), size_locked_(false)

  3. {

  4. if (track_unknown)

  5. costmap_.setDefaultValue(255);

  6. else

  7. costmap_.setDefaultValue(0);

  8. }

调用costmap_setDefaultValue 方法,实际上设定了类costmap_2d 的一个成员变量default_value_ 这个值在class costmap_2d 中是这样使用的:memset(costmap_, default_value_, size_x_ * size_y_ * sizeof(unsigned char)); 实际存储地图的变量就是class costmap_2dcostmap_ 数据成员。

析构函数中,所有的操作就是弹出plugin: plugins_.pop_back();

函数LayeredCostmap::resizeMap 就是给class costmap_2dcostmap_ 成员的大小重新做分配。然后根据plugin对每一层的地图调用其父类Costmap2D成员的initial 方法,实际效果就是将plugin所指向的每一层地图的大小都设置为和LayeredCostmap::costmap_ 数据成员一样的空间大小。


   
  1. {

  2. size_locked_ = size_locked;

  3. costmap_.resizeMap(size_x, size_y, resolution, origin_x, origin_y);

  4. for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins_.begin(); plugin != plugins_.end();

  5. ++plugin)

  6. {

  7. (*plugin)->matchSize();

  8. }

  9. }

函数 LayeredCostmap::updateMap 完成对每一层地图的更新,更新过程分为两步updateBoundsupdateCosts


   
  1.  
  2. void LayeredCostmap::updateMap(double robot_x, double robot_y, double robot_yaw)

  3. {

  4. if (rolling_window_)

  5. {

  6. double new_origin_x = robot_x - costmap_.getSizeInMetersX() / 2;

  7. double new_origin_y = robot_y - costmap_.getSizeInMetersY() / 2;

  8. costmap_.updateOrigin(new_origin_x, new_origin_y);

  9. }

  10.  
  11. if (plugins_.size() == 0)

  12. return;

  13.  
  14. minx_ = miny_ = 1e30;

  15. maxx_ = maxy_ = -1e30;

  16.  
  17. for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins_.begin(); plugin != plugins_.end();

  18. ++plugin)

  19. {

  20. (*plugin)->updateBounds(robot_x, robot_y, robot_yaw, &minx_, &miny_, &maxx_, &maxy_);

  21. }

  22.  
  23. int x0, xn, y0, yn;

  24. costmap_.worldToMapEnforceBounds(minx_, miny_, x0, y0);

  25. costmap_.worldToMapEnforceBounds(maxx_, maxy_, xn, yn);

  26.  
  27. x0 = std::max(0, x0);

  28. xn = std::min(int(costmap_.getSizeInCellsX()), xn + 1);

  29. y0 = std::max(0, y0);

  30. yn = std::min(int(costmap_.getSizeInCellsY()), yn + 1);

  31.  
  32. ROS_DEBUG("Updating area x: [%d, %d] y: [%d, %d]", x0, xn, y0, yn);

  33.  
  34. if (xn < x0 || yn < y0)

  35. return;

  36.  
  37. {

  38. // Clear and update costmap under a single lock

  39. boost::unique_lock<Costmap2D::mutex_t> lock(*(costmap_.getMutex()));

  40. costmap_.resetMap(x0, y0, xn, yn);

  41. for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins_.begin(); plugin != plugins_.end();

  42. ++plugin)

  43. {

  44. (*plugin)->updateCosts(costmap_, x0, y0, xn, yn);

  45. }

  46. }

  47. }

这里我们来看这两个更新过程参数:


   
  1. (*plugin)->updateBounds(robot_x, robot_y, robot_yaw, &minx_, &miny_, &maxx_, &maxy_);

  2. (*plugin)->updateCosts(costmap_, x0, y0, xn, yn);

更新Bounds过程由于传入的参数是&minx_, &miny_, &maxx_, &maxy_ 构成了一个矩形范围。由于针对不同的类的实例,调用不同的类的方法。
对于Static Map:


   
  1. {

  2. if (!map_received_ || !(has_updated_data_ || has_extra_bounds_))

  3. return;

  4.  
  5. useExtraBounds(min_x, min_y, max_x, max_y);

  6.  
  7. double wx, wy;

  8.  
  9. mapToWorld(x_, y_, wx, wy);

  10. *min_x = std::min(wx, *min_x);

  11. *min_y = std::min(wy, *min_y);

  12.  
  13. mapToWorld(x_ + width_, y_ + height_, wx, wy);

  14. *max_x = std::max(wx, *max_x);

  15. *max_y = std::max(wy, *max_y);

  16.  
  17. has_updated_data_ = false;

  18. }

Static map 只在第一次做更新,Bounds 范围是整张Map的大小,而且在UpdateBounds过程中没有对Static Map层的数据做过任何的更新。

而对于 ObstacleLayer::updateBounds :主要的操作是更新Obstacles Map层的数据,然后才是更新Bounds


   
  1. std::vector<Observation> observations, clearing_observations;

  2.  
  3. // get the marking observations

  4. current = current && getMarkingObservations(observations);

  5. // get the clearing observations

  6. current = current && getClearingObservations(clearing_observations);

  7. // update the global current status

  8. current_ = current;

  9. // raytrace freespace

  10. for (unsigned int i = 0; i < clearing_observations.size(); ++i)

  11. {

  12. raytraceFreespace(clearing_observations[i], min_x, min_y, max_x, max_y);

  13. }

  14. for (std::vector<Observation>::const_iterator it = observations.begin(); it != observations.end(); ++it)

  15. {

  16. const Observation& obs = *it;

  17.  
  18. const pcl::PointCloud<pcl::PointXYZ>& cloud = *(obs.cloud_);

  19. for (unsigned int i = 0; i < cloud.points.size(); ++i)

  20. {

  21. double px = cloud.points[i].x, py = cloud.points[i].y, pz = cloud.points[i].z;

  22. // now we need to compute the map coordinates for the observation

  23. unsigned int mx, my;

  24. if (!worldToMap(px, py, mx, my))

  25. {

  26. continue;

  27. }

  28. unsigned int index = getIndex(mx, my);

  29. costmap_[index] = LETHAL_OBSTACLE;

  30. touch(px, py, min_x, min_y, max_x, max_y);

  31. }

  32. }

  33. updateFootprint(robot_x, robot_y, robot_yaw, min_x, min_y, max_x, max_y);

对于InflationLayer::updateBounds 则保持上一次的min_x, min_y, max_x, max_y
对于VoxelLayer::updateBounds 更新过程和 ObstacleLayer::updateBounds 基本一致,只是增加了z 作为判断是否将2d地图的点设定为LETHAL_OBSTACLE

updateCosts: 完成updateBounds 后,开始调用(*plugin)->updateCosts(costmap_, x0, y0, xn, yn); 。函数的第一个参数是指的master map,后面的bounds是对每个plugin自己维护的map的更新界限做设定。这里需要分析每一个单独的costmap和master map是哪些类在维护:
Master map: 这是由类LayeredCostmapCostmap2D costmap_ 维护。
StaticLayer StaticLayer VoxelLayer: 这些类是继承于Costmap2D ,因此可以直接操作Costmap2D 的数据成员 unsigned char* costmap_;。因此可以看成每一层地图都是类Costmap2D 的一个实例。
InflationLayer 没有继承于Costmap2D 是因为这个类并不需要维护一张自己的地图,它仅仅是需要直接操作master map的数据就可以了。
每个plugin调用自己代表的层的updateCosts方法:
StaticLayerObstacleLayer 基本上都是调用了CostmapLayer::updateWithOverwrite,CostmapLayer::updateWithTrueOverwrite, CostmapLayer::updateWithMax 等方法。因为CostmapLayer 是这两个的父类。
但是InflationLayer::updateCosts 则不同,因为它既没有自己层的map实例,也不是从CostmapLayer 继承而来。它的updateCosts 是这个类的核心操作。关于他的updateCosts 操作,将在InflationLayer 篇具体分析这个算法的实现过程,这个算法实现了对障碍物膨胀操作。

函数bool LayeredCostmap::isCurrent() 主要的操作是对操作的实时性提供保证,提供是否发生超时的信息。

 bool current = (ros::Time::now() - last_updated_).toSec() <= expected_update_rate_.toSec();

函数void LayeredCostmap::setFootprint(conststd::vector<geometry_msgs::Point>& footprint_spec)


   
  1. {

  2. footprint_ = footprint_spec;

  3. costmap_2d::calculateMinAndMaxDistances(footprint_spec, inscribed_radius_, circumscribed_radius_);

  4.  
  5. for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins_.begin(); plugin != plugins_.end();

  6. ++plugin)

  7. {

  8. (*plugin)->onFootprintChanged();

  9. }

  10. }

inscribed_radius_, circumscribed_radius_ 是计算得到的机器人尺寸的内切圆和外切圆半径。
这里重点关注InflationLayer 类是如何调用onFootprintChanged() 的。对于其他类型的plugin实例来说,其本身并没有重载这个函数,所以都是调用的Layer类的空函数virtual void onFootprintChanged() {}


   
  1. cell_inflation_radius_ = cellDistance(inflation_radius_);

  2. computeCaches();

函数computeCaches()


   
  1. void InflationLayer::computeCaches()

  2. {

  3. if (cell_inflation_radius_ == 0)

  4. return;

  5.  
  6. // based on the inflation radius... compute distance and cost caches

  7. if (cell_inflation_radius_ != cached_cell_inflation_radius_)

  8. {

  9. deleteKernels();

  10.  
  11. cached_costs_ = new unsigned char*[cell_inflation_radius_ + 2];

  12. cached_distances_ = new double*[cell_inflation_radius_ + 2];

  13.  
  14. for (unsigned int i = 0; i <= cell_inflation_radius_ + 1; ++i)

  15. {

  16. cached_costs_[i] = new unsigned char[cell_inflation_radius_ + 2];

  17. cached_distances_[i] = new double[cell_inflation_radius_ + 2];

  18. for (unsigned int j = 0; j <= cell_inflation_radius_ + 1; ++j)

  19. {

  20. cached_distances_[i][j] = hypot(i, j);

  21. }

  22. }

  23.  
  24. cached_cell_inflation_radius_ = cell_inflation_radius_;

  25. }

  26.  
  27. for (unsigned int i = 0; i <= cell_inflation_radius_ + 1; ++i)

  28. {

  29. for (unsigned int j = 0; j <= cell_inflation_radius_ + 1; ++j)

  30. {

  31. cached_costs_[i][j] = computeCost(cached_distances_[i][j]);

  32. }

  33. }

  34. }

在函数定义中,维护两个指针:


   
  1. cached_costs_ = new unsigned char*[cell_inflation_radius_ + 2];

  2. cached_distances_ = new double*[cell_inflation_radius_ + 2];

第一阶段,计算出cached_distances_: cached_distances_[i][j] = hypot(i, j); ,其中i j 的范围都是0:cell_inflation_radius_ + 1
第二阶段,通过计算得到的cached_distances_ 计算cached_costs_cached_costs_[i][j] = computeCost(cached_distances_[i][j]);。通过这个操作,现在可以任意给出在0-cell_inflation_radius_ cell范围的两个cells的costs,以后对地图做膨胀时,只需要查看某个cell(i1,j1)和obstacle cell(i,j)的下标就可以通过查表知道这个cell的代价是多少。这个表的大小仅仅和机器人的几何尺寸相关,一旦机器人尺寸发生改变,这个函数就需要再次被调用。
OK, LayeredCostmap的分析就这么多了~相信这样来来回回反复的分析这些类之间的调用关系,对于理解costmap_2d这个小怪兽是必要的。

原文参考:

http://download.csdn.net/download/jinking01/10272584

http://blog.csdn.net/u013158492/article/details/50490490

http://blog.csdn.net/x_r_su/article/details/53408528

http://blog.csdn.net/lqygame/article/details/71270858

http://blog.csdn.net/lqygame/article/details/71174342?utm_source=itdadao&utm_medium=referral

http://blog.csdn.net/xmy306538517/article/details/72899667

http://docs.ros.org/indigo/api/costmap_2d/html/classcostmap__2d_1_1Layer.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ROS基础教程--CostMap_2D包的一些理解 的相关文章

  • vc++ 6.0bug C1853

    c documents and settingsstudent桌面musicplayermaindlg cpp 1 fatal error C1853 Debug MusicPlayer pch is not a precompiled h
  • 人工智能知识点汇总

    一 AI应用领域 AI目前主要的应用领域有3个方向 xff0c 包括 xff1a 计算机视觉 语音交互 自然语言处理 1 1 计算机视觉 xff08 CV xff09 计算机视觉是一门研究如何使机器 看 的科学 xff0c 就是指用摄影机和
  • Java17(291)之后 , 禁用了TLS1.1 , 使JDBC无法用SSL连接SqlServer怎么办,以下是解决办法

    修改java security文件 1 找到jre的java security文件 2 打开java security并搜索 jdk tls disabledAlgorithms 61 3 删掉TLSv1 TLSv1 1 4 保存 可以了
  • NumPy、Torch和Tensorflow 代码对比

    深度学习 在深入学习的基本单位上实施初级到高级操作 gt Excerpts 我习惯于为不同的问题创建新的深度学习架构 xff0c 但选择哪个框架 xff08 Keras Pytorch TensorFlow xff09 通常比较困难 由于其
  • 2023年 机器学习常用算法

    01 线性回归 线性回归 xff08 Linear Regression xff09 可能是最流行的机器学习算法 线性回归就是要找一条直线 xff0c 并且让这条直线尽可能地拟合散点图中的数据点 它试图通过将直线方程与该数据拟合来表示自变量
  • STM32上SPI+DMA实现大批量读取flash数据

    最近做项目需要使用SPI 43 DMA xff0c 为了做实验感受DMA传输数据块 xff0c 本人以SPI 43 DMA来读取flash中的数据 网上有很多例程是spi直接读取flash xff0c 无法提高性能 因为只是简单的实验SPI
  • stm32通用定时器1s延时实现LED闪烁

    stm32有很多定时器 xff0c 每种定时器的功能也不尽相同 xff0c 今天学习了如何用通用定时器实现1s延时 xff0c 使LED灯闪烁 xff0c 现总结如下 xff1a 步骤总结 xff1a 使能定时器时钟 gt 配置定时器结构体
  • JDK 中使用js调用java类、方法

    最近研究阅读这个APP 其主要功能就是通过一个个书源 从而实现移动端阅读的体验 比如说某些在线小说阅读网站 会加上相应的广告 从而影响用户阅读体验 于是阅读这个APP就是做了类似净化阅读体验 但是小说阅读网站千千万万 如果去适配每个小说阅读
  • Spring项目在tomcat启动时调用action

    1 实现ServletContextListener接口 添加 64 WebListener注解 2 按照示例写代码 xff1a 第一个是开启时 xff0c 第二个是销毁时
  • ubuntu16.04安装opencv3.4

    参考blog https blog csdn net u013066730 article details 79411767 直接进行 完全没问题 sudo apt get install build essential libgtk2 0
  • 使用switch-case语句输出成绩等级

    问题描述 xff1a 输入一个0 100范围内发分数 xff0c 在不同的等级范围内输出不同的值 xff0c 要求使用switch case控制 0 60 等级为E 60 70 等级为D 70 80 等级为C 80 90 等级为B 90 1
  • 输出图案(六)---输出空心矩形

    输入矩形的宽 xff0c 高 xff0c 输出该空心矩形 xff0c 用 来进行表示 参考代码1 xff1a span class hljs comment include lt stdio h gt span span class hlj
  • C语言中交换两个数的方法

    问题描述 xff1a 程序中有两个数a b 其中a 61 4 b 61 5 xff0c 现在希望交换两个数的值 xff0c 使得a 61 5 b 61 4 在这里我总结了一下目前我已经掌握的C语言中交换两个数的方法 xff0c 主要如下几种
  • 输出平行四边形图案(多种方案)

    问题描述 xff1a 使用 在控制台打印平行四边形 例如 xff1a 平行四边形中最长的一行输出的 是5个 xff0c 则平行四边为 xff1a span class hljs bullet span span class hljs emp
  • 自己实现strcat函数

    问题描述 xff1a 自己实现一个MyStrcat函数 xff0c 要和C语言库函数的strcat函数完成同样的功能 问题分析 xff1a 首先我们要了解一下strcat函数它到底做了什么事情 1 函数原型 char strcat char
  • 简易文件打包程序

    对指定目录下面的文件进行打包 简易解包程序参考博客另外一篇文章 xff1a http blog csdn net yi ming he article details 77689453 打包方式 xff1a 把目录下面的文件名 xff0c
  • 简易解包程序

    对压缩包进行解压 简易压缩程序请参考博客的另外一篇文章 xff1a http blog csdn net yi ming he article details 77689405 解包方式 xff1a 根据打包建立的索引表 xff0c 找到对
  • linux 挂载错误Transport endpoint is not connected

    mount了mfs后 xff0c 重新挂载之后 xff0c 出现如下错误 xff1a usr local mfs bin mfsmount H 192 168 103 101 mnt fuse bad mount point 96 mnt
  • 新字体的永久注册

    CString GetCurrentModuleDir TCHAR szPath MAX PATH 43 1 61 0 if 0 61 61 GetModuleFileName HMODULE amp ImageBase szPath MA
  • yolov5/v7/v8自动检测多个文件夹及截取锚框

    目前yolo仅支持检测图片或单个文件夹 xff0c 但在很多时候需要对成百上千个文件夹中图片进行检测 xff0c 再根据得到的位置信息txt文件来截取图片 xff0c 如何一步完成呢 xff0c 详情见下文 在detect py中将save

随机推荐

  • 带参数的宏定义、函数与内联函数

    文章目录 前言一 宏定义1 基本用法2 带参数的宏定义 二 函数1 定义与声明2 调用 三 内联函数 inline总结 前言 在实际项目开发 xff0c 尤其是嵌入式软件项目中 xff0c 经常可以看到大量宏定义的分布 xff0c 其中又多
  • C++语言为什么跨平台?

    xfeff xfeff 现在主流的手机平台很多 xff0c 比如 xff1a Windows开发的Windows Phone xff08 WP 34 X 34 xff09 Apple 苹果公司 开发的ios xff0c Google 谷歌
  • CMake 中的list操作

    Cmake 中定义了一系列的数组操作 xff0c 使用方法如下 list INSERT lt list gt lt element index gt lt element gt lt element gt list REMOVE ITEM
  • 解决error while loading shared libraries: libXXX.so.X: cannot open shared object file: No such file

    原文转自CSDN xff0c 本文有删减 一 问题 运行hydra时 xff0c 提示错误 xff1a hydra error span class hljs keyword while span loading span class hl
  • 栈(超简单讲解版

    没错又是我来了 xff08 上一篇DFS还没写好就先来写队列与栈了哈哈哈哈 是很简单的内容呢 xff08 比DFS简单到哪里去了 先来认识一下栈 什么是栈 xff1f 度娘是这样说的 xff1a 栈 xff08 stack xff09 又名
  • ROS下使用stm32 与rosserial进行通信的开发说明及源代码示例

    关于stm32下的ROS开发环境介绍说明 xff0c 此开发环境是在Linux下使用stm32的标准库 STM32F10x StdPeriph Driver3 5 xff0c 进行stm32开发 xff0c 整体开发框架已搭建完成 xff0
  • 【ROS Rikirobot基础-使用系列 第四章节】Rikirobot小车使用激光雷达进行自动导航

    利用激光雷达进行自动导航 这里我们教大家使用的是利用激光雷达导航 xff0c 关于深度摄像头的导航我们后面会教大家使用 1 上电启动小车 xff0c 主控端执行启动小车的命令 xff1a roslaunch rikirobot bringu
  • js函数的四种调用形式以及this的指向

    以函数的 形式调用 xff1a function fun alert this 61 61 window fun 调用成功 xff0c this代表window 以方法的形式调用 var obj 61 name 61 34 hello 34
  • warning: control reaches end of non-void function

    用gcc编译一个程序的时候出现这样的警告 xff1a warning control reaches end of non void function 它的意思是 xff1a 控制到达非void函数的结尾 就是说你的一些本应带有返回值的函数
  • 项目中遇到的问题及解决方案

    1 用到的视频播放插件只支持加载相对路径 xff0c 不能加载绝对路径上的资源 解决方案 xff1a 为tomca t配置 文件 创建索引 xff0c 在 server xml文件中增加配置 lt Context path 61 34 IM
  • Oracle批量更新sql写法

    select from test table for update begin for cur in select id from test table loop update test table set name 61 39 苏晓伟 3
  • JVM 垃圾回收机制

    JVM体系结构概览 xff1a 垃圾回收 xff08 GC xff09 发生在哪个区 xff1a heap xff08 堆 xff09 区 GC是什么 xff1f 分几种 xff1a GC 分代收集算法 次数上频繁收集young区 xff0
  • JAVA 自定义注解

    多说无益 xff0c 直接上代码 import java lang annotation Documented import java lang annotation ElementType import java lang annotat
  • Vuex 学习

    什么是vuex xff1a 专门在Vue中实现集中式状态 xff08 数据 xff09 管理的一个Vue插件 xff0c 对vue应用中多个组件的共享状态进行集中式的管理 xff08 读 写 xff09 xff0c 也是一种组件间通信的方式
  • zookeeper本地安装启动

    下载zookeeper xff1a 链接 xff1a https pan baidu com s 151ZdXYg6QDB A8TRK0wrpw 提取码 xff1a yyds 复制到linux上并解压修改配置文件的名字 xff0c 将 zo
  • zookeeper集群安装

    准备3台服务器 xff0c 安装三个zookeeper xff0c 修改zoo cfg配置 xff0c dataDir 61 opt module zookeeper 3 5 7 zkData 分别在zkData目录下创建一个文件myid
  • zookeeper 启动停止脚本

    bin bash case 1 in 34 start 34 for i in 192 168 66 133 192 168 66 134 192 168 66 129 do echo zookeeper i 启动 ssh i 34 opt
  • ElasticSearch-全文检索

    docker 下载安装 es镜像 docker pull elasticsearch 7 4 2 es的可视化工具 docker pull kibana 7 4 2 mkdir p mydata elasticsearch config m
  • atoi()和stoi()的区别----数字字符串的处理

    相同点 xff1a 都是C 43 43 的字符处理函数 xff0c 把数字字符串转换成int输出 头文件都是 include lt cstring gt 不同点 xff1a atoi 的参数是 const char 因此对于一个字符串str
  • ROS基础教程--CostMap_2D包的一些理解

    本文是在综合了多篇文章的基础之上进行的综合 1 基本概念 Voxel xff1a 体素 xff0c 即顾名思义是体积的像素 用来在三维空间中表示一个显示基本点的单位 类似于二维平面下的pixel xff08 像素 xff09 voxel是三