简介针对传统频谱感知算法性能分析提供一套可直接运行的Matlab仿真代码面向认知无线电、无线通信方向的学生与科研人员重点解决能量检测、匹配滤波检测与循环平稳特征检测在低信噪比条件下的性能评估与对比问题。压缩包共3个文件均为.m脚本分别对应三种算法的仿真实现整体仅3KB代码轻量、便于修改与扩展。目前已有723人学习浏览。运行脚本可生成不同信噪比SNR下的误检率与漏检率曲线直观反映各算法优势与局限能量检测实现简单但低SNR下虚警漏检较多匹配滤波检测灵敏度最高但需精确信号先验循环平稳特征检测能利用周期特性提升识别能力但计算复杂度较高。资源可作为课程设计、毕业设计或科研预研阶段的入门参考帮助读者快速掌握传统频谱感知算法的核心原理与仿真方法。1. 传统频谱感知算法在-10 dB边缘集体失效的可复现仿真频谱感知要做的事很简单判断一个频段里“有没有授权信号”。传统频谱感知算法指的是不依赖训练样本、纯粹靠统计判决理论的能量检测、匹配滤波器检测和循环平稳特征检测这三类方法。它们到今天仍然是动态频谱接入、频段占用检测等方案里最常见的兜底实现但性能边界比很多人想得更窄同一套能量检测器在信噪比-8 dB时检测概率还能接近0.9滑到-12 dB就跌破0.5而且不是把判决门限调高调低能救回来的——问题出在噪声功率本身已经“不确定”了。这篇文章围绕“传统频谱感知算法性能分析”这条主线给出一套能直接复现的蒙特卡洛仿真链路覆盖三类算法的原理、判决门限推导、参数设置和五个容易翻车的地方适合正在做认知无线电、动态频谱接入或目标频段占用检测方案评估的开发者参考。2. 三类传统检测器怎么选能量检测、匹配滤波器与循环平稳的边界2.1 能量检测噪声校准下最简单也最脆弱的判决能量检测把“有没有信号”变成“能量有没有超标”。假设接收序列为 r[n]s[n]w[n]H0表示只有噪声H1表示噪声加信号判决统计量写成 T(1/N)Σ|r[n]|²。门限 λ 由噪声方差 σ²、样本点数 N 和目标虚警概率 Pfa 共同决定。它最大的优点是不需要知道信号长什么样适合做宽带频段的快速粗扫所以很多工程第一版都拿它试水。需要特别留意的是复基带信号的能量统计量自由度是 2NN 足够大时才能用高斯近似此时 T 的均值为 Nσ²、方差为 Nσ⁴如果拿实信号的公式去套复信号门限计算会差出将近一倍这是做性能分析时一个很隐蔽的坑。性能分析时最先看的就是 SNR-Pd 曲线和 ROC 曲线。实测里你会发现能量检测对噪声功率的估计误差极其敏感。噪声功率估高了门限跟着抬高低信噪比信号被当噪声滤掉估低了虚警激增。更麻烦的是噪声估计存在相对误差 ρ 时系统存在一个“信噪比墙”SNR wall在墙以下无论怎么拉长观测时间都推不动检测概率。原因是噪声误差带来的门限偏差和信号能量同比例缩小积分增益抵消不掉。这条结论是整个传统频谱感知性能分析里最核心的一条能量检测的软肋不是采样点数不够而是噪声功率校准本身。选型逻辑也很直白如果项目要求低复杂度、低功耗、快速粗扫且目标频段信噪比普遍在 -8 dB 以上能量检测是性价比最高的起点一旦工作区间的下限低于 -12 dB就不该指望它兜底要么上匹配滤波器要么在确认环节换循环平稳检测。2.2 匹配滤波器已知先验时的最优检测器但同步是命门匹配滤波器检测的逻辑是“用已知信号模板做相关”。接收端预先知道授权信号的调制方式、导频序列或帧结构后把本地模板 s[n] 与接收序列做相关相关峰的强度和位置直接指示信号存在性和时延。在 AWGN 信道下匹配滤波器是 Neyman-Pearson 意义上的最优检测器输出信噪比随观测样本的相干积累而线性增长这点比能量检测强很多。衡量匹配滤波器性能时最关注的是理想同步、残余频偏、定时误差三种条件下的 SNR-Pd 曲线对比以及模板不匹配时的鲁棒性。我一般会给它单独加一个频偏参数做扫描当频偏 Δf 与符号速率 Rs 的比值超过几个百分点时相关峰会被周期摊平P_d 断崖式下跌。而仿真里如果没加频偏匹配滤波器的曲线几乎是一条理想直线检测概率在 -20 dB 都接近 1这会带来很重的虚假安全感。所以做这项性能分析时有个原则匹配滤波器必须带非理想同步条件一起评估否则这个算法的“最优性”没有任何工程参考价值。它的适用场景很明确有导频、有帧结构、感知节点能做时间同步的系统完全没有信号先验时它连启动的条件都不具备。2.3 循环平稳特征检测对噪声不确定度免疫但要先知道循环频率循环平稳检测利用的是调制信号统计特性随时间周期变化的性质。比如 QPSK、OFDM 这类信号其循环自相关函数在循环频率 α1/TsTs 为符号周期处会形成明显峰值而平稳噪声在非零循环频率处的能量恒为零。因此它天然不受噪声功率估计误差的影响还具备区分授权信号和同频干扰的能力。实现上比较常用的路线是用 FMA 类算法估计循环谱密度CSD在目标循环频率处取谱线幅度做判别门限计算量明显大于前两种。做性能分析时要注意三点一是循环频率的精度依赖符号速率估计速率估偏了谱线幅度会散掉所以要把“循环频率偏差”也作为参数加进仿真二是计算代价高一次循环谱估计的时间往往比能量检测高出几个数量级不适合做宽带粗扫通常只用在确认环节三是在低信噪比下它的检测性能上限受观测时长约束但相比能量检测没有“信噪比墙”限制所以很多低 SNR 场景的兜底验证都拿它当标尺。三类算法放在一起选择逻辑很清晰要快就能量检测有先验且能同步就匹配滤波器要对抗噪声不确定性就循环平稳。3. 搭一套可复现的蒙特卡洛仿真从QPSK信号到检测概率曲线3.1 仿真链路与默认参数取值先明确整条链路发射端生成 QPSK 基带信号叠加高斯白噪声感知端做判决统计用蒙特卡洛分别统计 H1 下的检测概率 Pd 和 H0 下的虚警概率 Pfa。所有算法都在复基带等效模型里跑不做正交上变频原因是基带高斯模型已经能反映主要性能差异上变频只增加计算量不改变判决统计量的分布特性。默认参数按下表设定参数取值说明采样率 fs8 MHz决定时间分辨率符号速率 Rs2 MHz每符号 4 个采样点每帧符号数256决定一帧的观测长度每帧采样点数 N1024能量统计与门限计算的基准调制方式QPSK星座功率归一化为 1SNR 扫描范围-20 dB 0 dB步进 2 dB目标虚警概率 Pfa1e-2门限推导的输入蒙特卡洛次数1e4 5e4低 SNR 拐点附近建议取大值这套参数里N1024 是刻意选的。它足够大让能量统计量的高斯近似误差可以忽略又足够小让单帧计算在普通笔记本上跑 5 万次蒙特卡洛也只需要几秒钟。实际项目里 N 往往根据感知时延要求来定比如 10 ms 的感知窗口在 8 MHz 采样率下就是 8 万点这时检测曲线会更陡但仿真时间也会变长。先拿 1024 点把方法跑通再按真实窗口缩放是我比较习惯的推进方式。3.2 能量检测的最小可运行实现下面这段代码是整套分析链路的核心跑完会输出一个 SNR 点上的 Pd 和 Pfa用来验证门限推导是否正确。import numpy as np from scipy.stats import norm fs 8e6 # 采样率 8 MHz Rs 2e6 # 符号速率 2 MHz sps int(fs // Rs) # 每符号采样点这里是 4 n_sym 256 # 每帧符号数 N n_sym * sps # 每帧采样点 1024 snr_db -12.0 # 目标信噪比 snr_lin 10 ** (snr_db / 10) target_pfa 1e-2 # 目标虚警概率 iters 10000 # 蒙特卡洛次数 rng np.random.default_rng(42) noise_var 1.0 / snr_lin # 信号功率归一化为 1由 SNR 反推噪声方差 def qpsk_frame(n_sym): bits rng.integers(0, 2, n_sym * 2) sym (bits[0::2] * 2 - 1) 1j * (bits[1::2] * 2 - 1) sym / np.sqrt(2) # 星座点功率归一化 return np.repeat(sym, sps) # 矩形成型基带等效 def energy_metric(x, noise_var): return np.sum(np.abs(x) ** 2) / noise_var # 除以噪声方差便于门限解析计算 qinv norm.ppf(1 - target_pfa) threshold N qinv * np.sqrt(N) # H0 下 T ~ N(N, N) 的高斯近似 hit_h1 0 hit_h0 0 for _ in range(iters): s qpsk_frame(n_sym) w (rng.standard_normal(N) 1j * rng.standard_normal(N)) * np.sqrt(noise_var / 2) x_h1 s w # 有信号假设 x_h0 (rng.standard_normal(N) 1j * rng.standard_normal(N)) * np.sqrt(noise_var / 2) hit_h1 int(energy_metric(x_h1, noise_var) threshold) hit_h0 int(energy_metric(x_h0, noise_var) threshold) print(fP_d {hit_h1 / iters:.4f}, P_fa {hit_h0 / iters:.4f})代码逻辑分四段先生成 QPSK 符号并用np.repeat做矩形成型让每个采样点都携带相同的平均功率避免稀疏星座点导致信噪比计算错位然后按 SNR 反推噪声方差复噪声的实部虚部各取一半方差接着用高斯近似计算判决门限这里依赖 N1024 足够大最后在同一个随机种子下分别统计 H0 和 H1 的命中次数。需要注意噪声方差是解析已知的这是理想假设后面第 5 章会专门讲破坏这个假设带来的后果。参数上最值得调整的是snr_db和target_pfa。把snr_db改到 -8 和 -16Pd 会有肉眼可见的差异把target_pfa从 1e-2 改成 1e-3门限右移Pd 会整体下降这正是 Neyman-Pearson 准则里“提高检测可靠性就要牺牲检测灵敏度”的体现。跑完如果发现仿真 Pfa 和 0.01 偏离超过一个百分点先检查门限公式里的自由度有没有用错。3.3 匹配滤波器对比模板已知时的最小实现能量检测跑通后把同样一帧信号交给匹配滤波器对比会非常直观。这里假设理想同步本地模板和接收信号完全对齐from scipy.stats import chi2 s qpsk_frame(n_sym) w (rng.standard_normal(N) 1j * rng.standard_normal(N)) * np.sqrt(noise_var / 2) x s w tpl s / np.sqrt(np.sum(np.abs(s) ** 2)) # 归一化模板能量为 1 y np.sum(x * np.conj(tpl)) # 相关输出纯量 metric_mf np.abs(y) ** 2 # 幅度平方H0 下服从指数分布 threshold_mf noise_var * chi2.ppf(1 - target_pfa, df2) hit_mf int(metric_mf threshold_mf) print(fmatched filter hit: {hit_mf})这段代码的关键点在于模板归一化。把模板能量归一为 1 后H0 下相关输出 y 是零均值复高斯方差仍为 noise_var所以 |y|² 服从自由度为 2 的卡方分布门限可以直接用chi2.ppf解析算不需要蒙特卡洛。H1 下相关器把 N 个样本的能量相干积累到一点输出幅值远大于能量检测的统计量偏移因此在同样 -12 dB 下匹配滤波器的检测概率通常明显高于能量检测——代价是它要求“知道模板”这个强先验。实际工程里模板不可能完全干净仿真里至少要给模板加幅相误差或频偏否则这条曲线会漂亮得不真实。我在做对比时一般会跑三档理想模板、相位误差 5 度、频偏 0.1Rs这样能看出匹配滤波器的性能对先验质量有多敏感。下面这张对比表是典型趋势具体数值因 N 和 Pfa 而异但相对关系稳定算法-12 dB 时典型表现对先验的要求对噪声估计的敏感度能量检测Pd 明显下降逼近 0.5无需先验很敏感存在 SNR wall匹配滤波器Pd 仍可保持高位需完整模板不敏感循环平稳检测取决于循环谱估计质量需符号速率免疫3.4 统一扫描框架一次跑出完整 SNR-Pd 曲线单独跑单个 SNR 点只是验证性能分析需要整条曲线。把 3.2 的蒙特卡洛循环包一层就能对任意算法做 SNR 扫描。为了避免重复造轮子我习惯把所有检测器包成“输入一帧 IQ、输出判决结果”的函数然后统一交给扫描器驱动def simulate_pd(detector, snr_db_list, iters, n_sym): pd_list [] for snr_db in snr_db_list: snr_lin 10 ** (snr_db / 10) noise_var 1.0 / snr_lin hits 0 for _ in range(iters): s qpsk_frame(n_sym) w (rng.standard_normal(N) 1j * rng.standard_normal(N)) * np.sqrt(noise_var / 2) x s w hits int(detector(x, noise_var)) pd_list.append(hits / iters) return pd_list # 用法示例: 能量检测的 SNR-Pd 曲线 snr_arr np.arange(-20, 1, 2) pd_ed simulate_pd( lambda x, var: energy_metric(x, var) threshold, snr_arr, 10000, 256 )注意simulate_pd里每次迭代都重新生成信号和噪声这是蒙特卡洛的基本要求不能复用同一帧数据去测不同 SNR否则曲线会带上同一批噪声样本的关联性导致统计特性失真。还有一个容易忽略的点threshold是在仿真外部预先算好的它只依赖 N、Pfa 和噪声方差模型不随 SNR 变化。如果有人在循环里对每个 SNR 重新估计噪声方差那么曲线会失真因为真实系统里噪声基底是缓变的不能跟着信号一起“重估”。这也是下一章要展开的核心参数问题。4. 性能分析关键参数怎么设SNR扫描范围、快照长度与目标虚警概率4.1 SNR 扫描范围从全亮到全灭的区间才有效我见过很多首次做频谱感知仿真的人把 SNR 范围设成 -5 dB 到 5 dB跑出来的曲线是一条接近满格的“门框”唯一的结论是“这个算法很好用”。这其实说明仿真区间选错了。有效的 SNR 扫描必须覆盖“所有算法都能正常检测”和“所有算法都接近失效”两个区间之间的过渡带否则看不到任何性能差异。以本文的参数为例能量检测在 -8 dB 附近开始明显下滑到 -14 dB 以下基本失效那么扫描范围至少要比这个区间各外扩 6 dB也就是 -20 dB 到 0 dB 打底。匹配滤波器由于有相干积累增益过渡带可能整体左移 6 dB 甚至更多所以对比时要把下限推到 -25 dB。做法是先粗扫一遍找拐点再在拐点附近把步进加密到 1 dB否则 2 dB 的步进可能正好跳过性能陡降的那一段画出来的曲线看不出“悬崖”在哪。低频段加密还有一个额外的好处拐点附近蒙特卡洛结果的方差最大细步进配合大迭代次数才能把曲线形状稳住。4.2 快照长度 N 与感知时间的取舍N 是整套仿真里最值得调的参数因为它直接对应真实系统的感知时间。N 每增大 4 倍能量检测在同等虚警概率下的灵敏度大约下移 3 dB原理是均值差 N·γ 线性增长而标准差只按 √N 增长信噪比等效增益就是 √N。这个换算关系在工程评估时非常有用想多扛 3 dB 的路径损耗就要付出 4 倍的感知时间这个代价在实时频谱感知系统里往往比调算法更敏感。匹配滤波器对 N 的反应更剧烈。相干积累增益与 N 成正比所以 N 翻倍输出信噪比翻倍3 dB这比能量检测的“4 倍换 3 dB”要划算。代价是它需要模板和同步而这两样在 N 变大后更容易失配。所以实际项目里 N 的选择不是简单取大而是先定感知时延上限再反推 N最后把 N 代进门限公式验证 Pfa 是否可控。另一个细节是加窗做检测一般不加窗或只用矩形窗加汉宁窗会让帧边缘的能量衰减减小有效观测长度这是不划算的。仿真阶段如果波形本身有成形滤波N 的定义要落在“参与判决统计的采样点”上而不是包含滤波延时的总数据块长度。4.3 目标 Pfa 与 Pd 的取值逻辑先定漏检风险再定虚警代价频谱感知的性能指标里Pfa 和 Pd 不是对称的两种概率而是两种完全不同的代价。Pfa 高最多是把空闲频段误判成占用损失的是频谱利用率Pd 低意味着授权信号出现了却没检测到这可能直接造成有害干扰。所以指标设计上我一般先按业务要求定 Pd 的下限比如 0.9然后留出余量定 0.95再在这个约束下倒推允许的 Pfa。门限只有一个所以不能在给定门限的同时同时要求 Pfa0.01 且 Pd0.99它们由门限公式绑在一起。判据只能固定其中一个让另一个被决定。Neyman-Pearson 准则的做法是固定 Pfa最大化 Pd这就是本文代码里threshold N qinv * sqrt(N)的来由。把 Pfa 拉低一个数量级比如从 1e-2 改到 1e-3门限右移约 0.6 倍标准差Pd 在整个过渡带上都会显著下移。实际系统里 Pfa 取 0.1 到 0.01 之间比较常见低于 1e-3 会让门限过高低信噪比场景基本无法满足 Pd 需求高于 0.1 则空口利用率损失太明显业务侧通常不会接受。5. 频谱感知仿真翻车点门限失配、噪声污染与同步敏感的五个排查记录5.1 门限和 Pfa 对不上仿真结果“看起来正常但细看偏了”现象设了 target_pfa1e-2仿真 1 万次Pfa 跑出 2% 甚至 5%而且怎么加迭代次数都稳定偏高。原因最常见的是门限公式里的方差或自由度用错比如复信号当成实信号处理N 少乘了 2导致门限偏左。解决先做纯噪声假设下的独立验证只跑 Pfa 并与目标比较Pfa 对不上时不要先怀疑蒙特卡洛次数先检查门限的解析推导和自由度。5.2 噪声功率估计被信号污染Pd 虚高现象在某个频段录了一段 IQ 数据能量检测的 Pd 曲线漂亮得异常但把纯噪声数据送进去Pfa 也爆表。原因接收端用“当前待检帧”自身的能量去估计噪声基底信号能量被算进了噪声功率门限被压低虚警自然爆炸。解决噪声功率必须在静默期或已知无信号的频段测量仿真里也要坚持“噪声方差由先验已知不从事后观测估计”的分离原则否则每个 SNR 点都在用不同门限曲线不可比。5.3 N 太小时硬套高斯近似低 Pfa 区域失真严重现象N 取 64 时仿真 Pfa 在 0.01 附近和理论值偏差很大在 0.001 区域偏差更夸张。原因能量统计量真正服从卡方分布高斯近似只在 N 足够大时才成立N64 时右尾概率被低估。解决N 小于 256 时改用scipy.stats.chi2.ppf查卡方分位数计算门限不要用norm.ppf同时把 Pfa 的仿真验证点加密优先在 0.01 和 0.001 两个点核对。5.4 蒙特卡洛次数不够曲线反复抖动现象连续跑两次同一组参数SNR-Pd 曲线在拐点附近差出 5 个百分点以上。原因蒙特卡洛估计的收敛速度只和 √M 成正比1e3 次迭代在概率 0.5 附近的单点标准差约 1.6%画成曲线当然不稳。解决报警门槛定在 1e4 次如果目标概率接近 0.1 或 0.9单点波动 0.3% 仍需要 1e4 量级要做发论文或出报告用的平滑曲线拐点附近单独跑到 5e4 次。注意固定随机种子否则同一段代码两次跑出不同曲线排查问题时分不清是算法差异还是随机差异。5.5 匹配滤波器仿真没加频偏性能好得不像真实系统现象匹配滤波器的 SNR-Pd 曲线在 -20 dB 还接近满格团队开始讨论是不是可以用更短的感知时间。原因仿真里模板和接收信号理想对齐没有任何载波频偏和定时误差。实际系统里晶振偏差加多普勒就能让相关峰碎裂性能断崖式下跌。解决在仿真链路里引入残余频偏参数从 0.05Rs 开始扫描看 Pd 的拐点位置同步误差必须和 SNR 一起作为横轴变量否则匹配滤波器的“最优性”是纸面上的落不了地。6. 从 AWGN 到 IQ 回放让频谱感知算法去面对真实电磁环境仿真曲线只是第一步我个人的习惯是仿真阶段确认算法形态后立刻转到 IQ 回放验证。所谓 IQ 回放就是把软件无线电前端录下来的真实频段数据离线喂给同一个检测器看算法在真实噪声基底、真实干扰和真实衰落下的表现。它比纯 AWGN 仿真多出三层信息噪声是非高斯的、信号是带频偏和定时漂移的、频段里可能同时存在多个瞬态脉冲。具体流程分三步。第一步在目标频段无信号时段录一段“静默期 IQ”长度至少覆盖几百帧用来估计真实噪声方差第二步录制目标频段的 IQ 数据按固定帧长切帧用仿真里同一套门限公式做检测第三步把检测结果按时间轴画出来和实际频谱占用参考比如用更大的 FFT 池做能量平均得到的占空比做对比校准门限偏移量。代码骨架如下import numpy as np def load_iq(path): return np.fromfile(path, dtypenp.complex64) # 软件无线电前端录制的复数 IQ def frame_energy_detect(iq, frame_len, noise_var, threshold): det [] for idx in range(0, len(iq) - frame_len, frame_len): frame iq[idx:idx frame_len] energy np.sum(np.abs(frame) ** 2) / noise_var det.append(int(energy threshold)) return np.array(det) # 静默期 IQ 用于评估噪声方差, 目标频段 IQ 用于检测 # noise_var 建议用静默期数据的中位数估计, 避免瞬态脉冲拉高均值真实数据会遇到一个仿真里没有的问题噪声方差不是平稳的。频段里的突发干扰、邻道泄漏都会让短时噪声功率跳动这也是为什么实际系统的能量检测都带自动增益控制和噪声跟踪模块。我在回放验证时习惯把静默期噪声方差的中位数作为基准不直接拿均值因为均值会被瞬态脉冲拉高。换到信号段检测时如果帧能量的分布明显分成了两簇说明门限位置基本合理如果两簇重叠严重再回头调 N 和 Pfa。这个环节通常能暴露仿真里藏得最深的乐观假设噪声高斯、同步理想、模板干净。我自己的教训是第一次拿真实 IQ 回放时能量检测的虚警率比仿真高出将近一倍追查下来发现是回放数据里有周期性的窄带脉冲高斯假设在短时帧上完全不成立。从那以后我养成了一个习惯仿真阶段就把噪声不确定性、频偏、定时误差这些非理想因素做成参数每个算法都跑一遍“理想 vs 带误差”的对照而不是等项目联调时才想起来补。留一个误差参数的开关比后面推倒重来省太多时间了。希望帮到你。本文还有配套的精品资源点击获取