简介:互质面阵在相同阵元数下能提供更大的阵列孔径和更低的互耦影响,为高精度二维DOA估计创造了条件,在雷达探测、目标定位等场景中具有实用价值。对应的MATLAB实现方案面向信号处理与雷达专业学生及需要解决阵列测向模糊问题的研究人员,代码涵盖互质面阵模型建立与基于二维MUSIC算法的解模糊处理,采用参数化编程、步骤清晰、注释完整,便于快速理解与二次开发;整套代码按主流程、核心算子与结果分析分层组织,既可直接运行也可修改参数进行算法对比。压缩包共4个文件,包含3个m脚本和1张分析图,m脚本分别对应主流程、Kronecker积与Khatri-Rao运算等关键模块,分析图直观展示仿真结果及解模糊效果,整体仅54KB,轻量易用;目前已有1739人学习下载。读者可通过源码与示意图,系统掌握互质面阵信号建模、角度估计以及模糊消除的完整链路,尤其适合课程设计、毕业设计等阵列信号处理场景。
1. 互质面阵下的二维DOA估计:为什么MUSIC会给出假方位
做阵列信号处理的人第一次把MUSIC算法搬到互质面阵上,十有八九会看到谱图上多出好几个长得一模一样的尖峰,有的峰甚至比真实来波方向还亮。这不是MUSIC失效,而是互质面阵的稀疏阵元间距触发了空间混叠,也就是常说的模糊峰。互质面阵用两套间距互质的均匀子阵嵌套在一起,用较小的物理孔径换来了远超阵元数目的自由度,代价就是单一子阵的MUSIC谱没法直接读。本文要解决的就是这件事:先把模糊产生的几何条件讲透,再给出一套从双子阵估计、差集共阵重构到峰配对的去模糊流程,最后用蒙特卡洛脚本验证结果可复现。
2. 互质面阵的信号模型与二维MUSIC谱:从子阵布阵到谱峰搜索
2.1 互质面阵的两种常见布局与自由度收益
互质面阵不是一种固定结构的阵列,而是「两个均匀子阵嵌套/平移」的设计思路。最常见的布局是L型互质阵列:x轴和y轴各放一组互质线阵,共享原点处的参考阵元,两组线阵分别负责方位角和俯仰角的信息采集,最后通过配对得到二维角度。另一类常见布局是矩形互质面阵,两个子阵本身都是稀疏均匀矩形栅格,一个间距为Md,另一个间距为Nd,M和N互质,d取半波长。两种布局都能做二维DOA估计,区别在于L型实现简单、配对麻烦;矩形面阵的差集共阵更完整,自由度更高,但数据量和计算量也大。
选择哪种布局取决于信号环境和硬件约束。如果你的阵元位置已经固化成矩形板,那矩形互质面阵是顺理成章的选择;如果只是想在现有均匀面阵基础上改造,把原来的阵元抽稀成两套互质栅格,L型方案改动更小。我一般建议先用矩形互质面阵做原理验证,因为差集共阵的虚拟阵元是天然的矩形栅格,后处理代码更直观。
互质面阵的自由度收益其实是很多人的第一直觉陷阱。一个8阵元的均匀面阵,最多分辨8个信号;同样的8个物理阵元拆成4+4的互质子阵,差集共阵能提供的虚拟阵元数远大于8,理论上能同时估计十几个信号。当然这只是自由度上限,实际还要看信噪比、快拍数和搜索网格密度。把方向向量当做「空间频率」来看,互质结构等于在空间频率域做了非均匀采样,再用两套采样网格的交集把模糊剔除。
2.2 二维MUSIC谱的数学形式与网格搜索实现
二维MUSIC的核心还是子空间分解。设阵列有P个物理阵元,第k个快拍的接收数据为x(k) = A s(k) + n(k),其中A是P×D的阵列流形矩阵,D是信号源数。协方差矩阵R = E[x x^H]做特征分解后,大特征值对应的特征向量张成信号子空间Es,其余张成噪声子空间En。MUSIC谱函数写作:
P_music(θ, φ) = 1 / (a^H(θ, φ) En En^H a(θ, φ))
其中a(θ, φ)是来波方向为(θ, φ)时的导向矢量。注意二维MUSIC需要在θ和φ两个维度上都做网格扫描,每个网格点计算一次谱值,所以复杂度是O(Nθ × Nφ × P²),Nθ和Nφ分别是两个角度的网格点数。实际工程里常用先粗搜后精搜的两步策略:先用5°步长找出谱峰大致位置,再在峰附近用0.1°步长细化,这样能把计算量降一到两个数量级。
导向矢量的构造是容易被忽视的地方。矩形互质面阵的阵元坐标是两组整数对的集合:子阵1的阵元坐标是{(mM, nN) d},子阵2是{(mN, nM) d},m和n取遍各自子阵的阵元序号。有了坐标集合,导向矢量就是a(θ, φ) = exp(j 2π/λ (x sinθ cosφ + y sinθ sinφ)),这里的θ是俯仰角,φ是方位角。写成Python实现时,务必把坐标单位统一到波长,否则sinθ项会差一个倍数,谱峰位置全部跑偏。
2.3 用Python构造互质面阵并生成单快拍测试数据
下面这段代码演示了如何构造一个M=3、N=5的矩形互质面阵,并生成两个远场窄带信号的接收数据。代码里的坐标生成方式可以直接当成模板复用。
import numpy as np def coprime_rect_array(M=3, N=5, d=0.5): """ 构造矩形互质面阵的物理阵元坐标 M, N 为互质因子,d 为半波长 返回坐标数组 shape=(P, 2),单位是波长 """ sub1 = [] sub2 = [] # 子阵1:x方向间距 M*d,y方向间距 N*d for ix in range(N): for iy in range(M): sub1.append((ix * M * d, iy * N * d)) # 子阵2:x方向间距 N*d,y方向间距 M*d for ix in range(M): for iy in range(N): sub2.append((ix * N * d, iy * M * d)) # 去掉原点重复阵元 coords = list(set(sub1) | set(sub2)) return np.array(coords) def steering_vector(coords, theta, phi, wavelength=1.0): """ 计算某个来波方向的导向矢量 theta: 俯仰角 (0~90度),phi: 方位角 (0~360度) coords 单位是波长,wavelength 归一化为1 """ k = 2 * np.pi / wavelength x = coords[:, 0] y = coords[:, 1] phase = k * (x * np.sin(theta) * np.cos(phi) + y * np.sin(theta) * np.sin(phi)) return np.exp(1j * phase) # 生成阵列坐标,P 是物理阵元数 coords = coprime_rect_array(M=3, N=5) P = coords.shape[0] print("物理阵元数:", P) # 两个真实来波方向 thetas = [30, 50] # 俯仰角,单位度 phis = [45, 120] # 方位角,单位度 D = len(thetas) # 构造流形矩阵 A A = np.zeros((P, D), dtype=complex) for idx in range(D): A[:, idx] = steering_vector(coords, np.deg2rad(thetas[idx]), np.deg2rad(phis[idx])) # 生成500个快拍,信号是随机复包络,加高斯白噪声 K = 500 S = np.random.randn(D, K) + 1j * np.random.randn(D, K) X = A @ S + 0.1 * (np.random.randn(P, K) + 1j * np.random.randn(P, K))这段代码里有三个关键点。第一,子阵1的坐标是(ixMd, iyNd),子阵2是(ixNd, iyMd),这样x轴和y轴的间距正好互换了,保证两个方向都能获得互质间距带来的自由度。第二,用set去重是因为原点处的阵元(0,0)会被两个子阵同时包含,真实硬件不可能放两个重叠的阵元,去掉后物理阵元数会少于M×N+N×M。第三,信号用随机复包络生成,快拍数K取500,噪声方差0.1对应约20dB的输入信噪比,这个设置在MUSIC里属于比较舒服的工作区间。
有了X和坐标,下一步就是把X的协方差矩阵做特征分解,然后计算二维谱。这部分我会在第4章结合去模糊一起给出完整代码,因为单子阵的MUSIC谱本来就是要被「怀疑」的中间产物。
3. 模糊峰是怎么来的:栅瓣周期与互质因子之间的关系
3.1 空间混叠的数学条件
均匀线阵的阵元间距如果大于半波长,导向矢量在空间频率上会出现多个等价的峰值。原因不复杂:导向矢量是exp(j 2π d sinθ / λ),当d超过λ/2时,sinθ每变化一个周期,相位就能绕回相同的值,MUSIC谱就在多个θ位置出现同样的峰,这些峰就是模糊峰。互质面阵的两个子阵间距分别是Md和Nd,都远大于半波长,所以每个子阵单独拿来做二维MUSIC,谱面上必然有成片的模糊峰。
模糊峰的位置不是随机的,它服从栅瓣公式。以x轴方向的子阵为例,设间距为L d,则第k个栅瓣满足sinθ_k = sinθ_true + k λ / (L d)。L=3时,真实方向为30°的信号,在sinθ域会额外出现对应k=±1的两个栅瓣位置,换算回角度就是大约70.5°和-30°(如果允许负角度)。二维情况下,x和y两个方向独立产生栅瓣,所以模糊峰在(θ, φ)平面上形成网格状分布,网格间隔由M和N的数值决定。
这就是为什么互质面阵不能直接套均匀阵列的MUSIC流程:你没法判断哪个峰是真实的。有人会想「谱峰最高的那个总是真的吧」,但在噪声影响下,模糊峰的幅度和真实峰几乎一样,因为导向矢量对这两个方向的响应是完全等价的。只有当信噪比非常高、快拍数非常大时,真实峰才会因有限样本效应略微高一点,拿这个当判据等于在赌运气,完全不可靠。
3.2 真实峰与模糊峰的判别特征
从谱图上观察,真实峰和模糊峰有一个几何上的区别:真实峰在两个子阵的MUSIC谱里都出现,且出现在同一个角度位置;模糊峰虽然也出现在两个子阵的谱里,但位置不重合。原因在于两个子阵的栅瓣周期不同:子阵1的栅瓣间隔由M决定,子阵2的间隔由N决定,M和N互质,所以两条栅瓣网格只在真实方向上有公共交点,其余交点全部错开。
这个性质就是「互质」名字的由来,也是所有去模糊算法的出发点。你可以把它想象成两把刻度不同的尺子去量同一个长度:两把尺子的刻度线只有在真实长度处才对齐,其余地方对不齐。于是最朴素的去模糊方法就是:分别对两个子阵做二维MUSIC谱,各自找出前Q个峰值,然后做峰配对,找两组峰集中距离小于某个容差的组合,剩下的峰值删除。
实际操作中还要注意一个细节:两个子阵的孔径不同,角度分辨率也不同。子阵1的物理孔径大约是N×Md,子阵2是M×Nd,两者差异明显。孔径大的那个谱峰更锐利,孔径小的谱峰更胖,所以在做峰值匹配时,容差不能设成一个固定值,最好根据两个子阵各自的角度分辨率分别设定,不然会把真实峰也滤掉。
3.3 为什么不能用「子阵去模糊」替代「联合处理」
有一种省事的思路:既然互质面阵可以拆成两个均匀子阵,那先让两个子阵各自做DOA估计,然后取交集,不就行了吗?这个思路方向对,但直接实现会踩到两个坑。
第一个坑是子阵孔径太小,分辨率不够。互质子阵的阵元间距大,但阵元数量少,阵列孔径通常比完整互质面阵的差集共阵小很多。两个信号角度接近时,子阵MUSIC谱可能只有一个峰,两个峰挨得太近没法分离。子阵估计给出的候选峰里压根没有真实方向,后面取交集自然一场空。
第二个坑是二维峰配对的组合爆炸。每个子阵谱提取20个峰,两组峰做最近邻匹配,最坏情况下要比较20×20个组合,还要处理一个峰被多个峰匹配的歧义。工程上真正靠谱的做法,是把两个子阵的数据放到一起做联合估计,或者用差集共阵构造一个虚拟的均匀阵列。前者是第4章要讲的联合谱处理,后者是利用差集共阵的虚拟阵元做传统MUSIC。两条路我都跑过,差集共阵的路更稳,因为它把互质结构彻底转化成一个虚拟均匀面阵,后续处理完全不用关心模糊问题。
4. 基于互质属性的去模糊实现:MUSIC谱矫正的三步走
4.1 第一步:分别对两个子阵做二维MUSIC,提取候选峰
去模糊流程的第一步是把互质面阵拆回两个子阵,各自独立地计算MUSIC谱并提取峰值。这里的峰值提取不能只取谱最大点,而是要取局部极大值。二维谱峰提取可以用简单的滑窗比较:某个网格点的谱值大于它周围8个邻居,就记为一个候选峰,然后按谱值从大到小排序,取前Q个。
from scipy.ndimage import maximum_filter def extract_peaks(spectrum, theta_grid, phi_grid, Q=20): """ 从二维MUSIC谱中提取前Q个局部峰值 spectrum: (Ntheta, Nphi) 的谱值 theta_grid, phi_grid: 搜索网格,单位是度 返回 (方位角列表, 俯仰角列表, 峰值列表) """ local_max = maximum_filter(spectrum, size=3) peak_mask = (spectrum == local_max) peak_indices = np.argwhere(peak_mask) # 按谱值排序 peak_values = spectrum[peak_mask] sorted_idx = np.argsort(peak_values)[::-1][:Q] theta_peaks = [] phi_peaks = [] for idx in sorted_idx: r, c = peak_indices[idx] theta_peaks.append(theta_grid[r]) phi_peaks.append(phi_grid[c]) return theta_peaks, phi_peaksmaximum_filter的size参数决定了峰与峰之间的最小间距。size=3意味着两个相邻峰至少要隔一个网格点,如果搜索步长是1°,那两个角度差小于2°的峰会被合并成一个,这可能把真实接近的两个信号吞掉。更稳妥的做法是先做一轮峰值提取,再对每个峰周围做抛物线插值精化,把峰值位置修正到亚网格精度。插值公式对二维谱可以分解成两个一维插值:在θ方向上取峰附近的三个点做二次插值,φ方向同样处理。
Q的取值要大于信号源数D,一般取D×5左右。取太少会把真实峰漏掉;取太多则会引入噪声峰,增加后续配对的误匹配率。我一般设D的上限为物理阵元数减1,Q设成这个上限的两倍,然后观察配对结果中是否有稳定的重复峰,再调小Q。这个调参过程是手工的,但做几次就有手感了。
4.2 第二步:差集共阵重构,把互质结构变成虚拟均匀面阵
差集共阵的思路是把互质面阵的非均匀物理阵元做自相关处理,等效出一个阵元位置更密集的虚拟阵列。具体做法是对接收数据的协方差矩阵做向量化(vec操作),得到一个新的数据向量,它的等价导向矢量由物理阵元的差集坐标给出。差集坐标集合定义为{x_i - x_j},其中x_i和x_j遍历所有物理阵元位置,这个集合经过整理后会包含一个完整的均匀矩形栅格区域,这个区域就是差集共阵的连续部分。
def difference_coarray(coords, M, N, d=0.5): """ 计算差集共阵的连续均匀栅格部分 返回虚拟阵元坐标列表,以及对应的选择矩阵 """ # 所有两两差集坐标,单位是d的整数倍 diff_set = set() P = coords.shape[0] for i in range(P): for j in range(P): dx = int(round((coords[i, 0] - coords[j, 0]) / d)) dy = int(round((coords[i, 1] - coords[j, 1]) / d)) diff_set.add((dx, dy)) # 找出能构成连续矩形栅格的最大矩形区域 dx_values = [v[0] for v in diff_set] dy_values = [v[1] for v in diff_set] dx_min, dx_max = min(dx_values), max(dx_values) dy_min, dy_max = min(dy_values), max(dy_values) virtual_coords = [] for ix in range(dx_min, dx_max + 1): for iy in range(dy_min, dy_max + 1): if (ix, iy) in diff_set: virtual_coords.append((ix * d, iy * d)) return np.array(virtual_coords)虚拟阵列的连续栅格区域大小直接决定了可分辨信号数的上限。M=3、N=5时,物理阵元大约有20个,差集共阵的连续区域能达到一个较大的矩形栅格,虚拟阵元数量通常超过80。这带来的直接好处是:虚拟均匀面阵可以直接用传统MUSIC,不需要再处理模糊峰,因为虚拟阵元间距就是半波长的整数倍,没有空间混叠。
但差集共阵重构有一个代价:向量化后的数据向量是单快拍的,等效协方差矩阵的秩为1,直接做特征分解只能得到一个非零特征值。解决办法是空间平滑:把虚拟阵列划分成若干重叠子阵,用子阵间的平移关系重建秩,通常叫SS-MUSIC。空间平滑会把虚拟阵列的有效孔径缩小,所以在平滑次数和孔径之间有个折中,这个折中参数我会在第5章展开说。
4.3 第三步:谱峰配对与模糊峰剔除的完整代码
最后一步是把两个子阵的候选峰做几何匹配,保留两边的公共峰,也就是真实方向。这里的关键是距离度量要放在方向余弦空间,而不是直接比较角度。因为MUSIC谱栅瓣在sinθ域是等间隔的,在角度域不是等间隔,直接用角度差做匹配,模糊峰也可能被误配进来。
def disambiguate_by_pairing(theta1, phi1, theta2, phi2, tol=0.03): """ 用方向余弦距离匹配两组峰,返回匹配对 theta1/phi1: 子阵1的峰方向(度) theta2/phi2: 子阵2的峰方向(度) tol: 方向余弦距离容差 """ matched = [] # 转方向余弦 cos1 = np.cos(np.deg2rad(theta1)) * np.cos(np.deg2rad(phi1)) sin1 = np.cos(np.deg2rad(theta1)) * np.sin(np.deg2rad(phi1)) cos2 = np.cos(np.deg2rad(theta2)) * np.cos(np.deg2rad(phi2)) sin2 = np.cos(np.deg2rad(theta2)) * np.sin(np.deg2rad(phi2)) for i in range(len(theta1)): dist = np.sqrt((cos1[i] - cos2)**2 + (sin1[i] - sin2)**2) min_idx = np.argmin(dist) if dist[min_idx] < tol: matched.append((theta1[i], phi1[i])) return matched配对完成后,你得到的匹配峰数量应该等于真实信号数D,因为两个子阵的栅瓣网格只在真实方向重合。但实际中噪声可能让某些假峰恰好落在容差范围内,所以匹配结果里会出现多余峰。处理多余峰的办法不是直接删,而是看匹配峰之间的谱值一致性:真实方向在两个子阵里都有较高的谱值,假峰至少有一边谱值偏低。可以设定一个谱值比门限,比如真实峰在两个谱里的值都大于各自最大谱值的0.3倍,否则剔除。这个门限需要根据实测噪声调,0.2到0.4之间比较常见。
最后一步是把匹配结果和差集共阵SS-MUSIC的结果做交叉验证。两种方法的物理原理不同,误差来源也不同,如果它们对同一信号输出角度差在1°以内,基本可以确认这个方向是真的。我把这种交叉验证当成发布结果前的例行检查,跑一次不到半分钟,但能避免很多后续解释不清的问题。
5. 互质面阵DOA估计避坑:阵列几何、搜索步长与相干源的5个坑
5.1 互质因子选得太小,自由度优势直接归零
现象:M=2、N=3的互质面阵,差集共阵的连续栅格区域很小,虚拟阵元数量寥寥无几,估计性能甚至不如同阵元数的均匀面阵。
原因:互质因子的乘积决定了差集共阵的连续区域范围。M和N太小,差集集合里能形成连续矩形栅格的点不够多,空间平滑后虚拟阵列孔径严重缩水。互质的好处是需要因子足够大才显现的,M和N至少取3和5,实际工程里我见过用5和7的。
解决:在设计阶段先算差集共阵的连续栅格尺寸,不要只看物理阵元数。用上一章的difference_coarray函数把M和N遍历一遍,找连续区域最大的组合。M=3、N=5和M=4、N=7是两组性价比不错的起步参数,前者阵元少、适合原理验证,后者孔径大、适合实际测向。
5.2 二维搜索网格太粗,MUSIC谱峰分裂成双峰
现象:搜索步长设为2°时,谱峰顶部出现两个相邻的局部极大值,峰值提取算法把它们当成两个峰,配对时产生大量虚假匹配。
原因:MUSIC谱峰的真实宽度和阵列孔径成反比,步长大于谱峰半宽时,离散采样会落在峰两侧而不是峰顶,形成双峰形态。这是离散化带来的伪峰,不是真实的两个信号。
解决:先粗搜后精搜是成熟的规避方案。粗搜步长用5°定位峰的大致区域,再对每个区域用0.1°步长重算谱。如果不想二次搜索,可以给峰值提取算法加一个最小峰间距约束,小于这个间距的相邻峰直接合并,峰间距取预期角度分辨率的1/2。
5.3 信号相干导致协方差矩阵秩亏
现象:两个来波信号完全相干(比如同一个发射源的多径),MUSIC谱只出一个峰,或者两个峰幅度严重不对称,去模糊后只恢复出一个方向。
原因:相干信号让协方差矩阵的秩降到1,噪声子空间的维度判断出错,MUSIC把两个信号当成一个来波。这不是互质结构的问题,是所有子空间类算法的共同弱点。
解决:在信号模型中加入去相干处理,常见做法有前向-后向空间平滑,或直接在数据域用Toeplitz重构。差集共阵方法里空间平滑本身就有去相干作用,所以相干源场景优先走SS-MUSIC路径,不要用双峰配对路径。
5.4 快拍数不足,子空间泄漏导致谱峰偏移
现象:快拍数只有50时,谱峰位置随机偏移约1°~2°,多次实验的估计方差大,甚至出现模糊峰比真实峰更稳定的假象。
原因:有限快拍让协方差矩阵的估计有误差,特征向量不再精确张成信号子空间,MUSIC谱峰会向噪声方向偏移。快拍越少,偏移越随机,而这种随机性对峰值配对的影响比绝对误差更大。
解决:快拍数低于100时,先用差集共阵重构,再对虚拟阵列做多次采样平均;还能用前后向平均来加倍有效快拍数。如果实时性不允许攒快拍,就一定要在配对容差里留出偏移余量,方向余弦容差建议从0.03放宽到0.05。
5.5 峰值配对时容差设置不当,真实峰被误删
现象:配对容差设为0.01,结果两个子阵的真实峰因为角度估计误差超出容差,匹配失败,最终输出结果里直接少了目标。
原因:容差写得太严苛,没有考虑两个子阵孔径不同导致的分辨率差异。孔径小的子阵谱峰胖,估计误差大,真实方向可能偏离理论值好几度。
解决:把容差设成与两个子阵各自的角度分辨率线性相关。以M=3、N=5为例,孔径小的子阵在30°俯仰角处的3dB谱宽约为6°,容差取谱宽的一半比较安全。经验法则:方向余弦空间容差0.05对应大约3°的角度误差,足够覆盖中等信噪比下的估计偏差。
6. 验证去模糊效果的一套指标与蒙特卡洛仿真脚本
去模糊做得到底行不行,不能靠肉眼数峰。三个指标最有说服力:RMSE(均方根误差)、检测成功率和模糊峰残留率。RMSE衡量估计精度,检测成功率衡量算法稳定性,模糊峰残留率专门看去模糊是否彻底。三者合在一起,能同时看出你的方案「偏不偏」「丢不丢目标」「干不干净」。
def monte_carlo_doa(coords, M, N, trials=200, snr_db=20): """ 蒙特卡洛验证互质面阵去模糊性能 返回: 两个方向的RMSE、检测成功率、模糊峰残留率 """ true_theta, true_phi = 30, 45 rmse_list = [] success = 0 residual_fog = 0 for _ in range(trials): # 生成单信号数据,信噪比由噪声方差控制 noise_var = 10 ** (-snr_db / 10) A = steering_vector(coords, np.deg2rad(true_theta), np.deg2rad(true_phi)) X = A @ (np.random.randn(1, 500) + 1j * np.random.randn(1, 500)) X += np.sqrt(noise_var) * (np.random.randn(coords.shape[0], 500) + 1j * np.random.randn(coords.shape[0], 500)) # 两个子阵分别估计,配对去模糊 theta_hat, phi_hat = doa_estimate(X, coords, M, N) if len(theta_hat) == 1: err = np.sqrt((theta_hat[0] - true_theta)**2 + (phi_hat[0] - true_phi)**2) rmse_list.append(err) if err < 1.0: success += 1 else: residual_fog += 1 rmse = np.sqrt(np.mean(np.array(rmse_list)**2)) return rmse, success / trials, residual_fog / trials每次蒙特卡洛试验里,doa_estimate返回的去模糊结果可能不止一个峰。只要输出峰数量大于1,就计一次模糊残留,说明该次去模糊没清理干净。正常参数下,残留率应该低于5%;如果残留率高于10%,优先检查配对容差和峰值提取数量,大概率是假峰被当成真实目标输出了。
我自己的经验是把蒙特卡洛试验当成一种「回归测试」:每改一次配对算法,就跑一遍200次试验,对比RMSE和残留率的变化。有一次我把容差从0.05改成0.03,检测成功率从92%掉到71%,但残留率也降了,这才意识到容差是在「漏检」和「误检」之间做权衡,没有绝对正确的值。现在我的做法是同时输出这两个指标,用Pareto前沿的方式选择参数,而不是单独盯着某一个数。
还有一个便宜好用的验证技巧:把谱峰图和配对结果画在同一张图上,用圆点标出真实方向,用叉号标出算法输出。目视检查能捕捉到很多指标发现不了的问题,比如谱峰合并、栅瓣与真实峰在同一条等值线上等。这套验证流程跑通之后,再去面对不同信噪比、不同来波数目的场景,心里就有底了。希望帮到你。
本文还有配套的精品资源,点击获取