学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为3087字,预计阅读8分钟
前言
上两篇我们主要就是说了A*算法结合OpenCV进行室内地图路线规划,在具体使用过程中发现,遇到比较复杂的地形路线后,计算的时间太长了,经过了一些基础的优化(最近开始学习算法了)后,还是不足以使用到生产环境中,所以就有了今天这篇JPS的跳点算法。
实现效果
上次A*算法用的是红色的线标的路线,这次JPS算法改为用蓝色的线标记,后续我会再写一篇两个算法的对比。
JPS算法
微卡智享
想了解JPS算法,最好是先明白了A*算法的整体思路,因为JPS算法实际上也是对A*算法一个改进,在A*算法中,每移动一次都会去计算周围八个邻居点的数据,这样我们的OpenList(开启列表)中的数量就会很多,然后每次都要从开启列表中找最近的点,所以搜索的效率会很慢。
而JPS的算法,是通过寻找跳点的方式,在过程中排除大量不感兴趣的点,这样就减少了我们OpenList(开启列表)中搜索的数量,大大地提高了搜索的速度,但是由于一部分点都用一个点来表示 . 路径表现上会感觉出寻路出的点 并不是最优路径。不过我觉得这个倒没有太大问题,因为速度快嘛。
JPS算法中,当我们走直线运动时,只要直线上一直畅通无阻,那我们就直接忽略别的点不去计算,行动的规则如下图中灰色的格式就是我们可以一直忽略的点。
而在斜线运动中,我们可以直接忽略侧后方的点,如下图中灰色的是我们可以忽略的点。
上面两个是JPS的一个行动的规则,但是在实际行动中,往往不可能是一条线走到底的情况,所以又引入了一个新的概念--强迫邻居。
直线运动的强迫邻居
当走直线运动的时候,我们需要判断一下行动方式左右两边是不是障碍点,如果是的话则这个点即为强迫邻居,并且这个强强迫邻居点的前方的点可要做为进行计算的点,而我们现在的当前点即可成为一个跳点加入到OpenList中,如下图中绿色点行动方向的左边黑点是障碍点,则紫色点可以列入到可进行计算的点中,而我们当前的绿色点即是跳点。
斜线运动的强迫邻居
斜线运动的时我们要判断行走方向的侧后方两个点是否为障碍点,如果是障碍点,则障碍点前方的点也要列入到可计算的点中,同时我们的当前点计为跳点,如下图中绿色点往右上行动,侧后方(左后方)黑色点为障碍点,则我们的紫色的点就要列入可计算的点中,而当前的绿色点即设置为跳点。
只要牢记住上面两种情况,我们就可以从中计算出跳点来加入到OpenList(开启列表)中即可。
计算流程方式(原贴:https://zhuanlan.zhihu.com/p/25093275)
核心代码
微卡智享
因为这个公司产品中需要,所以JPS我就不列表所有的代码了,其实从上面的流程里可以看到,JPS算法中最麻烦的就是跳点的计算了,所以我这里把计算跳点的函数列出来,别的相对来说我觉得都能写的出来。
//计算跳跃点,参数一:当前点,参数二:前一个点
Point JPSCalc::checkJumpPoint(Point targetpt, Point prept)
{
//计算前一点到当前点的行动路径,x和y都不等于0说明是斜线走的
//x=0说明是纵向移动,y=0说明是横向移动
Point dir = targetpt - prept;
//设置临时变量,如果没有跳跃点返回-1,-1代码跳出地图了
Point tmp = Point(-1, -1);
//检测当前点在地图中是否允许行动,如果当前点是障碍点直接返回tmp跳出
if (isInSites(targetpt.x, targetpt.y)) {
return tmp;
}
//如果是终点,直接返回当前点
if (targetpt.x == finalpoint.x && targetpt.y == finalpoint.y) {
return targetpt;
}
//1.检测当前点是否有强迫邻居,如果存在就返回当前点为跳跃点
//1.1判断是斜线移动
if (dir.x != 0 && dir.y != 0) {
if ((!isInSites(targetpt.x - dir.x, targetpt.y + dir.y)
&& isInSites(targetpt.x - dir.x, targetpt.y))
|| (!isInSites(targetpt.x + dir.x, targetpt.y - dir.y)
&& isInSites(targetpt.x, targetpt.y - dir.y))) {
//cout << "斜线强迫邻居:" << targetpt.x << "," << targetpt.y << endl;
return targetpt;
}
}
//1.2判断直线移动
else {
//1.2.1判断是横向移动
if (dir.x != 0) {
if ((!isInSites(targetpt.x + dir.x, targetpt.y + 1)
&& isInSites(targetpt.x, targetpt.y + 1))
|| (!isInSites(targetpt.x + dir.x, targetpt.y - 1)
&& isInSites(targetpt.x, targetpt.y - 1)))
{
//cout << "横向直线强迫邻居:" << targetpt.x << "," << targetpt.y << endl;
return targetpt;
}
}
//1.2.2判断是纵向移动
else {
if ((!isInSites(targetpt.x - 1, targetpt.y + dir.y)
&& isInSites(targetpt.x - 1, targetpt.y))
|| (!isInSites(targetpt.x + 1, targetpt.y + dir.y)
&& isInSites(targetpt.x + 1, targetpt.y))) {
//cout << "纵向直线强迫邻居:" << targetpt.x << "," << targetpt.y << endl;
return targetpt;
}
}
}
//2.不存在强迫邻居按行动路径继续寻找跳跃点
//2.1 判断是斜线移动,先按水平方向查找
if (dir.x != 0 && dir.y != 0) {
//2.1.1按水平方向继续寻找跳跃点
tmp = checkJumpPoint(Point(targetpt.x + dir.x, targetpt.y), targetpt);
//2.2.2再增加一个垂直方向跟踪的跳跃点进行判断
Point tmp2 = checkJumpPoint(Point(targetpt.x, targetpt.y + dir.y), targetpt);
//只有返回的两个点判断x不是-1,说明能找到跳跃点,这样就返回当前点
if (tmp.x != -1 || tmp2.x != -1) return targetpt;
}
//2.2 判断水平方向是否可移动,如果可以按原来轨迹方向继续寻找
if (!isInSites(targetpt.x + dir.x, targetpt.y)
|| !isInSites(targetpt.x, targetpt.y + dir.y))
{
tmp = checkJumpPoint(Point(targetpt.x + dir.x, targetpt.y + dir.y), targetpt);
if (tmp.x != -1) return tmp;
}
//最后返回tmp
return tmp;
}
运行图片
下一篇我们就看看A*算法和JPS算法同时进行对比的效果。
完
扫描二维码
获取更多精彩
微卡智享
「 往期文章 」
实战|A*寻路算法遇到的问题及解决方法
实战|OpenCV结合A*算法实现简单的运动路径规划
学习|OpenCV匹配相似轮廓