ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

星雀优化算法NOA的多无人机协同避障三维路径规划

星雀优化算法NOA的多无人机协同避障三维路径规划

做多无人机三维路径规划,最折腾的就是协同集群避障这件事。最近我用星雀优化算法NOA把一套多无人机协同集群避障路径规划方案完整跑通了,目标函数按最低成本来设计,同时考虑路径长度、飞行高度、威胁代价和转角平滑度,Matlab代码从头写到尾,最终能输出三维航迹、迭代收敛曲线和机间间距变化曲线。这篇文章我把整个思路、建模过程、目标函数设计、算法改法、代码结构和调试时踩过的坑全部整理出来,适合正在做无人机集群路径规划、组合优化算法应用,或者被多机避障罚函数折磨过的同学参考。

先说个结论:多无人机路径规划真正难的其实不是“找到一条能飞的路径”,而是“让多条能飞的路径在时间和空间两个维度上同时成立”。单机规划可以只关心地形碰撞、飞行成本、自身转弯能力,多机协同还要额外盯住机间安全间距、同时到达窗口、以及集群整体成本均衡。NOA这种仿生元启发式算法天然适合这类约束多、目标耦合强的问题,因为它不依赖梯度,能对离散控制点组成的路径进行整体搜索,把每架无人机的路径塞进一个种群个体里,一次性演化出整个集群的协同方案。

1. 项目整体思路:多无人机协同避障为什么难?这条路怎么走

1.1 单机规划与多机协同的本质差异

单机三维路径规划,本质上是在三维搜索空间里找一条从起始点到目标点的最优曲线,约束条件是地形碰撞、威胁源规避、动力学特性和自身成本。这个问题的复杂度主要来自连续空间的维度诅咒,但用一般的元启发式算法已经可以解决得很好了。

多无人机协同集群则完全是另一个量级。每架无人机输出的不是一条独立路径,而是一组在时间上需要同步的航迹。换句话说,两架飞机的路径如果从空间上看相安无事,但假设它们在同一时刻飞到了同一个坐标附近,那协同规划就是失败的。因为无人机不会像画图软件里的线条一样可以互相穿过,它们之间有实实在在的物理半径、编队间距和避撞责任。

所以多机协同路径规划通常要做两件事:第一,把每架飞机的路径表示成同一数量的离散控制点,点数相同,方便整体编码;第二,在适应度函数里加入机间距离惩罚项,对所有无人机在相同时间节点上的位置做距离检查。这也是我后来代码调试时花时间最多的部分——单机路径都挺正常,多机一跑就发现穿插碰撞。

1.2 为什么选星雀优化算法NOA

星雀优化算法(Nutcracker Optimization Algorithm,简称NOA)是近几年提出的元启发式算法,灵感来自星雀这种鸟储存和回收食物的行为。星雀会在秋天把松子等食物埋藏在很多隐蔽位置,到了冬天再靠空间记忆去回收。更有意思的是,它如果发现储存点被别人破坏,会重新调整策略,换个地方重新藏起来。这种“储存—搜索—回收—再储存”的行为循环,本质上就是全局探索和局部开采的交替过程。

相比经典的粒子群算法PSO和灰狼优化算法GWO,NOA有两个让我觉得值得用的特点:一个是它的搜索过程分为多个策略阶段,不同阶段用不同步长和更新公式,能减少后期种群聚集导致的早熟;另一个是Levy飞行机制被嵌在觅食策略里,长距离跳跃和短距离精细搜索结合,对路径规划这种高维、多峰、布满局部陷阱的问题很友好。

我在实际对比中也发现,在相同种群规模和迭代次数下,NOA在三维路径规划的收敛稳定性上比PSO好。PSO经常出现收敛过快、所有个体挤在一条局部较优路径附近的情况,而NOA在迭代后期仍有能力跳出局部区域。当然这不是说NOA绝对适合所有场景,路径规划问题的地形复杂度、威胁源数量、约束强弱都会影响最终效果,但从工程角度来说,NOA是一个值得放进方案里做对比的候选算法。

1.3 本文方案的整体架构

整体方案大致分成五个模块:三维环境建模、种群初始化、目标函数计算、NOA迭代搜索、结果可视化。环境建模模块负责把真实地图转成数字高程场和威胁源场;种群初始化模块负责生成多无人机的联合路径编码;目标函数模块把所有约束折算成一个标量成本;NOA模块负责不断优化这个成本;可视化模块把最终航迹、地形和迭代曲线画出来。

这套架构最核心的设计思路是“解耦”:环境建模和算法搜索之间不直接互通,环境只提供查询接口,比如任意一个三维点处的地形高度、威胁强度、到威胁源的距离。算法只负责更新路径控制点,然后调用环境接口评估这个三维点是否安全。这样做的好处是,以后不管换地形、换威胁源,还是换算法,各模块都能独立修改,重新组合时不用大动干戈。

2. 三维环境建模:把真实空域变成算法能算的数学场

2.1 地形高程的建模表达式

三维路径规划第一步是把地图转成数学形式。常见做法是用多个山峰函数叠加来模拟地表起伏,每个山峰用一个高斯型或余弦型的凸包表示,最终地形高度等于多个山峰贡献值的和加上一个基础海拔。比如用以下形式:

H(x,y) = H_base + sum( A_i * exp(-((x-cx_i)^2/(2*sx_i^2) + (y-cy_i)^2/(2*sy_i^2))) )

其中A_i是第i座山峰的高度,cx_i和cy_i是山峰中心坐标,sx_i和sy_i控制山峰在x和y方向上的陡峭程度。叠加两三个山峰之后,地面就不再是平的,无人机从上面飞过时自然会产生高度约束。

在实际编写Matlab代码时,我不会用循环一步步去算地形,而是用meshgrid先生成整个地图网格,再对整个网格矩阵做山峰叠加,这样速度快很多。地形分辨率取多少很关键,我一般取每10米一个网格点,地图范围是500米乘500米的话,就是51乘51的网格,足够路径规划使用。分辨率太细会导致环境查询变慢,太粗又会让路径看起来和实际地形对不上。

2.2 威胁源与禁飞区建模

除了地形,真实空域里还有雷达威胁区、防空火力区、禁飞区、气象危险区等。路径规划算法需要把这些区域表示成“成本很高”的空间区域,而不是二进制硬约束。因为硬约束会造成搜索空间严重不连续,算法在迭代中很难从“穿过禁飞区”的状态逐步演化到“绕过禁飞区”的状态。

我习惯将每个威胁源建模成一个圆柱体或球体,中心坐标为(cx, cy, cz),威胁半径R。某个路径点进入威胁源范围内时,威胁代价按距离的负指数或分段函数快速增大。这个设计的核心是“梯度引导”:离威胁源越近,代价越高,算法自然会把路径推向远离威胁源的方向。如果直接设成“进入就罚特大”,很多搜索个体找不到有效下降方向,收敛会很困难。

2.3 碰撞检测的工程化处理

对于无人机路径,碰撞检测不能只检查离散的几个路径点,因为相邻两个路径点之间的连线完全可能穿过山体或者威胁源。工程上最简单的做法是在两个路径点之间做密集插值采样,取20到50个插值点,逐一检查地形高度和威胁距离。

我在Matlab里实现了一个统一的check_collision函数,输入两个三维点,输出这条路径段是否安全、威胁代价累积量是多少。插值点数量不用太多,太多会拖慢性能。实际经验是每段取20个插值点,误差很小,速度快,足够在优化循环里调用成千上万次。

还有一种方式是只对每个路径点做威胁检测,但这种方法往往会漏掉穿越山脊的极端情况,导致最终画出来的航迹看起来没碰山,实际飞的时候撞了。所以路径段插值检测这个步骤不能省,尤其是目标函数里有路径长度代价时,算法为了缩短路径会倾向于让两个节点之间的连线贴近障碍物边缘。

3. 最低成本目标函数:路径、高度、威胁、转角四个维度怎么平衡

3.1 路径长度代价

路径长度代价是目标函数里最基础的一项,它代表飞行距离的经济性。无人机执行任务总希望尽快到达目标点,减少飞行时间、节约能耗。对于一条由离散路径点P1, P2, ..., Pn组成的航迹,路径成本是所有相邻路径点之间欧氏距离之和:

J_length = sum( sqrt((x(i+1)-x(i))^2 + (y(i+1)-y(i))^2 + (z(i+1)-z(i))^2) ), i = 1..n-1

如果只优化路径长度,算法会倾向于拉直线,但这往往会带来其他问题:直线可能穿过威胁源,可能让无人机贴地飞行,可能产生过大的转弯角。所以在多目标成本设计中,路径长度只是一个基础项,必须和其他代价项互相牵制。

这里要注意一个问题:路径长度代价不能直接用绝对距离作为统一量纲。地图范围大的时候,长度代价数值可能是几千,而角度代价可能只有几,权重设置就会很困难。所以我一般会将长度成本做归一化处理,除以起始点到目标点的直线距离,让这个指标在量纲上保持在1附近,方便和高度代价、威胁代价匹配。

3.2 飞行高度代价

飞行高度代价反映的是“是否飞在合理高度区间”。过低会撞地形或者受地面障碍物影响,过高会被威胁源更容易发现,也面临额外的能耗问题。因此目标函数中会设置一个最佳巡航高度h_ref,路径越接近这个高度,成本越小。

高度代价一般由两部分构成:第一部分是所有路径点偏离最佳高度的惩罚,第二部分是地形跟随惩罚,防止路径点低于当前地形高度。我的做法是:

J_height = (1/n) * sum( abs(z(i) - h_ref) / H_max ) + k_terrain * sum( max(0, H(x(i), y(i)) - z(i)) / H_max )

第二项实际上是个软约束,只要z坐标高于地形高度就是0,一旦低于地形高度就会产生巨大惩罚。h_ref的取值需要结合地形最高峰来设定。比如地图最高峰是250米,那么h_ref设为300到350米比较合理,既能避开大部分地形,又不至于飞得太高。实测下来,h_ref和最高山峰高度差小于50米时,算法压力会很大,因为路径很难绕开地形同时保持高度。

3.3 威胁代价

威胁代价是路径规划中最重要的安全指标。我的做法是把威胁代价拆成两部分:第一部分是离散路径点处的威胁累积值,第二部分是路径段插值点的威胁累积值。对于每个三维点,计算它到每个威胁源的距离,如果距离小于威胁半径,代价呈指数增长;如果距离大于威胁半径,代价也很小但不完全为零,以此保持连续的梯度引导。

威胁代价可以写成:

J_threat = (1/n) * sum( T(x(i), y(i), z(i)) )

其中T是威胁场强度函数,一般定义为所有威胁源贡献值的最大值或叠加值。对于单独的球体威胁源,T = exp( - (d - R) / alpha ),d是到威胁中心的距离,R是威胁半径,alpha控制衰减速度。alpha越小,威胁场衰减越快,边界越陡峭;alpha越大,威胁范围辐射越广,算法会更早避开威胁源。我调试时发现alpha取5到8比较合适,再小会导致路径贴着威胁源边界走,看着安全实际很差。

3.4 转角代价与平滑性

没有转角约束的路径,规划出来可能是锯齿状的,无人机根本飞不了。真实无人机有最大水平转弯角和俯仰角限制,不可能在相邻两个路径点之间做90度折转。转角代价就是为了让算法生成的路径看起来平滑、可飞行。

水平转角是相邻两段路径在水平面上的夹角,俯仰角是相邻两段路径在垂直方向上的夹角。我计算时会先得到三个连续路径点构成的向量v1和v2,然后分别计算水平投影后的角度差和垂直方向的角度差,把超过无人机极限转角的部分作为惩罚。

一个比较容易忽略的细节是:转角代价不能只看角度绝对值,还要看路径段的长度。如果两段路径都很短,即使夹角不大,实际飞行中要求的转弯半径也会很小,容易超出无人机机动性能。所以可以在转角代价中乘一个与段长相关的系数,让算法尽量把转弯分配在较长且均匀的路径段上。最终加入的三次样条平滑也会对转角代价做进一步改善。

3.5 多机协同约束的罚函数

多机协同部分的核心是机间避碰约束。我的实现方式是让所有无人机使用相同数量的路径节点,并且默认同一序号节点对应同一时间点。这样协同检查就变成了“在所有节点序号上,检查任意两架无人机的路径点距离是否大于安全间距”。

J_collision = sum over pair(uav_i, uav_j) sum over node k max(0, D_safe - dist(P_i(k), P_j(k)))^2

J_collision是罚函数,D_safe是安全间隔,通常取20米到50米,取决于无人机编队间距要求。这个罚函数必须用平方形式而非线性形式,因为平方形式在距离接近安全间距时会自动减小惩罚梯度,距离非常近时会急剧增大,能有效推动算法把机间距离拉远。

但机间避碰不止检查同步节点,还要检查路径段之间的动态接近。比如一架无人机在第3个节点到第4个节点之间飞行,另一架有可能在同一时间段从旁边穿越。最稳妥的做法是在每段路径上采样多个点,在所有采样点上检查任意两机距离。这样计算量会变大,但因为每段只采样5个点,整体上还是可以接受的。

3.6 权重系数与归一化设置

最终的目标函数是各项代价的加权和:

C_total = w1 * J_length + w2 * J_height + w3 * J_threat + w4 * J_turn + w5 * J_collision + penalty

权重系数的设计是让所有分项在一个数量级内,不至于某单项完全支配整个搜索过程。我常用的初始权重是:w1取1.0,w2取0.8,w3取2.0,w4取1.2,w5取10.0。注意w5要大,因为机间碰撞是必须避免的安全条件,一旦碰撞发生,即使路径再短、再平滑也不可用。

归一化也必须注意。我在每项代价计算中都会除以一个参考值,比如长度代价除以起始点到目标点直线距离,高度代价除以最大允许高度,威胁代价除以理论最大威胁值。这样各分项基本都在0到2之间,权重调整起来直观得多。刚开始做的时候我没有归一化,结果路径长度代价数值在300以上,高度代价在80左右,威胁代价在0.5左右,w1调成3后整体路径就极度倾向于缩短长度而忽视威胁,后来统一归一化才解决。

4. NOA算法原理与多无人机扩展实现

4.1 NOA的仿生机制:觅食、储存与回收

NOA的仿生行为大致可以分为三个阶段:觅食阶段、储存阶段、回收阶段。觅食阶段里,星雀在环境中随机寻找可获取的食物位置,位置更新偏向全局搜索,配合Levy飞行模拟鸟类长距离飞行寻找食物来源的行为。储存阶段里,星雀会把找到的食物埋在记忆中的多个地点,存储策略会结合当前最优位置和历史成功经验,这种机制相当于在搜索空间里做精细化局部搜索。回收阶段则是根据记忆重新访问存储点,如果发现存储点已经不可用(被破坏或转移),它会启动重新搜索机制。

映射到优化问题上,觅食阶段保持种群多样性,避免所有个体过早收敛;储存阶段在已发现的较好区域附近加密搜索,提高收敛精度;回收阶段允许算法重新评估并放弃劣质解,相当于给了算法跳出局部最优的机会。这个结构和很多“探索-开采”平衡做得差的算法不一样,因为回收阶段会主动抛弃当前解并重新搜索,不是一直围绕历史最优打转。

在实现NOA时要注意,原始NOA公式主要是为连续数值优化设计的,路径规划本身也是连续优化问题,适配起来比较自然。我的做法是把NOA当成位置更新器,每只星雀对应一个多维位置向量,多维向量里面包含所有无人机的路径点坐标,直接套用NOA公式更新即可。

4.2 多机路径的联合编码方式

编码是整个算法能不能跑起来的关键设计。我的做法是:假设有M架无人机,每架无人机的路径由K个控制点组成(包括起点和目标点),每个控制点有三维坐标x、y、z。那么一个个体就是一个长度为MK3的实数向量。前K个三维坐标属于第1架无人机,接下来K个三维坐标属于第2架无人机,以此类推。

以3架无人机、每条路径8个控制点为例,一个个体就是72维向量。这个维度对元启发式算法不算太高,但优化起来同样有难度,因为变量之间强耦合:改变其中一架无人机的路径点,可能导致机间距离发生变化,进而激活碰撞惩罚。

编码时会强制固定起点和目标点。如果起点和目标点也参与算法更新,迭代过程中可能出现起点偏移的问题,而且在解码回路径时还需要重新修正,比较麻烦。我直接固定首尾点,让NOA只优化中间的K-2个控制点,剩下的起点和目标点每次解码时手动拼回去。这样既减小搜索维度,又保证路径边界条件始终成立。

4.3 个体解码与航迹生成

每次评估适应度时,需要把一维向量解码成多架无人机的三维路径。解码逻辑非常简单:先按无人机数量切块,再把每块按K个控制点拆成三维坐标。得到离散控制点以后,一般不直接用折线当航迹,而是用三次样条插值对控制点做平滑,生成更细腻的飞行轨迹。

三次样条插值有两点好处:一是消除折线带来的锯齿,二是让转角代价在插值后的轨迹上更接近真实飞行情况。但要注意的是,评价航迹成本时应该使用平滑后的轨迹点,而不是原始控制点直接连线。我在调试时发现,如果只对原始控制点连线做成本评估,算法会以为路径很平滑,但样条插值后可能在某些位置出现大角度弯曲,导致生成航迹和评估结果不一致。

因此我的适应度计算流程是:解码控制点 -> 样条插值生成高密度轨迹 -> 对高密度轨迹逐点计算地形高度、威胁代价、转角代价 -> 对相邻无人机同步插值轨迹点计算机间距离。这套流程会增加计算时间,但换来的是评估结果和真实飞行轨迹高度一致,优化出来的路径可直接用于仿真。

4.4 迭代搜索流程中的关键处理

NOA迭代过程中有几个关键处理点。第一是边界处理:路径点的x和y坐标不能超出地图边界,z坐标不能低于地形高度也不能高于最大飞行高度。常规的做法是把越界变量拉回边界,但更好的做法是用随机反射:把超出边界的坐标反射回区间内部,这样能保持种群的多样性,不会出现大量个体贴着边界堆积。

第二是目标值的更新机制。路径规划问题中,单个个体的目标值变化很剧烈,因为罚函数在机间距离低于安全阈值时会瞬间拉高总成本。如果目标函数权重参数设置不当,可能出现某代最优解骤升骤降的情况。我建议保存一个历史最优解,并且只有在新的历史最优成本比旧值低时才更新,这样迭代曲线看起来更稳定。

第三是局部重启机制。当全局最优值连续几十代没有变化时,我会对种群中一部分个体重新做随机初始化,给种群注入新血。这个机制不改变NOA核心流程,但能明显改善后期收敛停滞的问题。具体实现时,我对30%的个体重置,同时保留当前最优个体不动,以免破坏已经找到的好解。

5. Matlab代码实现全过程,可直接套用

5.1 代码目录与模块划分

整个Matlab代码我按照功能拆成了多个文件,结构如下:

main_uav_path_planning.m 主脚本,设置参数、调用优化、输出结果 init_environment.m 环境初始化,生成地形和威胁源 generate_population.m 种群初始化,生成联合编码向量 decode_individual.m 个体解码,将一维向量转成多机三维路径 cal_objective.m 适应度评估,计算总成本 noa_search.m NOA主算法,包含觅食、储存、回收阶段 plot_terrain_and_route.m 三维地形与航迹可视化 plot_convergence.m 收敛曲线与机间距离曲线

这样的模块划分方便以后做交叉实验。想换一种算法的时候,只需要替换noa_search.m,目标函数和环境模块不用动。想换地图的时候,只需要改init_environment.m,算法模块不用动。这也是我反复调整之后形成的结构,一开始我把所有代码写在一个脚本里,后面每次调试都要从头找变量,效率极低。

5.2 主流程与参数配置表

主流程大概是:初始化环境->设置算法参数->生成初始种群->进入NOA迭代->每代评估所有个体->更新最优->记录收敛数据->画图。

参数配置我整理成一个表格,方便直接抄:

参数名称推荐取值说明
无人机数量 M3编队规模,越大问题越难
路径控制点数量 K8包含起终点,越多路径越灵活
种群规模100不少于维度10倍,这里维度是66
最大迭代次数300根据收敛曲线可调整
安全间距 D_safe30米由无人机安全半径决定
最优高度 h_ref300米结合地形最高峰设置
威胁半径80米环境设置项
权重 w1/w2/w3/w4/w51.0 / 0.8 / 2.0 / 1.2 / 10.0需要按归一化后调试

种群规模这里特别提一下:很多同学initial种群设置特别小,比如20个个体,这样在高维路径规划下很快收敛到很差的位置。我的经验是种群规模至少是优化维度的1.5倍以上,100个个体应对60维左右的多机问题比较合适,再大收敛速度会变慢,但稳定性更好。

5.3 适应度函数的伪代码

适应度函数是整个程序里最核心的部分。我写一个简化版的伪代码,方便理解逻辑顺序:

function cost = cal_objective(individual, env, params) % 1. 解码个体得到多机路径控制点 paths = decode_individual(individual, params.num_uav, params.num_nodes); % 2. 初始化各项代价值 J_length = 0; J_height = 0; J_threat = 0; J_turn = 0; J_collision = 0; % 3. 对每架无人机分别计算路径成本 for i = 1:params.num_uav % 样条插值生成密集轨迹点 traj = spline_interpolate(paths(i), params.sample_per_seg); % 路径长度代价 J_length = J_length + path_length(traj) / params.direct_distance; % 高度代价 J_height = J_height + mean(abs(traj(:,3) - params.h_ref)) / params.H_max; % 威胁代价:对轨迹点查威胁场 J_threat = J_threat + mean(threat_field(traj, env.threats)) / params.threat_max; % 转角代价:相邻轨迹点之间的角度变化 J_turn = J_turn + turn_cost(traj, params.max_turn); end % 4. 机间协同碰撞代价 for i = 1:params.num_uav for j = i+1:params.num_uav % 在同步时间点上检查两机距离 for k = 1:size(traj_i, 1) d = norm(traj_i(k,:) - traj_j(k,:)); J_collision = J_collision + max(0, params.D_safe - d)^2; end end end % 5. 总成本 cost = params.w1*J_length + params.w2*J_height + params.w3*J_threat ... + params.w4*J_turn + params.w5*J_collision; end

伪代码里我特意把威胁代价放在样条插值之后计算,原因前面已经说过:只有轨迹足够细腻,威胁检测才准确。机间碰撞检查也用插值后的轨迹点,而不是用稀疏的K个控制点。这是我从实际调试中获得的经验,只用控制点检查会漏掉很多穿插情况。

5.4 三维可视化与结果输出

三维可视化是检查路径规划结果最直观的手段。我用surf函数绘制地形,用plot3函数绘制每架无人机的航迹,用scatter3标记威胁源。地形着色可以根据高度来设置冷热色,让山峰的高低一目了然。航迹用不同颜色区分不同无人机,配合网格网格线能清楚看到每架飞机是怎么绕开威胁源的。

可视化代码里有个实用技巧:设置view函数的三维视角。默认视角看不出地形阻挡关系,我习惯用view([-45, 30])来设置一个俯视偏侧角度,这样既能看清楚水平绕飞,也能看清高度变化。还有一个问题是surf地形颜色和航迹重叠时容易互相遮挡,可以通过设置透明度,让地形半透明显示,航迹浮在上面,观感更好。

迭代收敛曲线我用semilogy画,因为总成本前期从几百急剧下降到几十,普通plot会把前期下降过程拉得太长。机间距离曲线则是画出所有无人机对之间最小距离随节点序号的变化,同时画一条安全间距的虚线,一眼就能看出有没有碰撞风险。

6. 调试实录与常见问题速查

6.1 算法早熟怎么办

第一个常见问题是算法早熟,典型表现是迭代50代以后收敛曲线基本不动,但最终路径质量很差,比如明显绕着大远路或者有一个无人机路径异常扭曲。原因通常是种群多样性丧失太快,所有个体在迭代中段就聚集到某个局部区域。

我用的解决手段有三个层次:第一,调整NOA中Levy飞行的步长参数,把步长拉大一些,让个体有更多机会跳出局部区域;第二,在算法中加入30%个体的随机重启机制,每迭代30代检查一次全局最优是否更新,如果没更新就重启一部分个体;第三,降低w1路径长度权重,稍微增加w4转角权重,因为路径长度权重过大时,算法会非常容易聚焦在“缩短路径”这个单一方向上,陷入局部最优。实际调试下来,随机重启是最有效的。

6.2 机间碰撞与路径穿插问题

第二种典型问题是单机看都正常,多机一叠加就出现穿插碰撞。这种问题的根源往往不是算法搜索能力不够,而是机间碰撞罚函数设置有问题。如果w5权重不够大,或者罚函数是线性的,算法会认为轻微的机间重叠是“可以接受的成本”,最终路径就会互相穿过。

我的经验是把机间碰撞罚函数直接设成平方形式,并且对每一对无人机、每一个同步轨迹点都逐一累积。另外还要检查目标函数中是否只计算了控制点之间的距离。如果只计算控制点,两段路径完全可能在控制点之间穿插,而控制点处距离都大于安全间距。把样条插值后的轨迹点纳入检查后,这个现象基本消失。

还有一个容易被忽略的点:不同无人机的轨迹插值点数量必须一致。如果不一致,同步距离检查时索引会错位,计算出来的距离根本不对应同一时间点,协同约束就是无效的。这个属于编码层面的低级错误,但一旦出现,问题非常隐蔽,调试一整天都可能找不到原因。

6.3 航迹贴地或穿山

第三种常见问题是航迹贴地飞行,甚至某些路径点低于地形高度却因为离散点稀疏而被漏检。我在调试中遇到过优化出的路径从两座山峰之间的沟谷穿过,但插值后轨迹比控制点更低,撞上了山脊。这属于检查精度不足。

解决方法是增加每段路径的插值点数,我通常每段采样30个点,并在地形比较陡峭的区域额外加密。地形陡峭程度可以提前算出来,如果某两个相邻路径点对应的地形高度差很大,就自动把采样密度翻倍,保证路径不会在无人察觉的情况下穿入山体。另外,目标函数里的高度惩罚项必须用平滑函数,不能简单设为“低于地形直接淘汰”,否则算法没有梯度信息,很难自动爬升。

6.4 计算耗时与效率优化

多机协同优化的计算量确实比单机大不少,因为每代100个个体,每个个体3架无人机,每架无人机8个节点,插值后每段30个点,所有路径点都要做威胁场查询。最开始整个300代流程跑下来大概要十几分钟,虽然结果能出来,但做参数调优时非常痛苦。

效率优化我做了三个方面:第一,威胁场提前网格化。环境初始化时就把地图区域的威胁场数值预先算好存成矩阵,后续路径点的威胁值直接用插值查询二维/三维网格,而不是每次实时计算指数函数;第二,避免在内部循环里重复计算不变的东西,比如地形矩阵、距离归一化常数,这些都放环境结构体里一次性算好;第三,协调检查只对相邻节点之间可能发生冲突的无人机对做计算,如果两架无人机起始航向和目标点差异巨大,可以先根据它们的直线路径粗略估计是否可能接近,跳过明显不相干的对。

这样优化之后,同样参数下运行时间从十几分钟压缩到四分钟左右。我建议在调参阶段先降低迭代次数和种群规模,看整体逻辑是否正确,确认无误后再把参数调大跑最终版本。

6.5 参数设置参考表

最后给出一个我在多组地图上都验证过的参数设置参考表,新手可以直接从这组参数起步:

项目参数值备注
地图范围500m x 500mx和y均为0到500
最大飞行高度500m限制z坐标上限
安全飞行高度300m目标高度h_ref
威胁半径80m两个威胁源,一个在山峰附近,一个在平原
无人机数量3可扩展到5
控制点数量8可扩展到10
种群规模100维度约66
最大迭代代数300观察收敛曲线后可减到200
插值密度30点/段检查碰撞用
机间安全距离30m按任务需求调整

提示:这组参数不是万能的,但它是很好的起点。遇到收敛不佳时,优先调整权重而不是算法参数。权重失衡导致的问题,调算法参数是治标不治本。

我个人实际操作中的体会是,多无人机路径规划这个方向,很容易让人把精力全放在改进算法上,折腾各种改进版本的NOA、混合其他策略,但最后真正决定路径好不好用的,往往是环境建模精度、目标函数归一化、碰撞检测密度这些看起来不怎么高大上的环节。先把这些工程细节打磨好,再回来比较不同算法的优劣,会顺畅得多。另外,做仿真验证的时候一定要把航迹多视角旋转检查,盯着一个视角看很容易漏掉空间交叉问题,我因为这个吃过不少亏。整个方案从建模到代码实现并不复杂,但每一步都需要反复试,调优的过程才是真正积累经验的地方。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进