datetimepicker 更新值无效_文献阅读之Voronoi图的生成与更新

0bab077bd7370c7689a5403c0f2cfb8a.png

通俗的说,在机器人导航方面,Voronoi图是一种将地图分区的方法,分区的界限即Voronoi图的边,常用作机器人远离障碍物避障的规划路径。本文主要参考了 Boris Lau 的论文和代码,对Voronoi图的生成和更新进行分析。相关的3篇论文内容重合度比较高,我主要以《Efficient Grid-Based Spatial Representations for Robot Navigation in Dynamic Environments》为主。对代码的理解和注释,我已在GitHub上开源,欢迎大家一起讨论。

Boris Lau 的论文和代码​www2.informatik.uni-freiburg.deComments for Voronoi​github.com

1. DM的更新概述

在机器人路径规划和避障的过程中,我们常常需要知道某个时刻机器人与最近障碍物的距离,以远离障碍物,或者进行碰撞检测。论文提出使用Distance Map(DM)和 Generalized Voronoi Diagrams(GVD)来解决这个问题。DM的建立和更新是GVD建立和更新的前提,方法来源于改进的brushfire算法,过程如图1-2所示。DM的每个栅格都会保存与最近障碍物点的距离,以及障碍物点的坐标(因此,障碍物的内部点是被忽略的,只有轮廓点被考察)。

图1A是论文算法的输入——已知的二值占据栅格地图,其中外围的黑色是地图外部区域,中间的黑色是障碍物,白色是可行驶区域。因为有边界和障碍物的存在,使得内部白色栅格与最近障碍物点的距离会减小(初始化为正无穷),因此要从障碍物栅格开始,逐步向外扩散更新,计算新的最近障碍物坐标与距离,反映为图1B-D中灰色逐步扩展,距离越近颜色越深。当所有栅格都被更新后,DM建立完成。

5fb44e97beefa1202bf9116151ac2db0.png
图1 建立DM

当图1中的障碍物消失、新的障碍物出现时(图2B),相应的二值占据栅格地图会被更新,进而触发DM和GVD的更新。因为旧的障碍物(记为P)消失,那么周围以P为最近障碍物的栅格,暂时没有最近障碍物,其保存的最近障碍物距离也会被置为无效值(或正无穷,或初始值),所以这些栅格的状态更新是一个距离增大(raise)的过程。

类似的,因为新的障碍物(记为Q)出现,那么Q周围的栅格,其保存的最近障碍物距离被重新计算(可能是到Q的距离),所以这些栅格的状态更新是一个距离减小(lower)的过程。

当raise和lower的过程相遇,lower处理过的栅格不会受影响,但是raise处理过的栅格,这时就要考虑新出现的Q对其的影响,就要重新计算最近障碍物(可能是Q)的距离,所以raise过程结束,转变为lower过程(图2C)。

当raise和lower都不再进展,DM更新结束。在DM更新的过程中,GVD会同步更新,我会在接下来的代码中展示GVD的更新过程。障碍物的移动,也可以分解为原位置的障碍物消失、新位置的障碍物出现的过程。因为更新不会遍历所有的栅格(比如最外层的栅格,其最近的障碍物一定是地图边界,无需更新也不会更新),所以这是一个增量更新的过程,访问栅格少,实时性好。

4c046352bb6255a19720f9fb20f78a54.png
图2 更新DM

2. Voronoi数据结构

// queues
//保存待考察的栅格
BucketPrioQueue<INTPOINT> open_;
//保存待剪枝的栅格
std::queue<INTPOINT> pruneQueue_;
//保存预处理后的待剪枝的栅格
BucketPrioQueue<INTPOINT> sortedPruneQueue_;//保存移除的障碍物曾占据的栅格
std::vector<INTPOINT> removeList_;
//保存增加的障碍物要占据的栅格
std::vector<INTPOINT> addList_;
//保存上次添加的障碍物覆盖的栅格
std::vector<INTPOINT> lastObstacles_;// maps
int sizeY_;
int sizeX_;
dataCell** data_;  //保存了每个栅格与最近障碍物的距离、最近障碍物的坐标、是否Voronoi点的标志
bool** gridMap_;          //true是被占用,false是没有被占用
bool allocatedGridMap_;   //是否为gridmap分配了内存的标志位

DM和GVD的栅格用dataCell二维数组表示,gridMap_是输入的二值占据栅格地图。

struct dataCell {float dist;char voronoi;   //State的枚举值char queueing;  //QueueingState的枚举值int obstX;int obstY;bool needsRaise;int sqdist;
};

使用到的枚举型状态量如下,最终state是voronoiKeep 的点,便是Voronoi的边上的点,组成了Voronoi图。QueueingState 的含义我没有搞明白,但是不妨碍理解算法的思路和流程。

typedef enum {voronoiKeep=-4, freeQueued = -3, voronoiRetry=-2, voronoiPrune=-1, free=0, occupied=1} State;
//下面这几个枚举状态没搞懂
typedef enum {fwNotQueued=1, fwQueued=2, fwProcessed=3, bwQueued=4,bwProcessed=1} QueueingState;
typedef enum {invalidObstData = SHRT_MAX/2} ObstDataState;
//表示剪枝操作时栅格的临时状态
typedef enum {pruned, keep, retry} markerMatchResult;

3. 地图数据初始化

//输入二值地图gridmap,根据元素是否被占用,更新data_
void DynamicVoronoi::initializeMap(int _sizeX, int _sizeY, bool** _gridMap) {gridMap_ = _gridMap;initializeEmpty(_sizeX, _sizeY, false);for (int x=0; x<sizeX_; x++) {for (int y=0; y<sizeY_; y++) {if (gridMap_[x][y]) {           //如果gridmap_中的(x,y)被占用了dataCell c = data_[x][y];if (!isOccupied(x,y,c)) {     //如果c没有被占用,即data_中的(x,y)没被占用,需要更新bool isSurrounded = true;   //如果在gridmap_中的邻居元素全被占用for (int dx=-1; dx<=1; dx++) {int nx = x+dx;if (nx<=0 || nx>=sizeX_-1) continue;for (int dy=-1; dy<=1; dy++) {if (dx==0 && dy==0) continue;int ny = y+dy;if (ny<=0 || ny>=sizeY_-1) continue;//如果在gridmap_中的邻居元素有任意一个没被占用(就是障碍物边界点)if (!gridMap_[nx][ny]) {isSurrounded = false;break;}}}if (isSurrounded) {         //如果九宫格全部被占用c.obstX = x;c.obstY = y;c.sqdist = 0;c.dist=0;c.voronoi=occupied;c.queueing = fwProcessed;data_[x][y] = c;} else {setObstacle(x,y);         //不同之处在于:将(x,y)加入addList_}}}}}
}

initializeEmpty()主要清空历史数据,为数组开辟内存空间,并赋初始值,将所有栅格设置为不被占用。然后,当gridMap_中的某栅格被占用,而data_中的该栅格却没被占用时,表示环境发生了变化,才需要更新栅格的信息。因为这是初始化操作,不会出现gridMap_中的某栅格不被占用、而data_中的该栅格却被占用的情况。如果一个栅格的8个邻居栅格全被占用,说明该栅格在障碍物内部,只需简单赋值,不会触发lower过程。如果8个邻居栅格没有全被占用,说明该栅格在障碍物边界上,调用setObstacle(),暂存会触发DM更新的点。

4. 添加障碍物

//要同时更新gridmap和data_
void DynamicVoronoi::occupyCell(int x, int y) {gridMap_[x][y] = 1;     //更新gridmapsetObstacle(x,y);
}//只更新data_
void DynamicVoronoi::setObstacle(int x, int y) {dataCell c = data_[x][y];if(isOccupied(x,y,c)) {               //如果data_中的(x,y)被占用return;} addList_.push_back(INTPOINT(x,y));    //加入addList_c.obstX = x;c.obstY = y;data_[x][y] = c;
}

676adf8aa1079ef6f4278ca94b3df950.png
图3

5. 移除障碍物

//要同时更新gridmap和data_
void DynamicVoronoi::clearCell(int x, int y) {gridMap_[x][y] = 0;     //更新gridmapremoveObstacle(x,y);
}//只更新data_
void DynamicVoronoi::removeObstacle(int x, int y) {dataCell c = data_[x][y];if(isOccupied(x,y,c) == false) {      //如果data_中的(x,y)没有被占用,无需处理return;} removeList_.push_back(INTPOINT(x,y)); //将(x,y)加入removeList_c.obstX = invalidObstData;c.obstY  = invalidObstData;c.queueing = bwQueued;data_[x][y] = c;
}

553b0165b7912bc98c869d6ba698f26c.png
图4

commitAndColorize()分别处理addList_ 和 removeList_ 中的栅格,将它们加入open_优先队列。commitAndColorize()运行后,才算完成了图3-4所列的添加、移除障碍物。接下来才会更新DM。

//将发生状态变化(占用<-->不占用)的元素加入open_优先队列
void DynamicVoronoi::commitAndColorize(bool updateRealDist) {//addList_和removeList_中是触发Voronoi更新的元素,因此都要加入open_// ADD NEW OBSTACLES//addList_中都是障碍物边界点for (unsigned int i=0; i<addList_.size(); i++) {INTPOINT p = addList_[i];int x = p.x;int y = p.y;dataCell c = data_[x][y];if(c.queueing != fwQueued){if (updateRealDist) {c.dist = 0;}c.sqdist = 0;c.obstX = x;c.obstY = y;c.queueing = fwQueued;        //已加入open_优先队列c.voronoi = occupied;data_[x][y] = c;open_.push(0, INTPOINT(x,y)); //加入open_优先队列,加入open_的都是要更新的}}// REMOVE OLD OBSTACLES//removeList_中是要清除的障碍物栅格for (unsigned int i=0; i<removeList_.size(); i++) {INTPOINT p = removeList_[i];int x = p.x;int y = p.y;dataCell c = data_[x][y];//removeList_中对应的元素在data_中已经更新过,解除了占用//如果这里又出现了该元素被占用,说明是后来加入的,这里不处理if (isOccupied(x,y,c) == true) {continue; // obstacle was removed and reinserted}open_.push(0, INTPOINT(x,y)); //加入open_优先队列if (updateRealDist) {c.dist  = INFINITY;}c.sqdist = INT_MAX;c.needsRaise = true;          //因为清除了障碍物,最近障碍物距离要更新-增加data_[x][y] = c;}removeList_.clear();addList_.clear();
}

6. 更新障碍物

//用新的障碍物信息替换旧的障碍物信息
//如果points为空,就是清除障碍物;
//初始时lastObstacles_为空,第一次调用exchangeObstacles()就是纯粹的添加障碍物
void DynamicVoronoi::exchangeObstacles(std::vector<INTPOINT>& points) {for (unsigned int i=0; i<lastObstacles_.size(); i++) {int x = lastObstacles_[i].x;int y = lastObstacles_[i].y;bool v = gridMap_[x][y];if (v) {    //如果(x,y)被占用了,不处理,怀疑这里逻辑反了。continue; //要移除旧的障碍物,这里应该是(!v)表示没被占用就不处理,占用了就移除}removeObstacle(x,y);}lastObstacles_.clear();for (unsigned int i=0; i<points.size(); i++) {int x = points[i].x;int y = points[i].y;bool v = gridMap_[x][y];if (v) {    //如果(x,y)被占用了,不处理。否则,添加占用continue;}setObstacle(x,y);lastObstacles_.push_back(points[i]);}
}

exchangeObstacles()用来使用新的障碍物替换旧的障碍物。当地图刚刚初始化,exchangeObstacles()第一次调用时,因为没有旧的障碍物需要移除,这就是纯粹的添加障碍物。当exchangeObstacles()的输入参数为空时,因为没有新的障碍物要添加,这就是纯粹的移除障碍物。所以,外界环境的变化通过exchangeObstacles()传入,这是更新DM的触发点。

7. 更新DM

这是论文和代码的核心环节,主要分为lower()和raise() 2部分,对应图5-7。

d0d28a522016a65ad4fc881bf6063fd0.png
图5

541b432a5ab020436a9f735c7b65f2fb.png
图6

dd7ef1cec8845e0efe5e487f9eb936e8.png
图7
void DynamicVoronoi::update(bool updateRealDist) {//将发生状态变化(占用<-->不占用)的元素加入open_优先队列commitAndColorize(updateRealDist);while (!open_.empty()) {INTPOINT p = open_.pop();int x = p.x;int y = p.y;dataCell c = data_[x][y];if(c.queueing==fwProcessed) {continue;}if (c.needsRaise) {// RAISE//2层for循环,考察8个邻居栅格for (int dx=-1; dx<=1; dx++) {int nx = x+dx;if (nx<=0 || nx>=sizeX_-1) continue;for (int dy=-1; dy<=1; dy++) {if (dx==0 && dy==0) continue;int ny = y+dy;if (ny<=0 || ny>=sizeY_-1) continue;dataCell nc = data_[nx][ny];//nc有最近障碍物 且 不raiseif (nc.obstX!=invalidObstData && !nc.needsRaise) {//如果nc原来的最近障碍物消失了if(!isOccupied(nc.obstX, nc.obstY, data_[nc.obstX][nc.obstY])) {open_.push(nc.sqdist, INTPOINT(nx,ny));nc.queueing = fwQueued;   //fwQueued表示刚加入open_排队?nc.needsRaise = true;     //需要raise,并清理掉原来的最近障碍物信息nc.obstX = invalidObstData;nc.obstY = invalidObstData;if (updateRealDist) {nc.dist = INFINITY;}nc.sqdist = INT_MAX;data_[nx][ny] = nc;} else {                    //如果nc原来的最近障碍物还存在if(nc.queueing != fwQueued){open_.push(nc.sqdist, INTPOINT(nx,ny));nc.queueing = fwQueued;data_[nx][ny] = nc;}}}}}c.needsRaise = false;c.queueing = bwProcessed;         //bwProcessed表示8个邻居元素raise处理完毕?data_[x][y] = c;}else if (c.obstX != invalidObstData && isOccupied(c.obstX, c.obstY, data_[c.obstX][c.obstY])) {//c是被占据的// LOWERc.queueing = fwProcessed;         //fwProcessed表示8个邻居元素lower处理完毕?c.voronoi = occupied;for (int dx=-1; dx<=1; dx++) {int nx = x+dx;if (nx<=0 || nx>=sizeX_-1) continue;for (int dy=-1; dy<=1; dy++) {if (dx==0 && dy==0) continue;int ny = y+dy;if (ny<=0 || ny>=sizeY_-1) continue;dataCell nc = data_[nx][ny];if(!nc.needsRaise) {int distx = nx-c.obstX;int disty = ny-c.obstY;int newSqDistance = distx*distx + disty*disty;//nc到c的最近障碍物 比 nc到其最近障碍物 更近bool overwrite =  (newSqDistance < nc.sqdist);if(!overwrite && newSqDistance==nc.sqdist) {//如果nc没有最近障碍物,或者 nc的最近障碍物消失了if(nc.obstX == invalidObstData || isOccupied(nc.obstX, nc.obstY, data_[nc.obstX][nc.obstY]) == false){overwrite = true;}}if (overwrite) {open_.push(newSqDistance, INTPOINT(nx,ny));nc.queueing = fwQueued;     //fwQueued表示加入到open_等待lower()?if (updateRealDist) {nc.dist = sqrt((double) newSqDistance);}nc.sqdist = newSqDistance;nc.obstX = c.obstX;         //nc的最近障碍物 赋值为c的最近障碍物nc.obstY = c.obstY;} else {checkVoro(x,y,nx,ny,c,nc);}data_[nx][ny] = nc;}}}}data_[x][y] = c;}
}

updata()先调用了commitAndColorize(),将状态发生了翻转变化(占用变成不占用、不占用变成占用)的栅格加入open_优先队列,然后遍历open_中的元素,并调用checkVoro()判断其是否属于Voronoi图边上的点。

8. 属于Voronoi的条件

void DynamicVoronoi::checkVoro(int x, int y, int nx, int ny,dataCell& c, dataCell& nc){if ((c.sqdist>1 || nc.sqdist>1) && nc.obstX!=invalidObstData) {if (abs(c.obstX-nc.obstX) > 1 || abs(c.obstY-nc.obstY) > 1) {//compute dist from x,y to obstacle of nx,nyint dxy_x = x-nc.obstX;int dxy_y = y-nc.obstY;int sqdxy = dxy_x*dxy_x + dxy_y*dxy_y;int stability_xy = sqdxy - c.sqdist;if (sqdxy - c.sqdist<0) return;//compute dist from nx,ny to obstacle of x,yint dnxy_x = nx - c.obstX;int dnxy_y = ny - c.obstY;int sqdnxy = dnxy_x*dnxy_x + dnxy_y*dnxy_y;int stability_nxy = sqdnxy - nc.sqdist;if (sqdnxy - nc.sqdist <0) return;//which cell is added to the Voronoi diagram?if(stability_xy <= stability_nxy && c.sqdist>2) {if (c.voronoi != free) {c.voronoi = free;reviveVoroNeighbors(x,y);pruneQueue_.push(INTPOINT(x,y));}}if(stability_nxy <= stability_xy && nc.sqdist>2) {if (nc.voronoi != free) {nc.voronoi = free;reviveVoroNeighbors(nx,ny);pruneQueue_.push(INTPOINT(nx,ny));}}}}
}

这段代码基本是复现图8的算法,不同之处在于,在检测(x,y)、(nx,ny)是否Voronoi备选点的同时,也把这2个点的各自8个邻居栅格也进行了检测,通过reviveVoroNeighbors()实现。通过检测的备选点加入到pruneQueue_ 中。因为还会对pruneQueue_ 中的元素进行剪枝操作,以得到精细准确的、单像素宽度的Voronoi边,pruneQueue_ 只是中间过程的存储容器,所以无需使用优先队列,只是普通的std::queue就可以。

图8中使用了6个判断条件,分别是:

  • 第1条:s和n至少有一个不紧邻其obs,若都紧邻,无法判断s和n是不是GVD
  • 第2条:n的最近obs是存在的,若不存在,无法判断s和n是不是GVD
  • 第3条:s和n的最近obs是不同的,若相同,无法判断s和n是不是GVD
  • 第4条:s的最近obs和n的最近obs不紧邻,若紧邻,则同属一个obs,无法判断s和n是不是GVD
  • 第5-6条:属于GVD的点,一定是距周边obs最近的,所以倾向于选择距obs更近的点作为候选。

a4cf52fb6bfb148b4ae1ff99ffada494.png
图8
//将符合条件的(x,y)的邻居栅格也添加到需剪枝的Voronoi备选中
void DynamicVoronoi::reviveVoroNeighbors(int &x, int &y) {for (int dx=-1; dx<=1; dx++) {int nx = x+dx;if (nx<=0 || nx>=sizeX_-1) continue;for (int dy=-1; dy<=1; dy++) {if (dx==0 && dy==0) continue;int ny = y+dy;if (ny<=0 || ny>=sizeY_-1) continue;dataCell nc = data_[nx][ny];if (nc.sqdist != INT_MAX && !nc.needsRaise && (nc.voronoi == voronoiKeep || nc.voronoi == voronoiPrune)) {nc.voronoi = free;data_[nx][ny] = nc;pruneQueue_.push(INTPOINT(nx,ny));}}}
}

9. 剪枝

prune()的主要目的是将2个栅格宽度的Voronoi边精简为1个栅格宽度,分为2步,对应代码中的2个while()循环。

第1,遍历pruneQueue_,用图9中的模式去匹配每个元素,及该元素上下左右紧邻的4个栅格。若匹配成功,就加入sortedPruneQueue_,等待剪枝。这一步的目的是将被2条相距很近的Voronoi边包裹的单个栅格加入到备选中。

第2,遍历sortedPruneQueue_,用图10中的左侧2个模式或者右侧2个模式去匹配每个元素,匹配的过程由markerMatch()完成。若匹配的结果是pruned,该栅格被剪枝;keep,该栅格就是Voronoi图上的点;retry,将该栅格重新加入到pruneQueue_。注意,第1步完成后,pruneQueue_已经空了。如果sortedPruneQueue_第一次遍历完毕,会将pruneQueue_中的需要retry的元素转移到sortedPruneQueue_中,继续执行第2步的遍历,直到sortedPruneQueue_为空。

c32e76c3baff59c6e83b12bd9f7ffe5a.png
图9

32c05b9e561032181e3aa9ef0421a4de.png
图10
void DynamicVoronoi::prune() {// filler//先遍历pruneQueue_中的元素,判断是否要加入到sortedPruneQueue_,//这一步的目的是合并紧邻的Voronoi边,将2条边夹着的栅格也设置为备选//再遍历sortedPruneQueue_中的元素,判断其是剪枝、保留、重试。while(!pruneQueue_.empty()) {INTPOINT p = pruneQueue_.front();pruneQueue_.pop();int x = p.x;int y = p.y;//如果(x,y)是occupied,无需处理,不可能是Voronoiif (data_[x][y].voronoi==occupied) continue;//如果(x,y)是freeQueued,已经加入到sortedPruneQueue_,略过if (data_[x][y].voronoi==freeQueued) continue; data_[x][y].voronoi = freeQueued;sortedPruneQueue_.push(data_[x][y].sqdist, p);/* tl t trl c rbl b br */dataCell tr,tl,br,bl;tr = data_[x+1][y+1];tl = data_[x-1][y+1];br = data_[x+1][y-1];bl = data_[x-1][y-1]; dataCell r,b,t,l;r = data_[x+1][y];l = data_[x-1][y];t = data_[x][y+1];b = data_[x][y-1];//文章只提了对待考察栅格判断是否符合模式,这里为什么要对待考察栅格的上下左右4个邻居
//栅格都判断呢?我认为判断模式的目的就是将Voronoi边夹着的、包裹的栅格置为备选,因为
//待考察栅格是备选了,才使得周围栅格可能会被Voronoi边包裹,所以才要逐一检查。 if (x+2<sizeX_ && r.voronoi==occupied) {// fill to the right//如果r的上下左右4个元素都!=occupied,对应文章的P38模式//    | ? | 1 | ? |//    | 1 |   | 1 |//    | ? | 1 | ? |if(tr.voronoi!=occupied && br.voronoi!=occupied &&data_[x+2][y].voronoi!=occupied){r.voronoi = freeQueued;sortedPruneQueue_.push(r.sqdist, INTPOINT(x+1,y));data_[x+1][y] = r;}}if (x-2>=0 && l.voronoi==occupied) {// fill to the left//如果l的上下左右4个元素都!=occupiedif(tl.voronoi!=occupied && bl.voronoi!=occupied &&data_[x-2][y].voronoi!=occupied){l.voronoi = freeQueued;sortedPruneQueue_.push(l.sqdist, INTPOINT(x-1,y));data_[x-1][y] = l;}}if (y+2<sizeY_ && t.voronoi==occupied) {// fill to the top//如果t的上下左右4个元素都!=occupiedif(tr.voronoi!=occupied && tl.voronoi!=occupied &&data_[x][y+2].voronoi!=occupied){t.voronoi = freeQueued;sortedPruneQueue_.push(t.sqdist, INTPOINT(x,y+1));data_[x][y+1] = t;}}if (y-2>=0 && b.voronoi==occupied) {// fill to the bottom//如果b的上下左右4个元素都!=occupiedif(br.voronoi!=occupied && bl.voronoi!=occupied &&data_[x][y-2].voronoi!=occupied){b.voronoi = freeQueued;sortedPruneQueue_.push(b.sqdist, INTPOINT(x,y-1));data_[x][y-1] = b;}}}while(!sortedPruneQueue_.empty()) {INTPOINT p = sortedPruneQueue_.pop();dataCell c = data_[p.x][p.y];int v = c.voronoi;if (v!=freeQueued && v!=voronoiRetry) {continue;}markerMatchResult r = markerMatch(p.x,p.y);if (r==pruned) {c.voronoi = voronoiPrune;     //对(x,y)即c剪枝}else if (r==keep) {c.voronoi = voronoiKeep;      //对(x,y)即c保留,成为Voronoi的边}else {c.voronoi = voronoiRetry;pruneQueue_.push(p);}data_[p.x][p.y] = c;//把需要retry的元素由pruneQueue_转移到sortedPruneQueue_//这样可以继续本while()循环,直到pruneQueue_和sortedPruneQueue_都为空if (sortedPruneQueue_.empty()) {while (!pruneQueue_.empty()) {INTPOINT p = pruneQueue_.front();pruneQueue_.pop();sortedPruneQueue_.push(data_[p.x][p.y].sqdist, p);}}}
}

10. 栅格模式匹配

观察图10的4个模式 ,很容易理解为什么这样设计,因为在这4个模式中,栅格s有非常重要的联结作用,不可或缺,否则Voronoi边就会断掉。因此,符合模式的栅格会被保留,不符合的被剪枝。

//根据(x,y)邻居栅格的连接模式,判断是否要对(x,y)剪枝
DynamicVoronoi::markerMatchResult DynamicVoronoi::markerMatch(int x, int y) {// implementation of connectivity patternsbool f[8];int nx, ny;int dx, dy;int i=0;//voroCount是对所有邻居栅格的统计,voroCountFour是对上下左右4个邻居栅格的统计int voroCount=0;int voroCountFour=0;for (dy=1; dy>=-1; dy--) {ny = y+dy;for (dx=-1; dx<=1; dx++) {if (dx || dy) {             //不考虑(x,y)点nx = x+dx;dataCell nc = data_[nx][ny];int v = nc.voronoi;//既不是occupied又不是voronoiPrune,即可能保留的栅格bool b = (v<=free && v!=voronoiPrune);f[i] = b;if (b) {voroCount++;if (!(dx && dy)) {      //对上下左右4个点voroCountFour++;}}i++;}}}// i和位置的对应关系如下://    | 0 | 1 | 2 |//    | 3 |   | 4 |//    | 5 | 6 | 7 |//8个邻居栅格中最多有2个,上下左右只有1个可能保留的栅格if (voroCount<3 && voroCountFour==1 && (f[1] || f[3] || f[4] || f[6])) {return keep;} // 4-connected// | 0 | 1 | ? |       | ? | 1 | 0 |      | ? | ? | ? |       | ? | ? | ? |// | 1 |   | ? |       | ? |   | 1 |      | 1 |   | ? |       | ? |   | 1 |// | ? | ? | ? |       | ? | ? | ? |      | 0 | 1 | ? |       | ? | 1 | 0 |//对应《Efficient Grid-Based Spatial Representations for Robot Navigation in //Dynamic Environments》中的4-connected P14模式,旋转3次90度if ((!f[0] && f[1] && f[3]) || (!f[2] && f[1] && f[4]) ||(!f[5] && f[3] && f[6]) || (!f[7] && f[6] && f[4])) {return keep;}//    | ? | 0 | ? |                       | ? | 1 | ? |//    | 1 |   | 1 |                       | 0 |   | 0 |//    | ? | 0 | ? |                       | ? | 1 | ? |//对应文章中的4-connected P24模式,旋转1次90度if ((f[3] && f[4] && !f[1] && !f[6]) || (f[1] && f[6] && !f[3] && !f[4])) {return keep;}// keep voro cells inside of blocks and retry later//(x,y)周围可能保留的栅格很多,此时无法判断是否要对(x,y)剪枝if (voroCount>=5 && voroCountFour>=3 && data_[x][y].voronoi!=voronoiRetry) {return retry;} return pruned;
}

11. Voronoi图可视化

void DynamicVoronoi::visualize(const char *filename) {FILE* F = fopen(filename, "w");...//fputc()执行3次,其实是依次对一个像素的RGB颜色赋值for(int y = sizeY_-1; y >=0; y--){for(int x = 0; x<sizeX_; x++){unsigned char c = 0;if (...) {...} else if(isVoronoi(x,y)){           //画Voronoi边fputc( 0, F );fputc( 0, F );fputc( 255, F );} else if (data_[x][y].sqdist==0) {  //填充障碍物fputc( 0, F );fputc( 0, F );fputc( 0, F );} else {                             //填充Voronoi区块内部float f = 80+(sqrt(data_[x][y].sqdist)*10);if (f>255) f=255;if (f<0) f=0;c = (unsigned char)f;fputc( c, F );fputc( c, F );fputc( c, F );}}}fclose(F);
}

依次对每个像素的RGB通道赋值,一个典型的二值图输入(图11)和Voronoi图输出(图12-13)如下。

9c258ca0bc0f38ae60ebec8196fd7920.png
图11 二值地图输入

38ac12009fede646c106c21d6b3da963.png
图12 prune前的Voronoi图

9bfeb02379eb7846babbf16a06c38bba.png
图13 prune后的Voronoi图

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

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

相关文章

马化腾:我创办腾讯的这些年

来源&#xff1a;全球创新论坛 概要&#xff1a;回顾腾讯的创业之路&#xff0c;我觉得机遇很重要&#xff0c;至少占了五成。我不觉得自己特别聪明&#xff0c;做的东西也都是很简单的判断。在这个过程中&#xff0c;时代的因素也是非常重要的&#xff0c;很多机遇是外界赋予的…

谷歌全方位自曝Waymo无人车技术方案 | 42页报告要点解读+下载

李杉 夏乙 编译整理 量子位 出品 | 公众号 QbitAI 谷歌今天发布了一份无人车重磅报告&#xff1a;《通往完全自动驾驶之路》。 这份42页的报告从技术层面详细展示了谷歌Waymo无人车的软件、硬件、测试流程&#xff0c;还讲了无人车行驶的限制条件&#xff0c;“失败”时如何安全…

Gartner公布:2018年十大战略科技发展趋势 研发战略演进研究榜单

来源&#xff1a;壹佰案例 全球领先的信息技术研究和顾问公司Gartner公布了将在2018年对大部分企业机构产生显著影响的首要战略科技发展趋势。 Gartner将战略科技发展趋势定义为具有巨大颠覆性潜力、脱离初期阶段且影响范围和用途正不断扩大的战略科技发展趋势&#xff1b;这些…

“芯”战争,人工智能芯片研发攻略

来源&#xff1a;半导体行业观察、安信证券 概要&#xff1a;深度学习作为新一代计算模式&#xff0c;近年来&#xff0c;其所取得的前所未有的突破掀起了人工智能新一轮发展热潮。 在全球科技领域&#xff0c;人工智能无疑是最热门的领域。这种并不算新的应用场景将会带动新一…

红米k30pro工程测试代码_3299起?红米K30Pro官宣3.24发 对比米10 追悼会来了?

声音 | 小白今天上午&#xff0c;官方正式宣布将于 3月24日 下周二 举行Redmi K30 Pro旗舰新品线上发布会。有些尴尬的是&#xff0c;官方宣布的K30Pro发布会最终日期和早前卢伟冰预热的完全不一样...似乎也间接说明卢总的话也不能全信(华为P40系列全球发布会是3月26日&#xf…

Facebook打算与Google的人工智能一较高下吗?

译者&#xff1a;彭婷 概要&#xff1a;每年&#xff0c;一些个人爱好者和大型团队会构建人工智能机器人&#xff0c;以争夺“星际争霸”。今年&#xff0c;Facebook在悄无声息中也加入了这场比赛。如此一来&#xff0c;他们的较量情形会是怎样的呢&#xff1f; 每年&#xff0…

微云存照片会变模糊吗_手机自带微云台防抖,VivoX50系列不一般

&#xfeff;喜欢我的文章吗&#xff1f;请点上方蓝色字体关注吧VIVO X50系列共发布三个版本&#xff1a;标准版&#xff0c;pro版&#xff0c;pro版。其售价格分别为&#xff1a;3498&#xff0c;4298&#xff0c;4998元。X50标准版很一般&#xff0c;完全不建议购买。毕竟都卖…

四大科技巨头都如何利用AI来相互竞争?

来源&#xff1a;全球人工智能 概要&#xff1a;想想时下大型科技公司悉数追逐的最火爆、竞争最激烈的那些行业&#xff1a;家居自动化&#xff0c;无人驾驶汽车&#xff0c;增强现实。而贯穿所有这些商业机会的主题又是什么呢&#xff1f;人工智能。 据国外媒体Fast Company报…

log4j 禁止类输出日志_SpringBoot统一日志处理原理

阅读推荐程序员跳槽时机已到&#xff0c;闲聊中面试官无意泄题SpringBoot作为日常开发利器&#xff0c;开箱即用&#xff0c;大量的star等已经成为节省开发的重要框架之一&#xff0c;但是各个框架的star中引入的日志框架却不尽相同&#xff0c;有的是log4j&#xff0c;有的是s…

AI 三大教父齐聚深度学习峰会,讨论尖端研究进展

来源&#xff1a;36氪 概要&#xff1a;近日&#xff0c;深度学习峰会正在加拿大蒙特利尔举行&#xff0c;有史以来第一次3位AI教父&#xff1a;Yoshua Bengio、Yann LeCun以及 Geoffrey Hinton聚在了一起出席RE•WORK举办的一个专题讨论会。 近日&#xff0c;深度学习峰会正在…

人工智能预测之七宗罪

译者&#xff1a;李凌 概要&#xff1a;一些有关人工智能和机器人未来发展的事情疯狂地将我们包围——人们对未来人工智能和机器人会变得如何强大、发展的如何快以及对我们工作产生的影响充满担忧。 错误的推断&#xff0c;有限的想象力和其他一些常见错误&#xff0c;会影响我…

xd使用技巧_魔兽世界怀旧服老玩家才会的治疗技巧,这四个技能需要看时机选择...

游戏中我们是朋友&#xff0c;聊天侃地&#xff0c;在这里我们可以无拘无束的发言&#xff0c;不会有任何人阻挠&#xff0c;还有大家最喜欢吐槽的小编&#xff0c;请把口水收集好&#xff0c;随时准备和小编一起吐槽&#xff01;魔兽世界怀旧服老玩家才会的治疗技巧&#xff0…

世界各大天文台联合预警:今晚公布“引力波重要发现”

来源&#xff1a;科学网 概要&#xff1a;引力波是爱因斯坦广义相对论实验验证中最后一块缺失的“拼图”。而发现引力波的三位美国科学家也刚刚在本月初毫无争议地获得了诺贝尔物理学奖。 央广网北京10月16日消息&#xff08;记者张加宁&#xff09;据中国之声《新闻纵横》报道…

3 运行时间太长_10大污水处理预处理系统动态图及运行管理、故障处理

污水处理厂的污水处理系统的维护、保养和故障维修&#xff0c;是每一位污师必备的技能&#xff0c;平时好的维护和保养可以很大程度的减少工厂的损失&#xff0c;今天小七从化工707app水处理板块的电子书《污水处理预处理系统的运行管理》&#xff0c;为大家截取了部分内容&…

jq处理返回来json_4个小窍门,让你在Python中高效使用JSON

全文共1990字&#xff0c;预计学习时长5分钟图源&#xff1a;unsplash字典和列表是 Python的两种数据类型&#xff0c;也是用来处理JSON的完美工具。本文将主要分享以下内容&#xff1a; 如何载入、编写JSON&#xff1f; 如何在命令行上优化、校验JSON&#xff1f; 如何通过使用…

谷歌、亚马逊、微软、IBM…这些巨头都已如何通过AI赚到钱了?

来源&#xff1a; 钛媒体 概要&#xff1a;巨头们各自所持有的AI技术里隐藏的商机&#xff1a;这些技术不仅能完善自家的产品&#xff0c;还能作为一种增值服务卖给企业级用户。 巨头们各自所持有的AI技术里隐藏的商机&#xff1a;这些技术不仅能完善自家的产品&#xff0c;还能…

ros自己写避障算法_slam导航避障算法,让无人机自主避障教学研究迈向更高处...

随着消费级无人机技术的不断成熟,不断完善的自动避障系统可以极大的减少因操作失误而带来的各项损失,目前避障能力正逐渐成为了无人机自动化或智能化的关键点所在。而根据无人机避障技术的原理和发展趋势,可以将无人机避障技术分为三重阶段&#xff1a;即感知障碍物阶段、绕过障…

库克:和App Store一样,AR技术必将改变世界

作者&#xff1a;李秀琴 概要&#xff1a;日前&#xff0c;苹果公司CEO蒂姆库克&#xff08;Tim Cook&#xff09;接受了英国《独立报》的专访&#xff0c;详谈了他对AR&#xff08;增强现实&#xff09;即将改变人类生活的看法&#xff0c;以及为何他会认为世界已经在慢慢变好…

机器人产业发展情况

来源&#xff1a; 战略前沿技术 全球机器人产业发展现状 一、机器人概述 1.机器人概念 采用国际机器人联合会对机器人的概念&#xff0c;即&#xff1a;机器人就是一种半自主或全自主工作的机器&#xff0c;能完成有益于人类的工作。 2.机器人分类 机器人包括工业机器人和服务机…

智能传感器深度报告:未来机器感官的百亿美元市场【附下载】

来源&#xff1a;智东西内参 概要&#xff1a;2019年&#xff0c;国内智能传感器市场规模预计将达到137亿美元&#xff0c;本土化率将从2015年的13%提升到27%。 2019年&#xff0c;国内智能传感器市场规模预计将达到137亿美元&#xff0c;本土化率将从2015年的13%提升到27%。 …