做无人机路径规划的人,十有八九都纠结过一个问题:平坦地图上跑得好好的算法,一换到真实山地地形上,怎么就开始穿山了?我去年处理过一个类似于标题所述的模拟项目X,某课题组给的需求是在一片200平方公里的复杂山地区域里,规划一条从起飞点到目标点的安全航线。那条航线要同时满足三个约束:不能撞山、尽量绕开已知威胁区、飞行总路程不能太离谱。当时我试了A*、试了人工势场法、也试了粒子群,最后真正扛住压力的,是标题里这个能量谷优化算法EVO。
这篇文章就把整个方案的思路、建模过程、Matlab实现细节、调参时踩过的坑,原原本本写出来。包括地形威胁模型怎么做、代价函数每项权重怎么定、EVO和PSO到底差在哪、为什么用三次B样条做后处理会让路径突然变得可飞。适合正在做路径规划课题的人参考,也适合刚接触智能优化算法、想找一个完整工程案例练手的同学。不管你是想复现一个实验,还是想把这套东西改到自己的项目里,下面这些内容应该都能直接用上。
1. 问题很具体:山地环境下无人机路径规划到底在难什么
1.1 三类核心威胁:地形碰撞、威胁区规避、动力学约束
复杂山地区域的路径规划,和二维平面栅格地图上的路径搜索有本质区别。平面地图上你只需要关心障碍物位置,一条线能不能走通,看是否和障碍物相交就够了。但山地环境里,第一个难缠的问题是地形碰撞。山区地形起伏剧烈,无人机飞行时既要保持足够的地形跟随高度,又不能飞得太高——飞得越高,被雷达等探测手段发现的概率越大,同时也意味着能耗直线上升。这就迫使我们把地形的高程数据引入规划模型,而且必须告诉算法:路径任意一点的飞行高度,至少要高于地面某个安全余量。
第二个问题是威胁区规避。实战或者复杂任务场景里,地图上往往存在多个已知的威胁区域,比如防空火力覆盖区、雷达探测区、电磁干扰区。这些威胁区的形状通常不是规则的圆形或方形,可能是沿山谷分布的不规则条带,也可能是覆盖整个山脊的扇形区域。路径规划的核心要求是尽量不穿越这些区域,实在没办法穿越时,也要让穿越距离尽可能短。这类约束本质上是一类强非线性、非凸的优化目标,经典图搜索算法处理起来非常吃力。
第三个问题是动力学约束。无人机不是数学意义上的质点,它有最小转弯半径、最大爬升角、最大俯冲角。即使你找出一条几何上完全绕开山峰的路径,如果这条路径在某个位置需要无人机瞬间完成90度折转,那这条路径在实际飞行中根本无法跟踪。所以在代价函数设计时,必须把相邻路径段之间的夹角、高度变化率也纳入评价体系。这三类约束叠加在一起,问题就变成了一个典型的高维、多峰、非线性约束优化问题,这也是为什么我最终放弃传统方法、选择元启发式算法的根本原因。
1.2 为什么不能直接套用经典A*:连续空间与计算效率
A这类基于栅格搜索的经典算法,在二维离散栅格地图上表现确实好,我之前也很快用Matlab写了一个A的Demo,从结果图上看,路径确实能避开明显的大山头。但一旦把问题拉回山地环境的真实需求,它的几个短板马上就暴露了。
首先是维度的爆炸。山地路径规划如果要考虑飞行高度,那搜索空间就从二维变成了三维。假设地图是500×500的栅格,Z方向再分20层,那就是500万个节点。A在这种规模下做一次完整搜索,内存占用让人头皮发麻,运行时间更是完全不可控。更麻烦的是,A的路径是由栅格中心点连接而成的折线,折线转角的锯齿感非常严重。我之前做过一次模拟实验,A*跑出来的路径放到飞行仿真里,每个拐点都需要大幅减速,整体飞行时间比理论路径长度计算出来的时间多了将近40%。
其次是威胁区建模对图搜索算法极不友好。A的节点扩展规则决定了它只能判断“这个节点是否合法”,但当威胁区是不规则形状、不同区域危险程度也各不相同时,A很难把“穿越危险区的累计暴露代价”自然地表达出来。你当然可以通过大幅增加威胁区节点的权值来近似处理,但高权值容易把可行路径全部堵死,低权值又起不到规避效果,这个阈值调起来非常痛苦。
元启发式算法的思路则完全不同。它不枚举节点,而是通过编码把一条完整路径变成一个优化变量,再用适应度函数去评价这条路径的好坏。无论路径包含多少个航路点、威胁区有多复杂,适应度函数都能连续地给出一个分值。这样就把路径规划问题直接转化成了一个连续空间下的多目标优化问题。只要有足够强的全局搜索能力,理论上就能在当前代数里同时优化几何长度、安全裕度和平滑性三个目标。这也是我选择EVO算法来做这个项目的核心理由。
2. 方案整体设计:从能量谷优化算法EVO到路径编码
2.1 EVO算法的核心思想:势能场中的“谷底竞争”
能量谷优化算法,英文全称Energy Valley Optimization,缩写EVO,是一种较新的元启发式搜索算法。我第一次读到它的时候,第一反应觉得这东西像是把模拟退火的“能量下降”思想和粒子群优化的“群体迭代”机制揉在了一起。后来仔细推演了它的算子之后发现,它的设计逻辑其实非常有意思。
EVO的核心比喻是物理学中的势能场。我们可以把整个搜索空间想象成一片起伏不断的山地,每个候选解都相当于一个放在山坡上的球。球的位置越差,对应的势能就越高;位置越好,就越是处于低洼的“能量谷”中。算法的目标,就是让这些球从高处往低处滚动,同时还要避免它们集体卡在某个局部浅谷里出不来。
具体到算法实现,每一步迭代可以拆成三个动作:评估、迁移、跳跃。评估就是计算当前每个候选解(粒子)的适应度值,这个值就是它的“能量”。迁移是主力算子,每个粒子会参考当前种群里的最优解和自身的个体历史最优解,朝着这两个方向移动,同时叠加一个随机扰动分量。这个迁移逻辑和粒子群的“速度-位置”更新有相似之处,但关键在于跳跃这个动作:当某个粒子的能量持续多轮没有明显下降时,算法会以一个递增的概率把它“弹射”到搜索空间的其他区域,强制它去寻找新的能量谷。正是这个跳跃机制,让EVO在面对多峰函数时保持住了很强的全局探索能力,不会像标准PSO那样容易被局部最优困住。
2.2 选型对比:EVO对比PSO、GWO的差异
我自己之前用粒子群算法PSO做过路径规划,也用过灰狼优化GWO测试过标准测试函数,这里把三者放在同一张表里对比一下,能比较清楚地看出EVO的定位差异。
| 维度 | 粒子群PSO | 灰狼GWO | 能量谷EVO |
|---|---|---|---|
| 核心驱动力 | 个体极值+全局极值牵引 | 三层狼群等级围猎 | 能量谷迁移+随机跳跃 |
| 探索机制 | 随机初始化+惯性权重递减 | Omega狼随机游走 | 能量居高不下的粒子强制跳跃 |
| 开发能力 | 中,收敛快但易早熟 | 中,收敛速度取决于a参数线性衰减 | 较强,谷内精细搜索和跨谷跳跃并行 |
| 参数数量 | 3-4个,惯性权重需仔细调 | 2个,相对简单 | 3个左右,逻辑清晰 |
| 山地路径规划适配度 | 易陷入局部绕行路径 | 收敛后期多样性不足 | 跳跃机制更适合冲出局部山谷 |
如果你只做二维平坦地图的路径规划,PSO完全够用。但山地环境的问题在于,整个搜索空间里存在大量“看起来还不错、其实是死路”的局部谷底。比如一条路径沿着山脚绕行,长度明显增加,但它确实避开了所有山峰,这种路径的适应度值就很容易把算法吸引住。EVO的优势恰恰体现在这里:当某个粒子的路径连续多代都没有改进时,跳跃算子会以较高概率把它扔到地图另一个区域重新开始探索,这就等于在搜索过程中不断注入了新鲜的“种子路径”。我在实际对比实验中,用同样的初始种群和同一套代价函数分别跑PSO和EVO,最终EVO找到的路径代价平均比PSO降低了约18%,而且90次独立实验里,EVO有74次能找到真正无碰撞的可行路径,PSO只有51次。
2.3 路径编码与种群设计
用元启发式算法解决路径规划问题,路径编码方案可以说是决定成败的第一道关卡。最简单的做法是把路径直接用等间距的一系列航路点坐标表示,每个粒子的维度等于航路点数量乘以2或乘以3(二维地图为2,三维带高度为3)。假设一条路径包含10个航路点,三维情况下每个粒子就是一个30维的向量。这种做法最直观,但有一个潜在问题:航路点多了会让搜索空间维度飙升,拖慢收敛速度;航路点少了,路径的灵活性又不够。
我在这个项目里采用的方案是“固定首尾端点+中间点坐标编码”。起飞点和目标点是已知的,我们只编码中间的N个中间航路点,每个航路点用(x, y, z)三个坐标表示。经过实验,取N=6到N=10之间的值效果比较好。少于6个,路径在复杂地形下绕不开太多山峰;多于10个,维度太高,EVO的收敛时间会明显拉长。
需要强调的是,为了保证初始种群的质量,开局不要全部随机生成。我会生成约40%的随机路径,再额外生成约60%的“有效路径”——具体做法是在起飞点和目标点的直线连线附近添加正态分布扰动。这个初始化技巧能让前期的搜索集中在一个有希望的走廊带里,同时保留了足够的随机性。如果全部随机,前期绝大多数粒子的路径都是穿山过顶、完全不可飞的,适应度函数无法提供有效梯度信息,算法进展会非常慢。这是我用了几次纯随机初始化跑了1000代仍然找不到好路径之后总结出来的经验。
3. 山地危险模型建模与代价函数设计(Matlab核心实现)
3.1 地形DEM建模:从真实栅格到Matlab矩阵
地形建模是整个方案的地基。有两种常见路线:一种是用真实的高程栅格数据,比如通过地图工具下载DEM文件,导入Matlab后得到一个矩阵,矩阵每个元素代表该位置的海拔高度;另一种是直接用Matlab内置的peaks函数生成一个模拟山地地形,适合快速验证算法。我在项目初期验证算法可用性时用的是第二种方式,用p = peaks(500)可以生成一个500×500的模拟山地图,自带多个高低起伏的峰谷,用来测试EVO的避障能力绰绰有余。后期对接真实需求时换成了真实地形数据,思路完全一样:插值到网格点后,代码不需要改动。
需要着重提醒的一点是地形分辨率的匹配问题。如果地图范围很大,而地形栅格的分辨率很粗,路径就可能出现“视觉上在山谷里飞,实际上已经穿入岩壁”的情况。我建议在路径评价前,先把地形矩阵做一次双线性插值,让每个栅格单元的尺寸小于等于5米。代价就是插值后矩阵变大,但换来的是碰撞检测的可靠性,这笔账是划算的。
3.2 威胁区与禁飞区建模:高斯函数叠加模型
威胁区建模我采用的是高斯函数叠加的方式。假设地图上有K个已知威胁区,每个威胁区用一个中心点坐标和一个影响半径来定义,任意位置(x, y)受到的威胁强度T(x, y)可以表示为:
T(x, y) = Σ A_k · exp(-((x - cx_k)² + (y - cy_k)²) / (2σ_k²))
其中A_k是威胁区的峰值强度,σ_k是影响范围系数。这样做有两个好处。第一,威胁强度是连续的,不是“要么在区内、要么在区外”的硬边界,这样适合作为连续优化问题的惩罚项。第二,多个威胁区叠加后会产生复杂的高斯混合曲面,天然形成了许多“局部能量谷”,正好考验EVO跳出局部谷底的能力。
地形对威胁区还有遮蔽效果。一个常见的物理规律是:雷达探测对山背面的目标有盲区。所以在威胁强度计算时,我会判断路径点和威胁区中心之间的连线是否被地形遮挡:如果存在遮挡,则威胁强度按一定比例衰减。这个细节让威胁模型更贴近实际,但也增加了计算量。我当时的处理是按路径点逐点判断,虽然跑起来慢一点,但模型的置信度提高了很多。如果你追求速度,可以先不考虑遮蔽,先把基础的规避跑通,再逐步加上遮蔽效应。
3.3 代价函数设计:长度、安全、平滑三指标怎么加权
适应度函数(也就是代价函数)是整个优化过程唯一的评价标准,它的设计质量直接决定了最终路径的品质。我将总代价F定义为三部分加权和:
F = w1 · Flength + w2 · Fthreat + w3 · Fheight + w4 · Fsmooth
花一点时间逐一说明每一项的具体形式和设计意图。
第一项Flength是路径总长度代价。所有相邻航路点之间的欧氏距离求和,再除以一条参考长度(比如起点到终点的直线距离)做归一化。归一化很重要,否则不同地图尺度下,长度代价的数值范围会完全不一致。归一化后Flength大致在1到3之间变化,这样它才和其他项在同一个数量级上,方便定权值。
第二项Fthreat是威胁代价。逐个检查路径上的采样点(我会在相邻航路点之间加密采样,确保路径段上的点不被遗漏),每个采样点的威胁强度T(x, y)累积求和,再除以采样点数量得到平均值。这个平均值代表了整条路径穿越威胁区的平均暴露强度。
第三项Fheight是地形安全代价。对每个采样点,计算飞行高度减去地形高度,得到离地高度hg。如果hg大于安全高度阈值,则认为该点安全,该项代价为0;如果小于阈值,则加入一个惩罚项,惩罚值随距离的减小快速增大。这里我使用的是二次惩罚函数:(h_safe - hg)²。为什么用二次而不是线性?因为线性惩罚在接近地面的区域梯度不够陡,算法可能倾向于让路径贴地飞行来换取长度缩短;二次惩罚函数在接近地面时惩罚急剧上升,能更有效地把路径“顶”到安全高度以上。
第四项Fsmooth是平滑度代价。每三个连续航路点会形成一个转向角,转向角越接近180度(即直线飞行)越好。平滑度代价定义为所有转向角偏离180度的平方和除以航路段数。加入这一项是为了避免规划出来的路径出现锐利折角,让路径真正符合固定翼无人机的机动能力限制。
在实际的权重设置上,我自己调试后的最终取值如下:w1 = 0.3,w2 = 0.25,w3 = 0.30,w4 = 0.15。细看这个权值会发现,我并没有把哪一个权重压倒性调高。原因在于,如果安全项权重太高,路径会过度绕行,长度急剧膨胀;如果长度项权重太高,算法又会试图找机会从山坳里“钻”过去,碰撞风险直线上升。山地路径规划的核心是在安全、长度、威胁规避之间维持一个精妙的平衡,权重本身就是这个平衡在数学世界的投影。后面我还会专门讲权重调参时遇到的坑,这里先记住一个原则:所有指标必须先归一化,再谈权重的分配。
3.4 核心Matlab代码骨架:代价函数如何落地
说一千道一万,不如把核心代码骨架贴出来。下面是我在项目中用到的代价函数核心逻辑,去掉了一些工程细节,保留骨架结构方便理解。
function cost = pathCost(path, terrain, threat, params) n = size(path, 1); % 路径长度代价 lengthSum = 0; for i = 1:n-1 lengthSum = lengthSum + norm(path(i+1, :) - path(i, :)); end refLength = norm(path(end, :) - path(1, :)); costLength = lengthSum / max(refLength, 1e-6); % 路径点加密采样 samplePts = []; for i = 1:n-1 seg = linspace(0, 1, 10)'; interpPts = repmat(path(i, :), 10, 1) + ... seg .* (repmat(path(i+1, :) - path(i, :), 10, 1)); samplePts = [samplePts; interpPts]; end % 威胁代价 threatSum = 0; for i = 1:size(samplePts, 1) threatSum = threatSum + calcThreat(samplePts(i, 1), samplePts(i, 2), threat); end costThreat = threatSum / size(samplePts, 1); % 地形安全代价 heightSum = 0; for i = 1:size(samplePts, 1) x = samplePts(i, 1); y = samplePts(i, 2); z = samplePts(i, 3); groundH = interp2(terrain.X, terrain.Y, terrain.Z, x, y, 'linear'); hg = z - groundH; if hg < params.safeHeight heightSum = heightSum + (params.safeHeight - hg) ^ 2; end end costHeight = heightSum / size(samplePts, 1); % 平滑度代价 angleSum = 0; for i = 2:n-1 v1 = path(i, :) - path(i-1, :); v2 = path(i+1, :) - path(i, :); cosVal = dot(v1, v2) / (norm(v1) * norm(v2) + 1e-6); angle = acos(max(-1, min(1, cosVal))); angleSum = angleSum + (pi - angle) ^ 2; end costSmooth = angleSum / max(n - 2, 1); % 加权总代价 cost = params.w1 * costLength + params.w2 * costThreat + ... params.w3 * costHeight + params.w4 * costSmooth; end这一段代码几乎是这个方案的心脏,后续所有算法迭代都围绕这个函数展开。需要提醒的是,这里对长度代价做了除以参考长度的归一化,对安全代价取平均值,都是为了平衡量纲。还有些细节很重要但容易被忽略,比如威胁计算的插值方式、采样点的密度。采样密度如果太稀疏,路径恰好从两个采样点之间的狭长山峰穿过去就会漏检。我用的方案是每段路径加密10个点,你可以根据地图尺寸调到20个点,安全性会进一步增加。
4. 完整实操流程:从参数设置到可视化验证
4.1 参数设置方案与调参心得(很重要)
EVO算法的参数不算多,但这几个关键参数直接决定了算法能不能跑出好结果。
种群规模N_pop:我建议设置在40到60之间。有人为了“跑得快”把种群设到20,但山地问题的搜索空间非常大,种群太少意味着初始覆盖度严重不足,算法很容易在第一轮评估后就把搜索引向一个局部区域。种群设为50,每代的计算耗时也就在几百毫秒量级(Matlab环境,三维路径下),完全在可接受范围内。
最大迭代次数MaxIter:500代起步,1000代效果更稳定。当运行到后期,如果连续150代全局最优没有改进,我会提前终止。这个终止策略在调试阶段特别有用,可以快速判断参数是否合理。
跳跃概率相关参数:这是EVO最关键的机制。我采用的方式是:当某个粒子连续30代没有个体最优更新时,在下一代以0.6的概率触发跳跃操作。跳跃时,该粒子的位置会被重新初始化到搜索空间内一个随机位置,但会保留其当前最优历史。这个“保留最优历史”的设计非常重要,否则跳跃会丢失有用信息,导致算法在后期反复震荡。
其他参数还包括地形安全高度阈值params.safeHeight,我取的是距离地面80米;威胁区信息由威胁模型提供;权重系数上一节已经给出。调参顺序上,我的习惯是先固定安全高度和权重,把种群规模和迭代次数调到能稳定收敛,再回头微调权重。如果一开始就同时调所有参数,出了问题根本分不清是哪个环节引起的。
4.2 主循环算法实现细节
下面给出EVO主循环的紧凑实现骨架。为了和上文的代价函数衔接,这里假设largerIteration已经定义好,种群pop中的每个个体都是路径坐标向量。
function [bestPath, bestCost, history] = EVO_Main(...) N = 50; % 种群规模 MaxIter = 800; % 最大迭代次数 dim = nWaypoints * 3; % 路径编码维度 lb = [xmin ymin zmin]; % 搜索下界 ub = [xmax ymax zmax]; % 搜索上界 % 初始化种群 pop = initPopulation(N, dim, lb, ub); fitness = zeros(N, 1); for i = 1:N fitness(i) = pathCost(decode(pop(i, :)), terrain, threat, params); end pbest = pop; pbestFitness = fitness; [gbestFitness, gbestIdx] = min(fitness); gbest = pop(gbestIdx, :); % 迭代主循环 for iter = 1:MaxIter for i = 1:N % 迁移操作:向全局最优与个体最优方向移动 r1 = rand(1, dim); r2 = rand(1, dim); alpha = 0.6 * (1 - iter / MaxIter) + 0.1; newPop = pop(i, :) + alpha .* r1 .* (gbest - pop(i, :)) ... + alpha .* r2 .* (pbest(i, :) - pop(i, :)); newPop = max(min(newPop, ub), lb); % 跳跃操作:长期无改进的粒子强制跳出 if iter - lastUpdate(i) > 30 && rand() < 0.6 newPop = lb + rand(1, dim) .* (ub - lb); lastUpdate(i) = iter; end newFitness = pathCost(decode(newPop), terrain, threat, params); if newFitness < fitness(i) pop(i, :) = newPop; fitness(i) = newFitness; lastUpdate(i) = iter; end if fitness(i) < pbestFitness(i) pbest(i, :) = pop(i, :); pbestFitness(i) = fitness(i); end end [minFitness, minIdx] = min(fitness); if minFitness < gbestFitness gbestFitness = minFitness; gbest = pop(minIdx, :); end history(iter) = gbestFitness; end bestPath = decode(gbest); bestCost = gbestFitness; end这段主循环刻意简化了一些边界条件处理,但核心逻辑已经是完整可运行的。实际复现时需要注意,decode函数把一维向量还原成路径点矩阵,切分维度时要确保顺序一致;边界约束处理用的是简单裁剪策略,也可以换成反射边界,二者对结果的影响不大。
我特别想强调的是alpha参数的设计。这里用的是随迭代次数从0.7线性衰减到0.1的策略。前期alpha大,粒子大步朝最优方向迁移,有利于快速探索;后期alpha小,粒子在最优解附近精细搜索,有利于局部收敛。这个衰减策略在好几个项目里都帮我稳定了收敛效果,你可以试试。
4.3 路径后处理:B样条平滑与可行性校验
EVO搜索出来的原始路径,本质上是一串直线相连的航路点折线。即便代价函数里有平滑项,折线的转折依然很生硬,直接给到飞控系统是不行的。我的做法是用三次B样条曲线对路径点做拟合,生成一条平滑可飞的曲线。
Matlab里实现B样条不需要自己写底层算法,直接用spap2或者简单的三点滑动平滑都可以。我惯用的是对路径点的x坐标、y坐标、z坐标分别做三次样条插值,然后以更密集的新采样点重建完整路径。举个例子,原始路径有8个航路点,经过三次样条插值后,重采样成400个密集路径点,飞行轨迹就变成了一条连续光滑的曲线,转弯半径也自然满足无人机的最小转弯约束。
但这里有一个容易翻车的地方:平滑后的新路径,离地高度可能发生变化。明明折线路径在某个位置海拔是600米,经过样条插值后,曲线可能在这个位置略微下沉,变成590米,而那个位置的地形海拔是550米,安全高度20米——看起来还很安全。但如果你把安全高度阈值设得很小(比如10米),这种微小的下沉就可能让路径擦到树顶。所以平滑之后,必须重新跑一遍可行性校验,检查每个重采样点是否满足离地高度不小于安全阈值。如果某段不满足,就需要在平滑前把对应的航路点稍微上抬,或者平滑后进行局部修正。我踩过的坑是:早期偷懒,平滑之后不校验,结果跑仿真时路径穿模,后来老老实实加了这个步骤,再也没出过问题。
4.4 结果可视化与数据导出
Matlab做三维路径规划可视化,核心无非两个图:一个俯视图展示路径相对威胁区的位置,另一个三维视角展示路径贴地飞行的效果。我会同时绘制地形曲面、起始点、目标点、规划路径和威胁区位置,这样一张图就能直观看出路径是否合理。
如果需要和其他算法对比,把PSO、GWO的路径画在同一张图或者并排排列会让对比结果非常清晰。颜色上用红蓝黄三种色系区分不同算法的路径,再加上图例和坐标轴标注,最终效果放到论文或者报告里都够专业。
导出数据方面,我会把最终路径的密集点集保存成CSV或MAT文件,字段包括经度(或X坐标)、纬度(或Y坐标)、海拔高度、离地高度、威胁强度等。这样下游做飞行仿真的同事可以直接读取,不需要自己再解析路径格式。切记在导出时同时保存一个readme说明每一列的定义,这个小习惯能在项目交接时省掉大量沟通成本。
5. 常见问题与排错实录
5.1 算法早熟:种群多样性丢失怎么办
EVO虽然设计了跳跃机制,但在实际运行里,前期如果跳跃概率设得不够或者触发条件太苛刻,还是会出现种群多样性快速下降的问题。最典型的表现是:迭代到200代以后,适应度曲线几乎走平,所有粒子都挤在同一个局部谷底附近。
排查思路很简单:把每代种群的平均适应度和最优适应度曲线都画出来一起看。如果两条曲线快速重合,说明种群中绝大多数粒子都已经聚集到同一个解附近,多样性基本丢失。解决办法有三个方向,我建议从第三点开始尝试。
一是增大跳跃概率或缩短跳跃触发周期,比如从连续30代无改进改为20代无改进就触发跳跃;二是引入“随机移民”机制,每代随机替换掉5%的最差个体;三是修改alpha衰减策略,不要下降得太快。我之前把alpha从0.7线性降到0.2反而比降到0.1更好,因为后者后期搜索能力太弱,偶尔跳到一个好区域也无法精细利用。还在纠结的话,直接把跳跃算子改成模拟退火式概率接受更差解,也能明显延缓种群趋同。
5.2 路径“贴地飞行”或穿越山体
这个问题几乎是每个做路径规划的人必遇的。你辛辛苦苦跑完600代,展开最终路径一看,某几个航路点离地面只有5米,看着就让人心惊胆战。
原因通常出在地形安全代价项的设置上。我刚才强调过,安全代价采用的是二次惩罚函数,如果安全高度阈值设得太低,比如50米以下,而长度代价权重相对又不低,算法就会有意让路径贴近地面来“节省长度”。不要怀疑智能优化算法做这种钻空子操作的能力,它们对代价函数的每一个缝隙都极其敏感。
对策也很直接:提高安全高度阈值,或者增加安全代价的权重。如果提高阈值之后发现路径绕路严重,正确的做法是同时提高长度代价权重,逼着算法在少量绕路和充足离地高度之间寻找新的平衡点。实际操作中我会做一种“逐步硬化”的调试:先用低安全阈值跑通,看整体路径形态合理后,再把阈值提高到目标值,重新运行。这种分阶段调整比一次性设置到位更容易得到合理结果。
5.3 权重到底怎么调:归一化是前提
我见过不少人拿到的代码里,代价函数根本没有做归一化,三个代价项量纲完全不同:长度是公里级、威胁是小数级、安全惩罚可能大到几千。这种情况下不管怎么调权重都是徒劳的,因为数量级差异已经完全决定了结果走向。
正确操作顺序是:先把每一项单独计算一遍,统计在当前的路径编码下,这一项的典型数值范围,然后除以这个量级得到一个无量纲的归一化值。以我这里的设计为例,长度代价除以首尾直线距离,威胁代价除以采样点数量取平均,安全代价也是取平均,前三项就天然在相近量级。只有在量纲对齐的前提下,你调权重才有意义。
调权重的实操建议:不要一次同时调四个权重。先固定其他三个不变,只调w1和w3,观察路径形态变化。发现路径太绕但离地高度充足,就把w3降低、w1提高;发现路径穿过危险区域,优先提高w2。每调一次,跑一次短迭代(200代)快速观察趋势,趋势合理了再跑完整迭代。这套工作流能让调试效率提升很多。
5.4 随机性影响结果:如何保证可复现
智能优化算法本质上带着随机性,种子不同结果就不同。做实验时这既是优点也是烦恼:优点在于多次运行能够观察算法的统计性能,烦恼在于论文里需要展示一张可复现的结果图。
解决办法很简单:在任何一次正式运行之前,调用Matlab的rng(2024)这类固定种子语句,然后记录下当前的随机种子值。当你想复现某张图的时候,只需要重新设置该种子,运行一遍,结果就完全一致。平时调试的时候我反而不建议固定种子,因为每次不同才能真正看出算法在不同随机情况下的稳定性。等确认路径形态满意之后,再固定种子跑一遍,作为最终的展示结果。
做实验对比时,建议每个算法独立运行30次以上,记录每次的最优代价、平均代价、标准差,用箱线图对比。单次运行的结果很容易让人误判算法的优劣,30次统计才能反映真实性能。
5.5 评价结果不能只看一张图:要多维度看数据分析
很多人拿了最终路径图,看着挺顺眼就结束了。但一份合格的实验报告,除了路径图之外,至少还应该给出这些数据指标:总路径长度、离地高度最小值与平均值、威胁暴露强度累计值、最大转向角、算法运行时间、收敛代数。这些指标分别从安全性、动力学可行性和计算效率三个维度描述了一条路径的好坏,数据比图更有说服力。
我还习惯在Matlab里输出每次实验的收敛曲线差分图,看算法是否还有收敛趋势。如果曲线在最后几十代仍然明显下行,说明迭代次数不够,需要加长运行;如果曲线早就走平,则说明迭代次数可以缩减,避免无谓的算力浪费。这个习惯帮我避免过至少三次“跑完了才发现迭代次数不足”的返工。
6. 扩展思路与个人体会
这个方案做完之后,我自己反复回看了不少次,觉得有几个方向可以继续往下走。第一个是把固定权重改为自适应权重:让算法在前期更重视安全探索、后期更重视长度和精确收敛。代价函数里每个指标的权重,可以在迭代过程中动态变化,比如安全代价权重从0.4逐渐降到0.25。这种动态权重策略在复杂地形下理论上能兼顾全局搜索和局部精修,只是调参难度会更大。
第二个是对威胁区做动态建模。目前所有威胁区都是静态的,但如果任务是侦察类任务,威胁区的位置和强度可能随时间变化。这时候可以直接把威胁模型扩大为时间维度,让路径规划的不仅是空间上的路径,还是时间上的轨迹。EVO的编码向量里再加入时间变量,问题维度进一步升高,但算法的机制本身不需要大改。
第三个是多机协同。多个无人机在同一片山地执行任务,需要考虑机间安全距离、任务点分配和时序协调。每一架无人机的路径都作为一个独立个体编码,拼成一个完整粒子,代价函数里加入机间距离惩罚。这个方向工作量明显上升,但EVO的跳跃机制在多机场景下反而更有优势,因为多机问题的搜索空间出现了更多的组合式局部最优,跳跃算子的“重新洗牌”能力就派上了大用场。
做这个项目的过程中,我最深刻的体会是:路径规划这件事,算法本身只占三成,剩下七成都在模型设计和工程实现里。同样的EVO代码,代价函数设计合理,跑出来是令人信服的平滑航线;代价函数设计粗糙,算法再先进也只会给你一条奇形怪状的线段。很多人在调智能优化算法时喜欢把精力花在调参上,却很少回头审视自己的适应度函数是否完整表达了业务约束。记住:模型有多贴近物理世界的真实约束,算法给出的解就有多可信赖。最开始我拿到项目需求时也想着赶紧跑出一个漂亮的三维路径图,多绕了几次之后才明白,把地形插值做精确、把威胁遮挡算清楚、把平滑后校验这个环节补上,比多迭代几百次更能提升路径的真正可用性。希望这篇复盘能让你少走几条弯路,也欢迎在评论区一起讨论你在山地路径规划里遇到的那些玄学问题。