栅格地图、障碍物地图与膨胀地图(膨胀地图(一))

2024-06-02 15:20
文章标签 地图 栅格 膨胀 障碍物

本文主要是介绍栅格地图、障碍物地图与膨胀地图(膨胀地图(一)),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前面看过了静态地图与障碍物地图,但是对于路径规划而言,这两个地图是不够的,还需要第三张地图,也就是膨胀地图。膨胀地图的意思还是比较好理解的,就是将地图的障碍物进行膨胀。为什么要这么操作呢,主要是考虑路径规划时,对于静态地图而言,不太好进行路径规划,因为一个栅格点虽然它可能是空闲的,但是如果它离障碍物很近的话,实际上这个点机器人是不能到达的,所以我们需要对地图按照机器人半径进行膨胀,这样子剩下的点才是真正可以运动的区域。

在move_base中,膨胀地图的内容不算多,只有简单的几个函数,这里简单看一下。

1、computeCaches

这个函数是一个预处理函数,所以我们将其放在最前面来看。这个函数主要执行的功能是预计算障碍物周围点到障碍物的距离以及代价值关系,后续会根据查表法来获取具体数据:

void InflationLayer::computeCaches()
{//Case 1:如果栅格的膨胀半径是0,那就直接返回,啥也不用干了。if (cell_inflation_radius_ == 0)return;// based on the inflation radius... compute distance and cost caches//Case 2:如果栅格的膨胀半径不等于缓存的栅格膨胀半径,那么: 基于膨胀半径...计算距离和成本缓存if (cell_inflation_radius_ != cached_cell_inflation_radius_){deleteKernels();cached_costs_ = new unsigned char*[cell_inflation_radius_ + 2]; //重新分配内存,建立cached_costs_cached_distances_ = new double*[cell_inflation_radius_ + 2];    //重新分配内存,建立cached_distances_//建立两层循环,从0到栅格膨胀半径+1,逐步填充代价和距离for (unsigned int i = 0; i <= cell_inflation_radius_ + 1; ++i){cached_costs_[i] = new unsigned char[cell_inflation_radius_ + 2];cached_distances_[i] = new double[cell_inflation_radius_ + 2];for (unsigned int j = 0; j <= cell_inflation_radius_ + 1; ++j){//hypot函数是一个C++标准库函数,它计算两个浮点数的平方和的平方根,即计算直角三角形的斜边长度。cached_distances_[i][j] = hypot(i, j);}}//更新缓存的膨胀半径cached_cell_inflation_radius_ = cell_inflation_radius_;}//计算代价for (unsigned int i = 0; i <= cell_inflation_radius_ + 1; ++i){for (unsigned int j = 0; j <= cell_inflation_radius_ + 1; ++j){cached_costs_[i][j] = computeCost(cached_distances_[i][j]);}}
}

这里的cell_inflation_radius_就是机器人的半径,当机器人的半径不为0时,需要计算一个膨胀表cached_costs_,它的作用类似于下图:
在这里插入图片描述
computeCost这个函数本身是计算一个点的代价值的,它的具体实现如下:

/** @brief  Given a distance, compute a cost.* @param  distance The distance from an obstacle in cells* @return A cost value for the distance *///首先进行距离的判断,如果距离为0,则返回代价为致命障碍物的代价值LETHAL_OBSTACLE;//再继续往下判断,距离分辨率是否小于内切半径,是的话,则返回代价为机器人内切圆膨胀障碍物的代价值INSCRIBED_INFLATED_OBSTACLE;//否的话,则代价按照欧几里德距离递减,欧几里德距离为距离分辨率,定义factor因子为指数函数,最后计算出cost值;virtual inline unsigned char computeCost(double distance) const{unsigned char cost = 0;if (distance == 0)cost = LETHAL_OBSTACLE;else if (distance * resolution_ <= inscribed_radius_)cost = INSCRIBED_INFLATED_OBSTACLE;else{// make sure cost falls off by Euclidean distancedouble euclidean_distance = distance * resolution_;double factor = exp(-1.0 * weight_ * (euclidean_distance - inscribed_radius_));cost = (unsigned char)((INSCRIBED_INFLATED_OBSTACLE - 1) * factor);}return cost;}

这段代码与上面的图片是契合的,对于障碍物的点,其值会为254,而离障碍物距离小于机器人半径的区域则是赋值253,再向外的点的值则是指数型下降。

2、updateCosts

这个函数是膨胀地图中的主要处理函数了,地图从障碍物地图变成膨胀地图就是通过这个函数实现的。首先需要获取一张现有的地图以及地图的尺寸信息:

  unsigned char* master_array = master_grid.getCharMap();//获取地图unsigned int size_x = master_grid.getSizeInCellsX(), size_y = master_grid.getSizeInCellsY();//获取网格x,y坐标

在获取到地图后,算法会遍历这张地图,然后将所有的障碍物点都先记录下来:

 std::vector<CellData>& obs_bin = inflation_cells_[0.0];for (int j = min_j; j < max_j; j++){for (int i = min_i; i < max_i; i++){int index = master_grid.getIndex(i, j);    //1)从master_grid地图获取(i,j)对应的索引indexunsigned char cost = master_array[index];  //2)从master_grid地图获取索引对应的代价costif (cost == LETHAL_OBSTACLE)               //3)先判断代价是否等于致命障碍物对应的代价,如果是,压入{ //注意这里的obs_bin是一个指针,数据其实是存储在inflation_cells_下//std::map<double, std::vector<CellData> > inflation_cells_;obs_bin.push_back(CellData(index, i, j, i, j));}}}

注意这里记录的点都被记录在inflation_cells_中了,这是一个std::map<double, std::vector >类型的数据。

既然我们知道了所有的障碍物点了,下一步自然是需要对其进行扩张了,这里的扩张使用的是两个for循环实现的:

//根据膨胀队列,进行膨胀//通过上面的操作,现在inflation_queue_里面全部都是obstacle cell,这些cell被打包成CellData类型,包含了这些cell本身的索引,最近障碍物的索引(即它们本身),距离(0)std::map<double, std::vector<CellData> >::iterator bin;for (bin = inflation_cells_.begin(); bin != inflation_cells_.end(); ++bin){ //遍历每一个障碍物点//map键值对映射,键是距离最近的障碍物的距离,值是cell//second是“键”“值”对中的“值”,即遍历次数为celldata的个数for (int i = 0; i < bin->second.size(); ++i){// process all cells at distance dist_bin.first//在该“double键”下记录迭代到的celldataconst CellData& cell = bin->second[i];//记录该celldata的索引indexunsigned int index = cell.index_;// ignore if already visited  如果已访问过,则忽略if (seen_[index]){continue;}seen_[index] = true;//得到该cell的坐标和离它最近的障碍物的坐标unsigned int mx = cell.x_;unsigned int my = cell.y_;unsigned int sx = cell.src_x_;unsigned int sy = cell.src_y_;// assign the cost associated with the distance from an obstacle to the cell//4)更新膨胀队列中点的cost 值//根据该CELL与障碍的距离来分配costunsigned char cost = costLookup(mx, my, sx, sy);unsigned char old_cost = master_array[index];  //从master_grid查找指定索引index的代价//如果old_cost无信息或者cost≥内接膨胀障碍物,将cost更新到master_arrayif (old_cost == NO_INFORMATION && (inflate_unknown_ ? (cost > FREE_SPACE) : (cost >= INSCRIBED_INFLATED_OBSTACLE)))master_array[index] = cost;else//否则比较新旧大小,将大的cost更新到master_arraymaster_array[index] = std::max(old_cost, cost);// attempt to put the neighbors of the current cell onto the inflation list//上面首先从inflation_cells_中取出最大distance的cell,再将这个cell的前后左右四个cell都塞进inflation_cells_。//由于下面关于enqueue的调用,最后两个参数(sx, sy)没有改变,所以保证了这个索引一定是obstacle cell。//由于在 enqueue入口会检查 if (!seen_[index]),这保证了这些cell不会被重复的塞进去。//由于这是一个priority queue,因此障碍物的周边点,每次都是离障碍物最远的点被弹出,并被检查,这样保证了这种扩张是朝着离障碍物远的方向进行。//尝试将当前单元的邻居(前后左右)放到队列中 if (mx > 0)enqueue(index - 1, mx - 1, my, sx, sy);if (my > 0)enqueue(index - size_x, mx, my - 1, sx, sy);if (mx < size_x - 1)enqueue(index + 1, mx + 1, my, sx, sy);if (my < size_y - 1)enqueue(index + size_x, mx, my + 1, sx, sy);}}inflation_cells_.clear();

其实上述这里实现的是一个对可能点的遍历,但是比较有意思的是一般这种遍历采用的都是while居多,但是这里使用的是一个对于map容器的for循环。它的主体思想是这样子的:


1.将所有障碍物点都存储到map容器内
2.遍历map容器,取出其中每一个障碍物点,遍历其周围四邻近点,计算四邻近点到最近障碍物的距离以及其代价值,并存储到map容器内
3.遍历这些离障碍物较近的点,遍历其周围四邻近点,找到下一组比当前点更远离障碍物的点,计算其代价值并存储
4.循环步骤3直到剩下的点离障碍物距离都超出阈值,跳出for循环。


注意这里for循环为什么能够实现这个功能,跟上面的数据类型有很大关系:std::map<double, std::vector >的数据类型中第一位数据为点到障碍物的距离,初始时,map下存储的点都是障碍物的点,所以其键值都为0,然后遍历这些点的过程中会产生一系列四邻近点,这些点到障碍物的距离都为1个栅格的值,因此map下会产生一个新的键值以及一组新的地图点信息,这样子就实现了inflation_cells_的扩张以及inflation_cells_的循环遍历。而当所有接近障碍物的点都遍历完成后,将不会产生新的键值,则inflation_cells_的遍历也就结束了。

另外这里面还有一个比较重要的东西是:

// ignore if already visited  如果已访问过,则忽略if (seen_[index]){continue;}seen_[index] = true;

对于每一个点都只遍历一次,这样做的方式是为了防止重复遍历导致的数据异常,因为对于上述的遍历过程而言,前一时刻遍历过的点其离障碍物的距离值一定是比下一个循环遍历时离障碍物的距离值更近的(以障碍物为起点向外扩张)。

3、enqueue

这是膨胀地图中另外一个主要的函数,它的作用是扩散,即从障碍物点向外逐渐扩散直到所有离障碍物较近的点都被遍历完。

/*** @brief  Given an index of a cell in the costmap, place it into a list pending for obstacle inflation* @param  grid The costmap* @param  index The index of the cell* @param  mx The x coordinate of the cell (can be computed from the index, but saves time to store it)* @param  my The y coordinate of the cell (can be computed from the index, but saves time to store it)* @param  src_x The x index of the obstacle point inflation started at* @param  src_y The y index of the obstacle point inflation started at*/
inline void InflationLayer::enqueue(unsigned int index, unsigned int mx, unsigned int my,unsigned int src_x, unsigned int src_y)
{if (!seen_[index]){// we compute our distance table one cell further than the inflation radius dictates so we can make the check below//找它和最近的障碍物的距离,如果超过了阈值,则直接返回//如果不超过阈值,就把它也加入inflation_cells_数组里double distance = distanceLookup(mx, my, src_x, src_y);// we only want to put the cell in the list if it is within the inflation radius of the obstacle pointif (distance > cell_inflation_radius_)return;// push the cell data onto the inflation list and markinflation_cells_[distance].push_back(CellData(index, mx, my, src_x, src_y));}
}

在updateCosts中,对于每一个点,会调用enqueue函数遍历其周围的四个点,在enqueue函数中,这个点如果已经被遍历过一次则跳过,否则会根据这个点离最近的障碍物的距离是否小于cell_inflation_radius_来决定是否需要对其进行updateCosts的计算,对于需要的点会被push进inflation_cells_,也就是我们第二个函数中两层for循环执行的点。

注意这里的cell_inflation_radius_与第一部分中计算时使用的inscribed_radius_是两个参数,这个参数应该会比inscribed_radius_要大一点。如果两个设置成一样的话应该是类似第一部分图中只保留254与253参数的格子,消除所有指数下降部分。

至此膨胀地图基本就看完了,这个部分在没看之前感觉比较高大上,但是实际看完发现东西其实不多。总结来说,就是预先计算一个查表,表中记录一组到达障碍物不同距离的点的代价值。然后遍历每一个障碍物点,这些障碍物点同步向外扩散,求其周围点到障碍物的距离,根据查表法求周围栅格的代价值。直到所有点都扩散到离障碍物较远的距离后,一张膨胀地图就算是计算完成了。

这篇关于栅格地图、障碍物地图与膨胀地图(膨胀地图(一))的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1024343

相关文章

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

全英文地图/天地图和谷歌瓦片地图杂交/设备分布和轨迹回放/无需翻墙离线使用

一、前言说明 随着风云局势的剧烈变化,对我们搞软件开发的人员来说,影响也是越发明显,比如之前对美对欧的软件居多,现在慢慢的变成了对大鹅和中东以及非洲的居多,这两年明显问有没有俄语或者阿拉伯语的输入法的增多,这要是放在2019年以前,一年也遇不到一个人问这种需求场景的。 地图应用这块也是,之前的应用主要在国内,现在慢慢的多了一些外国的应用场景,这就遇到一个大问题,我们平时主要开发用的都是国内的地

Imageview在百度地图中实现点击事件

1.首先第一步,需要声明的全局有关类的引用 private BMapManager mBMapMan; private MapView mMapView; private MapController mMapController; private RadioGroup radiogroup; private RadioButton normalview; private RadioBu

MMO地图传送

本篇由以下四个点讲解: 创建传送点 传送点配置 编辑器扩展:传送点数据生成 传送协议与实现 创建传送点 建碰撞器触发 //位置归零 建一个传送门cube放到要传送的位置(这个teleporter1是传出的区域 这是从另一张地图传入时的传送门 创建一个脚本TeleporterObject给每个传送cube都绑上脚本 通过脚本,让传送门在编辑器下面还能绘制出来

1-9 图像膨胀 opencv树莓派4B 入门系列笔记

目录 一、提前准备 二、代码详解 kernel = np.ones((3, 3), np.uint8) _, binary_image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY) dilated_image = cv2.dilate(binary_image, kernel, iterations=1) 三、运行现象 四

中国生态环境胁迫数据(栅格/县域尺度)-为研究生态环境压力提供数据支撑

中国生态环境胁迫矢量数据(2000-2010年) 数据介绍 2000-2010年中国生态环境胁迫数据为2000-2010年中国范围内人口、农业生产等生态环境胁迫因子的空间分布图,包括人口密度、农药使用强度、化肥施用强度。数据可用于分析全国生态环境胁迫因子及其对生态环境造成的压力的空间特征,主要通过社会经济统计资料获得,为县域尺度空间数据。 存储容量31.01 GB文件数量6数据类型栅

ArcGIS Pro SDK (十三)地图创作 3 特殊图层

ArcGIS Pro SDK (十三)地图创作 3 特殊图层 文章目录 ArcGIS Pro SDK (十三)地图创作 3 特殊图层1 高程表面图层1.1 创建具有地表图层的场景1.2 创建新的高程表面1.3 将自定义高程表面设置为 Z 感知图层1.4 将高程源添加到现有高程表面图层1.5 从地图中获取高程表面图层和高程源图层1.6 查找高程表面图层1.7 移除高程表面图层1.8 从曲面获

激光SLAM如何动态管理关键帧和地图

0. 简介 个人在想在长期执行的SLAM程序时,当场景发生替换时,激光SLAM如何有效的更新或者替换地图是非常关键的。在看了很多Life-Long的文章后,个人觉得可以按照以下思路去做。这里可以给大家分享一下 <br/> 1. 初始化保存关键帧 首先对应的应该是初始化设置,初始化设置当中会保存关键帧数据,这里的对应的关键帧点云数据会被存放在history_kf_lidar当中,这个数据是和

产品地图经典案例,为盲人设计一款闹钟

在产品设计的旅程中,复杂程度往往超乎你的想象。从理解产品设计的初衷,到制定具体目标,再到解决实际问题,这一过程涉及许多环节。以下是如何通过即时设计在线白板高效绘制产品地图,以便更好地进行产品设计的具体示例——为盲人设计一款闹钟。 即时设计在线白板是一个高效便捷的可视化工具,可以帮助团队更好地协作和整理思路。我们将通过实际案例来展示如何使用它绘制产品地图。https://js.design/?so

Uber开发的地图网格化的包h3

通常使用规则化的网格包括三角形,正方形和六边形。三角形或正方形的网格与其相邻的网格中心距离不同,而六边形则没有此种情况,因此,h3使用的是六边形网格. 地图的网格划分以南京为例子 api文档 https://github.com/uber/h3-py https://h3geo.org/docs/community/bindings 在这里插入图片描述h3包地图划分级别,第一列是边长 H3 P