赞
踩
Hybrid_astar是一种考虑机器运动学(转弯半径、车辆运动模型等)的非完整约束的全局路径规划算法,算法中主要实现了两大功能模块,第一是使用混合astar(2D(x+y)-astar + 3D(x+y+&)-astar)搜索出一条起到到终点的考虑非完整约束的全局路径;第二是对产生的全局路径进行平滑,主要考虑四种平滑系数项(wSmoothness + wCurvature + wVoronoi + wObstacle),分别代表路径的平滑层度、路径的曲率层度、路径的居中层度、距离障碍物远近的层度。其主体还是在于路径规划,而路径规划的核心思想仍就是Astar,所有本文主要介绍规划部分,如果读者对Astar非常了解的话,那么Hybrid_astar的原理及源码就很简单了。
APA(Auto Parking Asist,辅助泊车)、AVP(Automated Valet Parking,代客泊车)、U-Turn(行车场景下的车辆掉头)。其中AVP是目前较为热门的技术(用户在指定下客点下车,通过手机 APP下达泊车指令,车辆在接收到指令后可自动行驶到停车场的停车位,不需要用户操纵与监控,用车反之),旨在解决用户停车、用车最后一公里问题,属于低速自动驾驶技术,是能够实现商业落地的自动驾驶技术应用场景(如下图所示)。
传统Astar搜索 和 考虑车辆运动约束搜索的区别:
Astar:只需要将搜索空间进行栅格化,然后按照栅格坐标去扩展路径,即只需要考虑X,Y;
考虑车辆运动约束搜索:需要考虑车辆的坐标(X,Y)、车头朝向(决定下一时刻可搜索方向)、转弯半径(决定可到达点);
下面可以通过截取的部分核心代码及注释更加详细的了解整个Hybrid_astar的过程。
#include "algorithm.h" #include <boost/heap/binomial_heap.hpp> using namespace HybridAStar; //原始A*算法,用来搜索计算 holonomic-with-obstacles heuristic float aStar(Node2D &start, Node2D &goal, Node2D *nodes2D, int width, int height, CollisionDetection &configurationSpace, Visualize &visualization); //计算start到目标点goal的启发式代价(即:cost-to-go) 会比较Astar、 dubins、 Reeds-Shepp void updateH(Node3D &start, const Node3D &goal, Node2D *nodes2D, float *dubinsLookup, int width, int height, CollisionDetection &configurationSpace, Visualize &visualization); // Dubins shot的路径 Node3D *dubinsShot(Node3D &start, const Node3D &goal, CollisionDetection &configurationSpace); /** * 重载运算符,用来生成节点的比较 * “boost::heap::compare<CompareNodes>”获得使用 * 小顶堆 用于创建open_list(优先队列) */ struct CompareNodes { /// Sorting 3D nodes by increasing C value - the total estimated cost bool operator()(const Node3D *lhs, const Node3D *rhs) const { return lhs->getC() > rhs->getC(); } /// Sorting 2D nodes by increasing C value - the total estimated cost bool operator()(const Node2D *lhs, const Node2D *rhs) const { return lhs->getC() > rhs->getC(); } }; /** * 功能:Hybrid A* 的主调用函数 * 输入:起始点、目标点、配置空间的3维和2维表示(2D用来A*,3D用于hybrid A*)、搜索网格的宽度及高度、配置空间的查找表、Dubins查找表(程序实际上没有使用该表,而是直接调用OMPL库计算)、rviz可视化类(用于显示结果) * 输出:满足约束条件的节点(数据结构用指针表示) **/ Node3D *Algorithm::hybridAStar(Node3D &start, const Node3D &goal, Node3D *nodes3D, Node2D *nodes2D, int width, int height, CollisionDetection &configurationSpace, float *dubinsLookup, Visualize &visualization) { // 父节点 和 子节点的 index int iPred, iSucc; // 用于记录新扩展节点到起点的代价 float newG; // 可后退可前进6、只能前进3 int dir = Constants::reverse ? 6 : 3; // 迭代计数 int iterations = 0; // 优先列表 可以从小到大自动排序 typedef boost::heap::binomial_heap<Node3D *, boost::heap::compare<CompareNodes>> priorityQueue; // openList表 priorityQueue O; // 计算到起点到目标点的启发式值 混合Astar的核心点,使用 dubins或者reedsShepp 和 2D-astar计算代价H updateH(start, goal, nodes2D, dubinsLookup, width, height, configurationSpace, visualization); // 将起点标记为open start.open(); // 将起点加入open_list集合 O.push(&start); // 计算出起点的索引值(输入整副地图的宽高) iPred = start.setIdx(width, height); nodes3D[iPred] = start; // 定义父节点 、子节点 node Node3D *nPred; Node3D *nSucc; // 路径搜索 退出条件为 open表为空 或 搜索到目标点 或 搜索迭代次数超过最大限制 while (!O.empty()) { // 循环部分:从优先队列中取出一个最低代价的点 nPred = O.top(); // 计算索引位置(输入地图的宽高) i 代表index iPred = nPred->setIdx(width, height); // 记录迭代次数 iterations++; //如果为closed,说明该点已经处理过,忽略(将它从open_list 中移除) if (nodes3D[iPred].isClosed()) { O.pop(); continue; } else if (nodes3D[iPred].isOpen()) { //如果该点是在open状态,即正在扩张的点(常规astar中一般会有四种状态open、close、free、obs),这里应该是用碰撞检测 (configurationSpace)替代了obs // 标记为closed,即访问过 nodes3D[iPred].close(); // 从优先队列中移除 O.pop(); // 检测当前节点是否是终点或者是否超出解算的最大时间(最大迭代次数30000) if (*nPred == goal || iterations > Constants::iterations) { return nPred; } else { // 不是目标点,继续搜索 // 使用dubinsShot && 距离范围内 && 前进方向(<3) if (Constants::dubinsShot && nPred->isInRange(goal) && nPred->getPrim() < 3) { nSucc = dubinsShot(*nPred, goal, configurationSpace); //如果Dubins方法能直接命中,即不需要进入Hybrid A*搜索了,直接返回结果 if (nSucc != nullptr && *nSucc == goal) { // 是目标点点,直接返回 return nSucc; } } // 3/6 (6表示可以倒退、3表示只能前进) for (int i = 0; i < dir; i++) { //每个方向都搜索 // 创建下一个扩展节点,这里有三种可能的方向,如果可以倒车的话是6种方向(astar中只考虑八邻域或者四邻域) nSucc = nPred->createSuccessor(i); //找到下一个点 里面的x,y,t其实会变成浮点型 // 设置节点遍历标识 //索引值 浮点类型会强制转换为int类型 iSucc = nSucc->setIdx(width, height); // 首先判断产生的节点是否在范围内;其次判断产生的节点会不会产生碰撞; if (nSucc->isOnGrid(width, height) && configurationSpace.isTraversable(nSucc)) { // 确定新扩展的节点不在close状态,或者没有在之前遍历过(iPred == iSucc 表明是扩展出来的,且没有超过当前栅格) if (!nodes3D[iSucc].isClosed() || iPred == iSucc) { // 更新G值 nSucc->updateG(); newG = nSucc->getG(); // 如果扩展节点不在OPEN LIST中,或者找到了更短G值的路径,或者该节点索引与前一节点在同一网格中(用新点代替旧点) if (!nodes3D[iSucc].isOpen() || newG < nodes3D[iSucc].getG() || iPred == iSucc) { // 当前点到目标点的启发式值 混合Astar的核心点,使用 dubins或者reedsShepp 和 2D-astar计算代价H updateH(*nSucc, goal, nodes2D, dubinsLookup, width, height, configurationSpace, visualization); // 如果下一个点仍在相同的cell、并且cost变大,那继续找 if (iPred == iSucc && nSucc->getC() > nPred->getC() + Constants::tieBreaker) { // 删除这个子节点,继续搜索 delete nSucc; continue; } // 如果下一节点仍在相同的cell,但是代价值要小,则用当前successor替代前一个节点(这里仅更改指针,数据留在内存中) else if (iPred == iSucc && nSucc->getC() <= nPred->getC() + Constants::tieBreaker) { // 将当前节点的父节点设置为 其前父节点的父节点 nSucc->setPred(nPred->getPred()); } // 给出原地踏步的提示 if (nSucc->getPred() == nSucc) { std::cout << "looping"; } nSucc->open(); // 将生成的子节点加入到open list中 nodes3D[iSucc] = *nSucc; O.push(&nodes3D[iSucc]); delete nSucc; } else { delete nSucc; } } else { delete nSucc; } } else { delete nSucc; } } } } } if (O.empty()) { return nullptr; } return nullptr; } // 原始 2D A*算法,返回当前点到终点的启发值,即H float aStar(Node2D &start, Node2D &goal, Node2D *nodes2D, int width, int height, CollisionDetection &configurationSpace, Visualize &visualization) { int iPred, iSucc; float newG; // 将open list和close list重置 for (int i = 0; i < width * height; ++i) { nodes2D[i].reset(); } boost::heap::binomial_heap<Node2D *,boost::heap::compare<CompareNodes>> O; // Open list // astar中的启发值,代码中使用的是欧式距离 start.updateH(goal); // 将起点设置为open start.open(); // 推入open表 O.push(&start); // 其实是计算得到索引号 iPred = start.setIdx(width); nodes2D[iPred] = start; Node2D *nPred; Node2D *nSucc; while (!O.empty()) { //从Open集合中找出代价最低的元素 nPred = O.top(); // set index 得到索引号 iPred = nPred->setIdx(width); // 检查:如果已扩展,则从open set中移除,处理下一个 if (nodes2D[iPred].isClosed()) { O.pop(); continue; } else if (nodes2D[iPred].isOpen()) { //没有进行扩展 nodes2D[iPred].close(); //标记为close nodes2D[iPred].discover(); // 标记为探索过 O.pop(); // 如果当前节点为goal ,找到目标点,返回G值 if (*nPred == goal) { return nPred->getG(); } //非目标点,则从可能的方向寻找 else { // 8邻域搜索 for (int i = 0; i < Node2D::dir; i++) { nSucc = nPred->createSuccessor(i); iSucc = nSucc->setIdx(width); // 约束性检查:在有效网格范围内、且不是障碍(是不是障碍物其实可以通过访问地图当前栅格确定)、没有扩展过 open 或者 free if (nSucc->isOnGrid(width, height) && configurationSpace.isTraversable(nSucc) && !nodes2D[iSucc].isClosed()) { //更新G值 也使用的欧式距离 nSucc->updateG(); newG = nSucc->getG(); // 如果子节点不在open集中,或者它的G值(cost-so-far)比之前要小,则为可行的方向 if (!nodes2D[iSucc].isOpen() || newG < nodes2D[iSucc].getG()) { // calculate the H value //计算H值 nSucc->updateH(goal); // put successor on open list nSucc->open(); nodes2D[iSucc] = *nSucc; //将该点移到open set中 O.push(&nodes2D[iSucc]); delete nSucc; } else { delete nSucc; } } else { delete nSucc; } } } } } // 搜索不到路径的情况 返回一个较大值 return 1000; } // 计算当前点到目标的启发值 // 混合astar的精髓部分:计算astar 和 dubins(或者reedsShepp)到达终点的代价值,然后取大值 void updateH(Node3D &start, const Node3D &goal, Node2D *nodes2D, float *dubinsLookup, int width, int height, CollisionDetection &configurationSpace,Visualize &visualization) { float dubinsCost = 0; float reedsSheppCost = 0; float twoDCost = 0; float twoDoffset = 0; // 使用dubins 只能前进不能后退 if (Constants::dubins) { //这里改用open motion planning library的算法 ompl::base::DubinsStateSpace dubinsPath(Constants::r); State *dbStart = (State *)dubinsPath.allocState(); State *dbEnd = (State *)dubinsPath.allocState(); dbStart->setXY(start.getX(), start.getY()); dbStart->setYaw(start.getT()); dbEnd->setXY(goal.getX(), goal.getY()); dbEnd->setYaw(goal.getT()); dubinsCost = dubinsPath.distance(dbStart, dbEnd); } // 假如车子可以后退,使用Reeds-Shepp 算法 if (Constants::reverse && !Constants::dubins) { // 使用ompl库并设置最小转弯半径 ompl::base::ReedsSheppStateSpace reedsSheppPath(Constants::r); State *rsStart = (State *)reedsSheppPath.allocState(); State *rsEnd = (State *)reedsSheppPath.allocState(); rsStart->setXY(start.getX(), start.getY()); rsStart->setYaw(start.getT()); rsEnd->setXY(goal.getX(), goal.getY()); rsEnd->setYaw(goal.getT()); // 利用reeds-shepp计算起点至终点的代价 reedsSheppCost = reedsSheppPath.distance(rsStart, rsEnd); } // 使用2D启发值 && 没有被发现过(遍历) if (Constants::twoD && !nodes2D[(int)start.getY() * width + (int)start.getX()].isDiscovered()) { Node2D start2d(start.getX(), start.getY(), 0, 0, nullptr); Node2D goal2d(goal.getX(), goal.getY(), 0, 0, nullptr); //调用A*算法,返回cost-so-far, 并在2D网格中设置相应的代价值 nodes2D[(int)start.getY() * width + (int)start.getX()].setG( aStar(goal2d, start2d, nodes2D, width, height, configurationSpace, visualization)); } if (Constants::twoD) { twoDoffset = sqrt(((start.getX() - (long)start.getX()) - (goal.getX() - (long)goal.getX())) * ((start.getX() - (long)start.getX()) - (goal.getX() - (long)goal.getX())) + ((start.getY() - (long)start.getY()) - (goal.getY() - (long)goal.getY())) * ((start.getY() - (long)start.getY()) - (goal.getY() - (long)goal.getY()))); // getG()返回A*的启发式代价,twoDoffset为start与goal各自相对自身所在2D网格的偏移量的欧氏距离 twoDCost = nodes2D[(int)start.getY() * width + (int)start.getX()].getG() - twoDoffset; } //将两个代价中的最大值作为启发式值(虽然这里有三个数值,但Dubins和Reeds-Shepp的启用是互斥的,所以实际上是计算两种cost) start.setH(std::max(reedsSheppCost, std::max(dubinsCost, twoDCost))); } // 看能不能一次性规划到目标点的路径 Node3D *dubinsShot(Node3D &start, const Node3D &goal, CollisionDetection &configurationSpace) { // start double q0[] = {start.getX(), start.getY(), start.getT()}; // goal double q1[] = {goal.getX(), goal.getY(), goal.getT()}; DubinsPath path; // 计算路径 输入:起点、终点、转弯半径 输出:路径 dubins_init(q0, q1, Constants::r, &path); int i = 0; float x = 0.f; float length = dubins_path_length(&path); //采样dubins路径(中间取点,dubinsStepSize为分辨率),最后用于碰撞检测 Node3D *dubinsNodes = new Node3D[(int)(length / Constants::dubinsStepSize) + 1]; while (x < length) { //这是跳出循环的条件之一:生成的路径没有达到所需要的长度 基本都能够满足需求 double q[3]; dubins_path_sample(&path, x, q); dubinsNodes[i].setX(q[0]); dubinsNodes[i].setY(q[1]); dubinsNodes[i].setT(Helper::normalizeHeadingRad(q[2])); //跳出循环的条件之二:生成的路径存在碰撞节点 if (configurationSpace.isTraversable(&dubinsNodes[i])) { // set the predecessor to the previous step if (i > 0) { dubinsNodes[i].setPred(&dubinsNodes[i - 1]); } else { dubinsNodes[i].setPred(&start); } if (&dubinsNodes[i] == dubinsNodes[i].getPred()) { std::cout << "looping shot"; } x += Constants::dubinsStepSize; i++; } else { delete[] dubinsNodes; return nullptr; } } //返回末节点,通过getPred()可以找到前一节点。 return &dubinsNodes[i - 1]; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。