Hybrid Astar路径规划:目的、原理、代码实现、使用技巧一览~

作者 | 汤师爷  编辑 | 自动驾驶之心

原文链接:https://zhuanlan.zhihu.com/p/691311717

点击下方卡片,关注“自动驾驶之心”公众号

戳我-> 领取自动驾驶近15个方向学习路线

>>点击进入→自动驾驶之心BEV感知技术交流群

本文只做学术分享,如有侵权,联系删文

视频讲解

多个子目标命中的Hybrid Astar路径规划_哔哩哔哩_bilibili
www.bilibili.com/video/BV1dA4m1w7rq/

1. 目的和背景

给定起始位姿和终点位姿,生成一条长度尽可能短的无碰撞的满足曲率半径约束的路径;

机器参数:
rearToFront:1.3m;
rearToBack:0.3m;
width:0.9m;

89901e142da0a7ceb29073b2ad073dfc.png

常规的扩展方式会导致频繁的转弯或者不必要的转弯

8ca73241d3fcd0bff8b3957a9d19fffb.png

2. 实现原理

首先计算出起点到终点的最短折线路径,作为参考路径;

3e365a35f33623237fdd8a420cfc8b0d.png

在参考路径上等距选取多个子目标点位姿,每个子目标点的位姿朝向就是参考线在该点的直线方向;

199ad3d6d940923d54185da835fa247b.png

使用hybrid进行搜索;额外的,在搜索过程中会优先尝试命中子目标点,如果命中了就继续命中后续的子目标点,如果无法命中就使用传统的hybrid扩展方法,直到成功到达终点位姿;

上述操作中,命中子目标点能够在空旷的区域/参考线的长直线段上有效减少节点扩展的数量,加快搜索到终点位姿的速度;

3. 代码实现和技巧

5229ae9dd46e389b759a30c28d9ddb8c.png

地图参数

origin: -10,-10
width:100m
height:100m

给定起始位姿和结束位姿

Pose2D startPose(-5, 0, 1.7);
Pose2D endPose(85, 80, -1.7);
70d280c4626170a757dae279ebb79471.png

3.1. 生成最短折线路径

/// @brief 将astar路径分段。拉直
    /// @param mapPlan 二值化的占据地图,5cm分辨率
    /// @param path astar稀疏的路径点
    /// @return 返回拉直的路径点,也是比较稀疏的/或者原始的astar路径
    std::vector<LOCALCV::Point> PathShorten(TMapData &mapPlan, std::vector<LOCALCV::Point> &path)
    {
        // 传入的是像素路径点
        auto TaceSegSparse = [&](LOCALCV::Point point_one, LOCALCV::Point point_two) -> std::vector<LOCALCV::Point> {
            std::vector<LOCALCV::Point> path_sparse;
            path_sparse.push_back(point_one);
            const float k_seg_len = 0.5 / 0.05; // [pixel,]每一个小段的长度0.5m,对应10个像素距离
            float dis             = sqrt((point_one.x - point_two.x) * (point_one.x - point_two.x) + (point_one.y - point_two.y) * (point_one.y - point_two.y));
            int seg_num           = dis / k_seg_len + 0.5;
            if (seg_num < 2) {
                return path_sparse;
            }
            float delta_x = (point_two.x - point_one.x) / (1.f * seg_num);
            float delta_y = (point_two.y - point_one.y) / (1.f * seg_num);
            for (int i = 1; i < seg_num; i++) {
                LOCALCV::Point seg_point(point_one.x + delta_x * i + 0.5f, point_one.y + delta_y * i + 0.5f);
                path_sparse.push_back(seg_point);
            }
            return path_sparse;
        };

        auto TacePathSparse = [=](std::vector<LOCALCV::Point> basePoints) -> std::vector<LOCALCV::Point> {
            if (basePoints.size() < 3) {
                return basePoints;
            }
            std::vector<LOCALCV::Point> ret;
            for (unsigned int i = 0; i < basePoints.size() - 1; i++) {
                std::vector<LOCALCV::Point> temp = TaceSegSparse(basePoints[i], basePoints[i + 1]);
                // ret.emplace_back(basePoints[i]);
                ret.insert(ret.end(), temp.begin(), temp.end());
            }
            ret.emplace_back(basePoints.back());
            return ret;
        };

        // 优化拉直拉短
        auto path_points_sparse = TacePathSparse(path);
        std::vector<LOCALCV::Point> path_adjusted;
        bool is_path_adjust_work = false;
        do {
            path_adjust::PathShorten pp(path_points_sparse.size());
            if (path_points_sparse.size() < 3) {
                break;
            }
            for (size_t i = 0; i < path_points_sparse.size() - 1; i++) {
                auto point_one = path_points_sparse[i];
                auto point_two = path_points_sparse[i + 1];
                float dis      = sqrt((point_one.x - point_two.x) * (point_one.x - point_two.x) + (point_one.y - point_two.y) * (point_one.y - point_two.y));
                pp.AddEdge(i, i + 1, dis);
            }
            for (size_t i = 0; i < path_points_sparse.size() - 2; i++) {
                // linehit
                int meetHit = 0;
                for (size_t j = i + 2; j < path_points_sparse.size(); j++) {
                    int x1 = path_points_sparse[i].x;
                    int y1 = path_points_sparse[i].y;
                    int x2 = path_points_sparse[j].x;
                    int y2 = path_points_sparse[j].y;
                    int hitX;
                    int hitY;
                    if (LOCALCV::CCommonAlg::LineHit(hitX, hitY, 128, x1, y1, x2, y2,
                                                     &mapPlan.map[0], mapPlan.mapParam.width, mapPlan.mapParam.height)) {
                        //                     FLOGD("hitPoint = (%.2f, %.2f)", slamMap.idx2x(hitX), slamMap.idx2y(hitY));
                        meetHit++; // 连线发生hit最大允许向前搜索的次数
                        // note todo 这里注释掉之后路径会更短,但是增加耗时
                        // if (meetHit > 5) {
                        //     break;
                        // } else {
                        //     continue;
                        // }
                    } else {
                        //
                        auto point_one = path_points_sparse[i];
                        auto point_two = path_points_sparse[j];
                        float dis      = sqrt((point_one.x - point_two.x) * (point_one.x - point_two.x) + (point_one.y - point_two.y) * (point_one.y - point_two.y));
                        pp.AddEdge(i, j, dis);
                    }
                }
            }
            std::vector<int> ret = pp.GetShortestPath();
            for (auto item : ret) {
                path_adjusted.push_back(path_points_sparse[item]);
            }
            if (path_adjusted.size() < path_points_sparse.size()) {
                FLOGD("--PATH ADJUST WORKS.");
                is_path_adjust_work = true;
            }
        } while (0);
        // end 优化拉直
        if (is_path_adjust_work == true) {
            return path_adjusted;
        } else {
            return path;
        }
    }

3.2. 搜索过程中增加气泡带,限制搜索空间

// 绘制路径上的区域[局部走廊/气泡带BubbleZone],5个车身宽度足迹,四十个像素,只考虑路径足迹范围,用于优化voronoi生成时间
        // TODO 这里改成动态分配,预防图片太大
        {
            cv::Mat mask = cv::Mat::zeros(planMapCv_raw.rows, planMapCv_raw.cols, CV_8UC1);
            planMapCv    = planMapCv_raw.clone();
            planMapCv.setTo(0);
            // cv::line(mask, cv::Point(333,6666), cv::Point(200,200),cv::Scalar(255,255,255),40,8,0);
            for (unsigned int i = 0; i < astarPath.size() - 1; i++) {
                cv::line(mask, cv::Point(astarPath[i].X, astarPath[i].Y),
                         cv::Point(astarPath[i + 1].X, astarPath[i + 1].Y),
                         cv::Scalar(255, 255, 255), 20 * 4, 8, 0); // 这里绘制的是圆头直线,相当于圆形;astarPath路径点是稀疏的
            }
            // todo 注意在边缘画上黑线
            // cv::imwrite("mask.bmp", mask); // 读写文件很耗时to delete
            planMapCv_raw.copyTo(planMapCv, mask);
            // cv::imwrite("planMapCv.bmp", planMapCv); // 读写文件很耗时to delete
        }

3.3. [key]多个子目标的生成

在最短折线段上挑选长直线段,并间隔采样;

在参考路径上等距选取多个子目标点位姿,每个子目标点的位姿朝向就是参考线在该点的直线方向;

// step 计算子目标点
        std::vector<Pose2D> sub_goals;
        do {
            // 对astarPath 中的长直线段采样
            const double k_subGoal_dis_interval =  0.5f; // 间隔0.5m采样一个路径点
            const double K_cut_segment_thresh = 1.f; // 长度至少为1m才采样
            std::vector<Pose2D> pose_sparse_list; // 原始路径点pose,物理坐标
            for (auto item : astarPath) {
                pose_sparse_list.push_back(Pose2D(mapData.mapParam.idx2x(item.X), mapData.mapParam.idx2y(item.Y), 0));
            }

            for (size_t i = 0; i < pose_sparse_list.size() - 1; i++) {
                float dist = pose_sparse_list[i].GetDistance(pose_sparse_list[i+1]);
                if (dist < K_cut_segment_thresh || dist < k_subGoal_dis_interval) {
                    continue;
                }
                LineSegment2d segment({pose_sparse_list[i].X, pose_sparse_list[i].Y}, {pose_sparse_list[i + 1].X, pose_sparse_list[i + 1].Y});
                Pose2D segment_start; // 线段上的起始位姿
                segment_start.X = segment.start().x();
                segment_start.Y = segment.start().y();
                segment_start.SetPhi(segment.heading());
                int num_inserts =  std::ceil(dist / k_subGoal_dis_interval) - 1;
                for (int j = 1; j <= num_inserts; ++j) {
                    float ratio = static_cast<float>(j) / (num_inserts + 1);
                    // new_poses.push_back(interpolate(path.poses[i], path.poses[i + 1], ratio));
                    sub_goals.push_back(segment_start + Pose2D(ratio * segment.length(), 0, 0)); // 计算线段上的子目标点
                }
            }
            // poseEnd
            sub_goals.push_back(poseEnd);
        } while (0);

3.4. 单次命中单个子目标的代码实现

bool hybrid_a_star::DubinsShotSubGoal(Node *current_node, Pose2D subGoal, float real_dubins_radius, Node **newNode)
{
    Pose2D start = GetNodeSegEnd(current_node);
    Pose2D endPose = subGoal;
    // start
    double q0[] = {start.X, start.Y, start.Phi()};
    if (q0[2] < 0) {
        q0[2] = WrapTo2Pi(q0[2]);
    }
    // goal
    double q1[] = {endPose.X, endPose.Y, endPose.Phi()};
    if (q1[2] < 0) {
        q1[2] = WrapTo2Pi(q1[2]);
    }
    // initialize the path
    DubinsSimple::DubinsPath path;
    // calculate the path
    int res = DubinsSimple::dubins_init(q0, q1, real_dubins_radius, &path);
    if (res != 0) {
        return false;
    }
    std::vector<Pose2D> path_lists;
    float x = 0.f;
    // x += k_dubins_step_size;  // 防止路径点重叠,重叠了也无所谓,每个路径点的pose角度是单独算出来的,和下一个路径点没有关系
    float length = dubins_path_length(&path);
    if (length < 0.01f) {
        return false;
    }
    while (x <= length) {
        double q[3];
        dubins_path_sample(&path, x, q); // 在曲线上按照x长度取点
        Pose2D pose(q[0], q[1], WrapToPi(q[2]));
        // collision check
        if (CheckPoseCollisionSafe(pose) == false) {
            return false;
        }
        path_lists.push_back(pose);
        x += k_dubins_step_size;
    }
    {
        int index         = Pose2Index(path_lists.back());
        double added_cost = 0.0;
        added_cost += STEER_COST * fabs(0);                              // 转角,曲率代价,增大此项可减少尖锐的曲率 【转角与astar转角趋势相同时减小该项,顺应astar路径的转角趋势】
        // note todo 最好使用real_dubins_radius计算steer angle
        // added_cost += STEER_CHANGE_COST * fabs(WrapToPi(path_lists.back().Phi() - start.Phi())); // 转角变化代价,增大此项可减少频繁转弯【这里可以结合astar路径做调整,在astar路径长直段避免转角变化,在转弯处允许变化】
        double cost        = current_node->cost ;//+ added_cost ;//length * 10; // 这里arc_len最好乘上一个系数,arc_len用于优化路径长度
        auto neighbor_node = new Node(index, start, length, 0, current_node->state_grid_index, cost, true);
        // 记录子目标点命中时候的dubins路径
        *newNode = neighbor_node;
        mSubGoalPaths[neighbor_node] = path_lists;
    }
    return true;
}

3.5. 【key】命中多个子目标

优先尝试命中子目标点,如果命中了就继续命中后续的子目标点,如果无法命中就使用传统的hybrid扩展方法,直到成功到达终点位姿;

使用多个半径命中,优先使用大的半径命中;

注意,每一次尝试命中都会做碰撞检测,会耗时;

为了优化耗时,子目标点在十米范围内,而且优先命中最远的那个;

// 命中子目标
        // todo 优化:如果起始终止位姿距离很近就只给一个终点位姿作为子目标点,不要命中中间的其他点。
        {
            valid_sub_goal = false;
            // 确定子目标点的范围;
            int index_fathest = mSubGoals.size() - 1; // 当前搜索的最远的子目标点的序号 Farthest
            {
                // 子目标点在十米范围内
                const float k_SubGoalDisInReach = 10.f; 
                int index = std::max(sub_goal_counter, 0);
                float dis_sum = 0;
                do {
                    if (index == mSubGoals.size() - 1) {
                        break;
                    }
                    index++;
                    if (index == mSubGoals.size() - 1) {
                        break;
                    }
                    dis_sum += mSubGoals[index].GetDistance(mSubGoals[index - 1]);
                    if (dis_sum > k_SubGoalDisInReach) {
                        break;
                    }
                } while (1);
                index_fathest = index;
            }
            // 子目标点在十米范围内
            for (int i = index_fathest /*mSubGoals.size() - 1*/; i > sub_goal_counter; --i) {
                Pose2D sub_goal = mSubGoals[i];
                // 使用dubins连接并判断是否命中
                do {
                    // bool hybrid_a_star::DubinsShotSubGoal(Node *current_node, Pose2D subGoal, float real_dubins_radius, Node *newNode)
                    Node *newNode = nullptr;
                    // 使用多个半径命中,优先使用大的半径命中
                    bool res = false;
                    for (float dubins_radius = k_dubins_radius_max; dubins_radius >= k_dubins_radius_min; dubins_radius -= k_dubins_radius_add_step) {
                        res = DubinsShotSubGoal(current_node_ptr, sub_goal, dubins_radius, &newNode); // k_dubins_radius_min
                        if (res == true) {
                            break; // 优先命中就结束
                        }
                    }
                    // 未命中则换一个目标点
                    if (res == false) {
                        break;
                    }
                    if (mCloseList.find(newNode->state_grid_index) != mCloseList.end()) {
                        // 已经搜索和扩展过的
                        delete newNode;
                        break;
                    }
                    if (mOpenList.find(newNode->state_grid_index) == mOpenList.end()) {
                        // 计算G+H
                        // 直接命中到终点,结束搜索
                        if (i == mSubGoals.size() - 1) {
                            m_terminal_node_ptr_ = newNode;
                            return true; // 命中终点
                        }
                        double f_cost = newNode->cost + ComputeH(newNode);
                        mOpenList.insert(std::make_pair(newNode->state_grid_index, newNode));
                        openset_.insert(std::make_pair((uint64_t)(f_cost * 1000), newNode->state_grid_index));
                    } else if (mOpenList.find(newNode->state_grid_index) != mOpenList.end()) {
                        delete newNode;
                        break;
                    }
                    sub_goal_counter = i;
                    valid_sub_goal   = true; // 能够直接用dubins命中voronoi节点
                } while(0);
            }
            if (valid_sub_goal) {
                continue;
            }
        } // end 命中子目标
// 扩展节点
        GetNeighborNodes(current_node_ptr, neighbor_nodes);
        for (unsigned int i = 0; i < neighbor_nodes.size(); ++i) {
            neighbor_node_ptr = neighbor_nodes[i];
            int neighbor_node_index = neighbor_node_ptr->state_grid_index;// Pose2Index(GetNodeSegEnd(neighbor_node_ptr));
            if(mCloseList.find(neighbor_node_index) != mCloseList.end()) {
                // 已经搜索和扩展过的
                delete neighbor_node_ptr;
                continue;
            }
            if (mOpenList.find(neighbor_node_index) == mOpenList.end()  ) {
                // 计算G+H
                double f_cost = neighbor_node_ptr->cost + ComputeH(neighbor_node_ptr);
                mOpenList.insert(std::make_pair(neighbor_node_ptr->state_grid_index, neighbor_node_ptr));
                openset_.insert(std::make_pair((uint64_t)(f_cost * 1000), neighbor_node_ptr->state_grid_index));
            } else if (mOpenList.find(neighbor_node_index) != mOpenList.end()) {
                // 判断G的大小
//                 if (neighbor_node_ptr->cost < mOpenList[neighbor_node_index]->cost) {
//                     delete mOpenList[neighbor_node_index];
//                     double f_cost = neighbor_node_ptr->cost + ComputeH(neighbor_node_ptr);
                     mOpenList.insert(std::make_pair(neighbor_node_ptr->state_grid_index, neighbor_node_ptr));
//                     mOpenList[neighbor_node_ptr->state_grid_index] = neighbor_node_ptr;
//                     openset_.insert(std::make_pair(f_cost*1000, neighbor_node_ptr->state_grid_index));
//                 } else {
                    delete neighbor_node_ptr;
//                 }
            }
        }

4. 测试代码

选取多个随机位姿测试

// 选取多个随机位姿
void testHybridMany()
{
    auto copyFileWithIndex = [](int i) -> void {
        std::string sourceFilePath = "/tmp/debug_record/hybrid_canvas.png"; // hybrid_canvas.png
        std::string destinationFilePath;
        // 构建目标文件路径
        std::stringstream ss;
        ss << sourceFilePath.substr(0, sourceFilePath.find_last_of(".")) << "_" << i << sourceFilePath.substr(sourceFilePath.find_last_of("."));
        ss >> destinationFilePath;

        // 打开源文件和目标文件
        std::ifstream sourceFile(sourceFilePath, std::ios::binary);
        std::ofstream destinationFile(destinationFilePath, std::ios::binary);

        // 拷贝文件内容
        destinationFile << sourceFile.rdbuf();

        // 关闭文件
        sourceFile.close();
        destinationFile.close();
    };
    std::default_random_engine generator;
    std::uniform_real_distribution<double> distribution(-2, 2);
    std::uniform_real_distribution<double> distribution_angle(-3, 3);
    for (int i = 0; i < 100; i++) {
        TestHybrid test;
        test.SetVisulization(true);
        TMapData mapData({-10, -10, 50 * 20, 50 * 20}, 255);
        for (float x = 0; x < 35; x += 6) {
            for (float y = 0; y < 35; y += 6) {
                int xInt = mapData.x2idx(x);
                int yInt = mapData.y2idx(y);
                LOCALCV::CCommonAlg::circleMat(0, xInt + RandomInt(10, 20), yInt + RandomInt(10, 20), 10 + RandomInt(10, 40), &mapData.map[0], mapData.mapParam.width, mapData.mapParam.height,
                                               0, mapData.mapParam.width, 0, mapData.mapParam.height);
            }
        }
        Pose2D startPose(-5 + distribution(generator), 0 + distribution(generator), 1.7 + distribution_angle(generator));
        Pose2D endPose(35 + distribution(generator), 32 + distribution(generator), -1.7 + distribution_angle(generator));
        bool res = test.test(mapData, startPose, endPose);
        FLOGD("test hybrid result is %d", res);
        if (res == false) {
            continue;
        }
        copyFileWithIndex(i); // 拷贝结果另存
    }
}

5. 测试结果

c982c4615cc3adad7a4a78dc22dce06f.png f2e0e79da9c56523c3107cdfc4bdb4dc.png c274b17d82aeb8f659407eb2d6b5c540.png 57909ed3ff83ee077dbb1b2124e8081f.png 9d133fbfae5d6f35c3321cea92f53c81.png 8d269644f914211394b42f33a2a61adf.png 4808bdedea4ef6eee718eab6c8809689.png ce222c4b78e89fd950dd55cbcfe79409.png 198d47d94b8d90691df6b31587415b16.png 692c8c7f7e560ef7ac955dcbebdccccf.png 3067b61a7e690c1a2d093c756c4da374.png 7a9cd101395b099fc6645227f8ff4b45.png 4c345b95f2b3126bc33faa94af6fb583.png 3fcaaa5fe2254a74b387c972eb38f058.png addd4df5961a8cbe37aaccc09d90d167.png f214d25132b4b367accef66130dc33aa.png 5d7b3c249156479f72413f0e2c3c69e6.png caad9a893b95b261216641b0f835fe93.png 396f19a7da3021c1fdcbf77ce3c108c7.png 0c85e3b7d0962bd18cf1edba1c23cae2.png 6735ceccf7ac40ac080021d816187b14.png 37488f02cc685999013f958293d3ea63.png 40331708a46267427c32ff5cb0f0087f.png 383ad3c2ab05438c1a086d560aec4e4d.png c3d4f438ded536b2d9cb680202d8da85.png 8a582637ddad93c0f6a19038a818819a.png 9ca23c3fa9622221683030899048353c.png 9438c71d22f508fd5bc67fff124a2c43.png 79dcc5c9ec51a39408b078c294371eac.png c8b81bca2757c9f0fa0f4cf0bf4cbe16.png ffb26692eac52491b735e777e89bfbb9.png 27fe83aa85f5f1b24025a1fd9bfcfa1b.png cb62808a26081c803495bf77c7b1b54e.png 4b6609d9d9dbf0b5221e3315326b70c2.png 9e256012536913c9d35f016f7dab8008.png 68d8506daef036f028fda3e65166e2ef.png 7adc141065c40f1ac9f1cd36230e588c.png 83d4249338fb3431af7a582adcde2f28.png 3ba208f6d401ae2cb198b4ff4c61b723.png c2f2240f55c54501b3568afbef2bbcd3.png 120de8bbffb0d083e387ab8634a58b79.png 204cd9291524b96a47b28b82c43e29b2.png 8eac648c1ddae08e58144f03d27225c4.png 43a9ee4ebe656b0016dc883abb7f80fd.png 875b20145a678107cfa0b5dd4d4fe239.png e403cb60c4e6c05d6d53f307938254f3.png a866f77bc168ddb733e38484c8b03a34.png 55edf7913155adec5e6fbb33b02e84f7.png ccce952ea83655eb9256febabb548d44.png 6f39e5b18fe63b0c6d19ebb0136a4d50.png 9c2db7df4da8752a5bdc2fcc97dd6782.png 74150fd6b868297959479da26329d226.png 96eb4ec06af299616fc08d0fda35d224.png ed421b4e48f70aa240ebec2e5d2b3a97.png be437e529a3442ebf9a074205b53e0c1.png 818c996420d2ed97b5fcfe221170945a.png ca67afbf2660ecf83595ca65ddcebebd.png 19ed27094d4048314cb0b8a3ce280faa.png e2aeee6b27a066132513a9bf791c892c.png d8d1bb3e47990ceb350b6c4688aecf95.png 8307bf4f48bce9f83bd70279119216e8.png 34d08c9f9685f2121a8b7a8bbb14d136.png d2f584ff4dc71e2609fb3d3870985537.png 001f3fe3c112cc55135b548235cd3368.png f81a166a581174157c42884d0ea4d899.png a920c1228b32e528a2116cb52935ccfd.png c28ae99f4a628e98f4284bfc7ce1acef.png 08fb380048a196508e5987a84e74b341.png abb18e85ec8fd00671e5d15db86161b7.png 275289484a08fdaebb88088d94475f4b.png ae16cbe380a8f91ebbda734090742883.png 15a2a425987d9334d44b2e04c5467c1c.png d9e46b4f7017ef8207de5a59d8be1c6c.png 9762b78bc6b0b9019eb74c2acab5282a.png ff792a4defc0691d93badab19bcd4a42.png 9357671fb425ac621e5a6b1dd09d43e8.png 9112d3bdc3cdc0ad410e46f4a7b55b9e.png ec5c9d023a390975af4d6372e49405e1.png eb6f1b3d8e027b0fbb16c861f922dfa8.png f532f6c203bd7eeefded096d22ba321b.png 92de5f2c26efc7734eef247976e26233.png 0850f35876091200811b4ec1a3cc2268.png 518e3bc70965176c6dda879e9849a79e.png 932f035746b0f8ea6ce6af7921add333.png d64e758d794aea128459726ad875adae.png fb879d25cdd752e7a5344ac8841bc94c.png 8503c9d94c6d2f8d9d5dbda2eb01a1a8.png cb5c1fdeb09ad7a835f37bd75461fa0e.png fa4b9785f797a96895f231b12b305def.png 509f3d12bbff2f3d31997f1f0f400499.png 8f59b9cf6c91bd78ad2e0531b9fba0bc.png 6599976950408d888b4589ad6a88ef1a.png 2c5628b6d91ff0461f57e6ba56030451.png 6fa45bd7e5a13978513308fbf3f17c3f.png 312cde05305d9121edd044f4f07a2f7a.png 9375579f0625e241beb4893ca77aab3b.png 82388ab18a78e6faaf86d833fc530ce8.png e349286b32ba45a0d9b282df16968bf7.png

6. 改进

额外的,反向的搜索能够解决终点在墙边或者墙角时命中终点困难的问题,提高效率,增加成功率;
额外的,如果从起点开始扩展,close set在一定半径距离 节点数目达到指定数目后无法扩展即认为起点被困;如果从终点开始扩展,close set在一定半径距离内节点数目达到指定数目无法扩展即认为终点被困;

传入起始位姿和终点位姿;注意,最好提前保证两个位姿有好的扩展性:起点位姿能够朝着左前方右前方正前方正常前进运动有限圆弧长度无碰撞,终点位姿能够朝着左后方右后方正后方正常前进运动有限圆弧长度无碰撞,不具有扩展性的起始位姿和终点位姿可能导致规划失败或者消耗大量资源;

传入障碍物二值地图,只有0和255像素;
可选的,可传入多个潜在的目标位姿;
返回:规划结果,起点被困或者终点被困或者成功;如果有多个潜在目标位姿会返回命中的那一个;返回路径;
额外的,可以返回哪一段路径存在窄通道(机器在某个路径点位姿下左右两侧20cm范围内都有障碍物认为是窄通道路径点);

7. 参考repo

436addb09d94200c0b168fda3cb7cfda.png

7.1. 下载地址

https://github.com/tanujthakkar/Voronoi-Based-Hybrid-Astar

D:[opencv_source_navigation]\Voronoi-Based-Hybrid-Astar-main\src\hybrid_astar.cpp

代码仓库可在gitee或github搜索Voronoi-Based-Hybrid-Astar得到;

569d1415092e3e8dfd194ed678abf6b1.png

7.2. 实现要点

命中子目标点之后将子目标点放入openset;

e0e32d12fe5ced5fa12f17816cbd1af8.png

投稿作者为『自动驾驶之心知识星球』特邀嘉宾,欢迎加入交流!

① 全网独家视频课程

BEV感知、BEV模型部署、BEV目标跟踪、毫米波雷达视觉融合多传感器标定多传感器融合多模态3D目标检测车道线检测轨迹预测在线高精地图世界模型点云3D目标检测目标跟踪Occupancy、cuda与TensorRT模型部署大模型与自动驾驶Nerf语义分割自动驾驶仿真、传感器部署、决策规划、轨迹预测等多个方向学习视频(扫码即可学习

f0728c8f9ecff05a91934b4208381436.png

网页端官网:www.zdjszx.com

② 国内首个自动驾驶学习社区

国内最大最专业,近3000人的交流社区,已得到大多数自动驾驶公司的认可!涉及30+自动驾驶技术栈学习路线,从0到一带你入门自动驾驶感知2D/3D检测、语义分割、车道线、BEV感知、Occupancy、多传感器融合、多传感器标定、目标跟踪)、自动驾驶定位建图SLAM、高精地图、局部在线地图)、自动驾驶规划控制/轨迹预测等领域技术方案大模型、端到端等,更有行业动态和岗位发布!欢迎扫描下方二维码,加入自动驾驶之心知识星球,这是一个真正有干货的地方,与领域大佬交流入门、学习、工作、跳槽上的各类难题,日常分享论文+代码+视频

4af4c57b47ce9098c4ca1b8399eb0124.png

③【自动驾驶之心】技术交流群

自动驾驶之心是首个自动驾驶开发者社区,聚焦感知、定位、融合、规控、标定、端到端、仿真、产品经理、自动驾驶开发、自动标注与数据闭环多个方向,目前近60+技术交流群,欢迎加入!扫码添加汽车人助理微信邀请入群,备注:学校/公司+方向+昵称(快速入群方式)

083a43db6fb545420f8e95948307fe79.jpeg

④【自动驾驶之心】全平台矩阵

a8fafc80cd6fd20e233791c74b501b04.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值