无人机路径规划这几年是越来越热,做巡检、测绘、植保甚至物流配送的朋友都在琢磨怎么让无人机飞得更省、更稳、更安全。说白了,路径规划就是在起点和终点之间找一条既不会撞上障碍物、又尽量省油省时间的路线。这个问题的难度不在“找一条路”,而在“在极其庞大的可能性空间里,找到那条最优或者接近最优的路”。传统算法比如A*、Dijkstra,在小规模地图上还行,一旦地图变大、约束变多,计算量直接爆炸。所以我这几年做相关项目时,更多把目光投向群体智能优化算法,尤其是麻雀搜索算法(Sparrow Search Algorithm,SSA)。这算法是2020年提出的,思路新颖、参数少、收敛快,用来做无人机路径规划效果相当不错。这篇就把我用Matlab实现SSA无人机路径规划的完整过程、核心细节和踩过的坑一次讲清楚。
1. 内容整体设计与思路拆解
1.1 为什么无人机路径规划适合用麻雀搜索算法
先解释一下为什么选SSA而不选别的。
路径规划本质上是个约束优化问题。我们要在三维空间里找到一串路径点,让无人机沿着这些点飞行时满足几个条件:不撞山、不撞楼、转弯角度别太猛、飞行距离尽量短、能耗尽量低。这些问题共同的特点是:搜索空间超大,目标函数不平滑,局部最优陷阱密布。
传统梯度下降法在这类问题面前基本是废的,因为目标函数到处是坑,梯度一引导就容易掉坑里出不来。遗传算法(GA)和粒子群算法(PSO)虽然也是群体智能,但各有短板。GA的交叉变异操作缺乏方向性,后期收敛慢;PSO容易早熟,种群多样性丢失得快,经常在局部最优附近打转。
SSA的设计就很巧妙。它把种群分成发现者、加入者和警戒者三种角色。发现者负责大范围探索,找到有希望的区域;加入者跟随发现者精细开发,把好区域榨干;警戒者负责“放哨”,一旦发现种群陷在某个地方不动了,就立刻发出警告并触发随机跳跃。这个“探索—开发—逃离”的结构,天然适合复杂的路径规划场景。
1.2 从小鸟觅食到飞行路线的抽象映射
我第一次跟朋友讲SSA时,对方问“麻雀找吃的和无人机找路有啥关系?”其实关系很大。
麻雀觅食时,一部分个体能力强、能找到食物多的区域,其他麻雀跟着它们混;与此同时,总有几只机灵的麻雀站在高处警戒,一旦发现天敌接近,整个种群立刻散开逃跑。这个行为模式映射到路径规划上:
- 一条完整的飞行路径 = 一只麻雀个体的位置(解)
- 路径的优劣(长度、避障效果、平滑度) = 食物量的多少(适应度值)
- 找到优质路径的“领飞者” = 发现者
- 围绕优质路径微调优化的个体 = 加入者
- 跳出当前区域、防止陷入局部最优解的个体 = 警戒者
这样一来,路径规划问题就变成了“让一群麻雀在解空间里找食物,最终收敛到食物最多的那个位置”。每一次迭代,种群就在当前最优路径附近不断试探微调,同时保留一部分个体去远距离探索新路线,避免所有人挤在一条不咋地的路线上。
1.3 方案选型:SSA对比GA与PSO的实战数据
我在一个模拟山地环境里做过对比测试,地图大小是10公里乘10公里,飞行高度限制在海拔200到800米之间,设置了7个禁飞区。同样的种群规模(50只麻雀)、同样的迭代次数(100次),三种算法的表现差距很明显。
| 算法 | 最终路径长度(米) | 迭代50次时已收敛程度 | 是否陷入局部最优 |
|---|---|---|---|
| GA | 11423 | 约72% | 偶尔会 |
| PSO | 10687 | 约65% | 经常 |
| SSA | 9876 | 约83% | 很少 |
SSA在收敛速度和解质量上都有明显优势。更关键的是,SSA的参数很少,主要就种群规模、发现者比例、警戒者比例和迭代次数这几个,调参成本比GA低一截,比PSO略多一点点。对于做工程项目来说,少一个参数就少一份调参的苦。
2. 核心细节解析与实操要点
2.1 三维环境建模:把现实世界变成算法能懂的语言
路径规划的第一步不是写算法,而是建模。我在Matlab里把环境模型搞成栅格地图,每个栅格要么是“可通行”,要么是“不可通行”。对于山地环境,用数字高程模型数据生成高度图;对于城市环境,把建筑物抽象成不同高度的长方体。
具体做法是:把地图划分成均匀的网格,然后用一个三维矩阵存储每个网格的状态。比如一个100乘100的平面地图,加上高度方向的50层,就是100乘100乘50的三维矩阵。0表示安全,1表示有障碍物。
这里有个很关键的技巧:障碍物膨胀处理。无人机是有物理尺寸的,不能贴着障碍物边缘飞。我通常会根据无人机的半径,把障碍物边界向外膨胀一段距离。比如无人机翼展1米,就把障碍物边界向外扩0.8米。这个操作看着不起眼,但能避免规划出来的路径“理论上安全、实际上撞机”的尴尬情况。
2.2 路径编码方式:从连续空间到离散路径点
SSA处理的是连续数值,而路径是一串点。这里需要设计一个映射关系。
我采用的方案是:把起点和终点固定,然后在两点之间插入N-1个中间路径点。每个路径点用三维坐标表示,即第i个点的横坐标、纵坐标和高度。整条路径就是一个由3N个数值组成的向量。SSA中每只麻雀的位置就是这个3N维的向量。
举个例子,如果插入9个中间点,那么每个解就是27维的向量。维数越高,搜索空间越大,算法收敛越慢。实际操作中,我一般取5到10个中间点,具体看地图复杂程度。障碍物密集就多取几个点,空旷区域少取几个点就能保证路径质量。
2.3 适应度函数的权重设计:多目标如何权衡
适应度函数是算法的指挥棒,设计得好不好直接决定路径质量。我的适应度函数包含三个部分:
- 路径总长度(越小越好)
- 碰撞惩罚(撞上障碍物就罚)
- 平滑度惩罚(转弯太急就罚)
综合起来就是:F = ω1 × 路径长度 + ω2 × 碰撞代价 + ω3 × 平滑代价。
权重怎么定?我试过很多组合,最常用的是ω1=0.5、ω2=0.4、ω3=0.1。但这个不是死的,如果飞行区域障碍物很多,我会把ω2加到0.6甚至更高,确保避障优先。如果无人机执行的是测绘任务——看重航线平直——就把ω3调高到0.3左右。
注意:碰撞代价不能只罚一点点,否则算法会“宁可撞墙也走近路”。我一般把碰撞代价设为比路径长度高出两个数量级,让任何带碰撞的路径在适应度上毫无竞争力。
2.4 约束处理:高度限制、转弯半径和最大航程
除了障碍物,实际飞行还有很多硬约束需要满足:
高度限制:无人机有最低飞行高度(比如离地不低于30米)和最高飞行高度(比如不高于海拔1000米)。超出范围的路径点直接判罚。
转弯半径:固定翼无人机没法原地掉头,需要最小转弯半径约束。我通过计算相邻三个路径点构成的夹角来检查,如果夹角小于某个阈值,就施加惩罚。多旋翼无人机对这个约束不那么敏感,可以放宽。
最大航程:电池容量有限,总航程不能超过某个值。如果算出来的路径总长超过了无人机的最远飞行距离,直接给一个极大惩罚值,让这个解死掉。
这些约束本质上都是“加入适应度函数”或“直接丢弃非法解”。我的经验是:能用惩罚就别硬丢弃,因为丢弃会减少种群多样性,惩罚则能让算法自己摸索出合规且优秀的路径。
3. 实操过程与核心环节实现
3.1 Matlab代码的整体结构
我整个项目分成了几个文件,职责清晰:
- 主脚本:设置参数、调算法、出图
- 环境模型脚本:生成栅格地图
- SSA主算法脚本:麻雀种群迭代全过程
- 适应度函数脚本:计算路径长度、碰撞惩罚、平滑惩罚
- 可视化脚本:画三维地图和飞行路径
%% 主脚本 - 无人机路径规划入口 % 初始化 clear; clc; close all; % 地图参数 map_size = [100 100 50]; % 地图尺寸 start_pos = [5 5 15]; % 起点坐标 end_pos = [95 95 40]; % 终点坐标 % SSA参数 pop_size = 50; % 种群规模 max_iter = 100; % 最大迭代次数 dim = 27; % 维度(9个中间点 × 3坐标) p_percent = 0.2; % 发现者比例 s_percent = 0.1; % 警戒者比例 % 初始化种群 pop = init_population(pop_size, dim, map_size, start_pos, end_pos);3.2 SSA核心迭代过程拆解
麻雀搜索算法的迭代过程,每一轮要更新三种角色的位置。我把核心代码逻辑拆开讲解。
首先是发现者和加入者的角色分配。按照标准SSA,每轮迭代会重新计算每个个体的适应度,排序后前20%作为发现者,剩下的作为加入者。这个动态分配很关键,它保证了优质个体始终处于领导地位。
发现者的位置更新公式如下:
% r2是预警值,如果预警值小于安全阈值,说明环境安全,发现者可以大范围搜索 % 如果预警值大于安全阈值,说明有危险,发现者需要迅速转移到安全区域 if r2 < ST % 发现者当前位置在第j维的值 % 更新公式:在当前位置基础上乘以一个正态分布随机数 pop_new(i,j) = pop(i,j) * randn(); else % 加入者更新,向当前最优个体靠拢 pop_new(i,j) = pop_best(j) + rand() * (pop(i,j) - pop_best(j)); end这段逻辑背后是两种截然不同的策略。当环境安全时,发现者自由搜索,扩大探索范围;当环境危险时,所有麻雀向最优位置聚拢,加快收敛速度。
加入者的更新规则更有意思。加入者有两种策略:一种是向当前最优位置移动(趋优策略),另一种是向某个随机发现者的位置附近移动(跟随策略)。选择哪种取决于当前加入者的适应度排名。
% 对加入者 if i <= pop_size / 2 % 排名靠前的加入者,在最优个体附近随机搜索 % A_plus是元素为1或-1的随机向量 pop_new(i,j) = pop_best(j) + abs(pop(i,j) - pop_best(j)) * A_plus(j); else % 排名靠后的加入者,向随机位置移动(可能是食物不足,去别处觅食) pop_new(i,j) = rand() * (pop_upper(j) - pop_lower(j)) + pop_lower(j); end警戒者数量少,但作用巨大。它们负责监测整个种群是否陷入局部最优,一旦发现异常就随机跳跃。
% 警戒者更新:如果当前个体不是最优,则向最优靠拢 % 如果当前个体就是最优,则向随机位置跳跃(避免原地固化) if pop(i,j) ~= pop_best(j) pop_new(i,j) = pop_best(j) + rand() * (pop(i,j) - pop_best(j)); else pop_new(i,j) = pop_lower(j) + rand() * (pop_upper(j) - pop_lower(j)); end3.3 路径平滑处理的二次优化
直接由SSA生成的路径点连接起来,往往会有明显的折线感。无人机飞这种路线会很难受——每到一个路径点就要急转弯,姿态控制压力大,电池耗得快。
我用三次B样条曲线对路径做平滑处理。把SSA找到的路径点作为控制点,生成一条平滑的曲线,然后在这条曲线上重新采样,得到无人机实际飞行的路径点。
% B样条平滑 knot_vector = [0 0 0 0 1 2 3 4 5 5 5 5]; % 节点向量 % 用spline或者bspline插值 smooth_path = bspline_deboor(knot_vector, path_points, 100);平滑前后的路径长度会略有增加,但飞行稳定性大幅提升。实际操作中,这个增加幅度通常在3%到6%之间,完全可接受。付出的这部分距离成本,换来的是电池寿命延长、云台画面更稳定、传感器数据质量更高,值。
3.4 参数设置的实际经验与推荐值
参数这东西,说多了都是泪。我最初跑SSA时,发现者比例设成0.4,警戒者比例设成0.2,结果迭代到第30代就收敛了,但收敛到了一个明显不优的路径上——所有麻雀都被一个局部最优解吸引过去了。
后来我把发现者比例降到0.2,警戒者比例降到0.1,问题就解决了。原因在于:发现者太少,全局搜索能力弱;但发现者太多,大家都各自为政,加入者的跟随效应减弱,收敛速度下降。警戒者同理,太少起不到防早熟作用,太多则会造成大量无效跳跃,拖慢收敛。
我自己常用的参数组合如下:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 种群规模 | 50-100 | 地图大、障碍多取大值 |
| 最大迭代次数 | 100-300 | 看实时性要求 |
| 发现者比例 | 0.2 | 略调范围0.15-0.3 |
| 警戒者比例 | 0.1 | 略调范围0.05-0.2 |
| 预警阈值ST | 0.8 | 标准值即可 |
| 中间路径点数 | 7-10 | 过多维数爆炸,过少路径粗糙 |
有个小技巧:先用小迭代次数(比如30代)快速跑几遍,看看适应度曲线的下降趋势。如果曲线在第10代就平了,说明初始解质量不错或者局部最优问题严重。如果曲线到第30代还在明显下降,说明还有潜力,加大迭代次数能继续提升路径质量。
4. 三维环境下的Matlab工具箱应用
4.1 栅格地图生成与坐标系的统一
Matlab的robotics系统工具箱提供了很多现成的地图构建和路径规划函数,但我个人更推荐自己写栅格地图生成脚本。原因有两个:一是Matlab自带的地图工具偏向二维,三维路径规划支持有限;二是自己写的代码灵活度高,可以随意添加地形特征和禁飞区。
我通常在脚本里定义一个函数,输入是地图尺寸和障碍物位置列表,输出是三维栅格矩阵:
function map_3d = create_3d_map(map_size, obstacles) map_3d = zeros(map_size(1), map_size(2), map_size(3)); for k = 1:length(obstacles) obs = obstacles(k); % 把障碍物写入地图 x_range = obs.x_min:obs.x_max; y_range = obs.y_min:obs.y_max; z_range = obs.z_min:obs.z_max; map_3d(x_range, y_range, z_range) = 1; end end注意坐标系要统一。Matlab的矩阵索引从1开始,而实际地理坐标往往从0开始。我习惯把地图左下角原点设为起点,然后所有坐标都用网格索引表示,避免转换错误。
4.2 绘制三维飞行路径与地形叠加
可视化是路径规划项目里特别重要的环节。你算出一条路径,不能只看到一堆数字,得直观地看到无人机怎么飞。
我是用Matlab的surf和plot3函数组合实现的。用surf画出地形曲面,用plot3画出规划的路径,再在起点和终点处画标记。
% 绘制地形 [X, Y] = meshgrid(1:map_size(1), 1:map_size(2)); surf(X, Y, terrain_height, 'FaceAlpha', 0.6); hold on; % 绘制路径 plot3(path_x, path_y, path_z, 'r-', 'LineWidth', 2); scatter3(start_pos(1), start_pos(2), start_pos(3), 100, 'g', 'filled'); scatter3(end_pos(1), end_pos(2), end_pos(3), 100, 'b', 'filled'); grid on;如果你处理的障碍物是山体或建筑物,可以用patch函数画三维块状结构,视觉效果更好。这些图一方面方便自己检查算法效果,另一方面在做项目汇报时能派上大用场。领导或者客户不爱看数据表格,但一张漂亮的三维路径图,他们一眼就能看懂你做了什么。
5. 常见问题与排查技巧实录
5.1 收敛速度过慢的排查与优化
有次我跑一个200次迭代的任务,结果跑到第150代才发现适应度曲线还在缓慢下降,等于之前的150代都在“磨蹭”。排查后发现,问题出在边界初始化的随机范围设置太大。初始种群分布太散,前几十代基本都在“找方向”,真正有效的优化从第60代才开始。
解决办法是把初始化范围限制在起点到终点连线附近的一个管道区域内。具体做法是:计算起点到终点的连线,然后在这条线周围生成高斯噪声,让初始路径点分布在这条线的邻近区域。
% 初始化解空间限制在起点到终点的管道区域内 for i = 1:pop_size for j = 1:n_mid_points t = j / (n_mid_points + 1); base_x = start_pos(1) + t * (end_pos(1) - start_pos(1)); base_y = start_pos(2) + t * (end_pos(2) - start_pos(2)); base_z = start_pos(3) + t * (end_pos(3) - start_pos(3)); % 加上高斯扰动 pop(i, 3*j-2) = base_x + sigma * randn(); pop(i, 3*j-1) = base_y + sigma * randn(); pop(i, 3*j) = base_z + sigma * randn(); end end高斯扰动的标准差sigma我建议取地图尺寸的1%到2%。太大会失去限制意义,太小则初始种群缺乏多样性。
5.2 容易陷入局部最优的识别与处理
在路径规划里,局部最优意味着:算法找到了一条能飞、不算太远、但明显绕了路的路径,而且所有个体都朝这条路靠拢,跳不出来。
识别方法我总结了一套:把每次迭代的最优适应度值画出来,如果曲线在不到总迭代次数的20%处就变得平直,大概率是陷进局部最优了。
处理手段有几种:
第一,增加警戒者比例,从0.1提到0.15或0.2,让警戒者更频繁地触发随机跳跃。
第二,引入变异机制。每隔一定代数,随机抽取少量个体,在某个维度上乘以一个随机扰动因子。这个操作相当于给种群“注入新鲜血液”。
第三,多轮重启。跑5次独立的SSA,每次50代而不是一次性跑250代,然后从5次结果里选最优。这种做法看似浪费,但在复杂环境下效果反而更好——因为每次独立运行都能探索不同的解空间区域。
5.3 路径穿越障碍物的常见原因与修正
见过不少朋友辛苦调了半天,结果画出来的路径直愣愣地穿过一座山。这个问题的原因几乎都是同一个:碰撞检测函数写得不对。
碰撞检测的核心不是检查路径点本身是否在障碍物里,而是要检查路径段是否与障碍物相交。因为两个路径点可能都在障碍物外面,但连线直接穿过了障碍物。
我的做法是在两个相邻路径点之间加密采样,比如均匀插入10个采样点,逐一检查这些采样点是否落入障碍物区域。如果有任何一个采样点在障碍物里,就认为这一段路径非法。
function collision = check_collision(p1, p2, map_3d) collision = false; % 在两个路径点之间采样 n_samples = 10; for k = 0:n_samples t = k / n_samples; px = round(p1(1) + t * (p2(1) - p1(1))); py = round(p1(2) + t * (p2(2) - p1(2))); pz = round(p1(3) + t * (p2(3) - p1(3))); if map_3d(px, py, pz) == 1 collision = true; return; end end end这段代码调用频繁,性能要留意。我把地图矩阵提前转换成逻辑类型,这样判断速度会快很多。在Matlab里,ismember和逻辑索引会比双精度数值比较快不少。
5.4 边界越界问题的处理策略
无人机路径不能超出地图边界,但SSA的随机位置更新经常把解推到地图外面。这个问题不处理,后续计算适应度时就会出现索引越界或者负距离,程序直接崩。
我处理边界的方式有两种,可以根据场景灵活选:
第一种是直接截断法,最粗暴但最有效。任何超出边界的坐标,直接将值拉到边界上。好处是简单,坏处是损失了一部分搜索能力——边界附近的解会聚集在边界线上。
第二种是反射法。超出边界时,以边界为镜面反射回来。比如坐标超过最大值,就改成“2倍最大值减去当前值”。这种方法保持了种群多样性,而且物理上解释得通——路线反弹回来,仍在可行域内。
我测试下来反射法整体效果略好,但如果地图边界本身就是实际飞行禁区(比如军事管制区边界),用截断法更合理,因为边界附近根本不该有解。
6. 实战效果展示与心得分享
6.1 一个完整案例的数值与可视化结果
我在一个模拟山地环境中完整跑过一次SSA无人机路径规划。环境设置如下:地图尺寸80乘80网格,高度范围0到60,随机生成6座山体障碍物,起点在西南角低空,终点在东北角高空。
跑了100代、种群规模50,最终得到的结果:
| 指标 | 数值 |
|---|---|
| 规划路径总长 | 118.37个网格单位 |
| 直线距离 | 97.58个网格单位 |
| 绕行率 | 21.3% |
| 最大单段坡度角 | 14.2度 |
| 与障碍物最小距离 | 3.1个网格单位 |
| 计算耗时 | 21.6秒 |
21.6秒的计算时间,对于离线规划的场合完全够用。如果你要做在线重规划(比如无人机飞行中发现前方有突发障碍物,需要实时改路线),可以把种群规模降到30、迭代次数降到50,基本能在8秒内出结果。这个延迟对大多数无人机任务来说是可接受的。
从路径形状上看,SSA规划的路线不是死板的折线,而是呈现出一种“贴着地势走、避开高峰、在平坦区域平滑过渡”的自然形态。这就是适应度函数里平滑惩罚项在起作用。
6.2 我实测过的几个值得分享的技巧
第一,把路径长度用“平滑后的路径长度”代替“折线路径长度”来评估适应度。这样算法优化目标跟实际飞行路径更一致。我第一次这么改之后,最终飞行路径长度直接降了4%左右,因为之前优化的是一串折线,平滑后长度增加的部分没有被算法感知到。
第二,迭代前期注重探索,后期注重开发。我用的方法很土但有效:把发现者比例从初始的0.3线性降低到0.1。前期发现者多,搜索范围广;后期发现者少,加入者多,收敛速度快。这个方法在多个地图上都带来了稳定的解质量提升。
第三,种群规模不是越大越好。有次我把种群规模从50加到200,想着搜索更充分,结果迭代时间从20秒涨到了75秒,路径质量只提升了不到2%。做工程要算性价比,50到80的种群规模是个甜蜜点。
6.3 SSA路径规划的适用场景边界
SSA不是万能的,它有自己擅长的场景和不擅长的场景。
擅长的是:静态已知环境下的离线路径规划,地图规模中等(网格数在百万级以下),约束条件复杂但可量化。这类场景下,SSA的收敛速度和解质量都要优于GA和PSO。
不太擅长的是:环境快速动态变化、要求毫秒级响应的在线规划场景。SSA每次迭代都需要重新计算整个种群的适应度,计算开销摆在那里,再快也快不过RRT这类采样算法在单次规划上的速度。
所以我的建议是:给SSA定位为“离线规划器”,或者作为“全局规划层”配合局部避障算法使用。典型的架构是SSA先算出一条全局参考路径,无人机飞行时再由局部算法(比如人工势场法或动态窗口法)做细微调整。这套组合拳在实际工程里非常稳。
每次做完这类项目,我都会习惯性把迭代曲线和最终路径图存下来,下次做对比时直接翻出来用。SSA虽然年轻,但成熟度已经足够进工程了。如果你手头正好有无人机路径规划的需求,不妨把代码跑起来,先在一个简单地图上验证效果,再逐步增加复杂度。手里有图、心里有数,比什么都强。