无人机三维航迹规划这几年一直是个热门方向,尤其把智能优化算法用进去之后,论文和工程验证都出了不少成果。我在实际项目中试过粒子群、鲸鱼算法、差分进化这些常见套路,说实话单用一种算法,要么陷入局部最优,要么后期收敛慢,真正落地到三维航迹上总是差一口气。后来把粒子群优化(PSO)的位置记忆机制融合进鲸鱼优化算法(WOA)里,做了个改进版本 PSO-ImWOA,用 Python 完整实现了无人机三维航迹规划,效果比单独用 WOA 或者 PSO 明显提升。这篇就把整个思路、建模过程、代码实现和调参踩坑经验完整写一遍,给正在研究智能算法航迹规划的同学一个可以直接参考的工程样本。
1. 整体设计思路:三维航迹规划到底在优化什么
1.1 航迹规划问题的本质
无人机三维航迹规划,说白了就是在三维空间中找一条从起点到终点的路径,这条路径要满足飞行安全、航程短、平滑可飞这几个基本要求。和二维路径规划不同,三维场景下多了高度维度的约束,比如地形起伏、山峰障碍、禁飞区域,飞行高度太低容易撞山,太高又暴露目标或者油耗增加,转弯太急物理上飞不出来。
所以这个问题本质上是一个带约束的多目标优化问题。常规做法是把约束条件加权合并成一个适应度函数,然后用优化算法去搜索一组航迹点,使得适应度值最小。
搜索的空间长什么样?假设我们把航迹离散成 N 个中间航迹点,每个点有 x、y、z 三个坐标,那么优化变量的维度就是 3N。加上起点和终点是固定的,整条航迹就是从起点出发,依次经过这 N 个点,最后到达终点。算法的任务就是找出一组最优的中间点坐标。
1.2 为什么选择 WOA 作为基础算法
鲸鱼优化算法是模拟座头鲸捕食行为的元启发式算法,包括三个核心机制:包围猎物、气泡网攻击和随机搜索。我选择它作为基础,主要有几个原因。
第一,WOA 的参数相对少,核心就是收敛因子 a 和螺旋常数 b,调参压力比 PSO 小很多。第二,WOA 的螺旋更新机制在搜索过程中天然有更好的探索能力,不容易像标准 PSO 那样快速早熟。第三,WOA 的代码结构清晰,方便做改进和二次开发,不管是后续加约束还是改编码方式,改动成本都比较低。
但 WOA 也有明显的短板。最突出的是它的位置更新完全依赖当前最优个体,也就是种群中的头鲸,如果这个头鲸落在局部最优区域,整个种群都会被带着往局部收敛。另外,WOA 缺少对个体历史经验的记忆机制,每只鲸鱼只跟当前状态走,不考虑自己曾经到过的更优位置,这在复杂多峰的地形环境里是很吃亏的。
1.3 PSO 的融合价值在哪里
PSO 的核心思想就两条:个体记忆和群体共享。每个粒子记住自己的历史最优位置(pbest),同时通过群体最优位置(gbest)实现信息共享。这个机制在收敛速度和局部开发能力上非常有优势,但 PSO 的问题是后期多样性快速下降,所有粒子都朝 gbest 靠拢,容易早熟。
那能不能把两者的优势拼在一起?我在设计 PSO-ImWOA 的时候思路很明确:保留 WOA 的螺旋搜索骨架,同时在位置更新的时候引入 PSO 的速度惯性机制和全局最优引导。这样一来,算法前期靠 WOA 的全局探索能力铺开搜索面,后期靠 PSO 的 gbest 引导加快收敛,同时用 pbest 保留个体的历史经验,避免种群被单一个体带偏。
这个融合思路的实现形式是在 WOA 的位置更新公式后面增加一个修正项,相当于给鲸鱼加了一个速度向量:当前位移的一部分朝自己的历史最优位置修正,另一部分朝全局最优位置修正。这个修正项的强度由惯性权重和两个学习因子共同控制。
2. 航迹建模与适应度函数设计
2.1 航迹点的编码方式
三维航迹规划的第一步是把航迹变成算法可以优化的变量。我采用最直接的坐标编码:给定起点 S 和终点 E,在 S 和 E 之间均匀插入 N 个待优化的中间点,每个点包括 x、y、z 三个坐标。算法优化的就是这 N 个点的共 3N 个坐标值。
为什么用这种编码方式而不是用距离-角度编码或者贝塞尔曲线控制点编码?坐标编码实现最简单,改造成本低,调试方便。距离-角度编码虽然能天然满足一些运动学约束,但维度之间的耦合性更强,算法搜起来更吃力。贝塞尔曲线编码出来的航迹是很平滑,但控制点的数量和位置跟实际航迹点的关系不直接,后期检查威胁代价和高度约束的时候多一层转换,反而不直观。
不过坐标编码有一个必须处理的问题:边界约束。每个航迹点的 x、y、z 都必须落在飞行区域范围内,比如 x 在 0 到 2000 米之间,z 在 50 到 500 米之间。越界处理我采用“吸附到边界”的策略,而不是重新随机初始化。重新随机初始化会让种群在迭代中产生大量无意义的个体,拖慢收敛;吸附到边界则让个体始终在可行域内,只是位置固定在边界上,后续迭代中它还有机会被更新出去。
2.2 适应度函数的四项加权指标
适应度函数是航迹规划的灵魂,设计得好不好直接决定算法能不能产生一条可飞的航迹。我用了四个指标的加权和。
第一个是航迹长度代价。直接计算航迹所有相邻点之间的欧氏距离之和,然后除以起点到终点的直线距离做归一化。这样处理的好处是长度代价的量纲稳定在 1 附近,不会因为地图尺寸变化导致数值大小失真。
第二个是威胁代价。在实际场景中,威胁源可以建模成若干个以中心点为球心、具有作用半径的威胁区,无人机进入威胁区就面临被探测或被击落的风险。威胁代价的计算方式是逐段检查航迹段到威胁源中心的最短距离,如果距离小于安全半径,就按距离的倒数累加一个较大的惩罚值。这个设计逻辑是:离威胁源越近,代价越大,惩罚越剧烈,算法自然会尽量让航迹远离威胁区。
第三个是高度代价。无人机飞行高度不是越低越好,也不是越高越好。低空容易撞地形,高空耗油且暴露概率高。我在目标高度附近设了一个舒适区间,比如 100 米到 150 米,航迹高度落在这个区间内不产生高度代价,超出区间则按差值线性增加代价。这样算法会尽可能让整条航迹维持在一个合理的高度带里。
第四个是平滑度代价。无人机物理上无法完成直角转弯,所以航迹中相邻航迹段的转角和爬升角都有上限。平滑度代价计算每个中间点处两个相邻航迹段的夹角(包括水平转角和垂直爬升角),如果超过最大允许值就累加惩罚。
四个代价加权求和,权重系数根据任务需求调整。如果任务更重视隐蔽性,就把威胁代价的权重调高;如果任务更重视飞行效率,就把长度代价调高。
def calc_fitness(route, env, weights): # route: 航迹点列表 [[x1,y1,z1], [x2,y2,z2], ...] # weights: [w_len, w_threat, w_alt, w_smooth] total_len = sum(distance(route[i], route[i+1]) for i in range(len(route)-1)) straight_len = distance(route[0], route[-1]) len_cost = total_len / straight_len threat_cost = 0.0 for i in range(len(route)-1): threat_cost += segment_threat_cost(route[i], route[i+1], env) alt_cost = sum(height_penalty(p[2], env.target_alt, env.alt_tol) for p in route[1:-1]) smooth_cost = sum(smoothness_penalty(route[i-1], route[i], route[i+1]) for i in range(1, len(route)-1)) return (weights[0]*len_cost + weights[1]*threat_cost + weights[2]*alt_cost + weights[3]*smooth_cost)2.3 三维山地环境仿真
测试算法的环境我用的是经典的山峰函数叠加模型。先生成一张数字高程图,在上面叠加若干个高斯型山峰作为地形障碍,再在特定位置布置威胁源。山峰函数的公式是:
[ z(x,y) = \sum_{i=1}^{M} h_i \cdot \exp\left(-\frac{(x-x_i)^2 + (y-y_i)^2}{\sigma_i^2}\right) ]
其中 (h_i) 是第 i 个山峰的高度,((x_i, y_i)) 是山峰中心位置,(\sigma_i) 是山峰的宽度系数。这个模型简单可控,你可以随意调整山峰数量、位置和高度来构造不同难度的测试场景。实际验证时我一般生成 2000×2000 米的地图,网格分辨率 10 米,这样地形数据矩阵就是 200×200,计算速度很快,又能模拟足够的起伏细节。
威胁源我在地图上放了几个球形区域,半径 80 到 120 米不等。无人机航迹段如果进入威胁区域,就按进入深度计算威胁代价。威胁模型也支持扩展成圆柱形、多层级风险区,看具体场景需求。
3. 算法核心机制:WOA 与 PSO 的融合改进
3.1 标准 WOA 的三大位置更新机制
标准 WOA 有三种位置更新策略。第一种是包围猎物,当 (|A| < 1) 时,鲸鱼向当前最优个体位置收缩靠近,位置更新为:
[ X_{new} = X_{best} - A \cdot |C \cdot X_{best} - X| ]
第二种是气泡网攻击,当 (p < 0.5) 时采用螺旋收缩策略,位置更新为:
[ X_{new} = X_{best} + D' \cdot e^{bl} \cdot \cos(2\pi l) ]
第三种是随机搜索,当 (|A| \geq 1) 时,鲸鱼随机选取一个个体作为参照,进行全局探索:
[ X_{new} = X_{rand} - A \cdot |C \cdot X_{rand} - X| ]
这个机制是 WOA 探索能力的核心,但问题也在其中。整套流程中没有任何一个环节让个体回顾自己的历史搜索经验。如果某只鲸鱼之前已经到过一个不错的位置,但因为头鲸带偏导致它在后续迭代中偏离了那个位置,它自己没有任何机制把它拉回来。这在多峰地形中是致命的。
3.2 PSO 速度-位置模型如何嵌入 WOA
我改进的核心操作是给 WOA 的三种位置更新结果都加一个“PSO 修正项”。具体做法是:先按 WOA 的规则计算出 (X_{woa_new}),然后以这一结果为基准,叠加一个速度向量:
[ V_{new} = w \cdot V + c_1 \cdot r_1 \cdot (P_{pbest} - X) + c_2 \cdot r_2 \cdot (P_{gbest} - X) ]
[ X_{new} = X_{woa_new} + V_{new} ]
其中 (w) 是惯性权重,(c_1)、(c_2) 是学习因子,(r_1)、(r_2) 是 [0,1] 均匀随机数,(P_{pbest}) 是每个个体自己历史上适应度最好的位置,(P_{gbest}) 是整个种群的历史最优位置。这个设计的物理意义很直接:鲸鱼在按 WOA 的策略游动的同时,会朝自己曾经到过的最好位置和经验共享的最好位置各偏一点,相当于在原来的搜索策略上叠加了一层记忆力和群体智慧。
这样融合之后,WOA 的全局探索能力保留了下来,不容易像纯 PSO 那样早熟;同时种群的收敛速度明显加快,因为 gbest 的引导项提供了一条快速逼近最优区域的捷径。pbest 的加入则防止个体被头鲸带偏之后无法自拔,等于多了一道保险。
3.3 非线性收敛因子与惯性权重的自适应调整
标准 WOA 的收敛因子 (a) 从 2 线性递减到 0,这带来一个问题:如果最大迭代次数设得比较大,前期探索期过长,后期开发期又太仓促;如果设得小,又容易过早进入开发阶段。我改用余弦形式非线性递减:
[ a = a_{min} + (a_{max} - a_{min}) \cdot \cos\left(\frac{\pi \cdot t}{2 \cdot T}\right) ]
其中 (t) 是当前迭代次数,(T) 是最大迭代次数。这样算法在前期维持较大的 (a) 值来保障全局探索,中后期快速下降增强局部开发,比线性递减更容易兼顾搜索广度和收敛精度。
PSO 部分的惯性权重 (w) 我做了类似的调整,从 0.9 线性递减到 0.4。前期较大的 (w) 保持个体速度惯性,便于维持探索多样性;后期较小的 (w) 让速度项衰减,个体更快朝 pbest 和 gbest 收敛。实测下来,这个组合比固定权重或者纯线性递减都要稳定,多次独立实验的方差也更小。
4. Python 代码实现与关键步骤解析
4.1 算法主流程结构
整体代码分四个模块:环境建模、适应度计算、PSO-ImWOA 优化器、结果可视化。环境建模负责生成数字高程地图和威胁源;适应度计算负责把一组航迹点转化为一个标量适应度值;优化器是整个程序的核心,负责种群初始化、迭代更新和边界处理;可视化部分用 matplotlib 生成三维航迹图和收敛曲线。
整个流程伪代码如下:
初始化种群(随机生成 N 个航迹点组成的个体) 计算每个个体的适应度,初始化 pbest 和 gbest for t in range(max_iter): a = cosine_decrease(t, max_iter) w = linear_decrease(t, max_iter) for each whale in population: 按 WOA 规则更新位置(包围/螺旋/随机搜索) 计算 PSO 速度项 V 位置 = WOA位置 + V 边界吸附处理 计算新适应度 更新 pbest update gbest 记录收敛曲线 输出 gbest 对应的航迹,绘制三维图4.2 PSO-ImWOA 核心更新代码
下面是优化器中位置更新的核心代码片段,我把 WOA 的三种更新策略和 PSO 修正项都放在了一起:
def update_position(self, whale, idx, t, a, w, gbest_pos): # whale: 当前个体,shape (3*N,) # a: WOA收敛因子, w: PSO惯性权重 r1, r2 = np.random.rand(2) A = 2 * a * r1 - a C = 2 * r2 p = np.random.rand() b = 1.0 l = np.random.uniform(-1, 1) # 按WOA规则计算基准位置 if p < 0.5: if abs(A) < 1: # 包围猎物,向当前最优个体收缩 X_woa = self.best_pos - A * abs(C * self.best_pos - whale) else: # 随机搜索 rand_idx = np.random.randint(0, self.pop_size) rand_whale = self.population[rand_idx] X_woa = rand_whale - A * abs(C * rand_whale - whale) else: # 气泡网螺旋更新 dist = abs(self.best_pos - whale) X_woa = self.best_pos + dist * np.exp(b * l) * np.cos(2 * np.pi * l) # PSO速度修正项 v = (w * self.velocities[idx] + c1 * np.random.rand() * (self.pbest_pos[idx] - whale) + c2 * np.random.rand() * (gbest_pos - whale)) self.velocities[idx] = v # 融合位置更新 new_pos = X_woa + v return new_pos这个代码里有几个细节我特意处理过。第一,WOA 公式里的绝对值算子我用 numpy 的 abs 实现,注意和 np.linalg.norm 区分,前者是逐元素绝对值,后者才是范数。第二,螺旋更新时用 np.exp(b*l) 来控制螺旋的螺距,b 取 1.0 是经验值,过大或过小都会让螺旋展开太快或太慢。第三,PSO 的速度项直接加到 WOA 的更新结果上,而不是替换 WOA 的位置,这样保证 WOA 的探索骨架始终存在。
4.3 三维航迹可视化
可视化我用 matplotlib 的 3D 投影实现。除了画出航迹线本身,我把数字高程地图也叠加上去,用 plot_surface 绘制地形,用 scatter 标记起点、终点和威胁源位置。这样生成的三维图可以直观地看到算法规划的航迹是否避开了山峰和威胁区。
import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111, projection='3d') X_map, Y_map = np.meshgrid(range(map_w), range(map_h)) ax.plot_surface(X_map, Y_map, dem_map, alpha=0.6, cmap='terrain') route = np.array(best_route) ax.plot(route[:,0], route[:,1], route[:,2], 'r-o', linewidth=2.5, markersize=4, label='PSO-ImWOA route') ax.scatter(route[0,0], route[0,1], route[0,2], color='g', s=100, label='Start') ax.scatter(route[-1,0], route[-1,1], route[-1,2], color='b', s=100, label='End') ax.legend() plt.show() plt.figure(figsize=(8, 5)) plt.plot(convergence_curve, 'b-', linewidth=2) plt.xlabel('Iteration') plt.ylabel('Best Fitness') plt.grid(True) plt.show()这里有个小技巧:地形表面模型用 plot_surface 绘制的时候,如果网格太密会非常卡。我实际测试时把 DEM 网格做了隔行采样,降采样到 100×100 左右再绘制,图形的视觉效果几乎不变,但渲染速度快了好几倍。
5. 实验对比与参数调优经验
5.1 相同条件下的三组算法对比
我做了三组对照实验,分别用标准 WOA、标准 PSO、改进的 PSO-ImWOA,在完全相同的三维地图和相同的起点终点条件下各跑 20 次。种群规模 30,最大迭代次数 300,航迹中间点数 N=5,也就是 15 个优化维度。
结果很典型。标准 WOA 的平均最优适应度值最高,而且 20 次实验之间的方差很大,说明算法稳定性不好,有的运气好能找到比较短的航迹,有的就卡在某个山峰旁边。标准 PSO 的收敛速度是三者中最快的,前期曲线下降很猛,但后劲不足,20 次实验中有相当比例的结果陷入局部最优,绕过了山峰但航迹明显绕远路。PSO-ImWOA 的综合表现最好,平均适应度和最优适应度都优于前面两者,方差也控制在很小的范围。
从收敛曲线看,PSO-ImWOA 最明显的优势是后期。标准 PSO 在 100 代左右基本就停止下降了,标准 WOA 在 200 代以后还有小幅波动,而 PSO-ImWOA 在 200 代左右还能稳定地从局部区域中跳出来,找到更短更安全的航迹。
5.2 关键参数的经验取值
先说种群规模。这个参数和问题维度直接相关。航迹点数量 N=5 时维度是 15,种群 30 就足够了;如果 N 增加到 10,维度升到 30,种群规模建议调到 40 到 50。盲目加大种群规模效果有限,还让每轮迭代的计算量成倍上升,收敛速度反而会变慢。
学习因子 (c_1) 和 (c_2) 我都取了 1.5。这个值在 PSO 类算法中属于经典取值,你可以微调,但尽量不要取太大。超过 2.5 之后,速度修正项会主导整个位置更新,WOA 的探索机制实际上被淹没了,算法就退化成带 WOA 空壳的 PSO,融合的意义就没了。
惯性权重 (w) 的初值和终值我设成 0.9 和 0.4。这个区间是多次实验后的折中,前期能保留足够的探索速度,后期不会因为速度残余太大导致个体在 gbest 附近反复震荡。
5.3 航迹质量评估的三个维度
算法跑完之后,怎么判断航迹质量?我只看三个指标。第一是航迹总长度和直飞距离的比值,这个比值越接近 1,说明绕路越少。第二是威胁区进入情况,检查航迹所有航迹段是否都在威胁半径之外,如果有触碰,看触碰深度是多少。第三是航迹的平滑性,统计所有转角超过最大允许值的中间点数量。
用这三个指标评估改进效果时,我注意到一个现象:融合 PSO 之后,航迹总长度通常比标准 WOA 短 5% 到 10%,而且航迹的平滑度更好。原因是 gbest 引导让群体更容易朝短路径方向收敛,而 pbest 记忆机制让个体不太容易在局部区域反复绕圈,转弯处的航迹点分布更均匀。
6. 常见问题与避坑指南
6.1 航迹点飞出了地图边界
这个问题在初版代码里非常容易遇到。坐标编码方式下,优化变量是连续实数,算法在更新位置时不会自动考虑边界约束,航迹点很容易飞出地图范围或者扎进地底下。我的处理方式是在每次位置更新之后立刻做边界吸附,x、y、z 三个维度分别检查:
new_pos = np.clip(new_pos, [x_min, y_min, z_min], [x_max, y_max, z_max])这样处理之后航迹点始终在可行区域内,不会出现无意义的适应度值。但注意一个问题,如果 z 的下界设得太高,那么所有航迹点都被强制抬升到一个固定高度,算法实际上是在二维平面上搜索,三维优化的意义就打了折扣。z 的下界要设置得合理,比如地形最高点以上少量余量,让算法在高度维度上有足够的调整空间。
6.2 适应度各项权重的失衡问题
权重设置是另一个高频踩坑点。我之前试用过一组权重,长度代价权重 1.0,威胁代价权重 10,结果算法把主要精力全放在躲威胁上了,航迹绕了一个巨大的弧线,长度代价严重失控。后来我一律采用归一化处理,把每个代价项都归一到同一数量级,再设置权重才有意义。
具体做法是先用一个随机生成的可行个体集合,统计每个代价项的均值,然后用这个均值把对应代价项归一化。这样即使换了地图,权重也不用重新调。如果你不想做归一化,另一种务实办法是直接实验调权重,先固定长度权重为 1,调整其他项的权重观察航迹形态的变化。
6.3 算法提前收敛到局部最优
这是所有元启发式算法都会遇到的问题。我处理这个问题的经验有两条。一条是检查收敛曲线是否出现“断崖式”下跌后彻底平台化,如果是,说明种群多样性在早期就被破坏了,这时需要降低 (c_2) 的取值,减弱全局最优引导的强度,或者把惯性权重 (w) 的减速度放缓。另一条是检查是否所有个体在空间中高度集中于某片区域,如果是,可以在每次迭代时对部分个体做随机扰动,增加探索性。
实际测试中发现,PSO-ImWOA 比标准 WOA 和标准 PSO 发生早熟的概率都要低。原因是两个算法的机制互相制约,WOA 的随机搜索策略保证了探索下限,PSO 的记忆机制保证了开发上限,叠加后不容易出现单一机制主导导致的失衡。
6.4 高维场景下的维度灾难
当航迹点数量设置得很大时,算法效果会明显退化。我试过 N=20,也就是 60 维的优化问题,同样的种群规模和迭代次数下,收敛速度和航迹质量都比 N=5 差不少。这个问题的根源不是算法本身,而是高维空间搜索难度本身在指数级上升。
处理方式有两种。一种是分层优化,先用较少航迹点(比如 N=5)做粗规划确定航迹的大致走向,再在粗航迹的基础上插值细化为更多点,用局部优化微调。另一种是改用分段规划,把整条航迹切成若干段,每段分别用 PSO-ImWOA 优化,段与段之间用平滑算法衔接。两种方案我都试过,分层优化的实现更简单,效果也足够用。
7. 代码的扩展思路与后续方向
这套 PSO-ImWOA 代码框架的扩展性不错。如果要加新的约束条件,比如禁飞区、动态威胁源,只需要在适应度函数里再加一个代价项,其他部分不用大改。如果要做多无人机协同航迹规划,可以把适应度函数改成多目标形式,或者用分解策略把问题拆成多个单目标子问题并行优化。
我在实际项目里还做过一次扩展:把静态威胁源改成移动威胁源,每个威胁源的位置随时间变化,航迹规划从一条静态路径问题变成了路径和时间双重优化问题。做法是把时间也编码进航迹点,算法同时优化空间坐标和到达时间。虽然收敛速度明显变慢,但 PSO-ImWOA 的搜索能力仍然能支撑起这个更高维度的求解。
另外,关于 Python 运行速度,如果地图规模很大或种群规模调高,可以直接把适应度计算函数做向量化,去掉 for 循环改用 numpy 矩阵运算,效果立竿见影。我在 200×200 的 DEM 地图上测试过,向量化之后单次迭代时间从 1.2 秒降低到 0.3 秒,300 代跑完只需要一分半钟。
有个细节值得单独说一下:如果你准备把代码发给别人复现,环境依赖必须写清楚。重点就是 numpy 和 matplotlib,还有 Python 3.8 以上版本。其他库尽量不要引入,引入得越多,别人复现环境的成本就越高。我见过很多开源代码卡在依赖问题上一跑就报错,这个问题其实可以用 requirements.txt 轻松解决,只是很多同学容易忽视。
最后再分享一个自己用下来的心得:第一版代码跑通之后,先别急着调参和换算法,先拿一组固定的随机种子把基线结果稳定住。我用 random seed 42 固定之后,每次运行结果完全一致,后面所有改进的对比都有确定的参照系了。这个习惯看起来朴素,实际做算法对比实验时能帮你省掉大量重复调试的时间。