调试这套仿真的头一个星期,我做的最多的事情不是写算法,而是盯着那五条轨迹发愣:它们要么在搜索阶段互相撞在一起,要么在围捕阶段绕着目标画出一个歪歪扭扭的多边形,然后集体停在某个角落不动了。项目标题里"多无人机目标搜索与围捕"听起来是一个动作,实际跑起来才发现它是两个完全不同性质的子问题,中间还夹着一层很不显眼的耦合——搜索阶段留下的位置分布,直接决定围捕阶段能不能顺利收口。
这篇内容围绕基于 MATLAB 的多无人机协同目标搜索与围捕仿真展开,把环境建模、概率地图更新、任务分配、路径生成、环绕构型、势场收拢、通信约束和性能评估这一整条链路拆开讲,重点放在参数怎么定、为什么要这么定、以及实测中哪些地方最容易翻车。适合正在做多智能体协同控制课程设计、准备算法验证的新手,也适合已经跑通了单机程序、想把它扩到多机协同但被各种震荡和死锁卡住的同学。代码用 MATLAB 写,全部基于向量化矩阵运算和简单的函数拆分,不依赖任何额外工具箱,一台普通笔记本就能跑。
1. 为什么这套仿真必须拆成"搜索"和"围捕"两段
1.1 两个子问题的数学本质根本不一样
搜索阶段面对的核心矛盾是信息不确定。你不知道目标在哪,手里只有一张先验概率图和几个带噪声的传感器读数,任务是把整片区域的"不确定性"降到某个阈值以下。这是一个覆盖问题,评价指标是覆盖率、平均发现时间、单位时间搜索面积。算法的重心在于怎么把区域切开分给各机、怎么让扫描路径尽量不重叠。
围捕阶段面对的核心矛盾变成了几何构型。目标位置已经相对明确,剩下的问题是怎么让 N 个平台在有限时间内形成对目标的环绕包围,并且相邻间距不能小于安全距离、不能大于通信距离、还要保证目标跑不掉。这是分布式编队与合围问题,评价指标是收敛时间、包围圈半径稳定度、逃逸成功率。
把这两个问题塞进同一个目标函数里优化,基本等于自找麻烦。因为前者的最优解倾向于"分散铺开",后者的最优解要求"向心收拢",方向完全相反。我在第一版代码里试图用一个统一的势场函数同时表达"去没搜过的地方"和"靠近目标",结果就是无人机在搜索后半段开始成群结队往目标的反方向跑——因为那里的"未搜索势能"更高,吸引力盖过了目标引力。
提示:搜索与围捕务必用两个独立的状态机分支,用触发条件切换,而不是用权重混合。切换时刻的选择比权重调参重要一个数量级。
1.2 四层结构:环境层、决策层、运动层、评估层
我最终采用的骨架是四层分离的结构,各层之间只通过结构体(struct)传数据,互不越界调用。这样做的好处是改一层不会牵连其他层,调参的时候心里有底。
| 层级 | 职责 | 典型函数 | 更新频率 |
|---|---|---|---|
| 环境层 | 栅格地图、障碍物、目标真实运动、传感器模型 | env_update.m | 与控制周期同步 |
| 决策层 | 概率图更新、任务分配、围捕点分配、状态机切换 | assign_search.m/assign_encircle.m | 每 5 到 10 个控制周期 |
| 运动层 | 速度指令生成、避碰、动力学积分 | ctrl_apf.m/move.m | 每个控制周期 |
| 评估层 | 覆盖率、发现时间、包围误差、能量消耗 | metrics.m | 记录型,事后统计 |
决策层比运动层慢,这一点很关键。目标分配是一个组合优化问题,每帧都重算既没必要也太慢;而运动层的势场避碰必须每帧算,否则会撞。很多新手把两者都放在同一个时间步里算,跑 50 架规模的场景直接卡到帧率掉到个位数。
1.3 MATLAB 在这条链路里真正省事的地方
选 MATLAB 不是为了算法本身,而是为了三件事。第一是矩阵化操作:把 N 架无人机的状态堆成一个 N×2 的位置矩阵,一次pdist2就能拿到全连接距离矩阵,避碰判断不用写双重循环,上百架规模也不卡。第二是可视化:plot、quiver、contourf、animatedline这几样东西拼起来,概率热力图加轨迹加朝向箭头可以实时刷新,调试时能一眼看出是哪一层的输出不对。第三是中断调试:在循环里随便打断点,workspace里所有结构体都在,能直接画任意变量。
我用过其他环境写同样的逻辑,优势是可以做更细的内存管理,但代价是可视化要自己搭,一套概率热力图加实时轨迹的工具链搭下来得两三天。对于算法验证阶段来说,这个投入不划算。MATLAB 的短板也很清楚:大规模蒙特卡洛跑不快,所以我一般是先在 MATLAB 里跑通 20 次以内的对比,再把算法改写出去做上千次的统计。
2. 栅格地图和概率存在图:把"可能在哪"变成矩阵
2.1 栅格分辨率怎么定,别拍脑袋
这是整个流程里第一个会踩的坑。很多人直接取 100×100 的栅格,因为"看着挺细的"。但分辨率必须从传感器的探测能力倒推,否则会得出错误的覆盖结论。
设单机传感器的有效探测半径为 R,我用的经验规则是栅格边长 Δ 取 R/3 到 R/2 之间。道理很简单:当一个平台某个时刻位于某栅格中心时,它的探测圆至少要完整覆盖 3 到 5 个栅格,这样即使采样点稀疏,相邻两次采样之间也不会出现探测空白。取 Δ = R/2 时,探测圆内部格子数约为 πR²/Δ² = 4π ≈ 12.6 个,足够。取 Δ = R 时只剩 3 个格子,扫描路径稍有抖动就漏检。
给个具体数字。假设搜索区域是 1000 m × 1000 m,机载视觉有效半径 R = 60 m,那两个都不能用的极端取值是:Δ = 5 m 会得到 200×200 = 40000 个栅格,概率图更新一次就是四万次浮点运算乘以机数,几十架时明显掉帧;Δ = 60 m 只有 17×17,路径基本没法规划。折中取 Δ = 25 m 得到 40×40 = 1600 个栅格,既能保证每个探测圆覆盖 18 个左右的格子,计算又轻。
还有个容易忽略的点:区域尺寸要能被 Δ 整除,或者显式处理边界余数。我第一次写的时候,1000/30 除不尽,最后一列栅格只覆盖了 10 m 宽,概率更新时索引越界报错,排查了快一个小时才发现是尺寸问题。养成在初始化时assert(mod(L, delta) < 1e-9)的习惯。
2.2 概率图的初始化和贝叶斯更新
先验概率图有两种常用初始化。均匀初始化(每格 1/M)适用于完全无信息;带先验热点的初始化(给定若干可疑区域概率加权,其余归一化)适用于有情报引入的场景。第二种更贴近实际,但要注意归一化,我见过直接把热点区域置 0.8、其余全置 0.5 的写法,概率总和远大于 1,后续更新全部失真。
更新用简化贝叶斯。假设平台对栅格 k 探测一次,检测到目标的概率为 P_d(k),未检测到时,该栅格的目标存在概率按如下方式衰减:
p(k) = p(k) * (1 - Pd) / (p(k) * (1 - Pd) + (1 - p(k)))这个式子其实可以理解成"没看见"这件事本身就是证据:一个本来很可能有目标的格子,如果反复扫过都没看见,它的概率就该往下掉。检测到目标时直接跳到围捕状态,不再更新概率图。
Pd不该是常数,应该跟距离挂钩。我用的经验模型是Pd = exp(-(d/R)^2 * 2),中心处接近 1,边缘衰减到 0.13 左右。这样能自然形成"越靠近越可靠"的覆盖价值,搜索路径规划时会自动倾向于把探测圆中心对准热点。
function P = bayesUpdate(P, idx, Pd) % idx: 被探测的栅格线性索引,Pd: 对应探测概率向量 Pm = 1 - Pd; p = P(idx); P(idx) = p .* Pm ./ (p .* Pm + (1 - p)); P(idx) = min(max(P(idx), 1e-4), 1 - 1e-4); % 防止数值塌缩 endmin/max那一行别省。连续几百步更新后,概率会出现 0 或者 1,一旦到 0 就永远回不来,到 1 又会在除式中产生警告。夹在 1e-4 到 1-1e-4 之间是稳妥做法。
2.3 覆盖度指标不能只看"扫过的格子数"
覆盖率有两种算法,结论可能相差 20% 以上。第一种是硬覆盖:只要某个栅格的概率低于阈值(比如 0.1)就算已覆盖。第二种是概率残余:用1 - mean(P)表示整体不确定性。
我一般两个都记,因为它们的用途不同。硬覆盖用于判断"能不能切换到围捕模式",概率残余用于比较不同搜索策略的优劣。曾经有一版算法,硬覆盖率冲到 96%,但概率残余还有 0.35,原因是它在几个概率极高的热点区域反复盘旋、把那里的概率压到极低,而大片低概率区域只是被"扫了一遍",概率从 0.001 降到 0.0009,对残余贡献几乎为零。这种"看起来覆盖得很好、实际信息收益很低"的情况,只用硬覆盖率是发现不了的。
注意:概率图更新和覆盖统计一定要用同一套
Pd模型,否则统计出来的覆盖率和规划器认为的价值面不一致,会出现"规划器觉得该去的地方,统计器认为已经覆盖"的分裂现象。
3. 搜索阶段的分配逻辑:切区域还是拍卖
3.1 等面积切分和 Voronoi 的适用边界
区域切分是最省算力的做法,O(N) 复杂度,一次算完可以跑很久。等面积切分把矩形区域按机数纵向切成 N 条,每条宽度 L/N,各机只在自己那条里做平行线扫描。优点是绝对不会重复、通信量几乎为零、实现简单。
缺点是它完全不响应概率分布。如果热点全在第一条带里,其他四条带的无人机就白跑了。实测下来,在目标位置先验均匀的场景里,等面积切分的效果跟复杂算法差距不到 5%,但在目标有明确聚集倾向的场景里,差距会拉到 30% 以上。
Voronoi 切分是折中方案:按各机当前位置做 Voronoi 划分,各机负责自己单元内的搜索。它天然具有负载均衡的性质——谁离得近谁负责,转移成本低。代价是每次位置变化都要重算 Voronoi 图,如果每个控制周期都重算,N=10 时计算量还能接受,N=30 就明显拖慢。我的做法是每 20 个周期重算一次,中间用小范围调整补。
3.2 基于收益指标的拍卖分配
再往上一档是拍卖式分配。每个尚未被充分搜索的子区域算一个"标的",价值定义为该区域内概率残差之和除以到达该区域的估计代价:
value(i, j) = sum(residual(j)) / (1 + dist(pos_i, center_j) / v_max)各机对价值最高的标的出价,出价最高的获得。这是一个贪心的近似解,不保证全局最优,但在实际场景里表现出乎意料地好,而且天然分布式——每架无人机只需要知道自己的位置和公共的概率图。
实现时有两点要注意。一是代价项的分母要加 1,否则无人机正好在标的中心时分母为 0 直接爆掉。二是要加去重机制:如果多个标的中心非常接近,同一架无人机会连续中标好几个,导致其他区域的机一直闲着。我用的办法是设一个最小间距d_sep,一旦某机中标,它周围d_sep范围内的标的直接标记为不可选。
3.3 扫描路径的形状选择和间距计算
平行线扫描(也叫割草机路径)是覆盖率最高的单机路径,直来直去,转弯少,能耗低。间距的算法是:
W = 2 * sqrt(R^2 - (delta/2)^2) % 有效搜索带宽 L_scan = W * (1 - overlap) % 实际布线间距当 R = 60、Δ = 25 时,W = 2*sqrt(3600 - 156) ≈ 117.4 m。取 overlap = 0.15,则布线间距约 99.8 m。这个 15% 的重叠是为了补偿转弯处的漏检和传感器抖动,不是浪费。我在仿真里对比过 overlap 取 0、0.1、0.2、0.3 四档,覆盖率分别是 82%、91%、95%、96%,耗时分别是 100%、108%、120%、137%。0.15 是性价比拐点。
螺旋线路径适合"从一个热点向外扩展"的场景,尤其在先验信息集中时比平行线快。但螺旋线有个致命问题:它的路径不闭合,如果中途因为重规划被打断,重新进入时很难接上,容易造成大块遗漏。所以我只在初始阶段先验集中时用它,一旦概率图变平就切回平行线。
3.4 重规划的触发条件要设得保守
重规划不是越频繁越好。我最初设成"每当概率图变化超过 5% 就重规划",结果每 3 个周期就触发一次,无人机全在原地打转,一步都没走完。
后来改成三条触发条件,满足任意一条才重算:一是有平台完成了当前任务段(走完一条扫描线);二是有平台的探测发现了概率突变(单格概率超过 0.6);三是距上次重规划超过了固定周期数(比如 100 个周期)。这样平均每 40 到 60 个周期才触发一次,路径的连贯性明显变好。
提示:重规划时给旧路径加一点"惯性"——新分配的目标点和旧目标点如果距离小于某个阈值就保持不变。这个简单的滞回机制能大幅减少路径抖动,比调任何算法参数都有效。
4. 围捕阶段的几何问题:怎么围得住又不撞
4.1 目标状态估计:单机看不准,多机融合才稳
搜索阶段检测到目标只是"发现",围捕阶段需要持续跟踪。单机的测量有噪声,尤其是距离估计,测距误差在 5% 到 10% 之间很常见。直接用单机测量去做合围,包围圈会不停地抖。
工程上的做法是让每架无人机跑一个局部滤波器(卡尔曼或者简单的 α-β 滤波),并且在通信邻居之间做一次一致性融合。α-β 滤波器在这个场景里其实够用,因为它计算量小、参数物理意义明确:
% 一维 α-β 滤波,对目标 x 坐标做估计 function [x_est, v_est] = abFilter(z, x_est, v_est, dt, alpha, beta) x_pred = x_est + v_est * dt; r = z - x_pred; x_est = x_pred + alpha * r; v_est = v_est + beta * r / dt; endα 取 0.3 到 0.5,β 取 0.05 到 0.15,实测在 10 Hz 更新率下跟踪效果已经不错。α 太大跟踪快但抖,太小则平滑但滞后。我用 0.4 / 0.08 这个组合跑了很久,对各种机动目标都还稳。
融合用加权平均就够,权重按各机的估计协方差倒数分配。别在这里上太复杂的算法,因为围捕的时间窗很短,融合算法本身的收敛速度反而是瓶颈。
4.2 环绕点的角度分配和半径计算
环绕点分配最直观的做法是等角度分配:第 i 架无人机分配到角度 θ_i = θ_0 + 2πi/N,半径 R_enc。这里有两个约束要同时满足。
第一个是相邻间距不小于安全距离d_safe。在半径 R_enc 的圆上等角度分布时,相邻弧长对应的弦长为d = 2 * R_enc * sin(π/N)。要求 d ≥ d_safe,反解出R_enc ≥ d_safe / (2*sin(π/N))。N = 6、d_safe = 20 m 时,R_enc ≥ 20/(2*0.5) = 20 m,这个下界很宽松。
第二个是相邻距离不大于通信半径。同样的公式反解出R_enc ≤ d_comm / (2*sin(π/N))。N = 6、d_comm = 150 m 时上界是 150 m。所以 20 到 150 都是可行的,我一般取 60 到 80 m,留足余量应对位置偏差。
真正麻烦的是角度分配不是静态的。目标在移动,无人机在靠近,如果每帧都重新按等角度分配,会出现"抢位"——两架无人机同时往同一个角度飞。解决办法是加匈牙利算法做一次最小代价匹配,代价矩阵元素是"第 i 架到第 j 个环绕点的距离"。N 不超过 20 时用matchpairs一行搞定,超过的话还是得写简化的贪心匹配。
4.3 势场法为什么在这里容易卡死
人工势场法的吸引力加排斥力这套东西,写在论文里非常漂亮,跑起来问题一堆。典型症状是无人机在到达环绕点附近 2 到 3 m 的地方开始来回振荡,永远进不去。
根因是吸引力随距离线性衰减、排斥力随距离平方反比增长,两者在某个距离处必然相等,形成一个平衡点。如果这个平衡点恰好落在环绕点附近而不是环绕点本身,就会卡住。
三种改法我都试过。第一种是给吸引力加个"死区":距离小于 3 m 时吸引力恒定为一个小的常值,不再衰减。这个最简单,但会引入超调。第二种是把排斥力改成有界形式,比如F_rep = k_rep * (1/d - 1/d0) * (1/d^2)只在 d < d0 时生效,且乘一个饱和因子。第三种是干脆不用势场,改成"速度指令 + 优先级避碰":每架无人机朝目标点飞,同时检查有没有更高优先级的邻居在未来 3 秒内可能相撞,有就减速或侧移。
我现在默认用第三种,因为它行为可预测、调试直观,而且能明确知道"谁让谁"。势场法只在需要平滑轨迹的场合用。
4.4 收拢时序:别让所有人一起挤
围捕的最后一步是收缩包围圈。如果所有无人机同时向内收,会发生两件事:一是内部空间迅速压缩,轨迹互相干扰;二是围捕半径过小时,相邻机之间的实际距离可能跌破安全阈值(因为位置偏差累加)。
正确做法是分批收拢。先让包围圈内角度间隔最大的那部分无人机(通常是 1/3)向内收一步,等它们稳定后,再让下一批。判断稳定的标准是位置变化率连续若干个周期低于阈值。这样虽然总收拢时间长了 20% 到 30%,但过程非常干净,没有互相挤压。
同时要设一个最小包围半径下界,即 4.2 节算出来的R_min = d_safe / (2*sin(π/N))再乘一个 1.2 的安全系数。收到这个半径就停,不要试图收到 0。有些论文里把最终半径画成 5 m,那是理论值,实际多机系统里这个数字基本不可实现,位置误差本身就超过它。
5. 主循环怎么写:时间尺度和通信模拟
5.1 双时间尺度的主循环骨架
主循环的核心是把决策周期和控制周期分开。我的默认配置是控制周期 dt = 0.1 s(10 Hz),决策周期每 10 步执行一次(1 Hz),评估记录每步都做但只是存数组。这个比例在多数场景下够用。
dt = 0.1; T = 300; steps = T/dt; decEvery = 10; for k = 1:steps t = k * dt; % 1) 环境演进(目标真实运动、传感器采样) env = envStep(env, dt); % 2) 感知与概率图更新 [probMap, detFlag] = senseUpdate(probMap, env, agents); % 3) 状态机切换 if mode == "search" && detFlag mode = "encircle"; end % 4) 决策层(低频) if mod(k, decEvery) == 0 if mode == "search" agents = assignSearch(probMap, agents); else agents = assignEncircle(targetEst, agents); end end % 5) 运动层(高频) agents = ctrlMove(agents, probMap, mode, dt); % 6) 通信模拟:邻居状态延迟/丢包 agents = commStep(agents, commModel); % 7) 指标记录 log = recordMetrics(log, k, probMap, agents, env.target); end这个结构看起来简单,但有三个细节决定了它能不能跑稳。第一是第 2 步的概率图更新必须在决策层之前,否则分配用的是上一拍的信息。第二是第 3 步的切换判断要独立于决策层频率,检测到就立刻切,不然会耽误一个决策周期。第三是第 5 步的运动层里不能再次调用环境层,否则同一时间步内传感器会被采两次,概率图更新出错。
5.2 通信时延和丢包:不加这两样,仿真结果毫无参考价值
理想通信下的多机协同,结果总是漂亮得出奇。一加上 200 ms 时延和 10% 丢包率,协同就散架了。这两样必须建模,哪怕用最简陋的方式。
时延用固定延迟队列实现:每个平台维护一个长度为ceil(delay/dt)的历史状态数组,计算邻居信息时取队列头部而不是当前状态。丢包用伯努利抽样:每次通信有 p_drop 概率取到上一拍的状态。
若 rand() < p_drop,则用当前缓存的旧值; 否则用本次采样值并更新缓存。这个简单的模型能暴露出大量问题。我最典型的一次发现:搜索阶段的分配算法对丢包极度敏感,因为一旦某架无人机的分配结果丢了,它会认为那个区域没人负责,然后冲过去和其他人重复搜索。加上超时重传和分配结果的本地缓存后,问题解决。
注意:通信半径的建模不能只用欧氏距离硬截断。实际中链路质量随距离衰减,边缘处丢包率高。用
p_drop = 1 - exp(-((d_comm - d)/d_comm)^2 * 3)这种形式平滑过渡更接近实际,也能避免"刚好跨越边界时通信突然中断"的跳变。
5.3 状态机切换的坑:双向切换要加冷却
搜索到围捕的切换是单向的,容易。但如果目标跑出了探测范围,需要从"围捕"退回"搜索",这时候如果逻辑写成"是否检测到目标"直接决定状态,会出现高频来回切换——检测到就围捕,丢失就搜索,检测到又围捕。
解决办法是加一个状态保持计数器:只有连续丢失目标超过 M 个周期(比如 20 个),才退回搜索模式;期间保持围捕,并且往最后一次观测位置靠拢。这个计数器本质上是一个滞回环节,一行代码的事,但没有它,仿真跑起来状态机会疯狂抖动,所有统计指标都失效。
6. 那些让我熬夜的坑:现象、根因和处置
这一段是我最想分享的部分,因为常规教程里基本只讲理想情况,下面这些全是实际调试中撞出来的。
6.1 问题排查对照表
| 现象 | 根因 | 处置方式 |
|---|---|---|
| 无人机原地小幅画圈不前进 | 势场引力与斥力平衡,或者环绕点刚好落在障碍物内部 | 检查环绕点是否在障碍物内部,改用力指令法或加随机微扰逃逸 |
| 搜索轨迹大段重叠 | 分配结果丢失或分配后未加去重半径 | 加最小间距去重 + 分配结果本地缓存 |
| 概率图若干格恒为 0 或 1 | 贝叶斯更新未做数值夹取 | 概率钳制在 [1e-4, 1-1e-4] |
| 包围圈一直往里收直到互相碰撞 | 只有吸引力没有最小半径约束 | 显式设置 R_min 并加入安全系数 |
| 轨迹在目标附近来回跳变 | 环绕点每帧重分配且无滞回 | 环绕点分配加距离阈值保持 |
| 仿真跑一会儿索引越界 | 区域尺寸不能被栅格整除 | 初始化时校验或主动补齐边界 |
| 多机挤在一起效率反而低 | 通信延迟下各机信息不一致 | 增加超时重传、降低决策频率 |
6.2 角度归一化:一个能让人找半小时的 bug
计算邻居方位并做角度差的时候,如果不处理 ±π 的跨界,会得到一个巨大的虚假角度差。比如邻居在 -179°,自己在 +179°,直接相减是 358°,而真实差值是 2°。
标准的处理方式:
function d = wrapPi(a) d = mod(a + pi, 2*pi) - pi; end所有涉及角度差的判断——环绕点是否已达、邻居是否在前方、转向角是否超限——都必须经过这个函数。我第一版代码忘了在"是否到达环绕点"的判断里加,结果无人机在跨越 -180° 方位时集体判断"已经到位",直接停下来,五条轨迹卡在同一个方位上,排查了半天以为是势场问题。
6.3 索引顺序的一致性
MATLAB 的矩阵索引是 (行, 列),对应 (y, x)。而大多数运动学公式写的是 (x, y)。这两个东西混用,画出来的图和算出来的轨迹必然对不上。
我的做法是在每一层的入口处明确注释坐标约定,环境层内部统一用 (row, col) 表示 (y, x),一旦出到运动层立刻转成 (x, y)。转换只在两个函数里发生,其他函数一律不碰。这样虽然要多写几行转置,但避免了最难查的一类 bug——程序不报错,只是图是镜像的。
6.4 安全距离判定用平方距离省时间
避碰判断是每帧每对都要算的,N = 20 时有 190 对。用sqrt完全不必要,直接比平方距离。这个优化在 N 小的时候看不出来,跑蒙特卡洛 500 次时能省下可观的时间。同理,判断"是否在探测范围内"也是比平方,不用开方。
% 好 [nearIdx] = find(sum((P - P(i,:)).^2, 2) < dSafe^2); % 不需要开方7. 怎么证明这套东西真的有用:评估与可视化
7.1 指标要能对应到具体的问题
搜索阶段的指标我用四个:平均覆盖率随时间曲线、目标发现所需时间、单位时间搜索面积、重复搜索比例。第四个最容易被忽略但最能反映分配质量——重复率超过 25% 基本说明分配逻辑有问题,不是算法参数的问题。
围捕阶段的指标也是四个:合围收敛时间(从检测到目标到所有机到达环绕点)、稳态包围半径偏差、最小相邻间距的最小值、目标逃逸成功率。第三个是安全性的核心指标,只要出现过一次低于安全距离,这个方案的工程可行性就要打问号。
评估层不要只看最终值,一定要看过程曲线。我遇到过最终包围半径非常漂亮、但过程中有一次半径突然涨到两倍的方案,原因是某一架无人机中途丢包后去追了一个过期的目标位置。只看终值完全发现不了这个问题。
7.2 可视化不只是好看,是主要调试手段
概率热力图用imagesc或contourf,配合colormap用能体现"高概率偏红"的色标。轨迹用plot画累积线。朝向用quiver每 20 帧画一次,避免箭头太密。目标用大圆点标记。
真正有用的技巧是分窗显示:左边一个窗口显示概率图和轨迹,右边上下两个子图分别显示覆盖率和最小间距的实时曲线。这样跑的时候能立刻判断"是搜索卡住了还是围捕出问题了"。只看一个轨迹窗口,很多时候分不清"飞机在原地转"是因为没找到目标还是因为分配逻辑死循环。
7.3 单次跑通不算数,必须做蒙特卡洛
单次仿真的随机性很大,目标初始位置、传感器噪声、丢包序列都不一样。我一般是固定随机种子跑一次看细节,然后用 100 组不同种子跑统计。
统计表大概长这样:
| 场景 | 平均发现时间 (s) | 平均覆盖率 (%) | 合围收敛时间 (s) | 逃逸率 (%) |
|---|---|---|---|---|
| 均匀先验 + 理想通信 | 42.3 | 96.8 | 18.5 | 2 |
| 均匀先验 + 200ms 延迟 | 51.7 | 94.1 | 24.9 | 6 |
| 聚集先验 + 200ms 延迟 | 33.9 | 95.2 | 22.1 | 5 |
| 聚集先验 + 延迟 + 障碍 | 58.4 | 89.3 | 31.6 | 11 |
这张表最有价值的信息不是数值本身,而是趋势:加了延迟之后收敛时间增加 35%,加了障碍之后覆盖率下降 5%、逃逸率翻倍。这些趋势能直接指导后续优化方向——如果重点是抗逃逸,那就要重点处理障碍环境下的通信和估计问题,而不是优化搜索路径。
另外,跑蒙特卡洛时一定要把随机种子显式记录下来。我踩过这个坑:某次统计结果异常好,回头想复现,因为种子是用时间初始化的,怎么都跑不出同样结果,最后只能重跑全部。
提示:把每次运行的配置结构体存成
mat文件,包含种子、参数和结果摘要。三十次之后你根本记不住哪次改了什么。
最后分享一个实际体会:这套仿真的难点从来不在某个算法的实现,而在于层与层之间的耦合。搜索留给围捕的初始位置分布、通信延迟对分配稳定性的影响、更新频率和收敛性的匹配,这些东西没有任何一篇文献会明确告诉你取值多少合适。我的建议是先用最简单的算法把整条链路串起来——等面积切分 + 平行线扫描 + 等角度环绕 + 纯速度指令,保证它能从搜索一直走到围捕收口,再逐个模块替换成更复杂的方法。反过来做,从复杂算法开始搭,你会花大量时间在"搞不清是哪一层出的错"上。