无人驾驶路径规划(一)全局路径规划 - RRT算法原理及实现

news2025/1/14 1:05:57

前言:由于后续可能要做一些无人驾驶相关的项目和实验,所以这段时间学习一些路径规划算法并自己编写了matlab程序进行仿真。开启这个系列是对自己学习内容的一个总结,也希望能够和优秀的前辈们多学习经验。

一、无人驾驶路径规划

众所周知,无人驾驶大致可以分为三个方面的工作:感知,决策及控制

路径规划是感知和控制之间的决策阶段,主要目的是考虑到车辆动力学、机动能力以及相应规则和道路边界条件下,为车辆提供通往目的地的安全和无碰撞的路径。

路径规划问题可以分为两个方面:

(一)全局路径规划:全局路径规划算法属于静态规划算法,根据已有的地图信息(SLAM)为基础进行路径规划,寻找一条从起点到目标点的最优路径。通常全局路径规划的实现包括Dijikstra算法,A*算法,RRT算法等经典算法,也包括蚁群算法、遗传算法等智能算法;

(二)局部路径规划:局部路径规划属于动态规划算法,是无人驾驶汽车根据自身传感器感知周围环境,规划处一条车辆安全行驶所需的路线,常应用于超车,避障等情景。通常局部路径规划的实现包括动态窗口算法(DWA),人工势场算法,贝塞尔曲线算法等,也有学者提出神经网络等智能算法。

本系列就从无人驾驶路径规划的这两方面进行展开,对一些经典的算法原理进行介绍,并根据个人的一些理解和想法提出了一些改进的意见,通过Matlab2019对算法进行了仿真和验证。过程中如果有错误的地方,欢迎在评论区留言讨论,如有侵权请及时联系。

那么废话不多说,直接进入第一部分的介绍,全局路径规划算法-RRT算法。

二、全局路径规划 - RRT算法原理

RRT算法,即快速随机树算法(Rapid Random Tree),是LaValle在1998年首次提出的一种高效的路径规划算法。RRT算法以初始的一个根节点,通过随机采样的方法在空间搜索,然后添加一个又一个的叶节点来不断扩展随机树。当目标点进入随机树里面后,随机树扩展立即停止,此时能找到一条从起始点到目标点的路径。算法的计算过程如下:

step1:初始化随机树Tree。将环境中起点作为随机树搜索的起点,此时树中只包含一个节点即根节点qq_{start} 

stpe2:在环境中随机采样。在环境中随机产生一个点x_{random},若该点不在障碍物范围内则计算随机树Tree中所有节点到x_{random}的欧式距离,并找到距离最近的节点x_{near},若在障碍物范围内则重新生成x_{random}并重复该过程直至找到x_{near};  

stpe3:生成新节点。在x_{near}x_{random}连线方向,由x_{near}指向x_{random}固定生长距离growdistance生成一个新的节点x_{new},并判断该节点是否在障碍物范围内,若不在障碍物范围内则将x_{new}添加到随机树 Tree中,否则的话返回step2重新对环境进行随机采样;

step4: 停止搜索。当x_{near}和目标点x_{goal}之间的距离小于设定的阈值时,则代表随机树已经到达了目标点,将x_{goal}作为最后一个路径节点加入到随机树中,算法结束并得到所规划的路径 。

RRT算法由于其随机采样及概率完备性的特点,使得其具有如下优势:

(1)不需要对环境具体建模,有很强空间搜索能力;

(2)路径规划速度快;

(3)可以很好解决复杂环境下的路径规划问题。

但同样是因为随机性,RRT算法也存在很多不足的方面:

(1)随机性强,搜索没有目标性,冗余点多,且每次规划产生的路径都不一样,均不一是最优路径;

(2)可能出现计算复杂、所需的时间过长、易于陷入死区的问题;

(3)由于树的扩展是节点之间相连,使得最终生成的路径不平滑;

(4)不适合动态环境,当环境中出现动态障碍物时,RRT算法无法进行有效的检测;

(5)对于狭长地形,可能无法规划出路径。

三、RRT算法Matlab实现

使用matlab2019来编写RRT算法,下面将贴出部分代码进行解释。

1、生成障碍物

在matlab中模拟栅格地图环境,自定义障碍物位置。

%% 生成障碍物
ob1 = [0,-10,10,5];             % 三个矩形障碍物
ob2 = [-5,5,5,10];
ob3 = [-5,-2,5,4];

ob_limit_1 = [-15,-15,0,31];    % 边界障碍物
ob_limit_2 = [-15,-15,30,0];
ob_limit_3 = [15,-15,0,31];
ob_limit_4 = [-15,16,30,0];

ob = [ob1;ob2;ob3;ob_limit_1;ob_limit_2;ob_limit_3;ob_limit_4];  % 放到一个数组中统一管理

x_left_limit = -16;             % 地图的边界
x_right_limit = 15;
y_left_limit = -16;
y_right_limit = 16;

我在这随便选择生成三个矩形的障碍物,并统一放在ob数组中管理,同时定义地图的边界。

2、初始化参数设置

初始化障碍物膨胀范围、地图分辨率,机器人半径、起始点、目标点、生长距离和目标点搜索阈值。

%% 初始化参数设置
extend_area = 0.2;        % 膨胀范围
resolution = 1;           % 分辨率
robot_radius = 0.2;       % 机器人半径

goal = [-10, -10];        % 目标点
x_start = [13, 10];       % 起点

grow_distance = 1;        % 生长距离
goal_radius = 1.5;        % 在目标点为圆心,1.5m内就停止搜索

3、初始化随机树

初始化随机树,定义树结构体tree以保存新节点及其父节点,便于后续从目标点回推规划的路径。

%% 初始化随机树
tree.child = [];               % 定义树结构体,保存新节点及其父节点
tree.parent = [];
tree.child = x_start;          % 起点作为第一个节点

flag = 1;                      % 标志位


new_node_x = x_start(1,1);     % 将起点作为第一个生成点
new_node_y = x_start(1,2);
new_node = [new_node_x, new_node_y];

4、主函数部分

主函数中首先生成随机点,并判断是否在地图范围内,若超出范围则将标志位置为0

rd_x = 30 * rand() - 15;    % 生成随机点
rd_y = 30 * rand() - 15;    
if (rd_x >= x_right_limit || rd_x <= x_left_limit ||... % 判断随机点是否在地图边界范围内
    rd_y >= y_right_limit || rd_y <= y_left_limit)
    flag = 0;
end

调用函数cal_distance计算tree中距离随机点最近的节点的索引,并计算该节点与随机点连线和x正向的夹角。

[angle, min_idx] = cal_distance(rd_x, rd_y, tree);    % 返回tree中最短距离节点索引及对应的和x正向夹角

cal_distance函数定义如下:

function [angle, min_idx] = cal_distance(rd_x, rd_y, tree)
    distance = [];
    i = 1;
    while i<=size(tree.child,1)
        dx = rd_x - tree.child(i,1);
        dy = rd_y - tree.child(i,2);
        d = sqrt(dx^2 + dy^2);
        distance(i) = d;
        i = i+1;
    end
    [~, min_idx] = min(distance);
    angle = atan2(rd_y - tree.child(min_idx,2),rd_x - tree.child(min_idx,1));
end

随后生成新节点。

new_node_x = tree.child(min_idx,1)+grow_distance*cos(angle);% 生成新的节点
new_node_y = tree.child(min_idx,2)+grow_distance*sin(angle);
new_node = [new_node_x, new_node_y];

接下来需要对该节点进行判断:

① 新节点是否在障碍物范围内;

②  新节点和父节点的连线线段是否和障碍物有重合部分。

若任意一点不满足,则将标志位置为0。实际上可以将两个判断结合,即判断新节点和父节点的连线线段上的点是否在障碍物范围内。

for k=1:1:size(ob,1) 
    for i=min(tree.child(min_idx,1),new_node_x):0.01:max(tree.child(min_idx,1),new_node_x)    % 判断生长之后路径与障碍物有无交叉部分
        j = (tree.child(min_idx,2) - new_node_y)/(tree.child(min_idx,1) - new_node_x) *(i - new_node_x) + new_node_y;
        if(i >=ob(k,1)-resolution && i <= ob(k,1)+ob(k,3) && j >= ob(k,2)-resolution && j <= ob(k,2)+ob(k,4))
            flag = 0;
            break
        end
    end
end

在这我采用的方法是写出新节点和父节点连线的直线方程,然后将x变化范围限制在min(tree.child(min_idx,1),new_node_x)到max(tree.child(min_idx,1),new_node_x)内,0.01即坐标变换的步长,步长越小判断的越精确,但同时会增加计算量;步长越大计算速度快但是很可能出现误判,如下图所式。

            左图:合适的步长                                                          右图:步长过大

判断标志位若为1,则可以将该新节点加入到tree中,注意保存新节点和它的父节点,同时显示在figure中,之后重置标志位。

if (flag == true)           % 若标志位为1,则可以将该新节点加入tree中
    tree.child(end+1,:) = new_node;
    tree.parent(end+1,:) = [tree.child(min_idx,1), tree.child(min_idx,2)];
    plot(rd_x, rd_y, '.r');hold on
    plot(new_node_x, new_node_y,'.g');hold on
    plot([tree.child(min_idx,1),new_node_x], [tree.child(min_idx,2),new_node_y],'-b');
end
    
flag = 1;                   % 标志位归位

最后就是把障碍物、起点终点等显示在figure中,并判断新节点到目标点距离。若小于阈值则停止搜索,并将目标点加入到node中,否则重复该过程直至找到目标点。

%% 显示
for i=1:1:size(ob,1)        % 绘制障碍物
    fill([ob(i,1)-resolution, ob(i,1)+ob(i,3),ob(i,1)+ob(i,3),ob(i,1)-resolution],...
         [ob(i,2)-resolution,ob(i,2)-resolution,ob(i,2)+ob(i,4),ob(i,2)+ob(i,4)],'k');
end
hold on

plot(x_start(1,1)-0.5*resolution, x_start(1,2)-0.5*resolution,'b^','MarkerFaceColor','b','MarkerSize',4*resolution); % 起点
plot(goal(1,1)-0.5*resolution, goal(1,2)-0.5*resolution,'m^','MarkerFaceColor','m','MarkerSize',4*resolution); % 终点
set(gca,'XLim',[x_left_limit x_right_limit]); % X轴的数据显示范围
set(gca,'XTick',[x_left_limit:resolution:x_right_limit]); % 设置要显示坐标刻度
set(gca,'YLim',[y_left_limit y_right_limit]); % Y轴的数据显示范围
set(gca,'YTick',[y_left_limit:resolution:y_right_limit]); % 设置要显示坐标刻度
grid on
title('D-RRT');
xlabel('横坐标 x'); 
ylabel('纵坐标 y');
pause(0.05);
if (sqrt((new_node_x - goal(1,1))^2 + (new_node_y- goal(1,2))^2) <= goal_radius) % 若新节点到目标点距离小于阈值,则停止搜索,并将目标点加入到node中
    tree.child(end+1,:) = goal;         % 把终点加入到树中
    tree.parent(end+1,:) = new_node;
    disp('find goal!');
    break
end

5、绘制最优路径

从目标点开始,依次根据节点及父节点回推规划的路径直至起点,要注意tree结构体中parent的长度比child要小1。最后将规划的路径显示在figure中。

%% 绘制最优路径
temp = tree.parent(end,:);
trajectory = [tree.child(end,1)-0.5*resolution, tree.child(end,2)-0.5*resolution];
for i=size(tree.child,1):-1:2
    if(size(tree.child(i,:),2) ~= 0 & tree.child(i,:) == temp)
        temp = tree.parent(i-1,:);
        trajectory(end+1,:) = tree.child(i,:);
    if(temp == x_start)
        trajectory(end+1,:) = [temp(1,1) - 0.5*resolution, temp(1,2) - 0.5*resolution];
    end
    end
end
plot(trajectory(:,1), trajectory(:,2), '-r','LineWidth',2);
pause(2);

程序运行最终效果如下:

 红点都是生成点随机点,绿点是tree中节点,红色路径即为RRT算法规划的路径。

6、路径平滑(B样条曲线)

由于规划的路径都是线段连接,在节点处路径不平滑,这也是RRT算法的弊端之一。一般来说轨迹平滑的方法有很多种,类似于贝塞尔曲线,B样条曲线等。我在这采用B样条曲线对规划的路径进行平滑处理,具体的方法和原理我后续有时间再进行说明,这里先给出结果:

 黑色曲线即位平滑处理后的路径。

四、多组结果对比

① 相邻两次仿真结果对比:

可以看出由于随机采样的原因,任意两次规划的路径都是不一样的。 

② 复杂环境下的路径规划。选取一个相对复杂的环境,仿真结果如下:

可以看出RRT算法可以很好解决复杂环境下的路径规划问题。

③ 狭窄通道下的路径规划。选取一个狭窄通道环境,仿真结果如下:

 由于环境采样的随机性,在狭长通道内生成随机点的概率相对较低,导致可能无法规划出路径。

五、结语

由最终仿真结果可以看出,RRT算法通过对空间的随机采样可以规划出一条从起点到终点的路径,规划速度很快,同时不依赖于环境。但规划过程随机性很强,没有目的性,会产生很多冗余点,且每次规划的路径都不一样,对于狭窄通道可能无法规划出路径。

下篇文章我将对RRT算法的优化提出一些自己的想法,并在现有的程序上进行修改,最终对比改进前后的RRT算法效果。

文中如有错误或侵权的地方还欢迎各位指出,我会及时回复并进行修改。

PS:需要matlab源码的朋友可以在评论区留下邮箱。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1099367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Google Authenticator 和gitlab使用的方法配置Google AuthenticatorGoogle

Google Authenticator 和gitlab使用的方法 目录概述需求&#xff1a; 设计思路实现思路分析1.配置过程&#xff1a; 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a bette…

D201126 D201129 支持以太网高级物理层(APL)

D201126 D201129 支持以太网高级物理层(APL) 全球技术和软件领导者艾默生宣布了基于其无限自动化愿景&#xff0c;并作为其下一代以软件为中心的工业自动化架构的基础。新技术的发布将超越传统的控制系统&#xff0c;创建一个更先进的自动化平台&#xff0c;为人类和塑造世界…

【网络】网络层协议:IP(待更新)

文章目录 IP 协议1. 基本概念2. IP 报头解析 &#x1f53a;IP 的 网段划分&#xff1a; IP 协议 IP 不是面向字节流的&#xff0c;而是发送接收一个个的数据包 1. 基本概念 主机&#xff1a;配有 IP 地址的设备 路由器&#xff1a;配有单个或多个 IP 地址&#xff0c;且能进行…

【1314. 矩阵区域和】

目录 一、题目描述二、算法思想三、代码实现 一、题目描述 二、算法思想 三、代码实现 class Solution { public:vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {//先预处理数组int nmat.size();//行int mmat[0].size();…

flutter开发实战-下拉刷新与上拉加载更多实现

flutter开发实战-下拉刷新与上拉加载更多实现 在开发中经常遇到列表需要下拉刷新与上拉加载更多&#xff0c;这里使用EasyRefresh&#xff0c;版本是3.3.21 一、什么是EasyRefresh EasyRefresh可以在Flutter应用程序上轻松实现下拉刷新和上拉加载。它几乎支持所有Flutter Sc…

C++指针和引用

1、引用必须初始化&#xff0c;指针不必&#xff0c;所以说引用使你更安全的指针&#xff1b; 2、在汇编代码&#xff0c;指针和引用一模一样&#xff1b; 3、引用只有一级引用&#xff0c;没有多级引用&#xff1b; 4、引用必须引用一个能取地址的变量&#xff1b; 左值&…

第三章 内存管理 五、动态分区分配算法(首次适应算法、最佳适应算法、最坏适应算法、临近适应算法)

目录 一、首次适应算法 1、算法思想&#xff1a; 2、如何实现&#xff1a; 3、两种常用的数据结构: &#xff08;1&#xff09;空闲分区表、空闲分区链 4、例子 二、最佳适应算法 1、算法思想: 2、如何实现: 3、例子&#xff1a; 三、最坏适应算法 1、算法思想&…

蓝桥杯每日一题2023.10.16

数的分解 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 最开始想使用dfs&#xff0c;发现范围过大无法在规定时间运行 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int a[N], v[N], ans; void dfs(int dep, int sum, int start) {if(sum > 20…

Linux命令行下查看实时网速

Linux命令行下&#xff0c;用ifconfig可以看到每个网卡实时的收发的数据包了字节数&#xff0c;但并不方便查看当前网卡的实时网速。 近日偶得一个软件叫nload可以方便的在命令行下查看实时网速&#xff0c;不敢独享&#xff0c;分享给大家。 1、安装 sudo apt install nload…

第三章 内存管理 六、基本分页存储管理

目录 一、定义 二、例子 三、总结 一、定义 基本分页存储管理是一种操作系统的存储管理技术。在基本分页存储管理中&#xff0c;物理内存被划分成固定大小的块&#xff0c;称为页面&#xff08;Page&#xff09;&#xff0c;而程序代码和数据被分成相同大小的块&#xff0c;…

数据结构:二叉树(1)

目录 树的概念 树的表示形式 二叉树 二叉树的性质 题目 二叉树的存储 链式存储 初始化二叉树 二叉树的遍历 前序遍历&#xff1a;根&#x1f449;左子树&#x1f449;右子树 中序遍历&#xff1a;左子树&#x1f449;根&#x1f449;右子树 后序遍历&#xff1a;左子…

二十八、【滤镜】

文章目录 滤镜库Camera Raw滤镜神经网络滤镜(Neurel Filters)液化其他滤镜 滤镜库 可以在滤镜库中选择一些常用的滤镜方式&#xff0c;主要有六大类&#xff1a; Camera Raw滤镜 Camera Raw滤镜是photoshop最重要的一个滤镜&#xff0c;他帮助我们在摄影后期去进行颜色预处…

导数、偏导数、方向导数

一、导数 导数是描述函数变化率的数学概念。 导数的定义式: 那么就有一个问题&#xff0c;为什么求函数的最大值点是要对其自变量求导&#xff0c;并使其导数等于0 比如:求L(θ)3lnθ2ln(1-θ)的最大值点 既然导数表示函数的变化率&#xff0c;那么当函数L(θ)的导数等于0&…

Go语言基础之包

包&#xff08;package&#xff09; Go语言中支持模块化的开发理念&#xff0c;在Go语言中使用包&#xff08;package&#xff09;来支持代码模块化和代码复用。一个包是由一个或多个Go源码文件&#xff08;.go结尾的文件&#xff09;组成&#xff0c;是一种高级的代码复用方案…

构建高性能物联网数据平台:EMQX和CnosDB的完整教程

CnosDB 是一款高性能、高压缩率、高易用性的开源分布式时序数据库。主要应用场景为物联网、工业互联网、车联网和IT运维。所有代码均已在GitHub开源。本文将介绍如何使用EMQX 这一MQTT 服务器 CnosDB 构建物联网数据平台&#xff0c;实现物联网数据的实时流处理。 前言 在物联…

CLIP和改进工作

CLIP和改进工作 CLIP 改进方向 语义分割 Lseg、GroupViT 目标检测 ViLD、GLIP v1/v2 视频理解 VideoCLIP、CLIP4clip、ActionCLIP 图像生成 VQGAN-CLIP、CLIPasso、CLIP-Draw 多模态下游任务 VL Downstream 其他 prompt enginering&#xff08;CoOp等&#xff09; depthCLIP、…

(C++ STL) 详解vector模拟实现

目录 一.vector的介绍 1.vector的介绍 二.vector的定义模拟实现 三.vector各接口的模拟实现 1.vector迭代器的模拟实现 2.构造函数 2.1无参构造 2.2 n个val构造 2.3迭代器区间构造 2.4通过对象初始化&#xff08;拷贝构造&#xff09; 3.析构函数 4.size 5.operato…

PCB沉金包边工艺流程与主要作用经验总结

🏡《总目录》 目录 1,什么是PCB沉积包边2,PCB沉金包边作用2.1,射频屏蔽2.2,EMC认证2.3,防氧化2.4,强电屏蔽2.5,美观3,PCB沉金包边的工艺流程4,总结1,什么是PCB沉积包边 PCB沉金包边是指,在PCB的侧边也包裹上铜皮,并在铜皮的表面进行沉金工艺。在高频电路板中经常…

探索数字时代的核心:服务器如何塑造未来并助你成就大业

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【Java学习之道】Java网络编程API介绍

引言 在Java中&#xff0c;进行网络编程的主要方式是通过Java网络编程API。这些API提供了一组类和接口&#xff0c;用于创建网络应用&#xff0c;如TCP和UDP通信、URL访问等。在这一节中&#xff0c;我们将带你领略Java网络编程API的魅力。 一、InetAddress InetAddress类是表…