基于采样的RRT/RRT*/RRT_connect算法笔记及C++实现

2023-05-16

本文记录常见的基于采样的RRT算法及相关改进算法(RRT*,RRT_connect)的原理和代码实现效果。与上一章介绍A*算法的文章不同,本文会先给出几种算法之间的效果对比,先有个直观的感受,再介绍其内在算法差异。

1. RRT/RRT*/RRT_connect算法效果对比

表1.1 简单地图路径搜索对比

算法采样次数实现效果
RRT3198在这里插入图片描述
RRT*3570在这里插入图片描述
RRT_connect214在这里插入图片描述
RRT*_connect195在这里插入图片描述

表1.2 复杂地图路径搜索对比(最大采样次数设置为10000)

算法采样次数实现效果
RRT10000在这里插入图片描述
RRT*10000在这里插入图片描述
RRT_connect1650在这里插入图片描述
RRT*_connect3960在这里插入图片描述

通过对比上述结果可以得出以下结论:
1)若路径存在,则通过RRT采样方法终究能找到1条路径,RRT算法是概率完备的
2)随机采样的缘故,RRT算法找到的路径通常情况下都不是最优的
3)相比于RRT算法,RRT*算法能对搜索树进行“剪枝”操作,优化路径使之更接近于真实的最优路径
4)传统的RRT算法是从起始点向终点进行搜索寻找路径,而RRT_connect是从起点和终点同时开始向中间搜索路径,能够显著提高路径搜索效率,减少采样次数
5)对于狭小通道(narrow passage)工况,传统的RRT可能需要很长时间才能通过随机采样通过狭小通道,而RRT_connect由于是从两头同时往中间搜索,在遇到狭小通道时,比RRT更能快速高效地寻找到路径

RRT算法就是在空间中随机撒点,通过点之间的连线确定1条起点到终点的路径。在直观上感受到RRT系列算法的路径搜索过程及效果对比之后,接下来介绍其内在的算法原理。

2. RRT/RRT*/RRT_connect算法流程

便于理解,先上基础RRT算法伪代码流程,针对流程逐一介绍
在这里插入图片描述
输入:带障碍物的地图,起点,终点位置
输入:从起点到终点的位置
维护1个节点集T用于存放已经搜索过且可到达的节点,初始化T放入起始节点
进入循环,进行路径搜索:
(1)在地图M中随机采集节点Xrand;
(2)从节点集T中找到离Xrand最近的节点Xnear;
(3)从Xnear出发,沿着指向Xrand的方向走指定步长StepSize,得到节点Xnew;
(4)连接Xnear和Xnew点,得到边Ei;
(5)对Ei进行碰撞检测,若是collision free的,则将Xnew放入T中,并设置Xnew的父节点为Xnear;若与障碍物有碰撞,进入下1轮循环,继续随机采样节点;
(6)如果Xnew等于Xgoal或者Xnew距离Xgoal小于某一设定阈值,则路径寻找成功,退出循环。

通过算法流程可以看出,RRT算法旨在找到起点到终点的1条路径,从起点出发,向终点不断搜索。但是也存在一些缺点:搜索出的路径不是最优的;在整个空间采样,效率低下。特别是对于文中示例的复杂路况,存在“狭窄通道”情况,一般的RRT算法可能需要经历很多次采样才能穿过通道,比如文中复杂工况,RRT算法经过10000次采样,仍然未找到1条路径,尽管从直观上看可行的路径是肯定存在的。

基本的RRT每次搜索都只有从初始状态点生长的快速扩展随机树来搜索整个状态空间,如果从初始状态点和目标状态点同时生长两棵快速扩展随机树来搜索状态空间,效率会更高。为此,基于双向扩展平衡的连结型双树RRT算法,即RRT_connect算法
在这里插入图片描述
而RRT*算法是在RRT算法的基础上加了"剪枝”操作,使路径更趋向于最优路径。算法流程如下
在这里插入图片描述
可以看出,相对于RRT算法,主要区别在于,在循环中多了ChooseParent和rewire两步。其中ChooseParent是指得到Xnew点之后,不直接将Xnear和Xnew连接,而是将Xnew周围给定半径(要比stepsize大)区域内的点加入一个节点集中,依次比较以这个节点集中节点作为Xnew的父节点的cost,选取cost最小的节点作为Xnew的父节点。而rewire是指依次比较节点集中的节点当前cost1和若以Xnew节点作为父节点的cost2,若cost2<cost1,则将当前节点的父节点更新为Xnew。

3. C++代码实现

在上一篇A*算法代码框架基础上,重新写了二维平面内的RRT/RRT*/RRT_connect算法,并通过调用opencv库展示路径搜索效果。具体代码见:
https://github.com/immune1993/path_planning_algorithm/tree/master/Sample_based
下面仅贴出核心的搜索部分代码

//RRT/RRT*搜索算法
bool RRTPathFinder::RRTGraphSearch(Eigen::Vector2d start_pt, Eigen::Vector2d end_pt) {
    //初始化起始和目标点
    GridNodePtr startNode=new GridNode(start_pt);startNode->r=5;
    GridNodePtr endNode= new GridNode(end_pt);endNode->r=5;

    //起始和目标点绘制
    startNode->drawNode(picture,Eigen::Vector3i(0,0,255));
    endNode->drawNode(picture,Eigen::Vector3i(255,0,0));

    GridNodeSet.clear();

    //将开始点放入搜索树中
    GridNodeSet.push_back(startNode);
    int count=0;
    srand((unsigned)time(NULL));

    //for(int i=0;i<5000;i++){
    while (1){
        //生成随机点
        count++;
        int x=(rand()%100+1)*5;
        int y=(rand()%100+1)*5;
//        int x=goal_dis(goal_gen)*5;
//        int y=goal_dis(goal_gen)*5;
        Eigen::Vector2d randcoo((double)x,(double)y);
        GridNodePtr randNode =new GridNode(randcoo);

        //获取最近节点
        GridNodePtr nearNode= getNearestNode(randNode);
        //获取next节点
        GridNodePtr nextNode= getNextNode(nearNode,randNode,10);
        //碰撞检测
        if(!collisioncheck(nextNode,nearNode)){
            continue;
        }
        nextNode->father=nearNode;

        //RRT*
        nextNode->cost=nearNode->cost+10;
        //获取next节点周围半径R内的节点集合
        std::vector<GridNodePtr> neighborsNode= getNeighbors(nextNode,25);
        //为next确定父节点
        for(auto n:neighborsNode){
            if(n->cost+getCost(n,nextNode)<nextNode->cost&& collisioncheck(nextNode,n)){
                nextNode->father=n;
                nextNode->cost=n->cost+ getCost(n,nextNode);
            }
        }

        //为周围节点更新父节点是否为nextnode
        for(auto n:neighborsNode){
            if(nextNode->cost+ getCost(nextNode,n)<n->cost&& collisioncheck(n,nextNode)){
                //去除原来连线
                cv::line(picture,cv::Point(n->father->coord(0),n->father->coord(1)),cv::Point(n->coord(0),n->coord(1)),cv::Scalar(255,255,255));
                n->father=nextNode;
                n->cost=nextNode->cost+ getCost(nextNode,n);
                //重新连线
                cv::line(picture,cv::Point(n->father->coord(0),n->father->coord(1)),cv::Point(n->coord(0),n->coord(1)),cv::Scalar(0,0,0));
            }
        }
        //

        nextNode->drawNode(picture,Eigen::Vector3i(0,255,0));
        cv::line(picture,cv::Point(nextNode->father->coord(0),nextNode->father->coord(1)),cv::Point(nextNode->coord(0),nextNode->coord(1)),cv::Scalar(0,0,0));
        GridNodeSet.push_back(nextNode);
        cv::imshow("RRT",picture);
        if(count%15==0) {write.write(picture);std::cout<<count<<std::endl;}
        cv::waitKey(1);
        if(getCost(endNode,nextNode)<5){
            goalNode=nextNode;
            std::cout<<"Path has been found after " + std::to_string(count) +" times search"<<std::endl;
            return true;
        }
    }
    std::cout<<"Path has been not found!"<<std::endl;
    return false;
}
//RRT_connect搜索函数
bool RRTPathFinder::RRTGraphSearch(Eigen::Vector2d start_pt, Eigen::Vector2d end_pt) {
    //初始化起始和目标点
    GridNodePtr startNode=new GridNode(start_pt);startNode->r=5;
    GridNodePtr endNode= new GridNode(end_pt);endNode->r=5;

    //起始和目标点绘制
    startNode->drawNode(picture,Eigen::Vector3i(0,0,255));
    endNode->drawNode(picture,Eigen::Vector3i(255,0,0));

    FromStartNodeSet.clear();
    FromEndNodeSet.clear();

    //将开始点放入搜索树中
    FromStartNodeSet.push_back(startNode);
    FromEndNodeSet.push_back(endNode);
    int count=0;
    srand((unsigned)time(NULL));

    //for(int i=0;i<5000;i++){
    while (1){
        //生成随机点
        count++;
        int x=(rand()%100+1)*5;
        int y=(rand()%100+1)*5;
//        int x=goal_dis(goal_gen)*5;
//        int y=goal_dis(goal_gen)*5;
        Eigen::Vector2d randcoo((double)x,(double)y);
        GridNodePtr randNode =new GridNode(randcoo);

        //获取最近节点
        GridNodePtr nearNode_s= getNearestNode(randNode,FromStartNodeSet);
        GridNodePtr nearNode_e= getNearestNode(randNode,FromEndNodeSet);
        //获取next节点
        GridNodePtr nextNode_s= getNextNode(nearNode_s,randNode,10);
        GridNodePtr nextNode_e= getNextNode(nearNode_e,randNode,10);
        //碰撞检测
        bool collcheck_s=collisioncheck(nextNode_s,nearNode_s);
        bool collcheck_e=collisioncheck(nextNode_e,nearNode_e);
        if(collcheck_s){
            nextNode_s->father=nearNode_s;

            //RRT*
            nextNode_s->cost=nearNode_s->cost+10;
            //获取next节点周围半径R内的节点集合
            std::vector<GridNodePtr> neighborsNode= getNeighbors(nextNode_s,FromStartNodeSet,25);
            //为next确定父节点
            for(auto n:neighborsNode){
                if(n->cost+getCost(n,nextNode_s)<nextNode_s->cost&& collisioncheck(nextNode_s,n)){
                    nextNode_s->father=n;
                    nextNode_s->cost=n->cost+ getCost(n,nextNode_s);
                }
            }

            //为周围节点更新父节点是否为nextnode
            for(auto n:neighborsNode){
                if(nextNode_s->cost+ getCost(nextNode_s,n)<n->cost&& collisioncheck(n,nextNode_s)){
                    //去除原来连线
                    cv::line(picture,cv::Point(n->father->coord(0),n->father->coord(1)),cv::Point(n->coord(0),n->coord(1)),cv::Scalar(255,255,255));
                    n->father=nextNode_s;
                    n->cost=nextNode_s->cost+ getCost(nextNode_s,n);
                    //重新连线
                    cv::line(picture,cv::Point(n->father->coord(0),n->father->coord(1)),cv::Point(n->coord(0),n->coord(1)),cv::Scalar(0,0,0));
                }
            }
            //

            nextNode_s->drawNode(picture,Eigen::Vector3i(0,255,0));
            cv::line(picture,cv::Point(nextNode_s->father->coord(0),nextNode_s->father->coord(1)),cv::Point(nextNode_s->coord(0),nextNode_s->coord(1)),cv::Scalar(0,0,0));
            FromStartNodeSet.push_back(nextNode_s);
        }
        if(collcheck_e){
            nextNode_e->father=nearNode_e;

            //RRT*
            nextNode_e->cost=nearNode_e->cost+10;
            //获取next节点周围半径R内的节点集合
            std::vector<GridNodePtr> neighborsNode= getNeighbors(nextNode_e,FromEndNodeSet,25);
            //为next确定父节点
            for(auto n:neighborsNode){
                if(n->cost+getCost(n,nextNode_e)<nextNode_e->cost&& collisioncheck(nextNode_e,n)){
                    nextNode_e->father=n;
                    nextNode_e->cost=n->cost+ getCost(n,nextNode_e);
                }
            }

            //为周围节点更新父节点是否为nextnode
            for(auto n:neighborsNode){
                if(nextNode_e->cost+ getCost(nextNode_e,n)<n->cost&& collisioncheck(n,nextNode_e)){
                    //去除原来连线
                    cv::line(picture,cv::Point(n->father->coord(0),n->father->coord(1)),cv::Point(n->coord(0),n->coord(1)),cv::Scalar(255,255,255));
                    n->father=nextNode_e;
                    n->cost=nextNode_e->cost+ getCost(nextNode_e,n);
                    //重新连线
                    cv::line(picture,cv::Point(n->father->coord(0),n->father->coord(1)),cv::Point(n->coord(0),n->coord(1)),cv::Scalar(0,0,0));
                }
            }
            //

            nextNode_e->drawNode(picture,Eigen::Vector3i(0,255,0));
            cv::line(picture,cv::Point(nextNode_e->father->coord(0),nextNode_e->father->coord(1)),cv::Point(nextNode_e->coord(0),nextNode_e->coord(1)),cv::Scalar(0,0,0));
            FromEndNodeSet.push_back(nextNode_e);
        }
        cv::imshow("RRT_Connect",picture);
        if(count%15==0) {write.write(picture);std::cout<<count<<std::endl;}
        cv::waitKey(1);
        if(collcheck_s&&collcheck_e&&getCost(nextNode_s,nextNode_e)<10&& collisioncheck(nextNode_s,nextNode_e)){
            Mid_s_Node=nextNode_s;
            Mid_e_Node=nextNode_e;
            std::cout<<"Path has been found after " + std::to_string(count) +" times search"<<std::endl;
            return true;
        }
    }
    std::cout<<"Path has been not found!"<<std::endl;
    return false;
}

4. 参考

[1] 深蓝学院课程:移动机器人路径规划
[2] https://blog.csdn.net/qq_14977553/article/details/107494695

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于采样的RRT/RRT*/RRT_connect算法笔记及C++实现 的相关文章

随机推荐

  • 车道线识别/Opencv/传统方法

    车道检测 Advanced Lane Finding Project 实现步骤 使用提供的一组棋盘格图片计算相机校正矩阵 camera calibration matrix 和失真系数 distortion coefficients 校正图
  • 2019新买电脑必备软件

    都2019了 xff0c 怎么还要安装这些垃圾软件 xff01 群丫头买了个新电脑 xff0c 为了避免她踩到笔者曾今踩到的坑 xff0c 更舒心的使用电脑 xff0c 为此特写此文 作为一个电脑平凡使用者 xff0c 用过无数的软件 xf
  • Docker与anaconda+jupyter

    Arch docker的安装 pacman 安装docker sudo pacman S docker docker启动 sudo systemctl start docker 设置开机启动docker sudo systemctl ena
  • Opencv目标追踪

    参考 xff1a https www pyimagesearch com 2018 07 30 opencv object tracking 引言 Opencv作为图像处理开源库包含了Object Tracking目标追踪的一些API xf
  • 使用cmake编译,组织C++项目

    文章目录 前言例一例二例三 前言 这篇博客是我对cmake用法的一些经验总结 还很浅显 如果有错误或者更好的方案 欢迎指正 使用方法统一为在build目录中执行 cmake make 我觉得养成外部编译是一个好习惯 例一 目录结构为 lzj
  • Spring Security Oauth2.0认证授权

    基本概念 认证 用户认证就是判断一个用户的身份是否合法的过程 xff0c 用户去访问系统资源时系统要求验证用户的身份信息 xff0c 身份合法方可继续访问 xff0c 不合法则拒绝访问 常见的用户身份认证方式有 用户名密码登录 xff0c
  • C语言实现HTTP的GET和POST请求

    HTTP请求和IP TCP 所谓的HTTP协议是基于IP TCP协议的 xff0c 所以要获取远端的html数据只要创建socket对象就足够了 xff1b HTTP是基于IP TCP加上了网络请求的固定格式 get 请求 include
  • 英伟达GPU安装教程

    英伟达GPU安装教程
  • 四种插头类型:XH、VH、SM、HY

    PH1 25mm HY2 0mm XH2 5 2 54mm xff08 这两个其实是一样的 xff09 VH3 96mm 插头分成三种类型 xff1a XH xff1b VH xff1b SM xff1b HY XH 4Y 是插头 xff1
  • STM32F1C8T6Flash读取音频和DAC播放

    文章目录 一 Flash简介1 Flash原理2 STM32F1中的Flash 二 Flash地址空间的数据读取1 题目要求2 CUbeMX工程建立3 Keil工程修改4 STlink调试说明5 调试运行 三 基于片内Flash的提示音播放
  • C语言中的头文件为什么不能定义变量

    1 前提 在实际项目中 xff0c 会遇到这样的情况 xff0c 在链接阶段 xff0c 会提示定义在头文件中的变量 xff0c 重定义 xff0c 然后将将头文件中的变量定义成static类型 xff0c 然后 xff0c 不报错误了 例
  • Ubuntu16.04安裝ROS

    此文章在前人的基礎上 xff0c 並把安裝遇到的問題寫出來 Ubuntu16 04安装ROS Kinetic详细过程 https blog csdn net weixin 43159148 article details 83375218
  • B-spline的理解与路径规划中的应用及C++代码的实现

    研究项目 xff0c 无人机的路径规划 xff0c 需要用到B样条 xff0c 所以在此写下B spline的结合C 43 43 代码的理解以及在项目中的应用 一 阶数p 阶数 61 所有权重中t的最高次幂 61 控制点数量 1 xff1b
  • Jetson TX2 各个模式

    Jetson TX2 工作模式及相应的CPU和GPU频率 xff1a 上电的时候 xff0c 默认最低功耗模式1 xff0c 风扇不转 1 直接运行home下的jetson clocks sh xff0c 开启最大频率 sudo jetso
  • 网络通信基础知识—网络通信的发展历程

    网络通信基础知识 网络通信的发展历程 xff08 1 xff09 单机阶段 xff08 2 xff09 局域网阶段 xff08 3 xff09 广域网internet阶段 xff08 很多个局域网之间通信 xff09 xff08 4 xff
  • win7下装ubuntu双系统 硬盘安装详细教程

    本文转载自http www linuxidc com Linux 2014 10 108430 htm https jingyan baidu com article e4d08ffdace06e0fd2f60d39 html 在自己安装过
  • ROS导航——配置机器人的导航功能(move_base包)

    中间部分是整个导航的核心部分 xff0c 由move base功能包提供 配置如下 xff1a lt launch gt lt node pkg 61 34 move base 34 type 61 34 move base 34 resp
  • 基于docker安装tensorflow

    最近在自学机器学习 xff0c 大热的Tensorflow自然不能错过 xff0c 所以首先解决安装问题 xff0c 为了不影响本地环境 xff0c 所以本文基于Docker来安装Tensorflow xff0c 我的环境是Ubuntu16
  • okHttpUtil工具类

    pom文件 lt dependency gt lt groupId gt com squareup okhttp3 lt groupId gt lt artifactId gt okhttp lt artifactId gt lt vers
  • 基于采样的RRT/RRT*/RRT_connect算法笔记及C++实现

    本文记录常见的基于采样的RRT算法及相关改进算法 xff08 RRT xff0c RRT connect xff09 的原理和代码实现效果 与上一章介绍A 算法的文章不同 xff0c 本文会先给出几种算法之间的效果对比 xff0c 先有个直