RRT(Rapidly-Exploring Random Tree)算法是一种基于采样的路径规划算法,常用于移动机器人路径规划,适合解决高维空间和复杂约束下的路径规划问题。基本思想是以产生随机点的方式通过一个步长向目标点搜索前进,有效躲避障碍物,避免路径陷入局部极小值,收敛速度快。本文通过matlab实现RRT算法,解决二维平面的路径规划问题。
1)确定起点start和终止点goal;
2)在空间中随机生成新的点r(50%为随机点,50%为目标点,目的是增强RRT向goal点生成的导向性);
3)判断点r与轨迹树中哪一个节点的欧氏距离最小,记该点为最近父节点closetNode;
4)沿生长向量方向(最近父节点closetNode指向随机点r的方向)按照步长stepSize生成新的子节点newNode;
5)碰撞检测:检测closetNode到newNode的连线是否会与障碍物发生碰撞。若是,返回步骤2,重新生成随机点;若否,则将newNode添加到轨迹树中。
6)检测是否到达goal附近。若是,结束搜索;若否,返回步骤2继续搜索。
障碍物我们只设置了球形障碍物,也可以设置成立方体、圆柱体等等。
实施效果1:
左:RRT随机树 右:贪心算法优化后的随机树
优化前,RRT树有80个节点,并且路径并不平滑。优化后,随机树只有三个点,路径只有两条直线组成。当然还可以继续对路径进行平滑,本文没有再继续做这些工作了。
实施效果2:
%created by MW
%date: 2022/5/25
%func: RRT avoiding obstacle
clc;
clear;
%% 创建并绘制障碍物
circleCenter = [100 200 100;
200 700 100;
200 500 500;
700 700 300;
900 200 100];
radius = [100;100;200;200;300];
%绘制球形障碍物
figure(1);
[x, y, z] = sphere; %创建一个坐标在原点,半径为1的标准圆,用于绘制自定义的圆
for i = 1: length(radius)
mesh(radius(i)*x + circleCenter(i,1), radius(i)*y + circleCenter(i,2), radius(i)*z + circleCenter(i,3));
hold on;
end
axis equal;
%% 创建初始位置和目标位置并绘制
start = [0 0 0];
goal = [700 800 1000];
hold on;
scatter3(start(1),start(2),start(3),'filled','g');
scatter3(goal(1),goal(2),goal(3),'filled','b');
%% 图片标注
text(start(1),start(2),start(3),'起点');
text(goal(1),goal(2),goal(3),'终点');
view(3);
grid on;
axis equal;
axis([0 1000 0 1000 0 1000]);
xlabel('x');
ylabel('y');
zlabel('z');
%% RRT方法生成避障轨迹
path = RRT(start,goal,radius,circleCenter);
%% 贪心算法优化RRT轨迹
newPath = GreedyOptimize(path,radius,circleCenter);
figure(2);
%绘制球形障碍物
[x, y, z] = sphere; %创建一个坐标在原点,半径为1的标准圆,用于绘制自定义的圆
for i = 1: length(radius)
mesh(radius(i)*x + circleCenter(i,1), radius(i)*y + circleCenter(i,2), radius(i)*z + circleCenter(i,3));
hold on;
end
axis equal;
%绘制原RRT树
plot3(path(:,1), path(:,2), path(:,3),'LineWidth',1,'Color','r');
%绘制优化后的RRT树
for k1 =1: length(newPath)
point = newPath(k1,:);
scatter3(point(1),point(2),point(3),'filled','k');
end
plot3(newPath(:,1), newPath(:,2), newPath(:,3),'LineWidth',2,'Color','y');
%图片标注
text(start(1),start(2),start(3),'起点');
text(goal(1),goal(2),goal(3),'终点');
view(3);
grid on;
axis equal;
axis([0 1000 0 1000 0 1000]);
xlabel('x');
ylabel('y');
zlabel('z');
function path = RRT(start,goal,radius,circleCenter)
%% 定义RRT参数
stepSize = 20; %步长
maxIterTimes = 5000; %最大迭代步数
iterTime = 0; %当前迭代次数
threshold = 20; %阈值
searchSize = [1000 1000 1000]; %空间尺寸
RRTree = double([start -1]); %创建RRT树,共4列。前3列为节点坐标,第4列为当前节点的父节点的索引。初始点的索引为-1
%多个点到某一点欧式距离计算方法
calcDis = @(a,b) sqrt((b(:,1)-a(1,1)).^2 + (b(:,2)-a(1,2)).^2 + (b(:,3)-a(1,3)).^2);
%% 寻找RRT路径
tic % tic-toc函数,用于计时,记录完成整个RRT树的运行时间
pathFound = false; %标志物,记录是否正确找到避障路径
while iterTime <= maxIterTimes
iterTime = iterTime +1;
%step1 - 生成随机点
%为了提高RRT扩展的导向性,以50%的概率在空间中随机生成生成新的随机点,50%的概率以目标点为新的随机点
if rand < 0.5
sample = rand(1,3) .* searchSize + start;
else
sample = goal;
end
%step2 - 寻找树上最近父节点
[val,nearIndex] = min(calcDis(sample, RRTree(:,1:3)),[],1); %计算树上每个节点到随机点的欧氏距离,并返回最短距离的值和index
closestNode = RRTree(nearIndex,1:3);
%step3 - 沿生长向量方向按照步长生成新的子节点
growVec = sample - closestNode;
growVec = growVec/sqrt(sum(growVec.^2));
newPoint = closestNode + growVec*stepSize;
%step4 - 碰撞检测
feasible = collisionDetec(newPoint,closestNode,radius,circleCenter);
if ~feasible
continue; %如果发生碰撞,则重新寻找随机点
end
%为树添加新节点
RRTree = [RRTree;
newPoint nearIndex];
plot3([closestNode(1) newPoint(1)],[closestNode(2) newPoint(2)],[closestNode(3) newPoint(3)],'LineWidth',1,'Color','b');
pause(0.01);
%检测是否到达goal附近
if sqrt(sum((newPoint - goal).^2)) <= threshold
pathFound = true;
break; %如果节点已到达goal附近,则结束搜寻
end
end
%搜索结束后如果搜索失败,显示错误信息
if ~pathFound
disp('no path found. maximum attempts reached');
end
toc
%% 绘制回溯路径
path = goal;
lastNode = nearIndex; %父节点索引,这里的nearIndex是goal的父节点索引
while lastNode >= 0
path =[RRTree(lastNode,1:3); path]; %将当前节点的上一个节点添加进回溯路径中
lastNode = RRTree(lastNode, 4); %更新父节点索引
end
plot3(path(:,1), path(:,2), path(:,3),'LineWidth',1,'Color','r');
end
线段检测:
%新的生长向量是否发生碰撞
function feasible = collisionDetec(newPoint,closestNode,radius,circleCenter)
feasible = true;
checkVec = newPoint - closestNode; %检测向量
%将检测向量以0.5为步长等比均分为无数个检测点,检测每个检测点是否与球发生碰撞
for i = 0:0.5:sqrt(sum(checkVec.^2))
checkPoint = closestNode + i.*(checkVec/sqrt(sum(checkVec.^2))); %生成检测点
checkPointFeasible = pointCollisionCheck(checkPoint,radius,circleCenter);
if ~checkPointFeasible
feasible = false;
break;
end
end
end
点检测:
%监测点是否发生碰撞
function pointFeasible = pointCollisionCheck(checkPoint,radius,circleCenter)
pointFeasible = true;
for s = 1:length(radius)
if sqrt(sum((checkPoint - circleCenter(s,:)).^2)) <= radius(s)
pointFeasible = false;
break;
end
end
end
贪心算法,是指在对问题求解时,总是做出再当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是某种意义上的局部最优解。
贪心算法没有固定算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
2. 贪心算法基本步骤:
步骤1:从某个初始解出发;
步骤2:采用迭代的过程,当可以向目标前进一步时,就根据局部最优策略,得到一部分解,缩小问题规模;
步骤3:将所有解综合起来。
1)连接目标点和初始点。
2)检测是否发生碰撞。
3)若是,表明从初始点到该点中间不能被省略,沿目标点往上回溯到上一级父节点(上一级父节点更新为新的目标点),回到步骤1;若否,表明从初始点到该点中间的所有节点均可被省略,将该点添加到新的随机树中,并将该点更新为新的初始点,目标点复位到最后一个节点,回到步骤1.
4)检测直到start更新至goal结束。
function newPath = GreedyOptimize(path,radius,circleCenter)
startIndex = 1;
goalIndex = length(path(:,1));
detectTimes = length(path(:,1)) - 1; %检测次数
newPath = [path(startIndex,:)]; %添加初始位置
while detectTimes >0;
detectTimes = detectTimes-1;
start = path(startIndex,:);
goal = path(goalIndex,:);
%碰撞检测
feasible = collisionDetec(goal,start,radius,circleCenter);
if ~feasible
goalIndex = goalIndex - 1; %若碰撞,index减1,继续向上探索父节点
continue;
else
newPath = [newPath; goal]; %若未发生碰撞,表示找到一个最优节点,则从该节点往上的所有父节点均可省略,将该节点添加至路径中
detectTimes = length(path(:,1)) - goalIndex; %检测次数更位
startIndex = goalIndex; %将当前节点索引更新为新的树的起点
goalIndex = length(path(:,1)); %将终点索引复位
end
end
newPath = [newPath; path(end,:)]; %添加目标位置
end