global_planner解析(4)

7 篇文章 14 订阅
4 篇文章 0 订阅

前面简单介绍了路径搜索的基类Expander,里面主要的就是calculatePotentials路径搜索的方法接口,还有路径搜索的扩展方式-普通扩展方式类PotentialCalculator,下面我们来讲一下地杰斯特拉算法
该算法主要在dijkstra.h及dijkstra.cpp中
我们首先来看一下头文件dijkstra.h

//迪杰斯特拉算法

#ifndef _DIJKSTRA_H
#define _DIJKSTRA_H

//优先 大小
#define PRIORITYBUFSIZE 10000
#include <math.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include <nav_planner/planner_core.h>
#include <nav_planner/planner_core.h>

//在current buffer 添加当前点
#define push_cur(n) \
{\
    if(n>=0 && n<ns_ && !pending_[n] && getCost(costs, n) < lethal_cost_ && currentEnd_ < PRIORITYBUFSIZE)\
    {\
        currentBuffer_[currentEnd_++] = n;\
        pending_[n] = true;\
    }\
}
//在next buffer 添加当前点
#define push_next(n) \
{\
    if (n>=0 && n < ns_ && !pending_[n] && getCost(costs, n) < lethal_cost_ && nextEnd_ < PRIORITYBUFSIZE)\
    {\
        nextBuffer_[nextEnd_++] = n;\
        pending_[n] = true;\
    }\
}
//在end buffer添加当前点
//也就是把当前点压入到 end队列里面
#define push_over(n) \
{\
    if (n>=0 && n < ns_ && !pending_[n] && getCost(costs, n) < lethal_cost_ && overEnd < PRIORITYBUFSIZE)\
    {\
        overBuffer_[overEnd_++] = n;\
        pending_[n] = true;\
    }\
}
namespace nav_planner
{
class DijkstraExpansion : public Expander
{
public:
    DijkstraExpansion(PotentialCalculator* p_calc, int nx, int ny);
    ~DijkstraExpansion();

    //启发函数
    bool calculatePotentials(unsigned char* costs, double start_x, double start_y, double end_x, double end_y, int cycles,
                    float* potential);
    //设置地图大小
    void setSize(int nx,int ny);
    //设置中性成本
    void setNeutralCost(unsigned char neutral_cost){
        neutral_cost_ = neutral_cost;
        priorityIncrement_ = 2 * neu
    }
    //设置精确开始
    void setPreciseStart(bool precise){precise_ = precise; }

private:
    /* 方法 */
    //更新索引为n的单元格
    // costs为代价
    //potential 为正在计算的点位阵列
    //n为要更新的索引
    void updateCell(unsigned char* costs, float potential, int n);

    //获取某点的代价
    float getCost(unsigned char* costs, int n){
        float c = costs[n];
        //如果改点代价 小于 致命代价-1, 或者 未知参数为1, 并且 该点cost等于255
        if (c < lethal_cost_ - 1 || (unknown_ && c==255))
        {
            //代价等于  当前代价 * 因数 + 中立代价
            c = c * factor_ + neutral_cost_;
            //如果结果大于致命代价,则该点等于致命代价-1
            if (c >= lethal_cost_)
                c = lethal_cost_ - 1;
            return c;
        }
        //返回致命代价
        return lethal_cost_;
    }
    /* data */
    //块优先级缓冲区
    //优先级块的存储缓冲区
    int *buffer1_, *buffer2_, *buffer3_;
    //优先级缓冲区块
    //当前缓冲区/下一个缓冲区/停止缓冲区
    int *currentBuffer_, *nextBuffer_, *overBuffer_;
    //数组的终点
    int currentEnd_, nextEnd_, overEnd_;
    //传播过程中的未决细胞(估计是没有启发过的点)
    bool *pending_;
    //准确
    bool precise_;

    //终止扩展代价的临界值,刚开始为致命代价
    //如果搜索完毕未找到目标点则增大该临界值继续扩展直到扩展到目标点
    float threshold_;//阈
    //优先级阈值增量
    float priorityIncrement_; //优先级 递增
};
}//end namespace nav_planner
#endif

首先上来宏定义了优先级队列的大小为10000

#define PRIORITYBUFSIZE 10000

然后是宏定义往三个队列里面push点,分别有当前队列、下一个队列、关闭队列
currentBuffer_、nextBuffer_、overBuffer_

//在current buffer 添加当前点
#define push_cur(n) \
{\
    if(n>=0 && n<ns_ && !pending_[n] && getCost(costs, n) < lethal_cost_ && currentEnd_ < PRIORITYBUFSIZE)\
    {\
        currentBuffer_[currentEnd_++] = n;\
        pending_[n] = true;\
    }\
}

当然最重要的是地杰斯特拉算法类DijkstraExpansion,继承于Expander
主要的方法
启发函数实现方法(基于Expander)calculatePotentials
更新索引为n的单元格updateCell
获取某点代价值getCost

然后看一下cpp文件中具体实现方式

//迪杰斯特拉算法

#include <nav_planner/dijkstra.h>

//算法的头文件
#include <algorithm>

namespace nav_planner{

//构造函数
DijkstraExpansion::DijkstraExpansion(PotentialCalculator* p_calc, int nx, int ny) :
                    Expander(p_calc, nx, ny), pending_(NULL), precise_(false){
                        //初始化优先级 buf 长度为PRIORITYBUFSIZE = 10000;
                        buffer1_ = new int[PRIORITYBUFSIZE];
                        buffer2_ = new int[PRIORITYBUFSIZE];
                        buffer3_ = new int[PRIORITYBUFSIZE];

                        priorityIncrement_ = 2 * neutral_cost_;
}

//析构函数
DijkstraExpansion::~DijkstraExpansion(){
    delete[] buffer1_;
    delete[] buffer2_;
    delete[] buffer3_;

    if (pending_)
        delete[] pending_;
}

//设置或初始化地图的长度
void DijkstraExpansion::setSize(int xs, int ys){
    Expander::setSize(xs,ys);
    
    if (pending_)
        delete[] pending_;
    
    pending_ = new bool[ns_];
    //初始化未决全部设置为0
    memset(pending_, 0, ns_ * sizeof(bool));
}

//迪杰斯特拉主要的启发函数
//广度优先
//
bool DijkstraExpansion::calculatePotentials(unsigned char* costs, double start_x, double start_y, double end_x,
                                    double end_y, int cycles, float* potential){

    //设置已经遍历过的栅格为0
    cells_visited_ = 0;
    //阈值设置为致命代价
    threshold_ = lethal_cost_;
    //将buffer1的地址传递给当前缓冲区
    currentBuffer_ = buffer1_;
    //当前缓冲区的长度设置为0
    currentEnd_ = 0;
    //把第二个缓冲区给下一个缓冲区
    nextBuffer_ = buffer2_;
    nextEnd_ = 0;
    overBuffer_ = buffer3_;
    overEnd_ = 0;
    //初始化未决全部设置为0
    memset(pending_, 0, ns_ * sizeof(bool));
    //初始化 potential里面的值全部为 POT_HIGH = 10e10 10的10次方
    std::fill(potential, potential + ns_, POT_HIGH);

    //返回开始点的一维数组下标
    int k = toIndex(start_x, start_y);

    //判断准确值
    //使用true新的方式 还是 false:老版navfn启发方式
    if (precise_)
    {
        double dx = start_x - (int)start_x;
        double dy = start_y - (int)start_y;

        //向下取整 //保留小数点后两位
        dx = floorf(dx * 100 + 0.5) / 100;
        dy = floorf(dy * 100 + 0.5) / 100;

        //起始点代价等于  中立代价 *2 * dx * dy  不太明白为啥乘dxdy
        potential[k] = neutral_cost_ * 2 * dx * dy;
        potential[k-1] = neutral_cost_ * 2 * (1-dx) * dy;
        potential[k+nx_] = neutral_cost_ * 2 * dx * (1-dy);
        potential[k+nx_+1] = neutral_cost_ * 2 * (1-dx) * (1-dy);

        //把k附近的点全部压入 current队列
        push_cur(k+2);
        push_cur(k-1);
        push_cur(k+nx_-1);
        push_cur(k+nx_+2);

        push_cur(k-nx_);
        push_cur(k-nx_+1);
        push_cur(k + nx_*2);
        push_cur(k + nx_*2 + 1);

    }else{
        //当前点的代价设为0
        potential[k] = 0;
        //把前后左右全部压入current队列
        push_cur[k+1];
        push_cur[k-1];
        push_cur[k-nx_];
        push_cur[k+nx_];
    }
    
    //最大优先级块大小
    int nwv = 0;
    //放入优先块的信元数量(放进去的栅格的数量)
    int nc = 0;

    //设置开始单元(分明是关闭好吗)//就是目标点
    int startCell = toIndex(end_x, end_y);

    //进行多次循环,除非被打断,或者循环
    int cycle = 0;
    for (; cycle < cycles; cycle++)
    {
        //优先权块为空
        if (currentEnd_ == 0 && nextEnd_ == 0)
            return false;
        
        //统计资料
        nc += currentEnd_;
        //最大优先级块大小 不能小于 当前队列大小
        if (currentEnd_ > nwv)
            nwv = currentEnd_;
        
        //在当前优先级缓冲区上重置未决标志
        //吧当前缓冲区指针 给pb
        int *pb = currentBuffer_;
        int i = currentEnd_;
        //把当前队列的未决全部设置为 false
        while (i-- > 0)
            pending_[*(pb++)] = false;

        //处理当前优先级缓冲区
        pb = currentBuffer_;
        i = currentEnd_;

        while(i-- > 0)
            updateCell(costs, potential, *pb++);
        
        //交换优先级块currentBuffer_ <=> nextBuffer_
        currentEnd_ = nextEnd_;
        nextEnd_ = 0;
        pb = currentBuffer_;
        currentBuffer_ = nextBuffer_;
        nextBuffer_ = pb;

        //查看是否已完成此优先级
        if (currentEnd_ == 0)
        {
            //递增优先级阈值
            threshold_ += priorityIncrement_;
            //将当前设置为溢出块
            currentEnd_ = overEnd_;
            overEnd_ = 0;
            pb = currentBuffer_;
            currentBuffer_ = overBuffer_;
            overBuffer_ = pb;
            //交换over 和当前队列
        }

        if (potential[startCell] < POT_HIGH)
            break;
    }
    if (cycle < cycles)
        return true;
    else
        return false;
}

//关键函数: 根据邻居的值计算单元更新后的潜在值
//从四个网格中的两个最低相邻点进行平面更新计算
//内插值的二次近似
//这里没有边界检查,函数应该很快

//二分之根号二
#define INVSQRT2 0.707106781
inline void DijkstraExpansion::updateCell(unsigned char* costs, float* potential, int n){
    //已经遍历过的栅格 自加1
    cells_visited_++;
    //做平面更新
    float c = getCost(costs, n);
    //大于致命代价,不要启发到障碍里面去
    if (c >= lethal_cost_)
        return;
    //pot  =  前后左右最小的potential + 当前的costs 
    float pot = p_calc_->calculatePotential(potential, c, n);
    //现在将受影响的邻居添加到优先级块
    if (pot < potential[n])
    {
        //获得上下左右的 cost值, 但是不明白为啥要乘以二分之根号二
        float le = INVSQRT2 * (float)getCost(costs, n-1);
        float re = INVSQRT2 * (float)getCost(costs, n+1);
        float ue = INVSQRT2 * (float)getCost(costs, n-nx_);
        float de = INVSQRT2 * (float)getCost(costs, n+nx_);

        potential[n] =pot;

        //低成本缓冲块,暂时不清楚干啥的
        //如果当前 pot小于阈值
        if (pot < threshold_)
        {
            //如果当前点的潜力  大于 潜力-1 + cost*根号二/2
            //将其push到next的队列
            if (potential[n-1] > pot + le) push_next(n-1);
            if (potential[n+1] > pot + re) push_next(n+1);
            if (potential[n-nx_] > pot + ue) push_next(n-nx_);
            if (potential[n+nx_] > pot + de) push_next(n+nx_);
        }else{
            //如果当前点的潜力  大于 潜力-1 + cost*根号二/2
            //将其push到over的队列
            if (potential[n-1] > pot + le) push_over(n-1);
            if (potential[n+1] > pot + re) push_over(n+1);
            if (potential[n-nx_] > pot + ue) push_over(n-nx_);
            if (potential[n+nx_] > pot + de) push_over(n+nx_);
        }
    }
}
}

在构造函数中创建了三个优先级队列,析构函数中释放了他们
设置地图大小的创建了一个pending_的bool数组
主要的启发函数calculatePotentials继承自类Expander,函数流程如下
在这里插入图片描述

主要流程是首先初始化,将起始点四周的点加入优先级队列,然后进入主循环
主循环先判断当前优先级队列是否为空,如果为空则代表没办法扩展了直接退出return false
记录一下最大优先级队列大小
将当前优先级队列的pending全部设为false
重新获取当前队列的指针和长度
对当前队列每个点调用updatecell函数
调用完成之后,交换当前队列与下一队列
查看当前队列是否为空,空的话代表没有什么可以扩展的了
如果为空则调大临界点的阈值(最开始是致命代价),将关闭列表与当前列表互换,进行下一次扩展(这里表明了无论是否大于致命代价,地杰斯特拉算法总会扩展到目标点)
判断目标点是否被扩展到,如果扩展到了就退出

下面来讲updateCell函数
该函数主要是将输入点四周加入下一次扩展队列,或者是大于临界点加入到关闭列表

地杰斯特拉算法已经讲解完毕,后续会完善流程和每个变量的讲解

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: global_planner是ROS中的一个全局路径规划器,它可以根据地图和起点终点信息,生成一条全局路径。该路径规划器的源码解析可以帮助我们更好地理解其实现原理和算法global_planner的源码主要包括以下几个文件: 1. global_planner.cpp:该文件是全局路径规划器的主要实现文件,其中包含了路径规划的核心算法和逻辑。 2. grid_path.cpp:该文件实现了栅格地图的路径规划算法,包括A*算法和Dijkstra算法。 3. orientation_filter.cpp:该文件实现了路径方向的滤波算法,可以使路径更加平滑。 4. potential_field.cpp:该文件实现了基于势场的路径规划算法,可以避免障碍物和局部最优解。 在global_planner.cpp文件中,主要实现了以下几个函数: 1. makePlan:该函数是全局路径规划器的入口函数,它接收起点和终点的坐标信息,并调用其他函数进行路径规划。 2. initialize:该函数用于初始化全局路径规划器,包括读取参数、订阅地图和起点终点信息等。 3. computePotential:该函数实现了基于势场的路径规划算法,它会根据地图和障碍物信息计算每个点的势能值,并生成一张势能地图。 4. getPlanFromPotential:该函数根据势能地图和起点终点信息,使用A*算法或Dijkstra算法生成一条全局路径。 5. publishPlan:该函数将生成的全局路径发布出去,供其他节点使用。 总的来说,global_planner的源码实现了多种路径规划算法,可以根据不同的场景和需求进行选择。同时,它还实现了路径方向的滤波和势场的优化,可以使路径更加平滑和避免局部最优解。 ### 回答2: global_planner是ROS中的一个全局路径规划器,主要用于自动驾驶、机器人导航等领域。其核心算法是基于A*算法的Dijkstra和A*算法的融合。当起点和终点之间存在障碍物时,它会自动计算一条绕过障碍物的路径来达到终点。 global_planner的源码分为四个主要的文件:planner_core.cpp、dijkstra.cpp、astar.cpp和potential_fields.cpp。planner_core.cpp是这个路径规划器的主要文件,它定义了全局路径规划器的核心功能。 其中,Dijkstra算法是一个广度优先搜索算法,可以扩展当前可达节点的最短路径来找到起点到终点的最短路径。而A*算法则是在Dijkstra的基础上添加了启发式信息,即由启发式函数的估计值来指导搜索的方向,这样可以加快搜索速度。 在global_planner中,通过将Dijkstra算法和A*算法的两种算法进行融合,可以提高路径搜索的效率。同时,该算法还引入了potential_fields动力学概念,从而实现了避免障碍物的自适应路径规划,让机器人能够在不同场景下得到最有效的路径。 总的来说,global_planner的源码解析需要解释算法的原理、实现中的细节、运行过程中的各种参数以及算法效率等方面的内容。对于研究领域的专家和从事相关开发工作的人员,都需要有一定的数学和编程技能,才能够深入了解global_planner的工作原理和实现方式。 ### 回答3: global_planner是ROS中一个非常重要的全局路径规划器,它能够帮助机器人在未知环境中规划一条全局路径,使机器人能够尽可能地到达目标点,并且避开障碍物。本文将针对global_planner的源码进行解析,帮助读者更好地理解和使用该工具。 global_planner的源码分为两个部分:头文件和源文件。头文件定义了全局变量和函数声明,源文件实现了各种函数和算法。 在头文件中,首先定义了ros/ros.h和std_msgs/MapMetaData.h两个ros消息,用于表示全局地图的元数据和地图本身。同时,头文件中还定义了CellData和Cell的两个子类,用于表示地图中的单元格和单元格的信息。此外,还定义了一个Costmap2D类,用于存储3D地图数据和提供相关函数。 源文件中实现了多种路径规划算法,包括Dijkstra、A*、ARA*和Wavefront等。其中,Wavefront算法是一种基于搜索的路径规划算法,它以机器人当前位置为起点,传播到目标位置时,遇到的障碍物通过标记颜色进行标识。Wavefront算法可以在具有稀疏障碍物的环境中生成平滑的路径。 在global_planner源码中,还实现了一个名为makePlan的函数,用于在地图上规划一条全局路径。该函数首先在地图上搜索到目标位置,并使用A*算法计算出到达目标位置的最短路径。然后,该函数使用ARA*算法对路径进行平滑,避免机器人在前进时发生急剧转弯。最后,该函数返回一个包含路径的向量,并将该向量发送到Topic,供其他节点使用。 总结而言,global_planner是ROS中一个非常强大的全局路径规划器。它包含多种路径规划算法,可以在不同的场景下进行路径规划。在使用global_planner时,需要了解其中的算法与设计思路,才能更好地进行调用和使用。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值