global_planner源码阅读笔记

ROS的navigation stack中nav_core包定义了机器人的全局规划、局部规划和恢复控制三种plugin接口,navigation并给出了相应的plugin。全局路径规划BaseGlobalPlanner的plugin有三种: navfn/NavfnROS,global_planner/GlobalPlanner, carrot_planner/CarrotPlanner. 其中常用的为global_planner/GlobalPlanner,它是navfn/NavfnROS的改进版本,包含Dijskstra和A*算法进行全局路径规划,本文将对其源码包global_planner进行分析。
全局规划器的重要函数主要是初始化函数initialize和路径规划函数makePlan, 在planner_core文件中。

初始化函数initialize

使用全局规划器必须要首先进行初始化,代码及注释如下:

 void GlobalPlanner::initialize(std::string name, costmap_2d::Costmap2D* costmap, std::string frame_id) {
    if (!initialized_) {
        ros::NodeHandle private_nh("~/" + name);
        costmap_ = costmap;
        frame_id_ = frame_id;
        unsigned int cx = costmap->getSizeInCellsX(), cy = costmap->getSizeInCellsY();
        private_nh.param("old_navfn_behavior", old_navfn_behavior_, false);
        if(!old_navfn_behavior_)
            convert_offset_ = 0.5;
        else
            convert_offset_ = 0.0;
        bool use_quadratic;
        private_nh.param("use_quadratic", use_quadratic, true);
        if (use_quadratic)  // 使用二次差值近似的potential
            p_calc_ = new QuadraticCalculator(cx, cy);
        else
            p_calc_ = new PotentialCalculator(cx, cy);
        bool use_dijkstra;
        private_nh.param("use_dijkstra", use_dijkstra, true);
        if (use_dijkstra)  
        {
            DijkstraExpansion* de = new DijkstraExpansion(p_calc_, cx, cy);
            if(!old_navfn_behavior_)
                de->setPreciseStart(true);
            planner_ = de;
        }
        else
            planner_ = new AStarExpansion(p_calc_, cx, cy);   // A*算法
        bool use_grid_path;
        private_nh.param("use_grid_path", use_grid_path, false);
        if (use_grid_path)   //路径方法
            path_maker_ = new GridPath(p_calc_);  // 栅格路径,从终点开始找上下或左右4个中最小的栅格直到起点
        else
            path_maker_ = new GradientPath(p_calc_); // 梯度路径,从周围八个栅格中找到下降梯度最大的点
        orientation_filter_ = new OrientationFilter();
        plan_pub_ = private_nh.advertise<nav_msgs::Path>("plan", 1);
        potential_pub_ = private_nh.advertise<nav_msgs::OccupancyGrid>("potential", 1);
        private_nh.param("allow_unknown", allow_unknown_, true);
        planner_->setHasUnknown(allow_unknown_);
        private_nh.param("planner_window_x", planner_window_x_, 0.0);
        private_nh.param("planner_window_y", planner_window_y_, 0.0);
        private_nh.param("default_tolerance", default_tolerance_, 0.0);
        private_nh.param("publish_scale", publish_scale_, 100);
        double costmap_pub_freq;
        private_nh.param("planner_costmap_publish_frequency", costmap_pub_freq, 0.0);
        make_plan_srv_ = private_nh.advertiseService("make_plan", &GlobalPlanner::makePlanService, this);
        dsrv_ = new dynamic_reconfigure::Server<global_planner::GlobalPlannerConfig>(ros::NodeHandle("~/" + name));
        dynamic_reconfigure::Server<global_planner::GlobalPlannerConfig>::CallbackType cb = boost::bind(
                &GlobalPlanner::reconfigureCB, this, _1, _2);
        dsrv_->setCallback(cb);
        initialized_ = true;
    } else
        ROS_WARN("This planner has already been initialized, you can't call it twice, doing nothing");
}

规划函数makePlan

路径规划的主要函数是GlobalPlanner::makePlan,简化代码及注释如下:

bool GlobalPlanner::makePlan(const geometry_msgs::PoseStamped& start, const geometry_msgs::PoseStamped& goal,
                           double tolerance, std::vector<geometry_msgs::PoseStamped>& plan) {
    boost::mutex::scoped_lock lock(mutex_);
    double wx = start.pose.position.x;
    double wy = start.pose.position.y;
    unsigned int start_x_i, start_y_i, goal_x_i, goal_y_i;
    double start_x, start_y, goal_x, goal_y;
    if (!costmap_->worldToMap(wx, wy, start_x_i, start_y_i)) {
        ROS_WARN(
                "The robot's start position is off the global costmap. Planning will always fail, are you sure the robot has been properly localized?");
        return false;
    }
    if(old_navfn_behavior_){   // 兼容navfn
        start_x = start_x_i;
        start_y = start_y_i;
    }else{
        worldToMap(wx, wy, start_x, start_y);
    }
    wx = goal.pose.position.x;
    wy = goal.pose.position.y;
    if (!costmap_->worldToMap(wx, wy, goal_x_i, goal_y_i)) {
        ROS_WARN_THROTTLE(1.0,
                "The goal sent to the global planner is off the global costmap. Planning will always fail to this goal.");
        return false;
    }
    if(old_navfn_behavior_){
        goal_x = goal_x_i;
        goal_y = goal_y_i;
    }else{
        worldToMap(wx, wy, goal_x, goal_y);
    }
    //clear the starting cell within the costmap because we know it can't be an obstacle
    clearRobotCell(start, start_x_i, start_y_i);
    int nx = costmap_->getSizeInCellsX(), ny = costmap_->getSizeInCellsY();
    //make sure to resize the underlying array that Navfn uses
    p_calc_->setSize(nx, ny);
    planner_->setSize(nx, ny);
    path_maker_->setSize(nx, ny);
    potential_array_ = new float[nx * ny];
    outlineMap(costmap_->getCharMap(), nx, ny, costmap_2d::LETHAL_OBSTACLE);  // 地图外围轮廓设为障碍
    // 计算代价
    bool found_legal = planner_->calculatePotentials(costmap_->getCharMap(), start_x, start_y, goal_x, goal_y,
                                                    nx * ny * 2, potential_array_);
    if(!old_navfn_behavior_)
        planner_->clearEndpoint(costmap_->getCharMap(), potential_array_, goal_x_i, goal_y_i, 2);
    if(publish_potential_)
        publishPotential(potential_array_);
    if (found_legal) {
        //extract the plan
        // 通过代价用path_maker_->getPath得到路径
        if (getPlanFromPotential(start_x, start_y, goal_x, goal_y, goal, plan)) {
            //make sure the goal we push on has the same timestamp as the rest of the plan
            geometry_msgs::PoseStamped goal_copy = goal;
            goal_copy.header.stamp = ros::Time::now();
            plan.push_back(goal_copy);
        } else {
            ROS_ERROR("Failed to get a plan from potential when a legal potential was found. This shouldn't happen.");
        }
    }else{
        ROS_ERROR("Failed to get a plan.");
    }

    // add orientations if needed  给路径加方向
    orientation_filter_->processPath(start, plan);
    
    //publish the plan for visualization purposes
    publishPlan(plan);
    delete potential_array_;
    return !plan.empty();
}

A*算法

A*算法是启发式规划算法,比Dijskstra算法速度快,其算法思想可参考该博文,文中采用了堆排序对开启列表进行处理,具体代码及注释如下:

// 计算规划代价函数:
//  costs为地图指针,potential为代价数组,cycles为循环次数,代码里值为2*nx*ny为地图栅格数的两倍
bool AStarExpansion::calculatePotentials(unsigned char* costs, double start_x, double start_y, double end_x, double end_y,
                                        int cycles, float* potential) {
    queue_.clear();     // queue_为启发式搜索到的向量队列:<i , cost>
    int start_i = toIndex(start_x, start_y);   // 起点对应的序号
    queue_.push_back(Index(start_i, 0));
    std::fill(potential, potential + ns_, POT_HIGH);  // 代价数组值全设为∞
    potential[start_i] = 0;
    int goal_i = toIndex(end_x, end_y);   // 终点对应的序号
    int cycle = 0;
    while (queue_.size() > 0 && cycle < cycles) {
        Index top = queue_[0];   // 最小代价点
        std::pop_heap(queue_.begin(), queue_.end(), greater1()); // 将向量front最小的代价Index放到放到最后
        queue_.pop_back();  // 删除最小代价的点
        int i = top.i;
        if (i == goal_i) // 若是目标点则终止搜索,搜索成功
            return true;
        // 将代价最小点i周围点加入搜索队里并更新代价值
        add(costs, potential, potential[i], i + 1, end_x, end_y);
        add(costs, potential, potential[i], i - 1, end_x, end_y);
        add(costs, potential, potential[i], i + nx_, end_x, end_y);
        add(costs, potential, potential[i], i - nx_, end_x, end_y);
        cycle++;
    }
    return false;
}
// 添加点并更新代价函数
void AStarExpansion::add(unsigned char* costs, float* potential, float prev_potential, int next_i, int end_x,
                         int end_y) {
    if (next_i < 0 || next_i >= ns_)  // 超出范围, ns_为栅格总数
        return;
    if (potential[next_i] < POT_HIGH)  // 已经搜索过
        return;
    if(costs[next_i]>=lethal_cost_ && !(unknown_ && costs[next_i]==costmap_2d::NO_INFORMATION))  // 障碍物
        return;
     // p_calc_->calculatePotential() 采用简单方法计算值为costs[next_i] + neutral_cost_+ prev_potentia  地图代价+单格距离代价(初始化为50)+之前路径代价 为G
    potential[next_i] = p_calc_->calculatePotential(potential, costs[next_i] + neutral_cost_, next_i, prev_potential);
    int x = next_i % nx_, y = next_i / nx_;
    float distance = abs(end_x - x) + abs(end_y - y);
    // potential[next_i] + distance * neutral_cost_   为总代价 F = G + H,  H为到目标点距离代价
    queue_.push_back(Index(next_i, potential[next_i] + distance * neutral_cost_));  // 加入搜索向量
    std::push_heap(queue_.begin(), queue_.end(), greater1()); // 对加入的再进行堆排序, 把最小代价点放到front队头queue_[0]
}
  • 3
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: global_plannerROS中的一个全局路径规划器,它可以根据地图和起点终点信息,生成一条全局路径。该路径规划器的源码解析可以帮助我们更好地理解其实现原理和算法。 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_plannerROS中的一个全局路径规划器,主要用于自动驾驶、机器人导航等领域。其核心算法是基于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_plannerROS中一个非常重要的全局路径规划器,它能够帮助机器人在未知环境中规划一条全局路径,使机器人能够尽可能地到达目标点,并且避开障碍物。本文将针对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_plannerROS中一个非常强大的全局路径规划器。它包含多种路径规划算法,可以在不同的场景下进行路径规划。在使用global_planner时,需要了解其中的算法与设计思路,才能更好地进行调用和使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值