尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

RRT路径规划算法详解:从原理到MATLAB/Python实战实现

RRT路径规划算法详解:从原理到MATLAB/Python实战实现 1. 项目概述从“撞墙”到“找路”的思维转变在机器人、自动驾驶或者游戏AI的开发中路径规划是一个绕不开的核心问题。想象一下你让一个扫地机器人在一个堆满杂物的房间里工作或者让一个游戏角色在复杂的地形中自动寻路到目标点。最朴素的想法可能是让机器人像人一样从起点开始朝着目标点“看”过去然后沿着这条直线走过去。这听起来很合理对吧但在现实中这条直线上往往布满了障碍物——家具、墙壁、甚至是其他移动的物体。机器人如果一根筋地朝着目标冲结果只能是不断地“撞墙”。这就是传统基于梯度下降或者简单启发式搜索如A*算法在连续空间中的某些变体在复杂、高维的连续空间比如机器人的关节空间中会遇到的核心困境它们很容易陷入局部最优或者因为环境复杂而完全找不到解。我们需要一种更“聪明”的探索策略它不能只盯着目标而应该先摸清整个环境的地形。快速扩展随机树Rapidly-exploring Random Tree, RRT算法就是为解决这类问题而生的。它本质上是一种通过构建一棵空间填充树来搜索路径的算法。它的核心思想非常直观与其盲目地向目标冲刺不如先以一种高效的方式“探索”整个可通行区域直到偶然间触达目标。这就像你在一个巨大的、漆黑的迷宫里手上只有一个有限长度的棍子。你的策略不是朝着你认为的出口方向猛冲而是站在原地随机朝一个方向伸出棍子摸到那个点然后走过去再从这个新点继续随机探索。通过大量这样的随机探索你最终能以很高的概率摸遍迷宫的各个角落自然也包括出口。RRT算法自1998年由Steven M. LaValle提出以来因其简单、高效且易于在高维空间实现的特性迅速成为机器人路径规划领域的基石算法之一。它不依赖于对环境进行精确的网格划分离散化非常适合处理连续状态空间中的运动规划问题。本次实战我们将深入RRT的原理并用最清晰的思路实现它最后提供可直接运行的MATLAB和Python代码。你会发现这个听起来高大上的算法其核心代码可能比你想象的要简洁得多。2. RRT算法核心原理拆解一棵树如何填满空间要理解RRT我们需要暂时忘掉“寻找最短路径”这个最终目标而专注于“如何高效探索未知空间”这个过程。RRT的生长过程可以概括为四个步骤的循环我们可以通过一个在二维平面中为机器人寻找绕过障碍物路径的例子来具体说明。2.1 算法四步循环生长一棵探索之树假设我们的机器人是一个点它在一个充满矩形障碍物的二维平面内需要从起点q_start移动到终点q_goal。第一步随机采样算法在整个规划空间比如一个100x100的矩形区域内随机生成一个点q_rand。这个点的坐标(x_rand, y_rand)是在定义域内均匀随机选取的。这是RRT“探索”特性的来源它让算法有可能朝着任何方向生长。第二步寻找最近邻在当前已经生长出来的树初始时只有根节点q_start上计算所有节点到q_rand的欧氏距离找到距离最近的那个节点记为q_near。这决定了树将沿着哪个已有的分支方向进行延伸。第三步向随机点生长从q_near朝着q_rand的方向生长一小步。这一步的长度是固定的称为步长step_size。具体计算方式是取从q_near指向q_rand的单位向量然后让q_near沿着这个方向移动step_size的距离得到一个新点q_new。 用公式表示就是q_new q_near step_size * (q_rand - q_near) / ||q_rand - q_near||这里||.||表示向量的模长度。如果q_rand和q_near的距离本身就小于step_size那么q_new就直接取q_rand。这一步可以理解为树试图向随机点靠近一点。第四步碰撞检测与节点添加这是关键的安全检查。我们需要判断从q_near到q_new的这条线段是否穿过了任何障碍物。如果这条线段与所有障碍物都不相交即路径是“无碰撞”的那么这次生长就是有效的。我们将q_new作为一个新节点加入到树中并在q_near和q_new之间添加一条边表示这是一条可行的路径段。 如果检测到碰撞那么这次生长尝试就被丢弃算法回到第一步重新进行随机采样。这个四步循环采样-近邻-生长-检测会一直重复直到满足某个终止条件。最经典的终止条件是树中有一个节点进入了目标点q_goal的某个邻域内例如距离小于步长step_size。一旦满足我们就认为找到了一条从起点到目标区域的可行路径。2.2 为何“快速扩展”其效率优势分析RRT的核心优势“快速扩展”就体现在这个循环中。为什么这种随机方式能快速填充空间偏向于未探索区域由于q_rand是在整个空间均匀随机采样的那些尚未被树覆盖的广阔区域有更大的概率被采样到。而“寻找最近邻”的机制使得树总是从离未探索区域最近的现有节点向外生长。这产生了一种“拉力”将树不断地拉向空白区域。概率完备性随着迭代次数的增加树节点会以概率1覆盖整个连通的无障碍空间。只要存在一条可行路径只要迭代次数足够多RRT几乎一定能找到它。这是理论上的重要保证。适应高维空间算法的计算复杂度主要依赖于最近邻搜索和碰撞检测。对于高维空间如机械臂的7个关节角空间虽然最近邻搜索会变慢但算法框架本身不需要对空间进行离散化不像A*需要网格避免了“维度灾难”导致的存储空间爆炸。当然基础的RRT找到的路径通常不是最优的它只是可行的、连接起点和目标的、由一系列线段组成的折线路径。后续有很多改进算法如RRT*读作RRT-star就是为了在找到路径后进一步优化使其趋向于最短路径。2.3 关键参数与设计选择在实现RRT前有几个关键参数需要决定步长step_size这是最重要的参数之一。步长大树扩展得快但可能会“跨过”狭窄的通道导致找不到解步长小探索更精细能找到狭窄通道但生长速度慢路径节点多。通常需要根据环境尺度如房间大小和障碍物最小通道宽度来经验性设置。目标偏置采样纯粹随机采样虽然能保证探索性但效率可能不高。一个常见的技巧是以一个小概率如5%直接采样目标点q_goal作为q_rand。这样能给树一个明确的“目标导向”拉力加速找到解的过程。终止条件除了“到达目标邻域”还可以设定最大迭代次数防止在无解环境中无限循环。最近邻搜索在节点数很多时线性遍历所有节点找最近邻效率很低。在实际应用中通常会使用空间数据结构来加速如k-d树。但在我们的基础演示中为了代码清晰暂时使用线性搜索。3. MATLAB实战一步步构建RRT路径规划器下面我们将把上述原理转化为MATLAB代码。我们将构建一个在二维平面内避开多个矩形障碍物从起点规划到终点的RRT路径规划器。3.1 环境与问题定义首先我们定义规划场景。我们创建一个100x100的二维空间设置起点、终点并用几个矩形来代表障碍物。% 定义规划空间边界 xlim [0, 100]; ylim [0, 100]; % 定义起点和终点 q_start [10, 10]; q_goal [90, 90]; % 定义障碍物 (每个障碍物用 [x_min, y_min, x_max, y_max] 表示) obstacles [ 20, 20, 40, 60; % 障碍物1 60, 10, 80, 40; % 障碍物2 30, 70, 70, 85; % 障碍物3 5, 80, 25, 95 % 障碍物4 ];3.2 核心函数实现碰撞检测与RRT主循环碰撞检测是路径规划的基石必须准确且高效。我们实现一个函数用于判断一条线段是否与一个矩形障碍物相交。function collision checkCollision(q1, q2, obstacle) % 检查线段q1-q2是否与矩形障碍物相交 % 障碍物格式: [x_min, y_min, x_max, y_max] % 使用分离轴定理的一种简化实现检查线段与矩形四条边是否相交 collision false; % 将线段端点排序方便处理 x1 q1(1); y1 q1(2); x2 q2(1); y2 q2(2); ox_min obstacle(1); oy_min obstacle(2); ox_max obstacle(3); oy_max obstacle(4); % 快速排斥实验如果线段完全在矩形的一侧则不可能相交 if max(x1, x2) ox_min || min(x1, x2) ox_max || ... max(y1, y2) oy_min || min(y1, y2) oy_max return; end % 检查线段是否与矩形的四条边相交 % 定义矩形四条边的线段 rect_edges [ ox_min, oy_min, ox_max, oy_min; % 下边 ox_max, oy_min, ox_max, oy_max; % 右边 ox_max, oy_max, ox_min, oy_max; % 上边 ox_min, oy_max, ox_min, oy_min % 左边 ]; for i 1:4 edge rect_edges(i, :); if isLinesIntersect(x1, y1, x2, y2, edge(1), edge(2), edge(3), edge(4)) collision true; return; end end % 额外检查线段的一个端点是否在矩形内部这种情况也视为碰撞 if (x1 ox_min x1 ox_max y1 oy_min y1 oy_max) || ... (x2 ox_min x2 ox_max y2 oy_min y2 oy_max) collision true; return; end end function intersect isLinesIntersect(x1, y1, x2, y2, x3, y3, x4, y4) % 使用叉积方法判断两条线段是否相交 function d cross(ax, ay, bx, by) d ax * by - ay * bx; end d1 cross(x3-x1, y3-y1, x2-x1, y2-y1); d2 cross(x4-x1, y4-y1, x2-x1, y2-y1); d3 cross(x1-x3, y1-y3, x4-x3, y4-y3); d4 cross(x2-x3, y2-y3, x4-x3, y4-y3); % 严格相交不包括端点重合 if d1*d2 0 d3*d4 0 intersect true; else intersect false; end end注意这里的碰撞检测是一个简化版本。在更严格的场景中如机器人有体积你需要考虑机器人的形状通常用圆形或多边形包络与障碍物的碰撞这需要更复杂的几何计算。对于线段与矩形的精确碰撞检测也可以使用更高效的算法如Cohen-Sutherland算法。上述代码保证了基本功能的正确性便于理解。接下来是RRT算法的主循环。我们将树存储为两个列表nodes存储所有节点坐标parents存储每个节点的父节点索引根节点的父节点为0。function [nodes, parents, goal_idx] rrt_planning(q_start, q_goal, obstacles, xlim, ylim, max_iter, step_size, goal_radius) % RRT主算法 % 输入 % q_start: 起点坐标 [x, y] % q_goal: 终点坐标 [x, y] % obstacles: 障碍物列表每行一个矩形 [x_min, y_min, x_max, y_max] % xlim, ylim: 空间边界 % max_iter: 最大迭代次数 % step_size: 生长步长 % goal_radius: 目标区域半径树节点进入此半径即认为到达目标 % 输出 % nodes: 所有节点坐标每行一个节点 [x, y] % parents: 每个节点的父节点索引 % goal_idx: 最终连接到目标的节点索引若未找到则为空 [] % 初始化树根节点为起点 nodes q_start; parents 0; % 根节点的父节点索引设为0 goal_idx []; % 设置目标偏置概率例如5%的概率直接采样目标点 goal_bias 0.05; for iter 1:max_iter % --- 第一步随机采样 (带目标偏置) --- if rand() goal_bias q_rand q_goal; % 以一定概率直接采样目标点 else q_rand [xlim(1) (xlim(2)-xlim(1))*rand(), ... ylim(1) (ylim(2)-ylim(1))*rand()]; end % --- 第二步寻找最近邻 --- % 计算所有节点到q_rand的距离 dists sqrt(sum((nodes - q_rand).^2, 2)); [~, idx_near] min(dists); q_near nodes(idx_near, :); % --- 第三步向随机点生长 --- vec q_rand - q_near; dist_to_rand norm(vec); if dist_to_rand step_size q_new q_rand; else q_new q_near (vec / dist_to_rand) * step_size; end % --- 第四步碰撞检测 --- collision false; for i 1:size(obstacles, 1) if checkCollision(q_near, q_new, obstacles(i, :)) collision true; break; end end % 如果无碰撞添加新节点 if ~collision nodes [nodes; q_new]; parents [parents; idx_near]; % 检查是否到达目标区域 if norm(q_new - q_goal) goal_radius goal_idx size(nodes, 1); % 新节点的索引就是目标节点索引 fprintf(找到路径迭代次数%d\n, iter); return; % 找到路径提前退出 end end % 每1000次迭代打印一次进度 if mod(iter, 1000) 0 fprintf(已迭代 %d 次当前树节点数%d\n, iter, size(nodes, 1)); end end fprintf(达到最大迭代次数 %d未找到路径。\n, max_iter); end3.3 路径提取与可视化算法结束后如果goal_idx不为空我们就找到了一条路径。路径是隐藏在树结构中的我们需要从目标节点开始沿着父节点指针回溯到根节点从而提取出这条路径。function path extractPath(nodes, parents, goal_idx) % 从树中提取从起点到目标点的路径 path []; if isempty(goal_idx) return; end current_idx goal_idx; while current_idx ~ 0 % 根节点的父节点索引是0 path [nodes(current_idx, :); path]; % 在头部插入保证顺序是从起点到终点 current_idx parents(current_idx); end end最后我们编写一个主脚本将以上所有部分组合起来并绘制出最终的结果。% RRT路径规划主脚本 clear; close all; clc; % 1. 定义环境 xlim [0, 100]; ylim [0, 100]; q_start [10, 10]; q_goal [90, 90]; obstacles [ 20, 20, 40, 60; 60, 10, 80, 40; 30, 70, 70, 85; 5, 80, 25, 95 ]; % 2. 设置算法参数 max_iter 5000; % 最大迭代次数 step_size 5.0; % 生长步长 goal_radius 5.0; % 目标区域半径 % 3. 运行RRT规划 [nodes, parents, goal_idx] rrt_planning(q_start, q_goal, obstacles, xlim, ylim, max_iter, step_size, goal_radius); % 4. 提取路径 path extractPath(nodes, parents, goal_idx); % 5. 可视化结果 figure(Position, [100, 100, 800, 600]); hold on; grid on; axis equal; xlim(xlim); ylim(ylim); title(RRT路径规划结果); xlabel(X); ylabel(Y); % 绘制障碍物 for i 1:size(obstacles, 1) obs obstacles(i, :); rectangle(Position, [obs(1), obs(2), obs(3)-obs(1), obs(4)-obs(2)], ... FaceColor, [0.8, 0.2, 0.2], EdgeColor, k, LineWidth, 1.5); end % 绘制起点和终点 plot(q_start(1), q_start(2), go, MarkerSize, 12, MarkerFaceColor, g, LineWidth, 2); plot(q_goal(1), q_goal(2), ro, MarkerSize, 12, MarkerFaceColor, r, LineWidth, 2); % 绘制整棵RRT树 for i 2:size(nodes, 1) % 从第2个节点开始第1个是根节点 parent_idx parents(i); q1 nodes(parent_idx, :); q2 nodes(i, :); plot([q1(1), q2(1)], [q1(2), q2(2)], b-, LineWidth, 0.5, Color, [0.7, 0.7, 1]); end % 如果找到路径用粗红线绘制 if ~isempty(path) plot(path(:,1), path(:,2), r-, LineWidth, 3); fprintf(路径节点数%d\n, size(path, 1)); else fprintf(未找到可行路径。\n); end legend(障碍物, 起点, 终点, RRT树, 规划路径, Location, best); hold off;运行这个脚本你将看到一幅图蓝色细线是生长出的RRT探索树绿色点是起点红色点是终点红色粗线是从树中提取出的最终路径。树会密集地填充在自由空间白色区域并巧妙地绕过障碍物红色矩形连接到终点。4. Python代码实现面向对象的RRT规划器对于习惯使用Python的开发者我们也提供一个等价的、面向对象风格的实现。这有助于将算法模块化方便集成到更大的项目中如ROS机器人系统。我们将使用numpy进行数值计算matplotlib进行可视化。import numpy as np import matplotlib.pyplot as plt import random import math class RRTPlanner: def __init__(self, start, goal, obstacles, xlim, ylim, step_size5.0, goal_radius5.0, max_iter5000): 初始化RRT路径规划器。 :param start: 起点格式 [x, y] :param goal: 终点格式 [x, y] :param obstacles: 障碍物列表每个障碍物为 [x_min, y_min, x_max, y_max] :param xlim: 空间x轴边界 [x_min, x_max] :param ylim: 空间y轴边界 [y_min, y_max] :param step_size: 生长步长 :param goal_radius: 目标接受半径 :param max_iter: 最大迭代次数 self.start np.array(start) self.goal np.array(goal) self.obstacles obstacles self.xlim xlim self.ylim ylim self.step_size step_size self.goal_radius goal_radius self.max_iter max_iter # 初始化树 self.nodes [self.start] # 节点列表 self.parents [-1] # 父节点索引列表-1表示根节点 self.goal_idx None # 连接到目标的节点索引 # 算法参数 self.goal_bias 0.05 # 目标偏置概率 def _random_point(self): 在规划空间内随机采样一个点。 if random.random() self.goal_bias: return self.goal else: x random.uniform(self.xlim[0], self.xlim[1]) y random.uniform(self.ylim[0], self.ylim[1]) return np.array([x, y]) def _nearest_node(self, point): 在树中找到距离给定点最近的节点。 nodes_array np.array(self.nodes) dists np.linalg.norm(nodes_array - point, axis1) min_idx np.argmin(dists) return min_idx, self.nodes[min_idx] def _steer(self, from_point, to_point): 从from_point向to_point方向生长一步步长为step_size。 vec to_point - from_point dist np.linalg.norm(vec) if dist self.step_size: return to_point else: return from_point (vec / dist) * self.step_size def _is_collision_free(self, point1, point2): 检查线段point1-point2是否与任何障碍物相交。 for obs in self.obstacles: if self._check_line_rect_intersection(point1, point2, obs): return False return True def _check_line_rect_intersection(self, p1, p2, rect): 检查线段p1-p2与矩形rect是否相交。 x1, y1 p1 x2, y2 p2 rx_min, ry_min, rx_max, ry_max rect # 快速排斥线段完全在矩形一侧 if max(x1, x2) rx_min or min(x1, x2) rx_max or \ max(y1, y2) ry_min or min(y1, y2) ry_max: return False # 检查线段端点是否在矩形内 def point_in_rect(px, py): return rx_min px rx_max and ry_min py ry_max if point_in_rect(x1, y1) or point_in_rect(x2, y2): return True # 检查线段是否与矩形四条边相交 rect_edges [ ((rx_min, ry_min), (rx_max, ry_min)), # 下 ((rx_max, ry_min), (rx_max, ry_max)), # 右 ((rx_max, ry_max), (rx_min, ry_max)), # 上 ((rx_min, ry_max), (rx_min, ry_min)) # 左 ] for edge in rect_edges: if self._is_lines_intersect(p1, p2, edge[0], edge[1]): return True return False def _is_lines_intersect(self, a1, a2, b1, b2): 使用叉积判断两条线段a1a2和b1b2是否相交。 def cross(ax, ay, bx, by): return ax * by - ay * bx # 计算向量 v1 a2 - a1 v2 b2 - b1 # 计算叉积 d1 cross(b1[0]-a1[0], b1[1]-a1[1], a2[0]-a1[0], a2[1]-a1[1]) d2 cross(b2[0]-a1[0], b2[1]-a1[1], a2[0]-a1[0], a2[1]-a1[1]) d3 cross(a1[0]-b1[0], a1[1]-b1[1], b2[0]-b1[0], b2[1]-b1[1]) d4 cross(a2[0]-b1[0], a2[1]-b1[1], b2[0]-b1[0], b2[1]-b1[1]) # 严格相交 return d1 * d2 0 and d3 * d4 0 def plan(self): 执行RRT规划主循环。 for iter in range(self.max_iter): # 1. 随机采样 q_rand self._random_point() # 2. 寻找最近邻 idx_near, q_near self._nearest_node(q_rand) # 3. 向随机点生长 q_new self._steer(q_near, q_rand) # 4. 碰撞检测 if self._is_collision_free(q_near, q_new): # 添加新节点 self.nodes.append(q_new) self.parents.append(idx_near) # 检查是否到达目标 if np.linalg.norm(q_new - self.goal) self.goal_radius: self.goal_idx len(self.nodes) - 1 print(f找到路径迭代次数{iter1}) return True # 进度提示 if (iter 1) % 1000 0: print(f已迭代 {iter1} 次当前树节点数{len(self.nodes)}) print(f达到最大迭代次数 {self.max_iter}未找到路径。) return False def get_path(self): 如果规划成功返回从起点到终点的路径节点列表。 if self.goal_idx is None: return None path [] current_idx self.goal_idx while current_idx ! -1: path.append(self.nodes[current_idx]) current_idx self.parents[current_idx] path.reverse() # 反转使顺序从起点到终点 return np.array(path) def plot(self, pathNone): 可视化规划结果。 plt.figure(figsize(10, 8)) # 绘制障碍物 for obs in self.obstacles: rx, ry, rw, rh obs[0], obs[1], obs[2]-obs[0], obs[3]-obs[1] rect plt.Rectangle((rx, ry), rw, rh, colorsalmon, ecblack, lw1.5) plt.gca().add_patch(rect) # 绘制起点和终点 plt.plot(self.start[0], self.start[1], go, markersize12, labelStart, markeredgecolork, linewidth2) plt.plot(self.goal[0], self.goal[1], ro, markersize12, labelGoal, markeredgecolork, linewidth2) # 绘制RRT树 for i in range(1, len(self.nodes)): parent_idx self.parents[i] q1 self.nodes[parent_idx] q2 self.nodes[i] plt.plot([q1[0], q2[0]], [q1[1], q2[1]], b-, linewidth0.5, alpha0.6, colorlightblue) # 绘制规划路径 if path is not None: plt.plot(path[:, 0], path[:, 1], r-, linewidth3, labelPlanned Path) plt.xlim(self.xlim) plt.ylim(self.ylim) plt.grid(True) plt.axis(equal) plt.title(RRT Path Planning Result) plt.xlabel(X) plt.ylabel(Y) plt.legend() plt.tight_layout() plt.show() # 主程序 if __name__ __main__: # 定义环境 start [10, 10] goal [90, 90] obstacles [ [20, 20, 40, 60], [60, 10, 80, 40], [30, 70, 70, 85], [5, 80, 25, 95] ] xlim [0, 100] ylim [0, 100] # 创建规划器并执行规划 planner RRTPlanner(start, goal, obstacles, xlim, ylim, step_size5.0, max_iter5000) if planner.plan(): path planner.get_path() print(f路径节点数{len(path)}) planner.plot(path) else: planner.plot()这段Python代码将RRT算法封装成了一个类RRTPlanner结构清晰方法职责明确。运行后你会得到和MATLAB版本类似的可视化结果。面向对象的设计使得它更容易被扩展例如你可以继承这个类来实现RRT*、Informed RRT*等更高级的变种算法。5. 实战中的调参与避坑指南纸上得来终觉浅绝知此事要躬行。把代码跑起来只是第一步要让RRT在实际项目中稳定工作你需要理解并调整几个关键参数同时避开一些常见的“坑”。5.1 关键参数调优步长、偏置与迭代次数步长step_size这是影响算法性能和结果最直接的参数。设得太小树生长缓慢需要极多的迭代次数才能探索完空间路径会由大量密集的短线段组成显得非常“锯齿状”不光滑。计算成本高。设得太大树扩展很快但可能“跳过”狭窄的通道。想象一下你的步长比一个门缝还宽那么树永远无法穿过那扇门到达另一个房间。同时大步长下从q_near到q_new的线段更长更容易与障碍物相交导致碰撞检测失败率增高有效生长次数减少。调优建议步长应略小于环境中最窄通道的宽度。在实践中可以先设置为环境对角线长度的2%~5%然后根据结果调整。如果发现算法总是在某个区域“徘徊”无法前进可以尝试临时调小步长如果算法运行太慢可以适当调大步长但需观察是否错过可行通道。目标偏置概率goal_bias这是一个典型的效率与完备性权衡。偏置概率越高如0.2树会更有目的性地朝向目标生长在简单环境中能极大加快找到解的速度。但在复杂、狭窄的迷宫式环境中过高的目标偏置可能导致树过早地“执着”于冲向目标而忽略了探索那些通往目标的、需要绕行的关键区域反而可能找不到解或者需要更长时间。调优建议通常设置在0.05到0.1之间是一个不错的起点。你也可以实现动态偏置例如在迭代初期使用较低偏置进行广泛探索迭代后期提高偏置以加速收敛。最大迭代次数max_iter与目标半径goal_radiusmax_iter是你的安全网防止算法在无解环境中无限循环。这个值需要设得足够大以确保在存在解的情况下有充足的时间找到它。可以根据空间大小和步长来估算一个经验法则是(空间面积/步长^2) * 10的量级。goal_radius定义了“到达目标”的宽容度。设得太小树可能需要非常精确地碰到目标点这会增加不必要的迭代。设得太大虽然容易“到达”但提取出的路径终点离真实目标会有偏差。通常设置为step_size的1到2倍比较合理。5.2 常见问题与排查思路算法永远找不到路径即使存在首先检查碰撞检测这是最常见的问题。用一个极其简单的环境比如没有障碍物测试你的碰撞检测函数。确保线段与矩形相交的判断逻辑是正确的特别是端点位于矩形边上的边界情况。检查步长步长是否远大于可行通道的宽度尝试将步长减小到1或更小看算法是否能通过。检查采样空间随机点q_rand的采样范围是否正确覆盖了整个自由空间确保没有错误地限制了采样区域。可视化中间过程在每次成功添加节点后实时绘制出树的结构。观察树是在哪个区域停止生长的这能帮你定位问题是出在环境建模、碰撞检测还是采样策略上。找到的路径非常曲折、不光滑这是基础RRT的固有特点它只保证可行性不保证最优性。路径曲折是因为树是随机生长的。解决方案路径后处理在得到原始路径后可以执行一个“剪枝”或“平滑”操作。例如从起点开始尝试连接当前节点和后续更远的节点跳过中间节点如果这条长线段是无碰撞的就替换掉原来的一小段折线。重复这个过程可以拉直路径。使用改进算法直接实现RRT算法。RRT在添加新节点q_new后会在其周围一定半径内寻找“更优”的父节点使从起点到q_new的路径成本更低并重布线从而渐进地优化整棵树的路径成本最终得到一条平滑、接近最短的路径。算法在狭窄通道处效率极低狭窄通道的采样概率很低树很难“恰好”采样到通道内的点并成功连接。解决方案增加迭代次数最直接的方法给算法更多时间。使用双向RRT (RRT-Connect)同时从起点和目标点生长两棵树交替地让一棵树向另一棵树的方向生长。两棵树相向而行能更快地在通道中间“会师”显著提高在狭窄通道中的规划速度。采样偏向如果先验知道通道的大致位置可以在采样时以一定概率在通道附近区域进行采样引导树向该区域生长。5.3 从仿真到现实的考量我们目前实现的是最基础的、在二维点状机器人仿真环境中的RRT。要应用到真实机器人还需要考虑更多机器人形状与姿态真实机器人有体积和形状。碰撞检测需要从“点与线段”升级为“机器人的轮廓多边形或圆形与障碍物”的碰撞检测。这通常通过将机器人轮廓沿着路径进行“扫掠”或者计算机器人在每个位姿下的包络来实现计算量会大增。动力学约束我们的路径是由直线段组成的。但真实机器人如汽车、无人机有运动学如转弯半径和动力学加速度、速度约束。直接跟踪这种折线路径是不可能的。这就需要引入带有动力学约束的RRT变种或者在RRT规划后再用轨迹优化/样条曲线对路径进行平滑生成一条机器人可执行的轨迹。实时性与不确定性对于动态环境RRT需要能够快速重规划。一些变种如Anytime RRT、Dynamic RRT被提出。同时传感器噪声和定位误差也需要在规划中考虑这可能需要在碰撞检测中引入安全裕度。尽管有这些挑战RRT及其众多变种RRT*, Informed RRT*, RRT-Connect因其概念清晰、实现相对简单、适用于高维空间的特性仍然是解决复杂运动规划问题的首选工具之一。理解这个基础版本是你进入机器人路径规划广阔世界的一块坚实跳板。
返回列表