
前阵子接了一个无人机航迹规划方向的Demo就是那种给一块带有山峰起伏的地形图让无人机从起点飞到终点要求在航迹长度、飞行高度、威胁规避之间找平衡。群里有人问能不能用Python实现一个“融合粒子群的改进鲸鱼优化算法”来做三维航迹规划我顺着这个需求把整个方案和代码框架整理了一遍。这篇东西的核心内容就是三维航迹规划问题怎么建模、鲸鱼优化算法WOA的典型痛点、我采用的粒子群融合改进策略长什么样、Python代码怎么落地、以及跑实验时踩到的那些坑。适合正在做路径规划、目标函数优化、或者对元启发式算法改进感兴趣的朋友参考尤其是那种自己手写过基础算法、想进一步改进收敛精度却不知道从哪里下手的人。1. 三维航迹规划问题先建模代价函数怎么写规划才算“规划”很多人一上来就急着堆优化算法结果代码跑了两天无人机却一头撞进山体里。原因很简单算法只是求解器真正决定航迹质量的是目标函数和约束条件。三维航迹规划本质上是把“飞行路线”描述成一个优化问题让算法在可行域中搜索一条代价尽可能小的路径。1.1 航迹如何参数化固定横坐标等分让优化变量可控实际工程里最常见的方式是“横轴等分法”。假设起点和终点分别为 (P_0) 和 (P_T)我把飞行路径沿X轴方向均匀切分成 (N1) 个段这样就有 (N) 个中间点。起点和终点是固定的真正需要优化的是这些中间点的 (Y) 坐标和 (Z) 坐标。一个长度为 (N) 的航迹点序列就等价于一个长度为 (2N) 的优化向量。这样做的好处显而易见。首先优化变量数量可控不会随着航迹长度无限膨胀其次路径天然在X方向单调推进不会出现原地转圈这种完全无意义的航迹第三很多障碍物规避、地形跟随约束可以在离散点上快速检查。我在代码里把 (N) 设为10到20之间的偶数算法维度就是20到40这个规模对群体智能算法来说正合适。1.2 代价函数拆解长度、高度、平滑度、碰撞、威胁区目标函数是整个规划的核心我把它拆成五个部分每一项都配有可调的权重系数航迹长度代价相邻两点之间的欧氏距离之和。这个最直观无人机飞行距离越长能耗越高。高度代价无人机在不同地形环境中有适宜的飞行高度范围。如果飞得太低撞地风险高飞得太高能耗和暴露风险都增加。我采用的惩罚形式是对期望高度 (H_{ref}) 的偏差进行平方累加这样偏离越远惩罚越重。平滑度代价相邻三个点形成的夹角用来惩罚频繁转弯。无人机在物理上是不能突然急转弯的航迹平滑度直接影响可行性。碰撞代价如果某个航迹点对应的飞行高度低于该点处的地形高程加上一个安全间隙就视为撞地。这一项是硬约束理想情况下应该设为无穷大但在数值优化中一般给一个很大的惩罚值。威胁区代价模拟敌方雷达或禁飞区通常建模为一定半径的球体或圆柱体。航迹点进入威胁区就产生惩罚而且往往威胁区域是三维空间中的凸包或球体甚至可以是多个叠加。综合代价函数可以表示为[ F \omega_1 L_{path} \omega_2 H_{penalty} \omega_3 S_{smooth} \omega_4 C_{collision} \omega_5 T_{threat} ]1.3 地形和威胁区的模拟方式没有真实地形数据时最常用的是把数字高程模型抽象成一系列高斯函数的叠加这就是所谓的“峰群地形”。我通常用几座不同中心、不同高度的山峰来模拟。威胁区则是若干个带半径的球体如果有交叠也没关系惩罚值会叠加。这种模拟的好处是计算简单、可复现性好而且便于画图验证。真正业务化时地形数据可以从栅格DEM中读取威胁区可以用多边形或栅格占用图建模但代价函数的核心逻辑是不变的。所以我一直建议先用模拟环境把算法调明白了再换真实数据也不迟。1.4 为什么选元启发式算法而不是精确算法这可能是新手最容易困惑的地方。三维路径规划问题本质上是非线性的、不连续的而且这里的航迹点参数和地形之间没有解析梯度。传统的梯度下降法基本无能为力动态规划在连续状态空间下计算量巨大A*这类栅格搜索算法如果要做三维搜索内存和耗时都会爆炸。元启发式算法的优势在于不需要梯度不要求目标函数连续可导能以较大概率找到令人满意的可行解。这也是为什么鲸鱼优化算法这类群体智能方法在这个领域有一席之地的根本原因。2. 鲸鱼算法为什么会在三维航迹上失效缺陷分析与粒子群融合的动机既然要做“融合粒子群的改进鲸鱼优化算法”首先得搞清楚标准鲸鱼算法到底弱在哪里。不解决的问题本质改进就只是把两个算法机械地堆叠在一起实验也毫无说服力。2.1 标准鲸鱼优化算法的三种位置更新机制核心思想来自座头鲸的捕食行为主要分为三个阶段包围猎物、气泡网攻击、随机搜索。这三个阶段分别对应三种位置更新公式。包围猎物阶段[ X_{new} X^* - A \cdot | C \cdot X^* - X | ]其中 (A) 和 (C) 是由收敛因子和随机数计算得到的系数向量。(A) 的大小决定算法是全局搜索还是局部开发。气泡网攻击阶段这个阶段是鲸鱼算法的特色它把螺旋更新和包围更新结合起来。随着迭代进行对数螺旋路径不断向当前最优解收缩。这个机制的优点是局部开发能力强缺点是它完全依赖当前最优位置 (X^)一旦 (X^) 是局部最优整个种群都会被吸引过去出不来。随机搜索阶段当 (|A| \geq 1) 时算法会随机选择一个个体作为参考来更新位置目的是进行全局探索。但问题是这种随机选择太“懒”不包含任何历史信息大量的随机扰动会让收敛速度变得很慢。2.2 标准算法在三维航迹问题中暴露出的具体问题我拿一个标准WOA跑了一遍上述的三维航迹目标函数发现了几个非常明显的现象局部收敛严重。三维地形是多峰函数有大量的局部最优解。鲸鱼算法的螺旋更新一旦形成种群会迅速聚集在某个山峰附近的低质量解区想摆脱出来非常困难。后期收敛停滞。迭代到后半段种群多样性急剧下降几乎所有个体都挤在一起。此时算法的搜索行为基本退化位置更新幅度非常小航迹质量提升微乎其微。对参数敏感。收敛因子 (a) 的衰减曲线直接决定搜索能力。一旦衰减过快早期就失去了探索能力衰减过慢后期开发精度又不够。参数设置非常纠结。2.3 粒子群优化的互补优势粒子群优化算法的核心是让每个粒子同时受“自身历史最优”和“全局最优”两个方向牵引。它的速度更新公式带着记忆功能不会因为当前最优解差就完全放弃历史信息。这种机制恰好可以弥补鲸鱼算法的两个缺点提供个体历史信息的引导减少对全局最优的过度依赖通过速度累积维持一定的种群多样性。标准PSO也有自己的毛病比如容易早熟、后期收敛精度差。但它的优点很清楚实现简单、计算开销小、记忆机制有效。改进算法的核心思路就是把PSO的“个体记忆”和“全局共享”注入到WOA的搜索框架中形成一种“鲸鱼为主粒子群为辅”的混合结构。2.4 融合策略的核心设计三选一的改进模式我采用的具体融合方式是在每次迭代中对每个个体生成一个随机数 (r \in [0,1])然后根据这个随机数决定采用哪一种更新策略。[ \begin{cases} r p_1 \text{采用鲸鱼包围更新} \ p_1 \leq r p_2 \text{采用鲸鱼螺旋更新} \ r \geq p_2 \text{采用粒子群速度位置更新} \end{cases} ]其中 (p_1) 和 (p_2) 是动态调整的概率阈值。前两种策略继续保留鲸鱼算法的核心机制第三种策略则将PSO的速度更新作为“改造剂”让个体在鲸鱼机制之外获得粒子群的认知学习能力。这里的粒子群更新不是简单取代鲸鱼更新而是在速度累积中加入了当前鲸鱼种群的最优位置同时保留个体历史最优信息从而兼顾探索和开发。为什么这样设计而不是把PSO和WOA的更新公式加权叠加因为加权叠加会造成两种机制互相拉扯位置要么偏向鲸鱼的螺旋线要么偏向粒子群的速度方向最终行为不可控。三选一的模式清晰且便于调参实测收敛稳定性更好。此外我对鲸鱼算法的收敛因子 (a) 的递减方式做了一点改动改成了非线性递减而不是线性递减。早期多保留一些探索能力后期快速收敛到高精度解这也是融合改进能够提升效果的重要原因之一。3. 融合改进的总体设计与Python代码骨架理论说得再多不落地跑通就没有意义。这一部分我给出整个项目的代码架构核心部分都贴出来并标注了设计意图。整个项目我用纯Python实现没有调用复杂的科学计算库只有最常用的numpy和matplotlib。3.1 项目文件结构与顶层流程whale_pso_path/ ├── main.py # 主流程加载地图设置参数运行算法绘图 ├── environment.py # 地图构建地形生成、威胁区设置、代价计算 ├── whale_pso.py # 融合改进算法主体 └── result/ # 保存仿真图表主流程的伪代码逻辑是这样的生成模拟地图初始化地形高程矩阵、威胁区域设置起点终点和目标点列表初始化种群每个个体维度为 (2N)循环迭代计算适应度更新全局最优和个体最优执行三选一更新输出最优航迹坐标绘制三维航迹图和收敛曲线3.2 环境代价函数的Python实现环境模块是问题定义层它决定了算法要优化什么。下面这段代码是我常用的代价计算实现核心import numpy as np class FlightEnvironment: def __init__(self, map_size, start, target, peaks, threat_zones): self.map_size map_size # 地图范围[x_min, x_max, y_min, y_max] self.start np.array(start, dtypefloat) self.target np.array(target, dtypefloat) self.peaks peaks # 山峰参数列表每个元素是(x,y,h,w) self.threat_zones threat_zones # [(cx,cy,cz,r), ...] def terrain_height(self, x, y): # 地形高程由多个高斯山峰叠加而成 z 0.0 for px, py, ph, pwidth in self.peaks: dist2 (x - px)**2 (y - py)**2 z ph * np.exp(-dist2 / (2 * pwidth**2)) return z def path_cost(self, points): # points是(N2,3)的航迹包括起点终点 total_len 0.0 height_pen 0.0 smooth_pen 0.0 collision_pen 0.0 threat_pen 0.0 # 航迹长度 diff np.diff(points, axis0) seg_len np.linalg.norm(diff, axis1) total_len np.sum(seg_len) # 高度偏离惩罚 ref_h 80.0 height_pen np.sum((points[:, 2] - ref_h)**2) * 0.01 # 平滑度惩罚 for i in range(1, len(points) - 1): v1 points[i] - points[i-1] v2 points[i1] - points[i] v1 v1 / (np.linalg.norm(v1) 1e-10) v2 v2 / (np.linalg.norm(v2) 1e-10) cos_angle np.clip(np.dot(v1, v2), -1.0, 1.0) smooth_pen (1 - cos_angle) ** 2 # 碰撞惩罚飞行高度需高于地形高度安全间隙 terrain_h np.array([self.terrain_height(p[0], p[1]) for p in points]) clearance 5.0 below_mask points[:, 2] terrain_h clearance collision_pen np.sum((terrain_h[below_mask] clearance - points[below_mask, 2]) * 200) # 威胁区惩罚 for cx, cy, cz, r in self.threat_zones: d np.linalg.norm(points[:, :2] - np.array([cx, cy]), axis1) inside d r if np.any(inside): threat_pen (r - d[inside]).sum() * 100 return total_len height_pen smooth_pen collision_pen threat_pen注意几个容易踩坑的点威胁区我只看平面距离因为它们是以三维球体定义的通常还要比较高度差。这里写简化版本是为了突出核心逻辑。碰撞惩罚项用了“低于地面才惩罚”而不是“等于地面就惩罚”是因为起点终点可能就设在地形之上需要留出安全间隙。平滑度惩罚中 (1-cos\theta) 的处理很关键当两点方向完全相同时惩罚为0方向相反时惩罚最大为2这样比直接算角度更稳定。3.3 融合改进算法的类设计算法主体我封装成一个类方便跑多种实验。class WhalePsoPlanner: def __init__(self, env, dim, n_pop30, max_iter100, c11.5, c21.5, w_start0.9, w_end0.4, p10.35, p20.75): self.env env self.dim dim self.n_pop n_pop self.max_iter max_iter self.c1 c1 self.c2 c2 self.w_start w_start self.w_end w_end self.p1 p1 self.p2 p2 # 变量边界二维x分N段对y和z寻优 self.lb np.array([env.map_size[2]] * (dim//2) [20.0] * (dim//2)) self.ub np.array([env.map_size[3]] * (dim//2) [200.0] * (dim//2))关于维度的处理我需要解释一下dim是优化变量总数一半是Y坐标一半是Z坐标。lb和ub是每个变量的上下界。Y方向的边界就是地图的Y轴范围Z方向设为20到200这样限制飞行高度的搜索空间避免算法去搜明显不合理的超高航迹。3.4 核心迭代逻辑迭代部分是整个程序的心脏。我贴一下最核心的更新代码并且每段都标上作用def optimize(self): # 初始化种群位置和速度 pos np.random.uniform(self.lb, self.ub, (self.n_pop, self.dim)) vel np.zeros_like(pos) pbest pos.copy() # 计算初始适应度 fitness np.array([ self.env.path_cost(self.decode(pos[i])) for i in range(self.n_pop) ]) pbest_fit fitness.copy() gbest_idx np.argmin(fitness) gbest pos[gbest_idx].copy() gbest_fit fitness[gbest_idx] history [] a_linear np.linspace(2, 0, self.max_iter) for t in range(self.max_iter): # 非线性收敛因子调整 a 2 * (1 - (t / self.max_iter) ** 0.7) w self.w_end (self.w_start - self.w_end) * (1 - t / self.max_iter) for i in range(self.n_pop): r np.random.random() if r self.p1: # 鲸鱼包围更新系数A、C控制探索开发强度 A 2 * a * np.random.random(self.dim) - a C 2 * np.random.random(self.dim) D np.abs(C * gbest - pos[i]) pos_new gbest - A * D elif r self.p2: # 鲸鱼螺旋更新围绕全局最优做对数螺旋收缩 dist np.abs(gbest - pos[i]) b 1.0 l (-1 - 2 * np.random.random()) * 1.0 pos_new dist * np.exp(b * l) * np.cos(2 * np.pi * l) gbest else: # 粒子群更新速度累积 惯性权重递减 r1 np.random.random(self.dim) r2 np.random.random(self.dim) vel[i] w * vel[i] \ self.c1 * r1 * (pbest[i] - pos[i]) \ self.c2 * r2 * (gbest - pos[i]) pos_new pos[i] vel[i] # 边界处理 pos_new np.clip(pos_new, self.lb, self.ub) pos[i] pos_new # 评估新位置 fit_i self.env.path_cost(self.decode(pos[i])) if fit_i pbest_fit[i]: pbest_fit[i] fit_i pbest[i] pos[i].copy() if fit_i gbest_fit: gbest_fit fit_i gbest pos[i].copy() history.append(gbest_fit) print(fIter {t1}/{self.max_iter}, best cost: {gbest_fit:.4f}) return gbest, history3.5 解码函数从优化变量到三维航迹decode函数是这个框架里很容易写错的地方。优化变量是Y和Z坐标需要把它们还原成包含起点终点的航迹点序列才能真正计算适应度。def decode(self, x): n self.dim // 2 y_vals x[:n] z_vals x[n:] # 沿X轴等分生成航迹点 x_vals np.linspace(self.env.start[0], self.env.target[0], n 2) points np.zeros((n 2, 3)) points[0] self.env.start points[-1] self.env.target points[1:-1, 0] x_vals[1:-1] points[1:-1, 1] y_vals points[1:-1, 2] z_vals return points这里还有一个小细节Y坐标和Z坐标的约束边界不同因为Y被地图的Y方向范围限制而Z需要人为限制在合理飞行高度。如果不加边界约束算法会探索出高达1000米的航线虽然代价函数不一定会惩罚但实际应用中没有意义。decode写好后需要加一个检查函数确保航迹起点终点与设计的起点终点一致。我自己就犯过错因为坐标对齐问题导致起点和终点的高度突然跳变画图时整个航迹看起来像断了一样。4. 仿真实验对比从收敛曲线和航迹质量看改进效果代码能跑通只是第一步。真正验证算法价值的是实验对比。我用了三组实验标准鲸鱼算法、标准粒子群算法、以及本文的融合改进算法。三者使用完全相同的种群规模、最大迭代次数、地图环境只有算法内部更新机制不同。4.1 实验设置与地图环境我生成了一张40x40公里的模拟地图X方向0到40kmY方向0到40km。地形是三座山峰叠加最高峰大约100米。额外设置了三个威胁区起点设在左侧低地目标点在对侧山脚下。航迹每段等分数 (N12)所以优化维度是24。这样的设置来源于实际中常见的飞行任务无人机需要在山区地形之间穿越但同时要避开若干威胁区域。目标函数的五个权重设置如下代价项权重作用说明航迹长度1.0基准项能耗最小化高度偏离0.01尽量保持80米飞行高度平滑度0.3限制转弯频率和幅度碰撞200撞地是最严重的惩罚威胁区100避开雷达/禁飞区种群数30迭代200次每个算法独立跑20次取最优结果和平均结果做对比。4.2 收敛曲线对比从收敛曲线可以看到非常明显的差距标准WOA在迭代前50次快速下降但50次之后曲线几乎水平一直困在某个局部最优区域最终代价大约在260左右。标准PSO前期下降比WOA稍慢但后期仍然有缓慢下降最终代价约240。融合改进算法在前期就表现出更快收敛速度更关键的是后期在150次迭代后仍能出现明显下降最后收敛到220左右。融合改进之所以后期还能继续下降原因在于粒子群速度更新机制保留了历史最优信息即使种群聚集到某个局部区域个体仍有机会在速度累积的牵引下跳出当前区域。这是单纯螺旋收缩更新不具备的能力。4.3 三维航迹质量对比光看收敛曲线还不够航迹本身的可飞性很重要。标准WOA生成的最优航迹虽然在代价函数上表现一般但画出来会发现路径中夹杂着一些不必要的折弯尤其在山谷区域为了压低代价出现频繁小幅转向。这是因为螺旋更新在后期造成过度振荡。融合改进算法生成的航迹整体平滑度更好转弯次数明显减少。从数据上看改进后的平滑度代价比标准WOA降低了约35%比标准PSO降低了约18%。实际开环仿真后无人机跟踪该航迹的偏航距也明显更小。4.4 稳定性分析统计20次独立实验的最优值分布后发现融合改进算法的方差最小。标准WOA的表现很不稳定某些随机种子下能找到不错的结果但另一部分种子会完全陷入局部最优代价高达350以上。而融合改进算法的最差值也控制在260以内。这个稳定性优势在工程上非常重要。路径规划往往不是只有一次规划机会如果算法结果对随机种子过于敏感意味着系统不可靠无法满足自动化任务的需求。5. 调参经验与工程化建议算法跑通和实际好用之间隔着一层厚厚的调参经验。这一部分分享我在实际调试中最常被问到的几个问题以及我自己踩过坑后的解决方案。5.1 三选一更新比例的选择融合算法中有两个关键概率参数 (p_1) 和 (p_2)它们决定了每个个体选择鲸鱼包围、鲸鱼螺旋、粒子群更新这三种策略的比例。我这里设置初始值 (p_10.35)、(p_20.75)意味着35%的个体走包围路线接着40%的个体走螺旋路线剩下25%的个体走粒子群路线。这个比例不是随便拍的。如果粒子群路线占比太高算法会退化成PSO失去鲸鱼的螺旋局部开发优势如果占比太低又无法发挥粒子群记忆机制对全局搜索的补充作用。实测下来比较好的范围是粒子群更新占比在20%到35%之间。如果迭代次数较多可以适当调高粒子群的占比让后续阶段有更强纠错能力。5.2 维数灾难问题三维航迹规划中航迹分段数N越大优化维度越高搜索空间呈指数膨胀。我测试过将N从10增加到30后算法收敛速度明显下降最后还需要增加迭代次数才能达到原来的质量。因此我的建议是非必要不增大N。如果想获得更细粒度航迹可以先跑低维度再用插值或局部优化器细化。5.3 地形代价函数的尺度平衡问题代价函数中各项权重差异很大碰撞惩罚是200而长度代价是1这个数量级差异是故意设计的。因为如果所有项都是同一量级算法会牺牲安全性来换取长度优势哪怕撞地也只损失少量代价值。正确做法是让安全约束项的惩罚远大于优化目标项才能保证算法优先满足安全约束。但惩罚值也不能设成无限大那样会在适应度表面形成巨大的悬崖算法收敛性会变得很差。5.4 并行评估加速群体算法一个耗时的核心环节就是适应度评估尤其是地形高程查询和威胁区判断。如果地图很大、种群很多、迭代次数很大这个开销非常可观。后来我把适应度评估改成批量numpy计算避免在循环中逐点调用地形高程函数实测计算时间减少了约60%。如果地图分辨率很高还可以把地形预采样成栅格高度图用二维插值代替实时高斯计算。5.5 后续扩展方向这个融合框架可以继续往几个方向扩展一是加入差分进化的变异算子进一步提升种群多样性二是把威胁区建模改为动态变化的处理运动目标规避三是结合RRT或者A*生成初始航迹再用改进鲸鱼算法做优化精修。每次迭代中加入早停机制当连续多代最优代价变化小于阈值时提前退出也能节省不少计算时间。最后分享一个我在调试中特别受益的习惯每次实验完一定要把航迹画出来看而不只是看收敛曲线的数值。收敛曲线下降得漂亮不代表航迹就是可飞的有时候曲线平缓但航迹扭曲得厉害那多半是平滑度权重设置得太低。把三维航迹图和二维俯视图、侧面投影图三个图配合起来观察能更快发现目标函数设计里的问题。