做三维动态避障这个方向挺长时间了,前后试过好几种方案,最后稳定下来的一套组合是粒子群算法PSO做全局路径规划、动态窗口法DWA做局部实时避障,整套代码在Matlab里跑通,工作量不大但效果很直观。这篇文章把我的实现思路、关键代码、参数调整经验都整理出来,给正在做无人机路径规划的同学一个可复现的参考。
先说清楚这套东西定位:不搞复杂的强化学习,不看昂贵的硬件在环,就用Matlab做三维环境的仿真验证。适合刚接触路径规划、想快速上手看实际飞行效果的本科生和研究生,也适合有基础但想在三维动态场景下试水算法融合的工程师。
1. 项目整体思路:为什么让PSO和DWA“分工合作”
很多同学上来就问:一个算法搞不定吗,为什么非要融合两个算法?这其实涉及一个很核心的问题——全局规划与局部反馈之间的矛盾。
1.1 两种算法的性格差异与互补性
粒子群算法PSO属于群体智能优化算法,它的强项是全局搜索。你在三维空间里给它一个起点、一个终点、一堆静态障碍物,PSO能把一条全局最优或者近优的路径“搜索”出来。它的本质是对一群候选路径进行迭代进化,通过个体经验和社会经验不断修正路径点位置,最终收敛到一条比较合理的路径。我实测下来,在三维地图规模不大的情况下,PSO搜索全局路径的能力比图搜索算法更灵活,不需要把空间离散化成栅格,路径可以比较光滑,而且代码实现难度不高。
但PSO有个硬伤:它本质上是离线优化算法,它对环境的变化响应急速很慢。如果你把PSO用在动态避障上,每来一个动态障碍物就重新跑一遍全局搜索,计算代价太高,飞机可能还没算完路径就撞上去了。
动态窗口法DWA则是典型的局部规划算法。它的思路非常“工程”:在当前的速度窗口内采样一组可行速度组合,对每个速度组合模拟一小段时间的轨迹,再用一个评估函数挑出最好的一条轨迹作为下一步动作。它天然就是应对动态避障的——因为每一帧都在重新采样、重新评估,只要传感器数据更新频率够快,动态障碍物也能被实时绕过。
但DWA的局部视野非常窄,它只看得到前面一小段距离,如果前方是一个U形障碍物,或者需要绕开一大片危险区域,DWA会在局部环境里来回打转,陷入局部极值出不去。
打个比方,PSO像一个看全局地图的“导航员”,提前规划好整个路线,告诉你从A点到B点走哪条路最划算。DWA像一个经验丰富的“路面司机”,导航员给方向,但司机看到前面突然出现障碍物时,能灵活转向绕过去,绕完以后继续回到导航员指定的线路上。这套分层架构把“运筹帷幄”和“随机应变”结合了起来,也是目前工程上很成熟的规划范式。
1.2 融合架构:全局规划加局部跟随的分层逻辑
整个系统的运行逻辑分三层:
第一层,离线/低频阶段。无人机起飞前或者环境发生重大变化时,运行PSO全局规划器。它根据当前的三维静态地图信息(建筑物、山体、固定的高架桥等),生成一条从起点到终点的全局路径,路径由一串三维路径点组成。
第二层,在线/高频阶段。无人机飞行过程中,本地局部规划器DWA接管。DWA并不直接以最终终点为导航目标,而是盯住全局路径上的某个“前视点”。这个前视距离我一般取无人机速度乘以1.5秒左右的可达距离。DWA每秒执行若干次,每次在当前速度窗口内采样可行速度组合,生成模拟轨迹,然后通过避障评估函数选择最优速度。
第三层,异常处理阶段。如果DWA发现前视点所在的区域被动态障碍物霸占了,导致无法朝前视点前进,这时候无人机一个“临时变道”策略:DWA先以避障为最高优先级,绕过当前障碍物,绕过之后重新找回全局路径上的点继续追踪。如果偏离全局路径太远,比如超过设定的阈值,就触发全局重规划。
这套架构的优势非常明显:全局路径保证了大方向正确,避免了DWA陷入局部陷阱;DWA保证了飞行过程的安全性,让无人机对突发障碍物有实时响应能力;两层之间的耦合只通过“前视点”这一个交互接口,接口简单,配合起来很少出幺蛾子。
2. 三维环境建模:把现实场景装进Matlab
做路径规划仿真,第一步不是写算法,而是先把环境地图建模出来。三维环境的建模方式直接决定后续碰撞检测的复杂度和算法运行效率。
2.1 障碍物模型选择与数学表达
三维环境里常见的障碍物有两种抽象方式。一种是规则几何体,比如圆柱体、球体、立方体,适合模拟楼宇、树木、电线杆、空中悬浮物。另一种是不规则地形,用高度函数或者离散网格表示,比如山坡、丘陵。
我在这套代码里采用了“球体加圆柱体”的组合。球体的碰撞判定条件很简洁:路径点到球心的距离小于球半径就判定碰撞。圆柱体的判定稍复杂一点,要同时考虑水平面的半径和高度方向:
- 球体碰撞条件:(x-ox)^2 + (y-oy)^2 + (z-oz)^2 < R^2
- 圆柱碰撞条件:(x-ox)^2 + (y-oy)^2 < r^2 且 z < h
这个判定公式在Matlab里用矩阵运算几行就能搞定。我把所有障碍物的参数放在一个结构体数组里,比如:
obstacles(1) = struct('type', 'sphere', 'center', [10 10 8], 'radius', 2); obstacles(2) = struct('type', 'cylinder', 'center', [20 25 0], 'radius', 3, 'height', 15);生成随机障碍物时,我写了一个小函数,可以在地图范围内随机生成指定数量的球形障碍物,中心点位置随机、半径在一定范围内随机。这样每次跑仿真都能得到不同的场景,方便测试算法鲁棒性。
2.2 路径表示、插值平滑与适应度函数设计
PSO搜索出来的路径本质上是一组离散路径点。假设全局路径有N个路径点,那么一个粒子就表示成3N维的向量(每个路径点有x、y、z三个坐标)。粒子群里的每个粒子代表一个候选路径方案。
但离散路径点直接连成的折线在无人机实际飞行中是不可用的,因为拐角尖锐、轨迹不光滑。所以我加了插值平滑层,用三次样条插值把离散路径点加密成上百个连续轨迹点,同时做轻度的平滑滤波,让无人机飞起来更自然。
接下来是关键——适应度函数设计。适应度函数决定了PSO朝哪个方向进化,必须把路径长度、平滑度、安全距离三个指标都考虑进去。
fitness = w1 * pathLength + w2 * smoothness + w3 * penalty;其中pathLength是相邻路径点之间的欧氏距离之和,smoothness用路径方向角变化量的累积来表示,惩罚项penalty是路径点与最近障碍物距离低于安全阈值时的严重惩罚。权重w1、w2、w3我一般取0.5、0.3、1.2,注意安全惩罚权重最高,因为安全是底线。
我踩过一次坑:最初设计适应度函数时只考虑了路径长度,结果PSO进化出来的路径直接从障碍物中心穿过去——因为路径最短,没有惩罚。后来把安全距离惩罚项加进去,这个问题才解决。如果你在复现时发现PSO规划的路径穿模,第一反应就是检查适应度函数里有没有安全项、惩罚力度是否够大。
3. 核心算法与Matlab实现细节
这一部分是整篇博文的干货区。我会按PSO全局规划、DWA局部避障、融合机制三个层次拆解,给出可以直接拷贝的Matlab核心代码。
3.1 PSO全局路径规划的完整实现流程
PSO的完整流程分六步:初始化种群、计算适应度、更新个体最优、更新全局最优、更新速度和位置、判断是否收敛。
初始化种群时,我先在起点和终点之间用均匀插值生成一条初始直线路径,然后在路径点坐标上叠加随机扰动,生成一批初始候选路径。这样初始粒子质量比较高,不会在天上乱飞,收敛速度也更快。这个初始化技巧比全随机初始化效率高很多,我推荐你用。
核心的PSO更新公式如下:
v(it, :) = w * v(it, :) ... + c1 * rand(1, dim) .* (pBest(it, :) - x(it, :)) ... + c2 * rand(1, dim) .* (gBest - x(it, :)); x(it, :) = x(it, :) + v(it, :);参数上,我通常设置粒子数量N=50,迭代次数maxIter=100,惯性权重w从0.9线性递减到0.4,学习因子c1=c2=1.5。这里有两个关键细节:
第一,w线性递减是很实用的技巧。前期w大,粒子飞行速度快,探索空间广,不容易漏掉全局最优区域;后期w小,粒子在小范围内精细搜索,能收敛到局部精值。如果你用固定w=0.8,实测收敛精度会比递减方案差不少。
第二,约束处理。粒子更新后位置可能跑到地图外或者扎进障碍物里,必须在每轮迭代后做边界修正和碰撞修正。边界修正用clip函数就行,碰撞修正有两种处理方式:直接把粒子拉回最近的合法位置,或者给适应度惩罚。我做法是两者结合,位置跑出边界就硬拉回,扎进障碍物就加巨大惩罚,让它在进化过程中自然淘汰。
3.2 DWA动态避障的数学原理与工程简化
很多同学听到DWA就头疼,觉得速度空间采样很抽象。我尽量用大白话讲清楚。
DWA的核心思想是:先把无人机可能的速度组合限制在一个“动态窗口”里,这个窗口由无人机的最大速度、最大加速度和当前速度共同决定。比如当前水平速度为vx,加速度上限为ax,控制周期为dt,那么下一个周期可达速度范围就是[vx-axdt, vx+axdt]。这个窗口是动态变化的,所以叫动态窗口法。
然后在这个窗口内按一定分辨率采样多个速度组合,对每个采样速度组合,以当前状态为起点,按运动学模型向前推演一小段轨迹(比如往前推3秒的轨迹)。最后用评估函数给每条推演轨迹打分,选出分数最高的一条作为实际执行速度。
在三维环境下,我做了简化:水平面上保留经典的DWA采样机制,用速度x和速度y加上偏航角速度w来模拟轨迹;垂直方向单独维护一个高度控制器,只在有需要时才改变高度。这样既能降低计算维度,又符合大多数多旋翼无人机的飞行习惯——大多时候平飞,绕不过去才爬升。
评估函数设计是DWA算法的灵魂,我的实现如下:
score = alpha * heading_score + beta * obstacle_score + gamma * velocity_score;heading_score表示轨迹终点和局部目标点的方位角偏差,偏差越小分数越高;obstacle_score表示轨迹与最近障碍物的距离,距离太近直接淘汰;velocity_score鼓励无人机以较快速度行进,本质上是在保证安全的前提下少磨蹭。alpha、beta、gamma经过调参,我取0.5、0.4、0.1。动态障碍物速度变化快时,beta要适当调高,让避障优先级提升。
生成模拟轨迹的Matlab核心逻辑如下:
for i = 1:numVx for j = 1:numVy for k = 1:numWz dt_single = 0.1; x_temp = x_current; y_temp = y_current; z_temp = z_current; for step = 1:horizon x_temp = x_temp + vx(i) * dt_single; y_temp = y_temp + vy(j) * dt_single; z_temp = z_temp + vz_target * dt_single; % 检查轨迹节点是否碰撞 end % 计算评估分数 end end end注意这里底层的循环耗时比较高,实时性要求高时可以用向量化批量推演替代三层for循环。我在实际工程里用Matlab的矩阵运算一次性推演所有采样轨迹,计算速度提升了接近一个数量级。
3.3 融合机制:局部目标点如何“接力”
融合机制是整个项目中我花时间最多的地方,这里分享我的实现方案。
DWA不能直接盯最终目标点,否则遇到远距离全局规划路径带拐弯时,中间过程会被局部评估函数带偏。正确做法是找“前视点”。
具体做法:无人机当前状态为X_current,全局路径是一串路径点P = [p1, p2, ..., pn]。我从当前路径点索引出发,找到路径上距离无人机当前位置的曲线距离刚好超过L的点作为前视点P_lookahead。这个L就是前视距离,我一般取当前飞行速度的1.5到2倍作为基准值,再与最小安全前视距离做比较取最大值。
% 找前视点 for k = currentIndex:n dist = norm(P(k,:) - X_current(1:3)); if dist > lookaheadDist targetPoint = P(k,:); newIndex = k; break; end end这个前视距离的取值特别讲究。L取太小,无人机会被迫频繁转向、轨迹震荡,而且一旦前视点被动态障碍物挡住,DWA评估函数会在“避障”和“追目标”之间疯狂摇摆;L取太大,DWA只顾远处目标,动态障碍物出现在近处时来不及反应。我建议以3米/秒飞行速度为例,L取4到6米是比较合理的区间。
还有一个细节:DWA的评估函数中,障碍物距离项要同时考虑静态障碍物和动态障碍物。动态障碍物的位置是随时间推进的,所以在模拟轨迹的每个时间步上,动态障碍物也要按自己的速度推进位置,然后再做碰撞检测。这一步遗漏的话,无人机看似避开了动态障碍物的初始位置,实际上飞到一半恰好撞上动态障碍物移动后的位置。
3.4 主循环与关键函数接口设计
整套Matlab工程我拆成了独立函数模块:
- initEnvironment.m:初始化地图、障碍物、起终点
- psoGlobalPlanner.m:PSO全局路径规划,输出路径点序列P
- dwaStep.m:执行单步DWA规划,输入当前状态、前视点、障碍物,输出速度指令
- updateDynamicObstacles.m:更新动态障碍物位置
- main_simulation.m:主仿真循环,串起整个流程
主循环的结构大致是:
% 主循环 P = psoGlobalPlanner(mapInfo, obstaclesStatic, startPos, goalPos); for t = 1:maxTimeStep targetPoint = findLookaheadPoint(X_current, P, currentIndex); [v_cmd, bestTraj] = dwaStep(X_current, targetPoint, obstaclesDynamic); X_current = modelUpdate(X_current, v_cmd, dt); % 更新动态障碍物 obstaclesDynamic = updateDynamicObstacles(obstaclesDynamic, dt); end把函数拆分清晰之后,调试很方便。比如单独测试DWA时,可以直接固定一个起点、一个终点、一个障碍物场景,开一次仿真看效果,定位问题很快。
4. 参数调优与仿真结果分析
算法写出来只是第一步,把参数调整到合理区间,做出来的飞行轨迹才好看、好用。这一节我给出自己多次实验总结的参数表和结果分析。
4.1 关键参数对照表与调参经验
我整理了一张核心参数速查表,这里面的数值都是通过仿真实验得出的经验值:
| 参数 | 典型取值 | 设置过大的影响 | 设置过小的影响 |
|---|---|---|---|
| PSO种群规模 | 30-50 | 收敛变慢,计算时间长 | 全局搜索不充分,路径质量差 |
| PSO惯性权重 | 0.9递减至0.4 | 后期收敛慢,轨迹抖动 | 容易早熟陷入局部最优 |
| PSO学习因子 | 1.5 | 路径震荡 | 收敛过慢 |
| 安全距离阈值 | 0.5-1.0米 | 可行通路减少,路径绕远 | 碰撞概率上升 |
| 前视距离 | 1.5-2倍速度值 | 动态避障反应迟钝 | 轨迹震荡频繁 |
| DWA采样分辨率 | 0.1米/秒 | 计算量指数级增长 | 速度选择粗糙,轨迹不平滑 |
| 动态障碍物速度 | 不大于无人机最大速度 | 无法安全避让 | 失去测试意义 |
经验之谈:调参顺序很关键。先把PSO参数调好,确认全局路径合理且不穿模;再调前视距离,确认无人机能沿全局路径行走;最后调DWA的评估函数权重,针对动态场景调整避障权重。跳跃式调参会让你无法判断一个问题到底出在哪个模块。
4.2 三维仿真可视化与动态效果复现
Matlab的可视化部分用到了mesh、plot3、scatter3这几个函数。三维地形用mesh(gridX, gridY, gridZ)绘制,静态障碍物用球体或者圆柱画出来,动态障碍物用scatter3加更新句柄的方式在循环里反复刷新位置。
动态障碍物的更新可视化核心代码如下:
h = scatter3(obsPos(1), obsPos(2), obsPos(3), 200, 'filled'); for t = 1:maxTimeStep obsPos = obsPos + obsVel * dt; set(h, 'XData', obsPos(1), 'YData', obsPos(2), 'ZData', obsPos(3)); drawnow; end用句柄方式更新图形对象属性比反复调用scatter3效率高很多,不会出现画面卡顿。
我做了一个场景复现:三维地图范围是50米乘50米乘30米,起点在左下角,终点在右上角。地图里布置了8个静态球形障碍物和2个动态障碍物。动态障碍物被设置为垂直于全局路径方向横向穿梭。最终飞行轨迹呈现的状态是:无人机先按PSO给出的全局路径飞行,途经区域有动态障碍物横穿时,飞行轨迹发生平滑偏移,绕过动态障碍物后,又重新收敛回全局路径方向,最终抵达终点。
4.3 性能维度对比:单PSO与PSO+DWA融合
我做了对照组实验。单用PSO每隔0.5秒重规划一次全局路径,在静态场景下表现尚可,但动态障碍物出现时,路径规划的频率跟不上障碍物运动频率,轨迹和障碍物距离最小值一度逼近0,碰撞风险极高。
改用PSO+DWA融合后,同一场景下轨迹与障碍物最小距离保持在安全阈值以上。同时,由于DWA在前视点引导下平滑行驶,路径总长度比高频重规划的PSO短了约10%——原因是高频重规划每次都是从起点重算,路径出现很多不自然的抖转,而融合方案只在局部微调,保持了全局路径的简洁性。
计算耗时方面,单次PSO全局规划约0.8秒,DWA单步规划约20毫秒,完全满足仿真周期的实时性要求。如果做纯离线仿真,多跑几次求平均值即可。
5. 常见问题与避坑指南
这一节全部来自我的实操排查经验,整理成速查表加详细分析,希望能帮你少走弯路。
5.1 高频问题速查表
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 全局路径穿过静态障碍物 | 适应度函数缺少安全惩罚项或权重过低 | 增加安全距离惩罚项,权重设为最大 |
| DWA在局部目标点附近来回摆动 | 前视距离过小或heading权重过小 | 增大前视距离,调高heading权重 |
| 动态障碍物突然出现在路径上时反应迟缓 | 动态障碍物运动预测缺失 | 模拟轨迹时按时间步推进动态障碍物位置 |
| 飞行高度忽高忽低 | 垂直高度控制器缺少平滑约束 | 对高度指令做限幅和滤波处理 |
| PSO收敛结果每次不同 | 随机初始化导致 | 固定随机种子,增加迭代次数 |
| 仿真画面刷新卡顿 | 每帧重复创建图形对象 | 用句柄更新方式优化 |
5.2 几个值得展开的实战教训
第一个教训是关于动态障碍物的速度预测。我最开始做DWA模拟轨迹时,只检测了动态障碍物的初始位置,结果无人机在仿真中屡屡被“瞬移式碰撞”——看起来路径没碰到障碍物,实际飞到一半时障碍物已经移动到路径位置上了。后来在轨迹推演循环里同步推进动态障碍物位置,这个问题彻底解决。动态环境做规划,障碍物一定要按照当前速度外推位置,这是三维动态避障和静态避障本质的区别。
第二个教训是关于粒子群早熟。三维地图空间比二维大很多,粒子数量不足时,PSO经常会收敛到一条被障碍物包围的劣质路径。我试过把种群从20增加到50,迭代次数从50增加到120,路径质量显著提升。如果还是早熟,考虑引入变异机制——每轮迭代随机重置少部分粒子的位置,维持种群多样性。
第三个教训是DWA评估函数权重冲突的问题。最开始我把速度项的权重设置得比较高,无人机在空旷区域确实飞得很快,但一旦接近动态障碍物,速度权重拖后腿,使得无人机以高速撞向危险区域。后来我把速度权重的计算公式改成“距离障碍物越远速度权重越高”的动态加权方式,效果很好。评估函数不一定非得是固定权重,让权重跟随环境动态变化,往往能得到更好的轨迹。
第四个经验是关于前视点的更新方式。不要每个仿真步都从全局路径第一点开始扫,那样效率低而且会引起目标点跳变。我从当前路径点索引附近开始向后搜索,同时索引只增不减,除非无人机绕路导致上个索引对应的点已经落到身后,才允许索引回退。这个“单向前移”的方式让前视点切换非常平滑。
6. 扩展方向与个人的一点体会
如果你想把这套PSO+DWA方案做得更贴近真实工程,还有几个可行的扩展方向。
第一个方向是把全局规划器切换成多目标优化版本。无人机在真实任务里往往要同时考虑油耗、时间、隐蔽性、安全裕度等多个目标,把PSO扩展成多目标粒子群算法,帕累托前沿的路径就比较有工程参考价值了。
第二个方向是在局部避障层加上机动的物理约束。我在仿真中默认无人机速度切换是瞬时完成的,但真实无人机存在最大加速度限制、最大倾斜角限制,甚至偏航角速度限制。把运动学模型替换成更精确的模型,DWA的效果会向真实飞行系统逼近。
第三个方向是融合视觉传感器模型。仿真里把动态障碍物位置视为已知,实际系统需要通过相机、激光雷达去感知障碍物位置和速度。加上传感器感知模型后,这套规划算法就具备了一定的端到端仿真能力。
说点个人的实际感受。做这个项目之前,我一直觉得路径规划算法就是寻路算法,跑通最短路径就算完事。真正把PSO和DWA融合起来做三维动态仿真之后才意识到,不管是全局规划还是局部避障,难点都不在于单独把某个算法实现出来,而在于两个算法之间的接口设计——怎么让全局路径“指导”局部规划同时又不过度限制局部灵活性,这个分寸感才是整个项目最需要打磨的地方。前视距离、局部目标点更新规则、评估函数中各类权重设置,每一个都直接影响最终飞行效果,只能靠一遍遍跑仿真、观察轨迹、调整参数来积累感觉。
如果你也在做类似的无人机路径规划研究,建议拿到代码后先别急着改算法,把默认参数下的仿真跑通,用三维轨迹图感受一下飞机的整体飞行姿态,再针对性地调整参数、替换模块。这套方案本身的容错性不错,踩过几次坑之后你就会发现,动态避障最经典的组合拳,还得是全局规划加局部规划的路子。