
无人机巡检项目的机巢布点选址听起来像是个带约束的覆盖问题但真正接手的电力巡检规划人员都知道难点不在于把公式列出来而在于把约束从现场搬进Matlab代码。机巢不是越多越好太密的覆盖浪费成本机巢也不能只考虑直线距离巡检需求点在高山上、线路走向完全顺着山谷续航余量、返航条件、任务时间窗每一项都会让原有方案失效。这篇文章就把我在电力巡检机巢选址里常用的算法思路、Matlab代码框架和参考文献清单分开讲清楚适合正在做无人机巡检规划、准备给机巢布局避坑的工程师参考。1. 巡检机巢选址的核心矛盾约束不是加分项而是边界条件1.1 续航半径与线路走向的硬耦合做机巢选址第一步不是画圆覆盖而是把巡检需求点离散化。电力巡检场景里需求点通常是杆塔、耐张塔、变电站出线终端塔或者被地质监测点名要求定期查看的山体区段。机巢要覆盖这些点本质是让无人机在电池约束下飞得到、回得来。很多人建模时直接把无人机最大航程除以二当成覆盖半径这是理想情况实际工程里必须考虑返航余量。举例说明一款常见多旋翼巡检无人机满载带光电吊舱续航40分钟巡航速度大约12m/s理论航程约28.8km理论半径14.4km。但这个半径在巡检中基本不能用因为巡检输电线路时往往要悬停拍摄、调整云台角度消耗的功率比匀速巡航高遇到3~4级风顺风飞和逆风飞的时间差很大再加上电池老化后的容量缩水我一般按理论续航乘以0.7折算。也就是说理论半径14.4km实际参与覆盖计算的半径我通常取8~10km。这个折扣不是保守而是让算法结果真正能用于飞行。有了实际覆盖半径R距离约束就落到了“杆塔-机巢”之间的距离矩阵上。注意这里不是欧氏平面距离而是考虑了高差和地形绕行的等效飞行距离。山区线路高差动辄几百米直线距离5km的塔和机巢实际航线可能要走7~8km。所以我在Matlab里算距离矩阵时除了用平面坐标求XY距离还会叠加DEM高程差修正。具体做法是先加载数字高程模型沿机巢到目标点方向采样把剖面的累计爬升量按比例计入等效距离没有DEM数据也可以用线路走廊的杆塔海拔表做线性插值近似。这一步看似粗糙却能让选址结果避开“图上能覆盖、实际飞不到”的尴尬。1.2 地理起伏、通信链路和返航条件的隐性约束除了飞不飞得到还要考虑落不落得下、控制链路通不通。机巢不是随便一块平地就能建需要考虑地势平坦度、土方量、道路可达性、附近是否有干扰源以及最关键的一点是否处于可用空域。这几个条件在做候选点筛选时就要提前过滤掉不要把明显不能建的位置放进算法候选集里否则算法迭代半天给出一个看似省钱的点现场踏勘直接否定等于白算。通信链路约束更隐蔽。电力巡检无人机通常工作在2.4GHz或5.8GHz频段机巢选址如果选在山谷底部电台信号被山体遮挡地面站和无人机之间容易丢链路。所以我在预处理时会对每个候选点计算通视范围把对关键巡检目标存在通视遮挡的候选点做降权或者直接剔除。如果项目有固定翼无人机巡检任务还要考虑起降航线空间候选点周边不可有密集树林和高耸障碍物。这些约束没有写进目标函数但必须在候选点生成阶段就处理干净否则约束再精细候选点本身不合格后面全白搭。还要特别提一下返航条件。很多模型只算“去程”距离小于R但无人机飞抵最后一个巡检点拍照后需要回到机巢降落实际等效路径是“去程全部巡检点串飞路径返程”。巡检任务通常是机巢起飞到某线路段沿着线路依次巡视多基杆塔最后返航。所以覆盖一个“点”只是基础更合理的建模应该覆盖“一个区段”。把路径长度拆解成作业段之后每个候选机巢的覆盖半径不是一个固定圆而是随巡检方向变化的串形区域。这会让模型的约束更多但也更真实。1.3 时效约束巡检窗口不只是给无人机定的电力巡检有不同的响应等级。日常周期巡视以月为单位窗口很宽松但雷雨季节过后、外力破坏高发区、缺陷复测等场景要求发现异常后在几小时内完成复核。这意味着机巢不仅要覆盖目标还要满足响应时间约束。无人机到达目标点的飞行时间、准备起飞时间、降落回收时间要全部计入。如果某些机巢位置虽然覆盖目标但到达时间超过要求窗口就不能算作有效覆盖。所以在我的模型里每个需求点除了有坐标和权重还会带一个允许的最大响应时间T_i。在计算覆盖判定时不使用“距离小于R”而是使用“等效飞行距离/巡航速度 起飞准备时间 T_i”。这样把时间和空间两个维度统一进了一个表达式。响应时间约束对山区巡检来说往往比续航半径更苛刻因为线路塔之间的距离不长但每个塔都要悬停检查时间消耗集中在作业阶段而不是飞行阶段。2. 从实际需求到数学模型目标函数和约束怎么写2.1 目标函数覆盖优先还是成本优先机巢布点本质是设施选址问题目标函数取决于项目阶段。新建一张巡检网时通常希望在预算内覆盖尽量多的重点目标模型写成“最大覆盖问题”运行阶段希望控制运维成本则写成“最小成本问题”。实际项目中很少有人只用一个目标常见做法是把成本和覆盖加权成一个单目标或者用多目标智能算法求Pareto前沿。我用的是加权单目标形式$$ \min Z -\alpha\sum_{i \in I} w_i \cdot cov_i \beta\sum_{j \in J} C_j \cdot x_j \gamma\sum_{i \in I} w_i \cdot (1-cov_i) $$其中(x_j)是0-1决策变量表示第j个候选机巢是否被选中(cov_i)表示第i个需求点是否被至少一个选中机巢覆盖(w_i)是需求点的重要程度权重重点保供线路和重要负荷通道的塔位权重更高(C_j)是第j个机巢的建设成本(\alpha)、(\beta)、(\gamma)是权重系数。这个目标函数把成本项和未覆盖惩罚项放在一起避免“模型只追求覆盖而疯狂建机巢”的情况。未覆盖项(w_i(1-cov_i))本质是对漏检风险的惩罚它比覆盖项更直观因为优化结果可以看出来“还剩下哪些高权重塔没覆盖到”方便人工研判。2.2 约束条件的数学化表达模型需要写清楚的关键约束有以下几类。覆盖唯一性约束每个需求点至少被一个机巢覆盖但对于某些预设允许漏检的场景可以放宽为软约束只在目标函数里惩罚。$$ \sum_{j \in J} y_{ij} \ge 1,\quad \forall i \in I $$机巢启用逻辑约束一个需求点只有在其所属机巢被选中时才能被覆盖。$$ y_{ij} \le x_j,\quad \forall i \in I, j \in J $$距离/时间约束$$ d_{ij} \cdot z_{ij} \le R_j M(1-y_{ij}) $$这里(z_{ij})是等效飞行距离当需求点i被机巢j覆盖时必须满足距离不大于该机巢的有效覆盖半径。(M)是足够大的常数用来保证未覆盖时不约束。容量约束每个机巢在某一巡检周期内能执行的任务次数有限。如果一个机巢同时覆盖太多高密集杆塔无人机一天内往返架次数会超过电池和运维人员上限。约束写为$$ \sum_{i \in I} q_i \cdot y_{ij} \le Q_j \cdot x_j,\quad \forall j \in J $$预算约束$$ \sum_{j \in J} C_j \cdot x_j \le B $$数量约束有些区域受空域、土地审批限制机巢总数不能超过(N_{max})。$$ \sum_{j \in J} x_j \le N_{max} $$这套模型本质上是一个带容量和预算的集合覆盖问题变体。需求点和候选点规模一旦超过几十个精确算法就会卡住所以要用启发式算法找近似最优解。2.3 多目标与量纲归一化在把目标函数写进代码前必须先做量纲归一化否则成本是几十万量级覆盖率是0到1的小数加权时覆盖率项会被完全淹没。我习惯把成本项也化为相对值除以总预算上限B让三个分项都在0~1之间。权重系数则在归一化后调整。公式就变成了$$ \min Z -\alpha\sum_{i} \frac{w_i cov_i}{\sum_i w_i} \beta\frac{\sum_j C_jx_j}{B} \gamma\sum_{i} \frac{w_i(1-cov_i)}{\sum_i w_i} $$这样三个分项量纲一致权重系数更容易解释。(\alpha)和(\beta)调节覆盖和成本的偏好试点期看重覆盖率(\alpha)取0.7、(\beta)取0.2运行期控制成本(\alpha)取0.4、(\beta)取0.5。(\gamma)通常略大于(\beta)让漏检惩罚高于成本节约的诱惑。3. 求解思路规模让你放弃穷举启发式算法是主力3.1 先算一道“穷举算力账”很多第一次做选址的工程师第一反应是枚举所有组合。我们算一笔账假设候选机巢10个计划建3个组合数(C(10,3)120)穷举确实完全可以。但实际项目里候选点至少30个目标选6~8个组合数就是(C(30,6)593775)每个组合还要评估30个需求点的覆盖情况和路径检查。即便Matlab循环写得很快也要跑很久更别提50个候选点选10个组合数已经上亿。工程上做方案比选时我们经常要做参数敏感性分析改一下覆盖半径跑一遍改一下预算上限再跑一遍。如果一次求解要花几十分钟根本没法做多方案对比。所以需要一个能在几十秒内给出稳定近似解的算法。3.2 为什么我最后选遗传算法选址问题常用的启发式算法有遗传算法、模拟退火、粒子群、蚁群优化。这几个我都试过对于“机巢数量少、候选点规模中等”的问题粒子群容易在离散编码上施展不开模拟退火对初始温度敏感蚁群算法参数更多最后我主力用的是遗传算法。原因很简单决策变量天生是0-1离散型正好对应二进制染色体交叉、变异算子语义清晰Matlab里无论是用全局优化工具箱的ga函数还是自写主循环调试都方便而且遗传算法能配合罚函数处理多种约束不用为每个约束单独设计修复逻辑。另一个重要原因是团队同事接手容易遗传算法的概念大家多少都了解后续改需求时沟通成本低。要注意的是遗传算法不是“原地起高楼”它还需要一个不错的初始种群。我的做法是先用贪心策略生成一批解每次优先选择“未覆盖权重最大”的候选点建巢重复直到满足数量约束。把贪心解混入随机初始种群能让算法前期收敛速度快很多最终结果也更稳定。3.3 约束处理的两条路罚函数法与可行解修正约束处理直接影响算法能不能在可接受时间内找到可用解。一条路是罚函数法在目标函数值上加上违约量的倍数。另一条路是可行解修正每次交叉变异后检查染色体是否满足硬约束不满足就修复。我在代码里两条路都用了。预算和数量约束用可行解修正如果染色体里选中的机巢数超过(N_{max})就把超出的机巢按覆盖率贡献排序删掉贡献最小的机巢直到数量达标。这样的好处是算法搜索空间始终在可行域附近不会浪费时间在明显不可能的方案上。覆盖率本身是目标的一部分不用强制约束容量约束我用罚函数处理因为容量稍微超过一点可以通过调度排班来弥补不值得为它严格惩罚到排除所有解。但罚函数系数不能拍脑袋。我一般先跑一次不带容量罚项的实验看目标函数各分项的量级再让容量罚系数比未覆盖惩罚高5~8倍。太大容易让算法过早收敛到少数几个可行解太小则可能出现大量超容量解霸占种群。4. Matlab代码实现核心模块与可直接套用的骨架4.1 数据组织与距离矩阵预处理写代码之前先把数据管理好。我习惯用三个矩阵demandCoord是需求点坐标candCoord是候选机巢坐标demandW是需求点权重向量。候选点的建巢成本存在candCost向量里。距离矩阵预处理是整个程序的第一个关键模块。如果坐标是经纬度不要直接用pdist2算欧氏距离那样误差很大。我一般先用projcrs投影到当地平面坐标系或者用deg2km做近似。无人机巡检覆盖半径通常只有几公里到十几公里投影后的平面坐标足够用。如果是小范围高精度需求用UTM投影。处理高差时我可以把距离矩阵写成% avoid: pdist2 in geographic coordinates directly x1 demandCoord(:,1); y1 demandCoord(:,2); x2 candCoord(:,1); y2 candCoord(:,2); h1 demandCoord(:,3); h2 candCoord(:,3); dx x1 - x2; % nDemand x nCand dy y1 - y2; dh h1 - h2; horizDist sqrt(dx.^2 dy.^2); % 将累计爬升按比例计入等效飞行距离 climbFactor 0.15; % 经验系数看地形复杂度 D sqrt(horizDist.^2 (dh * climbFactor).^2);climbFactor我取0.1~0.3山区取大值。这个经验系数对应的是“额外爬升能耗折算成水平距离”不要求很精确只要能让算法避开高差过大的候选点即可。如果项目有DEM数据可以把这部分替换成沿剖面的真实累计爬升效果更准。4.2 遗传算法主循环结构遗传算法主循环我通常不用工具箱的ga而是自己写一个短循环方便在中间插入调试代码和自定义约束。主流程就是初始化种群、计算适应度、选择、交叉、变异、修复、重复迭代。% 参数 popSize 80; maxGen 300; nCand size(candCoord, 1); needSelect 4; % 计划建巢数量 % 种群初始化每行是一个染色体1表示选中该候选点 pop initPopMix(nCand, needSelect, popSize, D, R, demandW); bestFit zeros(maxGen, 1); for gen 1:maxGen fit calcFitness(pop, D, R, demandW, candCost, budget, needSelect); bestFit(gen) min(fit); % 选择 parents tournamentSelect(pop, fit, 2, 3); % 交叉 newPop crossoverUniform(parents, popSize); % 变异 newPop mutateBitFlip(newPop, 0.05, nCand); % 修复保证每个染色体的选中数等于 needSelect newPop repairChromosome(newPop, needSelect); % 精英保留 pop elitistReplacement(pop, newPop, fit, 2); end选择我用锦标赛选择锦标赛规模3交叉用均匀交叉每个基因位以0.5的概率交换父本染色体变异采用位翻转变异率0.05。修复函数把0/1个数调整到恰好等于needSelect保证种群中所有解都满足数量硬约束。4.3 适应度函数里最容易被忽略的细节适应度函数是整个代码的灵魂写错一个符号算法结果就全偏了。我最容易踩的坑有两个一是没有对空染色体设Inf导致一窝蜂选择“什么都不建”的解二是距离矩阵在覆盖判定时用了“所有选中机巢中最小距离”但忘了给每个机巢加容量上限。先看适应度函数骨架function [fit, info] calcFitness(pop, D, R, demandW, candCost, budget, needSelect) nPop size(pop, 1); nDemand size(D, 1); fit zeros(nPop, 1); info zeros(nPop, 1); for k 1:nPop sel find(pop(k, :)); if length(sel) needSelect fit(k) Inf; % 硬性约束不满足 continue; end % 每个需求点到最近选中机巢的距离 minD min(D(:, sel), [], 2); covered minD R; % 覆盖收益归一化 coverRate sum(demandW(covered)) / sum(demandW); % 成本项归一化 cost sum(candCost(sel)); costTerm max(0, cost - budget) / budget; % 容量罚项 serviced sum(minD R, 2); % 被几个机巢覆盖 capacityViol sum(max(0, serviced - 3)); % 假设每巢最多覆盖3个需求点(示例) % 目标越小越好没覆盖惩罚 成本惩罚 容量罚 fit(k) (1 - coverRate) 0.3 * costTerm 0.8 * capacityViol; end end注意这里serviced sum(minD R, 2)写的是每个需求点被多少个选中机巢覆盖但容量罚应该是“每个机巢服务了多少需求点”方向反了。实际代码里需要分别循环机巢统计覆盖数量再对超额的机巢叠加惩罚。我故意把这个容易写反的地方点出来因为这是我在调试时卡过一个下午的问题。正确的容量统计思路是对每个选中的机巢统计落在其半径内的需求点集合判断集合大小是否超过上限。若超过把超出部分的覆盖率折算成罚项。实际项目中我会把容量约束放进修复阶段而不是罚函数这样迭代更快。但如果你希望保留少量超容量解以便后续调度调整就留在罚函数里。4.4 可视化与结果输出算完不能只给一串0和1要把结果画出来给现场评审看。我通常画三样东西候选点和需求点分布图、机巢覆盖范围图、迭代收敛曲线。覆盖范围图用viscircles画圆展开来看是否真的覆盖了计划中的高权重目标。但我提醒一句这个圆只是等效覆盖半径的示意不是真实航迹范围向领导汇报时最好标注“示意”二字避免被误解为精确空域范围。收敛曲线也有讲究。我关心的不是最后的适应度值而是曲线是否在最后20代还继续下降。如果还在明显下降说明迭代代数不够要加大maxGen如果前期就平了可能是种群多样性丢失要调高变异率或提高锦标赛规模。5. 算例演示一组模拟数据下算法相比随机布点强在哪5.1 测试参数设定为了说明算法效果我构造了一组模拟数据尽量贴近实际。假设一片巡检区域内共有30个需求点对应30基重要杆塔或线路巡检区段预选候选机巢10个计划建巢数量固定为4个无人机等效覆盖半径R取9km需求点权重按线路重要程度分成1、2、3三档机巢建设成本在20万~35万之间预算上限100万。迭代参数种群规模80迭代300代交叉概率0.8变异概率0.05锦标赛规模3精英保留2个个体。因为成本约束用罚函数处理所以还需要设定成本罚项权重。我设成本罚项权重为0.3覆盖漏检权重为1容量罚权重0.5。5.2 结果对比与分析先跑随机布点作为基准。随机从10个候选点里抽4个重复50次取覆盖效果最好的一次覆盖需求点23个覆盖权重和占总权重的81.6%总建设成本83万元。再用遗传算法跑一遍结果选中4个机巢覆盖需求点28个覆盖权重和达到95.3%总建设成本92万元。表面看成本增加了9万元但漏检的重点需求点从4个降到1个而且漏掉的那个点权重是最低的。从资产风险角度讲这个增加非常划算。方案机巢数覆盖需求点数覆盖权重占比建设成本随机布点50次最优423/3081.6%83万遗传算法方案428/3095.3%92万加权理想方案426/3091.2%79万方案差异的核心在于随机布点往往把机巢扎堆放在需求点密集的区域导致某些边缘塔位没人管遗传算法在适应度函数里加了漏检权重惩罚高权重的保供线路塔位一旦漏检会大幅拉低适应度所以算法宁可多花点成本也要把高权重目标覆盖掉。5.3 权重变化对选址结果的影响真正做项目时预算与覆盖率的关系不会那么理想通常是一个折中曲线。我通过调整目标函数中成本项的权重系数(\beta)得到了一组曲线当成本权重从0.1增加到0.6算法选中的机巢数量从5个降到2个覆盖权重占比从97.2%降到68.4%。这组数据说明一个问题如果项目总预算压得太死必然牺牲对部分重要杆塔的巡检覆盖。这个折中曲线应该提前算给决策者看而不是等他们拍板之后再做方案。我还尝试过把需求点权重全设为1即不考虑重要性差异。这时算法倾向于均匀布点覆盖率高但高价值线路的冗余覆盖少了成本反而可能更高。实际操作中权重设置要听取运维班组意见他们会告诉你哪些塔在雷雨季最容易遭雷击哪些线路段是外破高发区这些信息比任何公式都值钱。6. 参考文献清单与实战避坑经验6.1 可直接检索的参考文献方向“附Matlab代码参考文献”这个需求其实是想给算法一个理论依据避免方案评审时被说“不严谨”。我建议按下面几个方向查文献都是设施选址和无人机调度领域的经典脉络。方向代表性文献/书籍用途中位点与中心点选址Hakimi, 1964, Optimum Locations of Switching Centers and the Absolute Centers and Medians of a Graph设施选址的理论源头最大覆盖选址Church ReVelle, 1974, The Maximal Covering Location Problem覆盖优先目标函数的原型设施选址综述Daskin, 1995, Network and Discrete Location: Models, Algorithms, and Applications模型体系参考无人机充电/换电机巢调度在IEEE Xplore检索“UAV battery swapping depot location”和巡检机巢最接近的延伸方向国内电力巡检应用知网检索“无人机机巢选址电力巡检”国内线路工况和约束场景参考要特别提醒一句海外文献里无人机机巢更常见的词是“depot”或“landing site”而不是“nest”。“nest”在英文文献里多是鸟巢或仿生结构。如果你拿“UAV nest placement”去搜效果可能不理想换“UAV depot location problem”或“battery swapping station location”会好很多。6.2 踩坑记录坐标系、时间窗和惩罚系数这个算法我前后改了三版踩过的坑值得单独记录。第一坐标系坑。最早版本直接用WGS84经纬度算距离导致候选机巢在高纬度地区计算距离偏小、覆盖半径虚高。后来改成投影坐标才正常。只要项目范围跨几个县就必须用当地中央经线的高斯-克吕格投影否则覆盖边界会偏差好几公里。第二时间窗约束的“等效距离”一开始我写到了约束条件里但罚函数系数和覆盖项打架。后来我把时间窗约束转成硬约束凡是不满足响应时间的需求点直接视为未覆盖进入漏检惩罚项。这样比罚函数更干净也方便解释结果。第三造价权重过大时算法会倾向于建一个机巢都没有。这个情况初始化时就要避免我通过硬约束确保选中机巢数不小于1再在修复阶段把数量目标锁死。否则迭代前几代会出现大量全零染色体适应度函数直接崩了。第四Matlab自带的ga函数虽然能用但想加入容量罚和候选点通视过滤还是要自己写主循环。工具箱函数适合交作业演示不太适合工程上反复调整约束的场景。6.3 从选址算法到巡检方案的后续扩展这套代码跑通后还可以往两个方向扩展。一是把单一机巢选址升级为“机巢临时起降点”的两级部署巡检线路特别长的区域可以设置临时起降点配合人工更换电池完成更远距离任务。二是把静态选址改为动态调度当某个机巢故障或空域临时管控时算法快速重算覆盖把受影响任务转移给相邻机巢。这两个方向的Matlab框架都已经有了后面有时间再单独写一篇展开。最后分享一个我个人沿用的验证习惯每次用启发式算法拿到结果我都会把小规模问题比如10个候选点选3个用intlinprog跑一遍精确解对比一下遗传算法的误差。如果差距超过5%就去检查罚函数系数和变异算子而不是急着改参数。这个习惯帮我抓出了好几次因为编码错误导致的“伪最优解”。