拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Matlab实现多目标路径规划:混血算法生成Pareto最优解

做路径规划课题的人应该都有过这种体验单目标的A*跑出来的路径确实最短但实际用起来总有点别扭——要么拐弯太急要么贴障碍太近要么在机器人、无人机上根本飞不出来。多目标寻径这件事本质上不是在“找一条路”而是“在好几条路之间做权衡”。今天这篇就用Matlab把一个多目标寻径项目完整串一遍核心是“混血算法”——行业里习惯叫法标准说法是混合算法Hybrid Algorithm把全局搜索、局部搜索和启发式策略揉在一起让路径规划从“只能给一个解”变成“给出一组可选Pareto解”。这篇文章适合正在做机器人导航、无人机航迹规划、AGV调度、自动泊车甚至喷漆路径规划等相关课题的同学和工程师。会涉及到栅格地图建模、多目标评价函数设计、遗传算法与局部搜索融合、Pareto前沿筛选、以及最后如何从一堆候选解里挑一条能用的路。环境为Matlab不依赖额外工具箱核心代码可以直接抄。1. 多目标寻径先搞清楚我们到底在优化什么很多人一上来就写代码结果写了半天目标和约束都还没想清楚。多目标路径规划和单目标最本质的区别在于单目标只有一个答案多目标给的是“一组答案”你需要在这组答案里做选择。1.1 单目标路径规划差在哪传统Dijkstra、A*这类算法本质都是在解“最短路径”问题。如果你是在拓扑地图或者栅格地图上找一个长度最短的无碰撞路径A*已经做得够好了而且有完备的最优性保证。但是在真实工程里我们很少只关心长度这一个指标。举几个最常见的场景。自动导引车AGV走最短路径如果没有考虑转弯次数可能在仓库通道口频繁原地打转无人机规划最短航迹如果不考虑航迹的平滑度飞控在拐点处会出现剧烈姿态变化直接导致能耗飙升甚至失控服务机器人贴着墙走最短路径看着没问题但一旦有人开门或者通道有点杂物机器人几乎没有反应空间。这些场景都指向同一个结论路径规划不是一维优化问题而是多维优化问题。而多维目标往往互相冲突——路径越短平滑度可能越差越追求安全距离路径就越绕。你没法把这些目标简单地加成一个数因为不同场景对目标的偏好完全不一样。上一组直观数据对比同样的30x40栅格地图起点在左上角终点在右下角纯A*最短路径长度约58个栅格单位但是中间包含45度和90度的急转弯接近8处如果要求最大转角不超过30度路径长度会涨到73左右安全距离阈值从1.5提到3个栅格单位之后路径长度会进一步涨到85。这些数值完全取决于任务本身你不能预先替用户拍板。这就是多目标优化存在的意义。1.2 多目标模型的四个常用指标在Matlab里做多目标寻径第一步就是把路径抽象成一串有序节点数组。每条路径可以表示成一个nx2的矩阵第一列是x坐标第二列是y坐标。基于这个结构最常用的目标函数有这么几类。路径长度是最基础的指标计算所有相邻节点欧氏距离之和。这个不用多说。平滑度指标我习惯用路径上所有相邻线段夹角的总和来衡量夹角越大说明转折越急也可以直接用最大转角或者曲率积分。安全裕度指标把栅格地图做一次距离变换得到每个自由栅格到最近障碍物的距离然后把路径经过的栅格距离取倒数求和距离障碍越近这个惩罚值越大。任务效率指标比如AGV的总转弯时间、无人机的总能耗这类指标和目标函数直接挂钩通常需要用路径几何特征二次计算。这四个指标之间往往是冲突的。一条绝对最短的路径很可能贴着障碍物走而且拐弯极多。一条绝对平滑的路径可能会绕一个很大的弧线。一条绝对安全的路径可能会在空旷区域绕很久。这篇文章的核心不是帮你决定“哪个更重要”而是把选择权交还给你——一次性生成一组Pareto前沿解你在这组解里根据实际场景挑。1.3 从无人机到喷漆场景决定目标权重这里把热词里那些花式路径规划场景统一收拢一下。无人机路径规划和地面AGV最大的区别在于三维空间和更严格的动力学约束但目标函数建模思路是一样的把高度变化、能耗、雷达探测风险等加进平滑度或风险项就行。泊车路径规划更特殊车辆有最小转弯半径这个约束其实可以转成“最大曲率限制”在平滑度目标里加一个硬约束。喷漆路径规划则不太一样它主要关心的是喷枪覆盖路径的总长度和转向次数本质上还是路径平滑和效率的综合优化。这些场景不是要你重新写算法而是告诉你怎么把领域约束翻译成目标函数和约束条件。框架是通用的变的只是目标函数。2. “混血算法”的选型逻辑2.1 为什么单一算法不够用很多人会问既然要做多目标直接用NSGA-II不行吗答案是NSGA-II可以跑但在路径规划这个问题上纯遗传算法有一个致命弱点——邻域搜索能力弱后期收敛慢而且很容易生成大量不可行路径。路径规划的解空间和传统函数优化不一样一个节点序列要同时满足连通性、无碰撞、起点终点确定这三大约束随机交叉变异出来的个体大概率是失效的。反过来如果你只用A*或者Dijkstra这种精确算法你只能得到一个长度最优解完全无法生成多样化的候选路径。RRT和RRT*这类采样算法随机性强能探索出不同形态的路径但解的质量波动很大而且同样没有“多目标”的概念。这就是“混血算法”要解决的问题。我的方案是A*负责生成高质量初始种群遗传算法负责全局多目标搜索2-opt和样条平滑作为局部搜索算子负责精细雕刻距离变换地图负责快速评价安全性。四层配合各干各擅长的事。用混血算法有这么几个实实在在的好处。初始种群里有A*生成的路径打底种群整体质量高进化过程中不会从零开始蒙。遗传算法天然支持多目标框架用非支配排序加拥挤度距离就能给出Pareto前沿。局部搜索算子弥补遗传算法“爬山能力弱”的短板能对前沿解的局部路径做微调。距离变换地图bwdist让安全评价从逐点碰撞检测变成向量化计算速度提升一个数量级。2.2 混合策略的三层含义“混血”这个词虽然听起来不太学术但很形象。这里说的混合其实包含三个层次。第一层是算法类型的混合把A*这种精确算法、遗传算法这种元启发式算法、2-opt这种局部搜索算法放在同一个框架里。A*保证基础解质量遗传算法保证解集的多样性2-opt保证局部收敛性。第二层是全局搜索和局部搜索的混合这也是Memetic Algorithm的核心思想——种群进化到一定代数后对当前Pareto前沿的个体做局部搜索相当于在大范围搜索的同时把优秀解“磨”得更精细。这个思路来自“文化基因算法”最早是Dawkins在《自私的基因》里提的meme概念后来应用在优化上效果出奇地好。第三层是多目标机制和单目标机制的混合多目标进化负责生成多样化的前沿解最后用TOPSIS或者加权评分从Pareto前沿里挑选单条最优路径兼顾“多解探索”和“单解决策”。我实际测试下来这种三层混合比纯NSGA-II在同样迭代次数下Pareto前沿的解分布更均匀而且靠近右下角那条“最短路径”的解质量明显更高原因就是A*种子和局部搜索在起作用。2.3 Matlab为什么要强调“玩转”Matlab在路径规划领域被嫌弃“跑得慢”但其实它有一批非常趁手的工具。bwdist距离变换函数是图像处理工具箱里的矩离变换一次就能得到全图所有栅格最近的障碍物距离后面评价任意一条路径的安全指标时只需要查表几分钟就能跑完几百次迭代。sub2ind函数用于把二维坐标快速转成一维索引这在查距离场时必不可少比逐点循环快得多。cscvn样条插值函数专门做曲线平滑生成的路径可以直接给飞控和车辆控制器用。paretofront或者自己写的非支配排序代码配合scatter可以实时画出Pareto前沿调试的时候非常直观。有人可能会提到Real-Time Pacer那个老梗——Matlab确实不适合做严格实时系统但离线路径规划本来就不是实时任务一次规划30秒内出结果完全够用你甚至可以用parfor把种群评价并行化。2.4 环境准备运行前的关键设置开始写代码之前确认几件事。推荐用R2021a以上版本最基础的Matlab本体加Image Processing Toolbox就够了。安装方面直接从MathWorks官网下载对应平台的安装程序登录账号激活工具箱在安装时勾上即可没有特殊配置要求。启动后设置当前文件夹为你的工程目录保证中文注释不乱码的话建议用UTF-8编码保存脚本Matlab的slCharacterEncoding参数在R2021b之后默认UTF-8问题不大。3. 核心实现Matlab代码逐段拆解3.1 栅格地图与路径表示栅格地图是路径规划里最常见的地图表达方式相当于把连续空间均匀离散化每一格要么是空闲要么是障碍。下面用30x40的栅格做演示左上角为起点右下角为终点。% 初始化地图0 空闲1 障碍 mapRows 30; mapCols 40; map zeros(mapRows, mapCols); % 随意放几个矩形障碍物 map(10:13, 5:9) 1; map(18:25, 20:24) 1; map(5:8, 30:33) 1; % 起点和终点用 [x, y] 表示 startNode [1, 1]; goalNode [mapCols, mapRows]; % 距离变换distField 里每个值表示该栅格到最近障碍物的距离 freeSpace map 0; distField bwdist(freeSpace);这个距离变换非常关键。你想想如果不做距离变换评价一条路径的安全性就得逐段判断每个点是否碰撞还要算到最近障碍物的距离那是O(n*m)的复杂度。有了distField任何路径节点往地图里一查安全距离直接O(1)拿到。这就是“预处理换速度”的思路。路径用一个nx2的数组表示n是路径节点数。注意第一列是x方向列索引第二列是y方向行索引访问distField时要用sub2ind(size(distField), path(:,2), path(:,1))行列别搞反了。这个坑我踩过好几次坐标索引交换会导致距离场查表全乱。3.2 多目标评价函数的Matlab实现核心目标函数我拆成三个路径长度、平滑度、安全裕度。当然你可以继续加能耗、时间等专用目标这里先给三个最通用的。function [len, angleCost, safeCost] evaluatePath(path, distField) % 路径长度 dxy diff(path, 1, 1); segLen sqrt(sum(dxy.^2, 2)); len sum(segLen); % 平滑度统计所有相邻线段的转角绝对值之和 vec1 dxy(1:end-1, :); vec2 dxy(2:end, :); norm1 sqrt(sum(vec1.^2, 2)); norm2 sqrt(sum(vec2.^2, 2)); cosTheta sum(vec1 .* vec2, 2) ./ (norm1 .* norm2 eps); cosTheta max(-1, min(1, cosTheta)); % 防止浮点越界 angleCost sum(acos(cosTheta)); % 安全裕度路径节点到最近障碍物距离的倒数求和 idx sub2ind(size(distField), path(:,2), path(:,1)); pathDist distField(idx); safeCost sum(1 ./ (pathDist 1e-3)); end三个目标放在一起量纲完全不同。长度是几十转角是几弧度安全倒数可能是几百。如果后续用了TOPSIS或者归一化加权需要先对各目标做标准化处理。如果直接用NSGA-II思路非支配排序对量纲不敏感因为只比较支配关系不用加起来这也是多目标进化算法比加权求和的优势——你不需要提前设计权重权重在最后决策阶段再给。3.3 混合算法主流程这一节我直接给主循环的架构这个结构是项目真正核心的部分。% 参数设置 popSize 120; % 种群规模 maxGen 150; % 迭代次数 crossProb 0.85; % 交叉概率 mutProb 0.15; % 变异概率 localSearchInterval 10; % 每10代做一次局部搜索 % 初始化种群 pop cell(popSize, 1); % 用 A* 生成 10 条高质量可行路径作为种子 for i 1:10 pop{i} astarPath(map, startNode, goalNode); end % 其余个体用随机游走 可行性修复生成 for i 11:popSize pop{i} randomFeasiblePath(map, startNode, goalNode); end % 评价初始种群 [objValues, feasibleFlag] evaluatePopulation(pop, distField); % 进化主循环 for gen 1:maxGen % 选择锦标赛选择 parents tournamentSelect(pop, objValues, 3); % 交叉按交叉概率生成子代 offspring crossover(parents, crossProb, map, startNode, goalNode); % 变异按变异概率扰动节点 offspring mutate(offspring, mutProb, map); % 合并父代和子代 combinedPop [pop; offspring]; combinedObj [objValues; evaluatePopulation(offspring, distField)]; % 非支配排序 拥挤度距离 [fronts, crowdDist] nonDominatedSort(combinedObj); % 环境选择保留前 popSize 个个体 [pop, objValues] environmentalSelect(combinedPop, combinedObj, fronts, crowdDist, popSize); % 每隔一定代数对Pareto前沿个体做局部搜索 if mod(gen, localSearchInterval) 0 pop localSearchOnFront(pop, objValues, map, distField); end end % 输出最终Pareto前沿 paretoIdx find(objValues(:,3) 1); % 简化为用非支配层标记 paretoPaths pop(paretoIdx); paretoObj objValues(paretoIdx, 1:3);这部分逻辑很清楚但有几个细节需要重点说明。A*生成的10条路径虽然都是“最短路径”如果地图确定A*结果其实是同一条。那多样性从哪来做法是给A*的代价函数加不同权重比如扩大安全项系数让它绕开障碍物远一点这样就得到形态不同的高质量种子。这是我实际调试时发现的处理办法不然初始种群多样性不够后面怎么进化都拉不开差距。随机可行路径的生成最稳妥的方法是随机取若干个路径点然后用A*连接相邻点这样能保证整条路径连通且无碰撞。直接在地图上随机游走很容易走进死胡同产生一堆废品。nonDominatedSort和environmentalSelect这两个函数思路来自NSGA-II。快速非支配排序核心逻辑是对每个个体统计“有多少个体支配它”以及“它支配哪些个体”然后分层剥离。拥挤度距离计算是每个目标维度上相邻个体的距离和边界个体置为无穷大。这套逻辑不复杂网上有很多参考实现Matlab纯代码一百行以内能写完。3.4 局部搜索与平滑处理混血算法的点睛之笔在“局部搜索”。遗传算法擅长全局撒网但不擅长精细加工。路径规划里的精细加工有两个操作极其有效。第一个是2-opt。原理非常简单如果一条路径里存在两条交叉的线段把交叉点之间的部分反向连接路径长度立刻缩短而且不会有碰撞问题。这个操作在路径规划领域相当于“剪刀手”能快速消除环和交叉。function newPath opt2(path, distField) newPath path; n size(path, 1); improved true; while improved improved false; for i 2:n-2 for j i1:n-1 % 反转 i 到 j 之间的路径段 candidate [path(1:i-1,:); flip(path(i:j,:), 1); path(j1:end,:)]; % 检查可行性不能经过障碍物 if isFeasible(candidate, distField) if pathLength(candidate) pathLength(newPath) - 1e-6 newPath candidate; improved true; end end end end path newPath; end end第二个是样条平滑。路径是一系列离散节点直接给控制器用会存在速度突变。用cscvn做三次样条插值然后重采样到固定密度路径就变成了一条连续可导的曲线。function smoothPath splineSmooth(path, numPoints) % 使用 cscvn 做样条插值 pp cscvn(path); tt linspace(pp.breaks(1), pp.breaks(end), numPoints); pts fnval(pp, tt); smoothPath pts; end注意一个关键点样条平滑后的路径点可能微微穿入障碍物轮廓所以平滑后必须重新做碰撞检测和安全性检查。如果穿障碍了就把样条的控制点往离障碍远的方向微调或者直接在该区域保留原始折线别强行平滑。这个矛盾本身就是多目标问题平滑度和安全性天生冲突。4. 常见问题与排查技巧实录这部分是踩坑记录每一个都是我实际调代码时遇到的不是从文档里抄的。4.1 问题速查表现象可能原因处理办法种群跑了好几代全是不可行解随机初始化路径质量太低或者交叉变异产生大量穿障碍路径强制用A*生成30%以上初始种子交叉变异后加可行性修复函数Pareto前沿只有3~5个点严重稀疏种群多样性不足拥挤度选择失效提高变异概率每隔30代引入随机新个体移民策略有一段路径总是贴着障碍走安全指标很差安全目标权重不够或者局部搜索只优化长度在局部搜索里同时检查安全距离对过近节点做排斥式偏移结果路径有直角平顺性差平滑度目标只在进化后期才生效优化不充分增大平滑度目标系数对最终Pareto解统一做样条平滑计算速度慢一轮评价要好几秒目标函数逐点循环没有向量化使用bwdist距离场sub2ind查表避免在逐节点循环里调用find每次运行结果差异大不稳定随机数种子未固定在脚本开头用rng(42)固定随机种子4.2 参数调优经验关于交叉概率和变异概率教科书一般推荐交叉0.8到0.9变异0.1到0.2我在路径规划这个场景实测下来变异率反而要稍微调高一点0.15到0.25比较合适。原因在于路径编码是节点序列交叉操作的破坏性比二进制编码小但变异操作对路径形态的更新贡献更重要——它负责把路径“掰”向另一个方向。种群规模和迭代次数的关系也值得提。路径规划解空间很大但因为有A*种子和局部搜索兜底种群规模不需要太大120到200就够。迭代次数150代左右可以收敛太多反而浪费时间因为局部搜索已经把每个前沿解磨到底了。这里的“底”是相对的——每次局部搜索把个体推到局部最优遗传算子再把它拉出来探索新区域形成交替。还有一个容易被忽略的点局部搜索的执行间隔。我做localSearchInterval 10也就是每10代才对前沿个体做一次局部搜索。如果每代都做计算量会翻倍而且过早把种群锁定在几个局部最优附近反而破坏了多样性。4.3 由本框架扩展的场景动态避障小车路径规划可以在每次重规划时把上一时刻的路径作为种群中的一个个体这样环境变化后搜索起点离上一个解很近规划速度快很多。多机器人路径规划比如热词里那篇“基于改进冲突搜索的多机器人路径规划算法”可以把冲突消解写成额外目标函数。多机路径的冲突次数、等待时间都加进评价体系再用时间窗约束修正路径和我这套框架完全兼容。喷漆路径规划喷漆机器人更关心覆盖率和路径转折次数可以把转折次数作为平滑度目标的一部分把覆盖率作为约束条件。这个场景本质上是“覆盖路径规划”而不是“点到点路径规划”但在生成覆盖路径后连接各个覆盖段的顺序仍然可以用这个框架来做。Matlab里其他功能模块联动也是一个扩展思路。比如把路径规划结果导出到Simulink里做轨迹跟踪仿真或者用图像处理工具箱处理真实地图照片生成栅格地图再交给路径规划算法整体链路都能在Matlab生态内闭环。最后再说一个我自己的心得体会。多目标路径规划这个项目最容易犯的错误不是算法写错而是“不知道下一步该看哪里”。Pareto前沿解可视化之后一切就都豁然开朗了。scatter(paretoObj(:,1), paretoObj(:,3))一行代码就能把路径长度和安全风险的关系画出来你会看到一条典型的“弓形曲线”往下弯——长度越短风险越高。看到这张图优化逻辑就通了你以后选路径也有依据了。我自己做这个框架踩过不少坑比如sub2ind行列写反那次折腾了一上午后来养成了一个习惯所有涉及坐标访问的地方统一用[x, y]结构存路径访问矩阵时统一sub2ind(size(mat), y, x)再没出过问题。这个项目本身可以继续向很多方向延伸我就先分享到这里。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门