1. 路径规划与RRT算法家族概述
路径规划就像在一个陌生城市里找厕所——既要快速到达,又要避开死胡同。在机器人领域,这个问题变得更加复杂,因为我们需要考虑机器人的运动约束、环境障碍物以及实时性要求。快速探索随机树(Rapidly-exploring Random Tree,RRT)算法家族就是为解决这类问题而生的。
RRT算法最早由Steven M. LaValle在1998年提出,其核心思想是通过随机采样来构建一棵探索空间的树。与传统的网格搜索方法不同,RRT特别适合高维空间中的路径规划问题,比如机械臂的运动规划。算法会像树根生长一样逐步探索自由空间,直到找到连接起点和目标点的可行路径。
RRT是RRT的优化版本,它在基础RRT上增加了"重布线"(rewiring)机制,能够渐进优化路径质量。就像园丁修剪枝条一样,RRT会不断检查是否可以通过调整树的连接关系来获得更优的路径。这种优化使得RRT*能够收敛到渐近最优解,而不仅仅是找到一个可行解。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. RRT算法核心实现解析
2.1 数据结构设计
在MATLAB中实现RRT,首先需要设计合适的数据结构来存储树的节点。我们定义一个Node类来封装每个节点的信息:
matlab复制classdef Node
properties
pos % 节点坐标[x,y]
parent % 父节点索引
cost % 从起点到该节点的累积成本(RRT*使用)
end
end
这种面向对象的设计比单纯使用数组更清晰,也更容易扩展。pos属性存储节点的坐标,parent指向父节点(对于起点,parent为0),cost记录从起点到该节点的路径长度,这在RRT*的优化中会用到。
2.2 算法初始化
初始化就像种下一棵树苗,我们需要设置起点和终点:
matlab复制start = [0,0]; % 起点坐标
goal = [10,10]; % 终点坐标
obstacles = [5,5,3]; % 障碍物列表[x,y,radius]
% 创建根节点
tree = Node();
tree.pos = start;
tree.parent = 0;
tree.cost = 0;
nodes = [tree]; % 节点集合
这里我们假设环境中有一个圆形障碍物,中心在(5,5),半径为3。在实际应用中,障碍物可以更复杂,可能由多个多边形组成。
2.3 随机采样策略
RRT的核心在于随机采样,但纯随机采样效率可能很低。一个实用技巧是设置小概率直接采样目标点:
matlab复制function sample = randomSample(goal, bounds)
if rand < 0.05 % 5%的概率直接采样目标点
sample = goal;
else
% 在边界范围内随机采样
sample = rand(1,2).*(bounds(2,:)-bounds(1,:)) + bounds(1,:);
end
end
这个"目标偏向"策略能显著提高收敛速度。bounds参数定义了采样空间的边界,比如[-10,-10;20,20]表示x和y的范围。
2.4 最近邻搜索
找到距离随机点最近的树节点是RRT的关键操作。虽然可以遍历所有节点,但效率太低:
matlab复制function nearestNode = findNearest(nodes, randPoint)
positions = reshape([nodes.pos], 2, [])'; % 提取所有节点位置
[~, idx] = min(vecnorm(positions - randPoint, 2, 2)); % 计算欧氏距离
nearestNode = nodes(idx);
end
这里使用了MATLAB的矩阵运算来向量化计算,比循环快得多。对于大规模问题,可以考虑使用KD-tree等空间划分数据结构来进一步优化。
2.5 树扩展与碰撞检测
从最近节点向随机点方向扩展新节点时,需要限制步长并检查碰撞:
matlab复制function newNode = extend(fromNode, toPoint, stepSize)
direction = toPoint - fromNode.pos;
distance = norm(direction);
if distance > stepSize
direction = direction/distance * stepSize; % 限制步长
end
newNode = Node();
newNode.pos = fromNode.pos + direction;
newNode.parent = fromNode;
end
碰撞检测是路径规划中最关键也最耗时的部分。对于圆形障碍物,检测线段与圆的相交相对简单:
matlab复制function collision = checkCollision(start, finish, obstacles)
collision = false;
for i = 1:size(obstacles,1)
center = obstacles(i,1:2);
radius = obstacles(i,3);
% 检查端点是否在障碍物内
if norm(start-center) < radius || norm(finish-center) < radius
collision = true;
return;
end
% 计算线段到圆心的最短距离
ab = finish - start;
t = dot(center-start, ab)/dot(ab,ab);
t = max(0, min(1, t));
nearest = start + t*ab;
if norm(nearest-center) < radius
collision = true;
return;
end
end
end
对于更复杂的多边形障碍物,可以使用射线投射法或分离轴定理进行检测。
3. RRT*优化算法实现
3.1 近邻节点查找
RRT*的核心改进是在添加新节点后,检查附近是否存在更优的父节点。首先需要定义"附近"的范围:
matlab复制function nearIndices = findNearNodes(nodes, newNode, radius)
positions = reshape([nodes.pos], 2, [])';
distances = vecnorm(positions - newNode.pos, 2, 2);
nearIndices = find(distances < radius);
end
这个半径通常与树的规模和维度有关。在实践中,可以动态调整这个值以获得更好的性能。
3.2 重布线优化
找到近邻节点后,RRT*会尝试寻找成本更低的路径:
matlab复制% 在添加newNode到nodes数组前执行优化
nearIndices = findNearNodes(nodes, newNode, 2.0);
minCost = inf;
minNode = newNode.parent;
for i = 1:length(nearIndices)
nearNode = nodes(nearIndices(i));
% 检查路径是否无碰撞且成本更低
if ~checkCollision(nearNode.pos, newNode.pos, obstacles)
cost = nearNode.cost + norm(newNode.pos - nearNode.pos);
if cost < minCost
minCost = cost;
minNode = nearNode;
end
end
end
newNode.parent = minNode;
newNode.cost = minCost;
这一步确保了新节点通过最低成本的路径连接到树。
3.3 反向重布线
RRT*还会尝试用新节点来优化已有节点的路径:
matlab复制% 继续在之前的nearIndices循环中
for i = 1:length(nearIndices)
nearNode = nodes(nearIndices(i));
% 检查是否可以通过newNode获得更低成本
if ~checkCollision(newNode.pos, nearNode.pos, obstacles)
newCost = newNode.cost + norm(nearNode.pos - newNode.pos);
if newCost < nearNode.cost
nearNode.parent = newNode;
nearNode.cost = newCost;
% 需要递归更新所有子节点的cost
updateChildCosts(nodes, nearNode);
end
end
end
updateChildCosts函数需要递归地更新所有子节点的累积成本,这保证了树中路径成本的正确性。
4. 完整算法实现与可视化
4.1 主循环实现
将上述组件组合起来,得到完整的RRT*算法:
matlab复制function [nodes, path] = rrtStar(start, goal, obstacles, bounds, maxIter)
% 初始化
tree = Node();
tree.pos = start;
tree.parent = 0;
tree.cost = 0;
nodes = [tree];
figure;
hold on;
axis equal;
rectangle('Position',[bounds(1,1),bounds(1,2),...
bounds(2,1)-bounds(1,1),bounds(2,2)-bounds(1,2)]);
% 绘制障碍物
for i = 1:size(obstacles,1)
rectangle('Position',[obstacles(i,1)-obstacles(i,3),...
obstacles(i,2)-obstacles(i,3),...
obstacles(i,3)*2,obstacles(i,3)*2],...
'Curvature',[1,1],'FaceColor',[0.5 0.5 0.5]);
end
path = [];
for iter = 1:maxIter
% 随机采样
randPoint = randomSample(goal, bounds);
% 寻找最近节点
nearest = findNearest(nodes, randPoint);
% 扩展新节点
newNode = extend(nearest, randPoint, 0.5);
% 碰撞检测
if ~checkCollision(nearest.pos, newNode.pos, obstacles)
% RRT*优化
nearIndices = findNearNodes(nodes, newNode, 2.0);
minCost = nearest.cost + norm(newNode.pos - nearest.pos);
minNode = nearest;
for i = 1:length(nearIndices)
nearNode = nodes(nearIndices(i));
if ~checkCollision(nearNode.pos, newNode.pos, obstacles)
cost = nearNode.cost + norm(newNode.pos - nearNode.pos);
if cost < minCost
minCost = cost;
minNode = nearNode;
end
end
end
newNode.parent = minNode;
newNode.cost = minCost;
% 添加到树中
nodes = [nodes, newNode];
plot([newNode.parent.pos(1), newNode.pos(1)],...
[newNode.parent.pos(2), newNode.pos(2)], 'g');
% 反向重布线
for i = 1:length(nearIndices)
nearNode = nodes(nearIndices(i));
if ~checkCollision(newNode.pos, nearNode.pos, obstacles)
newCost = newNode.cost + norm(nearNode.pos - newNode.pos);
if newCost < nearNode.cost
% 更新父节点和成本
plot([nearNode.parent.pos(1), nearNode.pos(1)],...
[nearNode.parent.pos(2), nearNode.pos(2)], 'w');
nearNode.parent = newNode;
nearNode.cost = newCost;
plot([newNode.pos(1), nearNode.pos(1)],...
[newNode.pos(2), nearNode.pos(2)], 'g');
% 需要实现updateChildCosts
end
end
end
% 检查是否到达目标
if norm(newNode.pos - goal) < 0.5
path = reconstructPath(nodes, newNode);
plot(path(:,1), path(:,2), 'r', 'LineWidth', 2);
break;
end
end
end
end
4.2 路径回溯
找到目标后,我们需要从终点回溯到起点以提取完整路径:
matlab复制function path = reconstructPath(nodes, endNode)
path = endNode.pos;
parent = endNode.parent;
while ~isequal(parent, 0)
path = [parent.pos; path];
parent = parent.parent;
end
end
4.3 可视化效果
运行算法后,你会看到:
- 灰色区域代表障碍物
- 绿色线条表示树的生长
- 红色线条表示找到的最终路径
- 白色线条表示被优化的旧路径(在RRT*中)
随着迭代进行,路径会逐渐优化,最终收敛到一条相对平滑的路径。
5. 实战经验与调优技巧
5.1 参数选择与调优
-
步长(Step Size):
- 太大:容易穿过狭窄通道,碰撞检测不准确
- 太小:收敛速度慢,树生长缓慢
- 经验值:环境尺度的5-10%
-
目标偏向概率:
- 通常5-10%效果较好
- 太高:可能陷入局部极小值
- 太低:收敛慢
-
近邻搜索半径:
- RRT*性能对此敏感
- 理论最优值:γ*(log(n)/n)^(1/d),其中n是节点数,d是维度
- 实践中可以从环境尺度的10-20%开始调整
5.2 常见问题排查
-
算法无法找到路径:
- 检查碰撞检测是否正确
- 增加最大迭代次数
- 调整步长和目标偏向概率
-
路径质量差:
- 使用RRT*而非基础RRT
- 增加迭代次数
- 考虑使用Informed RRT*,它在找到初始路径后会聚焦于优化
-
运行速度慢:
- 优化最近邻搜索(使用KD-tree)
- 简化碰撞检测(使用层次包围盒)
- 考虑降低采样维度或使用并行化
5.3 高级改进方向
-
动态障碍物处理:
- 定期检查路径有效性
- 局部重规划受影响的部分
-
运动约束考虑:
- 在扩展步骤中加入运动学约束
- 使用状态格(state lattice)代替直线连接
-
多目标规划:
- 考虑路径长度、平滑度、安全性等多目标
- 使用多目标优化版本的RRT*
-
机器学习结合:
- 用学习到的分布指导采样
- 预测动态障碍物运动
在实际机器人应用中,RRT/RRT*通常与其他技术结合使用。例如先进行全局规划,再用局部规划器处理动态障碍物。MATLAB的Robotics System Toolbox提供了更完善的实现,但理解这些底层原理对于调试和优化至关重要。
