简介:面向移动机器人路径规划研究,这套MATLAB代码将改进的RRT全局路径规划与DWA局部动态窗口法结合,实现全局轨迹引导下的实时避障。压缩包共14个m文件,大小仅14KB,主要包含主程序、动态窗口计算、轨迹生成、碰撞检测与评价函数等模块,代码结构清晰,便于按需调用。目前已有370人学习下载。代码基于DWA三维框架设计,可加载三维RRT算法生成的全局参考轨迹,当机器人感知到全局路径存在障碍物时,自动计算离开原路径的切出点与返回原路径的切入点,完成局部重规划并绕过障碍物后继续沿全局路径运动。全部m文件均附详细注释,涵盖参数初始化、障碍物构建、安全距离判定与速度采样等关键环节,适合学习路径规划算法原理、复现混合规划流程并进行二次开发。
1. RRT+DWA这套组合拳:先想清楚全局和局部谁说了算
做移动机器人的路径规划,最稳的组合不是把某个算法调到万能。用改进后的RRT算法先算出一条可走的全局路径,再用DWA动态窗口法在局部做重规划、实时绕开传感器新发现的障碍,是室内轮式机器人上最常见的分层方案。全局层管大方向,局部层管刹车和转向,两者按视野与频率拆开,各自都保持简单可靠。这篇笔记适合已经接触过基础RRT但嫌路径绕、或者单独写过DWA但发现它容易陷进死胡同的从业者。下面是带详细中文注释的RRT改进实现、DWA采样与评价实现,以及联调时最常出现的几个排查场景。
2. 改进RRT全局路径:目标偏置采样与路径剪枝的落地写法
2.1 基础RRT的痛点与三处改进
基础RRT的逻辑是从起点开始随机长树,直到某个扩展节点够到终点附近。它实现简单、概率完备,但生成路径很粗糙:节点多、折线密、路径贴障碍边缘走。这对上层DWA是有压力的——DWA视野有限,拿到这样的全局路径,容易在某个折点突然变向,造成轨迹震荡甚至原地打转。所以工程里几乎不会直接拿基础RRT的原始路径给DWA用,中间必须加改进和剪枝。
改进集中在三个地方:目标偏置采样,让树尽量朝终点生长而不是满图乱钻;固定步长扩展,每一步长度受限,碰撞检测在扩展的同时完成;路径剪枝,把能直连的冗余节点删掉。这三处改动代码量都很小,但对路径形态的影响是质变。先看采样函数,改进点在“以一定概率直接采目标点”:
import random import math def sample_with_bias(goal, width, height, bias=0.15): """ 目标偏置采样:每次撒点前掷一次骰子。 - 命中bias区间:直接用目标点作为采样点 - 否则:整张地图范围内均匀随机采样 作用:让RRT树更快朝目标伸展,减少空旷区域的无效生长, 路径整体更直,迭代次数通常能下降一半以上。 """ if random.random() < bias: return (goal[0], goal[1]) return (random.uniform(0, width), random.uniform(0, height))bias取0.15,含义是平均每7次采样约一次直接指向目标点。bias太高时树会近似贪心地向目标冲,一旦中途碰壁容易僵住;太低则改进效果消失。一般从0.1起步,地图障碍率低时可以往0.2调。这个值不影响正确性,只影响收敛速度和路径质量,所以是全局层第一个值得打印出来对比的参数。
有了采样点,下一步从现有树里找最近节点,并向采样点方向扩展一个固定步长:
def steer(nearest, sample_point, step_size): """ 从nearest出发,朝sample_point方向扩展step_size长度。 如果sample_point就在步长范围内,直接返回sample_point。 返回的是一棵树上新节点的坐标。 """ dx = sample_point[0] - nearest[0] dy = sample_point[1] - nearest[1] dist = math.hypot(dx, dy) if dist < step_size: return sample_point ratio = step_size / dist return (nearest[0] + dx * ratio, nearest[1] + dy * ratio)这段代码没有难点,但步长取值直接影响路径锯齿程度。步长太小,树长得慢,迭代次数必须上调;步长太大,新节点容易跨过障碍边缘,碰撞检测会频繁拒绝新点,在窄通道里连续失败。我习惯先按地图尺寸的1%~2%给初值,再根据碰撞失败率微调。如果新版节点被拒绝的比例超过三成,优先减小步长而不是加大迭代次数。
2.2 碰撞检测与膨胀半径:几何判断和性能取舍
碰撞检测是RRT里调用最频繁的函数,它的快慢直接决定规划耗时。常见做法是把两点之间的线段离散成等分点,逐个检查是否落入障碍膨胀区。下面这个版本按点障碍列表实现,适合圆形或近似圆形的障碍描述:
def is_collision_free(p1, p2, obstacle_list, inflation=5): """ 线段碰撞检测:把p1到p2的线段按约2像素间隔切分, 检查每个中间点是否进入障碍物膨胀后的范围。 - obstacle_list: 每项为(x, y, radius) - inflation: 障碍半径额外扩大的安全距离 返回True代表两点之间可通行。 """ dist = math.hypot(p2[0] - p1[0], p2[1] - p1[1]) steps = max(1, int(dist / 2)) for i in range(steps + 1): t = i / steps px = p1[0] + (p2[0] - p1[0]) * t py = p1[1] + (p2[1] - p1[1]) * t for (ox, oy, r) in obstacle_list: if math.hypot(px - ox, py - oy) < r + inflation: return False return True离散间隔取2像素。间隔太粗会把细缝误判成可通行;太细则每条线段要做几百次距离计算,RRT一次规划要检测上千条树枝,耗时明显放大。如果地图是栅格格式,更高效的做法是提前把障碍栅格做一层膨胀,再在检查时逐个查栅格值,省去距离计算。用栅格地图时,is_collision_free的离散点直接索引地图数组,比遍历障碍列表快一个数量级。
膨胀半径单独拎出来说,是因为它经常被忽略。RRT的膨胀半径不应等于车体半径,而应等于“车体半径 + DWA安全余量 + 定位误差”。这层余量不加上,全局路径就会贴着障碍边缘走,DWA在高速度下根本来不及躲。我见过不少案例,全局规划和局部参数分别调得都不错,联调时反复碰撞,最后发现就是全局膨胀半径少给了半个车身宽度。
2.3 主循环与路径剪枝:把绕路节点删掉
把上面的函数串成完整主循环,逻辑并不复杂。下面是带注释的RRT主函数骨架,包含了采样、扩展、碰撞检测、终止判断四个环节:
def build_rrt_path(start, goal, obstacle_list, width, height, config): """ 改进RRT主流程: 采样 -> 找最近节点 -> 扩展 -> 碰撞检测 -> 判断到终点 -> 剪枝 config里至少包含:bias, step_size, inflation, max_iter, goal_tolerance """ node_list = [start] parent = {start: None} for i in range(config.max_iter): if i % 500 == 0 and i > 0: # 调试时打印进度,确认树在正常生长 print("iter", i, "nodes", len(node_list)) rand = sample_with_bias(goal, width, height, config.bias) nearest = nearest_node(node_list, rand) new = steer(nearest, rand, config.step_size) if is_collision_free(nearest, new, obstacle_list, config.inflation): node_list.append(new) parent[new] = nearest if math.hypot(new[0] - goal[0], new[1] - goal[1]) < config.goal_tolerance: raw_path = extract_path(parent, new) return prune_path(raw_path, obstacle_list, config) return Nonenearest_node的计算就是遍历node_list找欧氏距离最近的点,数据量到几千节点时性能尚可;节点过万后建议用KD树索引,否则每轮采样都要全表扫描。extract_path沿着parent字典从终点回溯到起点,得到原始路径。原始路径通常节点稀疏且存在绕路,所以最后一步交给prune_path。
路径剪枝的本质是贪婪拉直:从起点开始,尽量跳到更远的节点上,只要两点之间没有障碍就跳过中间的节点。这样可以把几十个节点的路径压缩到个位数。实现在第2.1节里给了一个版本,这里再补充一个更贴合实际调用的改进版,它接受容差参数,避免剪枝后折点太尖锐:
def prune_path(path, obstacle_list, config): """ 剪枝:从起点开始,把能直连的远节点直接相连。 最终保留的节点数通常只有原来的1/5到1/10。 """ if len(path) <= 2: return path pruned = [path[0]] i = 0 while i < len(path) - 1: for j in range(len(path) - 1, i, -1): if is_collision_free(path[i], path[j], obstacle_list, config.inflation): pruned.append(path[j]) i = j break else: i += 1 if pruned[-1] != path[-1]: pruned.append(path[-1]) return pruned这段代码的关键是内层循环从最远节点往前扫,保证每次跳跃都尽量大。如果中间全被障碍挡住,它会退化为原来的节点序列,不会破坏路径。剪枝后路径是折线骨架,真正执行要靠DWA把折线跟踪成平滑轨迹。我一般还会在剪枝后做一步角度松弛:对相邻线段夹角过小的节点,尝试向两边偏移一点再做碰撞检测,如果偏移后路径更顺滑就替换。这一条不是必须的,但在窄通道场景里对DWA的过冲问题有明显改善。
RRT改进这块的参数较多,我整理了一张速查表,按室内小车、地图比例尺1像素≈5厘米来给初值:
| 参数 | 初值 | 作用 | 调参方向 |
|---|---|---|---|
| 目标偏置bias | 0.15 | 采样倾向目标的概率 | 障碍多下调,空旷上调 |
| 步长step_size | 10 | 每次扩展长度 | 窄通道变小,大地图变大 |
| 膨胀半径inflation | 5 | 障碍安全距离 | 小则缝变窄,大则保守 |
| 最大迭代max_iter | 5000 | 规划超时上限 | 复杂地图调大 |
| 终点容差goal_tolerance | 5 | 判定到达的距离 | 按定位精度定 |
两个常见误区。第一个是把步长当成“速度”来调,想省迭代就调大,结果碰撞检测失败率飙高,反而更慢。第二个是全局膨胀半径只按车体半径给,忘了给DWA留活动余量,导致局部层一加速就压线。这两点在第4章的联调避坑里还会反复出现。
3. DWA局部避障:动态窗口里的速度采样与代价评价
3.1 动态窗口到底是什么窗口
DWA的核心假设是:路径规划不需要遍历整个速度空间,只需要考虑当前速度在当前加速度限制下、一个控制周期内能到达的速度集合;再叠加“能在前方障碍前刹停”的约束后,剩下的速度组合就是动态窗口。这个窗口每帧都在变,所以叫动态窗口。
口径先立住:如果机器人当前速度是0.5 m/s,线加速度是0.4 m/s²,控制周期0.1 s,那么下一个周期速度最多到0.54 m/s。window的名字就是指这个“当前可达速度集合”,而不是全速度区间。下面参数表是一类室内轮式机器人的典型初值,后面代码里的每个数字都从这儿来:
| 参数 | 含义 | 典型值 |
|---|---|---|
| 最大线速度v_max | 小车能跑多快 | 1.0 m/s |
| 最大角速度w_max | 小车能转多快 | 20 deg/s |
| 线加速度a_v | 油门响应 | 0.4 m/s² |
| 角加速度a_w | 转向响应 | 45 deg/s² |
| 控制周期dt | 每次决策间隔 | 0.1 s |
| 前向模拟时间sim_time | 预测多远的轨迹 | 1.5 s |
结合当前速度计算动态窗口的代码如下。我加了可读性注释,实际工程里还会再并上“能刹停”约束:
def compute_dynamic_window(v, w, config): """ 计算当前速度(v,w)下,一个控制周期内可达的速度窗口。 返回(v_min, v_max, w_min, w_max) """ v_min = max(config.v_min_, v - config.a_v * config.dt) v_max = min(config.v_max, v + config.a_v * config.dt) w_min = max(config.w_min, w - config.a_w * config.dt) w_max = min(config.w_max, w + config.a_w * config.dt) # 能刹停约束:v^2 / (2*a) 必须不大于可观测的最短障碍距离。 # 这里obstacle_dist是传感器探测到前方最近障碍的距离, # v_max超出安全值就往下压。 max_safe_v = math.sqrt(2 * config.a_v * config.obstacle_dist) v_max = min(v_max, max_safe_v) return (v_min, v_max, w_min, w_max)能刹停约束在实际工程里怎么取obstacle_dist,是个容易被问到的点。常见做法是把激光雷达或深度相机点云投影到车体坐标系,取前方扇形区域内的最近点距离;也可以对局部代价地图做一条射线求交,得到机器人到障碍的最近距离。这个约束是DWA区别于普通速度采样方法的关键,不建议省略。省略后机器人在高速状态会直直冲向突然出现的障碍,因为它没有把“停不下来”的情况排除在外。
3.2 轨迹预测:一组速度对应一条弧线
动态窗口内每一组(v, w)都对应一条前向模拟的轨迹。模拟时长由sim_time决定,太长会把远处的预测精度浪费掉,太短则看不到前方急弯。下面这段是按差速小车运动学模型做的轨迹预测,注释里写了每个变量:
def predict_trajectory(pose, v, w, dt, steps): """ 前向模拟一条轨迹,返回[(x, y, theta), ...]序列。 pose是当前位姿(x, y, theta),steps = sim_time / dt。 """ x, y, theta = pose traj = [] for _ in range(steps): x += v * math.cos(theta) * dt y += v * math.sin(theta) * dt theta += w * dt traj.append((x, y, theta)) return traj这是自行车模型的最简版本,差速小车用足够。阿克曼底盘在高速转弯时轮胎侧偏明显,theta更新需要换成带滑移角的模型,否则轨迹预测会偏乐观,DWA以为能转过去,实际转不过去。另外注意traj里存的是位姿,不是单纯的点序列;后面算朝向代价时要用到终点的theta。
3.3 评价函数:三个代价的权重怎么分配
评价函数是DWA的决策核心。常用三项:朝向代价heading,让轨迹终点的朝向尽量对准目标方向;障碍代价obstacle,让轨迹尽量远离障碍;速度代价velocity,鼓励机器人别太慢。下面实现里把三项都做了归一化和安全判定:
def evaluate_trajectory(traj, v, w, goal, local_obstacles, config): """ 对一条轨迹打分,返回(total, heading_cost, obstacle_cost, velocity_cost) 轨迹上任何一点进入safety_radius,直接返回无穷大代价。 """ end_x, end_y, end_theta = traj[-1] target_angle = math.atan2(goal[1] - end_y, goal[0] - end_x) angle_diff = abs(normalize_angle(target_angle - end_theta)) # 用指数把朝向误差压到[0,1),角度差大时代价趋近1 heading_cost = 1.0 - math.exp(-angle_diff * config.weight_heading) # 障碍代价取轨迹与最近障碍距离的倒数,越近越危险 min_dist = float("inf") for (ox, oy) in local_obstacles: for (px, py, _) in traj: d = math.hypot(px - ox, py - oy) if d < min_dist: min_dist = d if min_dist < config.safety_radius: return float("inf"), heading_cost, 0.0, 0.0 obstacle_cost = 1.0 / min_dist # 速度代价用1 - v/v_max,鼓励差不多接近最大速度 velocity_cost = 1.0 - abs(v) / config.v_max total = (config.alpha * heading_cost + config.beta * obstacle_cost + config.gamma * velocity_cost) return total, heading_cost, obstacle_cost, velocity_cost注意normalize_angle需要把角度差折叠到[-pi, pi],否则朝向代价在跨越±pi时会突变,机器人会在目标正后方时出现诡异的掉头行为。这项细节是调参血泪经验里排在第一位的问题。障碍代价用倒数而不是线性值,是为了让“接近障碍”的惩罚随距离急剧上升,给安全半径一个明显的代价悬崖。
三个权重alpha、beta、gamma的分工要说明白:alpha管“朝目标去的欲望”,beta管“躲障碍的保守程度”,gamma管“往前走的速度”。调参时最忌讳三个一起动,我一般固定gamma=0.3,先调beta到不碰障碍,再调alpha到转弯干脆,最后回头看gamma决定整体速度偏好。再看几组经验组合:
| 场景 | alpha | beta | gamma | 效果 |
|---|---|---|---|---|
| 障碍稀疏 | 0.5 | 0.2 | 0.3 | 走得快,偶尔绕远 |
| 障碍密集 | 0.3 | 0.6 | 0.1 | 保守,速度起不来 |
| 走廊直行 | 0.4 | 0.3 | 0.3 | 直道快,拐弯慢 |
把每轮选中的轨迹的heading/obstacle/velocity逐项打印出来,是调参最有效的习惯。看到具体哪一项代价主导了决策,比盲调权重快很多。很多开发者只知道“调参很玄学”,其实玄学的根源在于代价没有归一化,三个量纲不同的数字硬加在一起,权重自然无从调起。
DWA主循环把上面的函数串起来,就是每帧决策的核心代码:
def dwa_plan(pose, v, w, goal, local_obstacles, config): """ DWA主循环:遍历动态窗口内的(v,w)组合, 模拟轨迹并打分,选总代价最小的一组作为控制输出。 """ v_range, w_range = compute_dynamic_window(v, w, config) v_min, v_max = v_range w_min, w_max = w_range best_cost = float("inf") best_v, best_w = 0.0, 0.0 # 把速度空间离散成网格。20x16起步,性能允许再加密。 v_samples = np.linspace(v_min, v_max, config.v_samples) w_samples = np.linspace(w_min, w_max, config.w_samples) for candidate_v in v_samples: for candidate_w in w_samples: traj = predict_trajectory(pose, candidate_v, candidate_w, config.dt, int(config.sim_time / config.dt)) cost = evaluate_trajectory(traj, candidate_v, candidate_w, goal, local_obstacles, config) if cost[0] < best_cost: best_cost = cost[0] best_v, best_w = candidate_v, candidate_w return best_v, best_w采样密度是另一处容易出问题的点。线速度分成20份、角速度16份,起步是够的。如果机器人表现滑顺,说明密度够;如果行为忽左忽右,先加大采样密度而不是怀疑权重。采样太粗会让真正合适的(v,w)组合根本不在网格里,DWA在相邻网格之间跳来跳去,表现出来就是轨迹抖动。
4. RRT与DWA联调避坑:4类翻车现场与排查手记
4.1 现象一:全局路径刚生成,DWA却完全不认账
现象:RRT规划出一条看起来不错的全局路径,但机器人一启动就朝着终点直冲,完全无视路径,甚至直接穿过全局路径明确绕开的区域。
原因:DWA只被喂了最终终点坐标,没有被喂全局路径上的“局部目标点”。DWA的视野只有几米,对它而言全局路径是个黑匣子,它只认当前的目标点。正确做法是沿全局路径取一个前瞻点作为DWA的目标点;如果取的就是终点,DWA会从当前位置直接拉直线过去,全局路径自然被绕开了。另一个常见原因是坐标系不统一——全局路径的坐标在MAP系,DWA在ODOM系,没有做位姿变换。
解决:加一个局部目标点选择函数,沿全局路径按路程取前瞻点,而不是取欧氏距离最近的点:
def pick_local_goal(global_path, robot_pose, lookahead=1.2): """ 沿全局路径找距机器人路程为lookahead的点, 作为DWA的局部目标点。 lookahead太小会蛇形,太大则会在岔路口把DWA带偏。 """ dist = 0.0 for i in range(len(global_path) - 1): seg = math.hypot(global_path[i+1][0] - global_path[i][0], global_path[i+1][1] - global_path[i][1]) if dist + seg >= lookahead: t = (lookahead - dist) / seg x = global_path[i][0] + t * (global_path[i+1][0] - global_path[i][0]) y = global_path[i][1] + t * (global_path[i+1][1] - global_path[i][1]) return (x, y) dist += seg return global_path[-1]lookahead初值取1.2米,车越大、速度越快,取值越大。验证方法看航向误差曲线:航向误差平滑贴近零说明跟踪正常;周期性摆动说明lookahead太小。如果没有航向误差曲线,可以打印局部目标点的ID,观察它是否随机器人移动而平滑切换。
4.2 现象二:窄通道口反复横跳,就是过不去
现象:全局路径明明规划穿过了窄门,机器人到了门口却左右摆动,迟迟不进,严重时直接掉头。这时全局路径看起来没问题,局部层却在门口“晕头转向”。
原因:DWA的sim_time太短,轨迹预测还没覆盖到门后的区域,机器人到门口时“看不清”门后的路;又或者动态窗口内能穿门的速度组合太少,采样网格太粗,恰好漏掉了那组能通过的速度。还有一层原因:全局路径在门口有两个极近的折点,局部目标点在两个折点之间来回切换,导致DWA目标不断抖动。
解决:先把sim_time拉长到覆盖门后1~2米;同时检查局部目标点切换的逻辑,确保它按路程平滑变化而不是按欧氏距离跳动。如果这两个都做了还不行,在门口减速并观察:把lookahead从1.2逐步降到0.5,如果能进门,说明是前瞻距离问题;如果所有取值都进不去,说明门本身在全局层就被膨胀半径堵死了,这时候改DWA没意义,要回全局层调膨胀半径。
4.3 现象三:局部目标抖动,触发RRT重规划风暴
现象:机器人每走一小段,全局路径就重新规划一次,路径形态不断变化,轨迹歪歪扭扭,像喝了酒一样。
原因:重规划触发条件太敏感。常见实现里只要局部目标点更新超过某个阈值就重新调用RRT;而局部目标点在障碍边缘轻微抖动时,RRT会返回一条形态差异很大的新路径,形成“重规划风暴”。全局路径每次都换,DWA的跟踪目标一直在变,机器人自然走不稳。
解决:给重规划加死区。只有当新局部目标点距离当前全局路径的投影距离超过阈值(比如0.3米)时才触发重规划;或者固定按时间周期检查,每3秒一次而不是每帧触发。更稳妥的做法是:重规划完成后,对新旧路径做对比——新路径终点一致、总长不超过旧路径的1.2倍,才切换使用;任何一条不满足就沿用旧路径直到终点。这套逻辑不复杂,但对稳定性的提升很直观。
4.4 现象四:权重怎么调都崩溃,问题在归一化
现象:alpha/beta/gamma怎么改,机器人表现都感觉是随机变化。调高beta它还是撞障碍,调高gamma它又原地转圈,看起来没有任何规律。
原因:三个代价没有归一化,量纲不同。heading是弧度,obstacle是1/米,velocity是速度差,三者直接加权相加等于在调量纲换算系数。换一张地图尺度的变化、速度范围的变化,原来的权重就失效。另一个隐藏原因是采样密度太粗,表现也是“调参无效”——真正合适的速度组合根本不在网格里,权重怎么动都选不到它。
解决:先把三项代价全部归一到[0,1]再调权重。heading除以pi,velocity除以v_max,obstacle用一个距离截断后做倒数归一化。完成归一化后,alpha/beta/gamma的行为才可预测。再检查采样密度,线速度20份、角速度16份起步,调完一轮后可以降采样看性能损失。这两步做完,调权重的“玄学感”会消失大半。
5. 衔接与验证:从仿真曲线到实车的实测技巧
5.1 全局路径跟踪的平滑处理
RRT剪枝后的路径是折线,直接喂DWA会让机器人在拐角处走出锯齿状轨迹。我习惯把剪枝后的路径做等距插值,再对相邻插值点做滑动平均,把折线软化。软化后的点仍然沿原路径分布,DWA跟踪的是平滑后的道路中心线,转弯时不会因为折点突变而猛打方向。插值间距取0.2米左右,太密浪费计算,太疏拐角形变明显。叠加速度限幅:根据路径曲率动态限制v_max,曲率大时降速,避免转弯过冲。这个逻辑放在DWA外部,相当于给局部层加了一个“路况提示”。
5.2 三组验证场景与曲线判读
验证这套RRT+DWA是否落地,我建议在仿真里固定跑三组场景,每次记录横向误差和控制频率。横向误差是机器人当前位置到全局路径的垂距,直道上应小于一个车身宽,弯道处放宽到1.5倍。
| 场景 | 设置 | 通过标准 |
|---|---|---|
| 空旷直道 | 障碍物极少 | 全局路径近似直线,DWA保持接近v_max |
| 单障碍绕行 | 中间一块圆形障碍 | 机器人绕行,横向误差不超过0.3米 |
| 窄门穿行 | 门宽略大于车宽 | 减速通过,无横跳,一次性过门 |
看曲线不能只看结果,要看“过程形态”。横向误差曲线如果平滑,说明前瞻点切换稳定;如果出现等间隔的锯齿,多半是局部目标点按欧氏距离取值导致的前瞻跳动。控制频率稳定在预期值附近,说明DWA每帧计算量可控;如果掉帧明显,优先检查评价函数里对local_obstacles的双层循环,这是最常见的性能瓶颈。
5.3 从仿真到实车:先加噪声再上车
仿真里表现完美,实车一开就抖,最常见的两个原因:传感器噪声让DWA看到“幽灵障碍”,以及坐标系漂移让全局路径和局部代价地图错位。我吃过一次大亏,当时在仿真里一切正常,上车后DWA频繁急刹车,排查了很久发现是点云里个别离群点被当成障碍物,obstacle代价剧烈波动。后来我在仿真里给传感器加了高斯噪声和丢点率,把探测距离截短到3米来模拟实车传感器极限,问题在仿真阶段就暴露出来了。
另一个习惯是全局规划放后台线程跑,用路径版本号管理。DWA永远取“最近一次成功规划”的结果,而不是在规划完成前阻塞等待。全局规划耗时不稳定,但局部控制不能断,用版本号切换让双层解耦,体感会流畅很多。我调这套RRT+DWA时踩过最深的坑,就是全局规划放在主循环里同步跑,RRT一卡顿整台车就停,后来改后台线程一步到位。希望帮到你。
本文还有配套的精品资源,点击获取