简介:这套资源面向无线通信与认知无线电方向的学习者,聚焦三种传统频谱感知算法的仿真实现与性能对比。压缩包内共三个脚本文件,分别对应能量检测、匹配滤波检测与循环平稳特征检测的独立代码,整体体积仅三KB,便于直接运行和修改参数。目前已有七百二十三人学习使用,适合正在研究频谱感知、需要快速搭建仿真实验环境的本科生或工程师。通过调节不同信噪比条件,可直观比较三者在误检率与漏检率上的差异:能量检测实现简单但低信噪比下性能受限,匹配滤波检测灵敏度高却依赖信号先验信息,循环平稳检测对周期性结构信号更稳健但计算开销较大。资源代码结构清晰,注释简明,可作为课程设计或学位论文验证的起点,也便于在现有框架上继续扩展与优化。
1. 频谱感知的仿真瓶颈:为什么传统检测算法仍值得重跑一遍
频谱感知是认知无线电最基础的环节,但真正能把仿真跑通的人比想象中少。很多人以为三大传统算法——能量检测、匹配滤波检测、循环平稳特征检测——只是翻翻公式就能出结果,实际用 MATLAB 一跑才发现:噪声功率怎么估计、检测门限怎么设、采样点数取多少、蒙特卡洛跑多少轮,每一处都在左右最后的性能曲线。这套资源把三种检测器的仿真实现和性能对比打包在一起,信号模型、检测器函数、曲线绘图脚本都是现成的。适合正在做课程设计、想复现算法对比、或者需要给课题凑一组可信仿真数据的从业者,直接改参数重跑,比自己从零搭省掉大量排错时间。
2. 三种传统检测算法:原理、假设与选型判断
先把三大检测器分开梳理一遍。后面所有 MATLAB 函数都是建立在公式和前提上的,仿真结果一旦不符合预期,回到原理层找原因是最快的路径。
2.1 能量检测:统计量构造与噪声不确定性的代价
能量检测的出发点很朴素:接收信号平方求和再取平均,在“只有噪声”和“信号加噪声”两种假设下会呈现可区分的差异。设接收序列为 x[n],统计量写为
T = 1/N · Σ |x[n]|²
假设噪声是零均值高斯白噪声,功率为 σ²,那么纯噪声下 T 近似服从均值为 σ²、方差为 2σ⁴/N 的高斯分布;信号存在时,均值变为 σ² + P_signal。两者分布的间隔由信噪比决定,信噪比越低,两个分布重叠面积越大,检测错误就越多。
门限不是随便给的,它应该由目标虚警概率 Pfa 和噪声功率 σ² 推导出来。高斯近似下理论门限是
η = σ² · (1 + sqrt(2/N) · Φ⁻¹(1-Pfa))
写成 MATLAB 代码就这么几行:
% 理论门限推导,高斯近似下 pfa = 0.01; % 目标虚警概率 1% noisePow = 1; % 归一化噪声功率 normInv = -sqrt(2) * erfinv(1 - 2*pfa); % 标准正态分位数,等价于 norminv eta = noisePow * (1 + normInv * sqrt(2/N)); T = mean(abs(rx).^2); % 检测统计量:采样点能量求平均 D = T > eta; % 判决结果:1 判有信号,0 判无信号注意normInv这行,我用erfinv代替了统计工具箱的norminv,在没有额外工具箱的机器上也能跑。N 是单次感知的采样点数,N 越大统计量方差越小,同信噪比下检测概率越高,但感知时间也拉长,这是能量检测的第一组权衡。
能量检测最被诟病的点是噪声不确定性。仿真里噪声功率是设定好的常数,但真实场景中噪声底每时每刻在变。实际做项目时,如果噪声估计值只差 1 dB,检测性能就会明显下滑。所以在对比算法时,能量检测适合当基线,不适合当最终方案。
2.2 匹配滤波检测:相干增益背后的先验条件
匹配滤波在已知发送波形的前提下是最优检测,接收机只需要把接收序列和已知波形副本做互相关:
T_mf = |Σ x[n] · s*[n]|² / σ²
这里 s[n] 是已知发送波形,σ² 是噪声功率。相关累积会把信号能量相干叠加,等效信噪比提升约 N 倍,所以匹配滤波在低信噪比下表现非常抢眼。
但代价同样明显:接收机必须知道信号的调制方式、脉冲成型、载波频率和相位。仿真里模板就是发送信号本身,所以一切顺利;真实场景中只要出现载波频偏或定时偏差,相关增益就会大幅跌落。
% 匹配滤波检测:与已知波形副本做相关累积 template = tx; % 无噪声发送信号波形副本 corrVal = sum(rx .* conj(template)); % 相关累积,N个采样点相加 T_mf = abs(corrVal).^2 / noisePow; % 归一化统计量 D_mf = T_mf > eta_mf; % eta_mf 同样由 Pfa 反推这段代码的关键在template。必须保证它是纯信号的副本,不能混入当前这轮接收信号中的噪声分量。不少仿真翻车就是因为在构造模板时顺手把接收序列复制过去了,结果相当于把答案写进模板,检测概率当然虚高。
匹配滤波的门限理论分布是卡方分布,但 N 足够大时直接用高斯近似也够用。实际代码里我习惯先跑一段纯噪声,统计统计量的经验分布,再按分位数取门限,比直接套公式更可靠。
2.3 循环平稳特征检测:用循环频率做区分
循环平稳检测是我个人最喜欢的一个,因为它的物理含义清楚:通信信号经过调制、脉冲成型和编码之后,统计特征不是平稳的,而是呈现周期性。这种周期性会在循环频率(cyclic frequency)处产生谱相关峰值。噪声没有这种结构,所以在非零循环频率处理论上谱相关是零。
实际实现常用周期相关函数(Cyclic Autocorrelation Function, CAF),核心计算是
R_x^α(τ) = (1/N) · Σ x[n] · x*[n-τ] · e^{-j2παn/fs}
其中 α 是循环频率,τ 是延迟。对 BPSK 信号,峰值通常出现在 α = 2·fc 和 α = ±Rs 附近,所以不用全频段扫描,扫几个重点位置就行。
% 循环平稳特征检测:在候选循环频率上求周期相关函数 tau = round(fs / Rs); % 延迟取一个符号周期 alphaList = [2*fc, Rs, 2*fc+Rs]; % 扫描重点循环频率 for k = 1:length(alphaList) alpha = alphaList(k); R = rx(1:end-tau) .* conj(rx(1+tau:end)) ... .* exp(-1j*2*pi*alpha*(0:N-tau-1)' / fs); feat(k) = abs(mean(R)); % 取模平均得到特征值 end % 判决:峰值相对背景均值超过阈值则判有信号 D_cyc = max(feat) > 3 * mean(feat);这里的循环频率列表是 BPSK 信号的关键位置。如果换成 QPSK 或 OFDM,谱相关峰的位置会变化,需要按调制方式调整alphaList。tau 取一个符号周期是经验做法,太小或太大都会削弱特征峰值。
判决阈值3 * mean(feat)是工程简化,严谨做法是先跑纯噪声统计特征值的分布,再按 Pfa 取分位数。这个检测器的优势是抗噪声不确定性,但计算量明显高于前两者,在长序列仿真里耗时很可观。
2.4 三种检测器怎么选:一张表看明白边界
放到一个具体场景里对比,假设主用户信号是 BPSK,采样率为 10 MHz,符号速率 1 Msps,目标虚警概率 1%,信噪比范围 -16 dB 到 4 dB。三种检测器在参数条件和适用场景上的边界大致如下。
| 检测器 | 先验信息要求 | 抗噪声不确定性 | 计算量 | 典型适用场景 |
|---|---|---|---|---|
| 能量检测 | 只需噪声功率 | 最差 | 最低 | 宽带盲检、频谱占用图 |
| 匹配滤波检测 | 完整波形+载波同步 | 中等 | 中 | 已知主用户信号的协作感知 |
| 循环平稳特征检测 | 只需循环频率位置 | 最好 | 最高 | 低信噪比、对抗噪声波动 |
选型时一个常见误区是认为循环平稳检测永远最好,但它对采样长度和计算资源的要求高,在实时的频谱感知场景里往往撑不住。反过来,能量检测虽然简单,做频率占用情况的粗筛非常实用。
这份资源里三个检测器都给了完整可跑的脚本,我建议先跑默认参数,把三条检测概率曲线叠在一张图上,再按上面表格的逻辑去理解曲线的差距,比单纯改参数要有用得多。
3. 频谱感知的 MATLAB 仿真:信号模型、检测器函数与蒙特卡洛框架
原理讲完,落到实现。这一章把仿真工程拆成三块,每一块都有可直接替换的代码,改参数就能得到自己的结论。
3.1 公共仿真框架:全局参数与信号源
一个完整的频谱感知仿真流程是:生成发送信号、叠加信道噪声、送入检测器、统计判决结果、蒙特卡洛循环上万次得到检测概率。先搭总框架:
%% 全局仿真参数区,改这里即可重跑一组实验 fs = 10e6; % 采样率 10 MHz fc = 200e3; % 基带等效载波频率 200 kHz Rs = 1e6; % 符号速率 1 Msps N = 4096; % 单次感知的采样点数 M = 500; % 蒙特卡洛重复次数 snrList = -16:2:4; % 信噪比扫描范围,单位 dB pfa = 0.01; % 目标虚警概率 rng(7); % 固定随机种子,保证结果可复现 %% 基带信号生成:BPSK + 矩形脉冲成型 numBits = floor(N * Rs / fs); tx_bits = 2 * randi([0 1], 1, numBits) - 1; % BPSK 符号,±1 up_tx = zeros(1, N); up_tx(1:round(fs/Rs):end) = tx_bits; % 零插值,等效矩形脉冲 tx = up_tx(1:N);参数说明:fs/Rs是过采样倍数,这里是 10,即每个符号采 10 个点。矩形脉冲成型在数学上最简单,实际通信系统会用根升余弦脉冲成型来压缩带宽,如果换了脉冲成型,信号带宽和频谱特征会改变,循环平稳检测的峰值位置也要重新扫。为教学仿真考虑,矩形脉冲已经够用。
注意rng(7)这一行。固定随机种子是仿真可复现的前提,不固定的话每次跑出来的曲线都不完全一样,对比算法时很容易把随机波动误判成性能差异。
3.2 能量检测函数实现
能量检测器作为一个独立函数封装:
function [T, D] = energyDetect(x, eta) % x 输入接收采样序列 % eta 检测门限 % T 能量统计量 % D 判决结果,1 有信号,0 无信号 T = mean(abs(x).^2); D = T > eta; end函数本身只有两行,真正的工作在调用之前——门限怎么算。门限的计算放在主脚本里,因为需要知道 N、pfa 和噪声功率这些上下文信息。我把理论门限推导放在主循环之前:
% 主脚本内,蒙特卡洛循环前的门限计算 normInv = -sqrt(2) * erfinv(1 - 2*pfa); eta = noisePow * (1 + normInv * sqrt(2/N));这里noisePow由主脚本统一设置,整个仿真里的噪声功率都用它,保证三种检测器面对同样条件。
3.3 匹配滤波检测函数实现
function [T_mf, D_mf] = matchFilterDetect(x, template, noisePow, eta_mf) % x 接收采样序列 % template 已知发送波形副本,不含噪声 % noisePow 噪声功率 % eta_mf 匹配滤波门限 % T_mf 相关统计量 % D_mf 判决结果 corrVal = sum(x .* conj(template)); T_mf = abs(corrVal).^2 / noisePow; D_mf = T_mf > eta_mf; end模板必须来自发送信号tx,而不是接收信号rx。我见过有人图省事直接拿rx当模板,结果相关值里混进了噪声能量,检测概率在高信噪比下接近 100% 还不觉得异常,直到换低信噪比才发现曲线下不来。这个问题在真实系统里不存在,但在仿真里稍不注意就会犯。匹配滤波的eta_mf沿用同一套 Pfa 反推逻辑,但统计量的分布不同,更严谨的做法是单独跑一段纯噪声来标定。
3.4 循环平稳特征检测函数实现
function [feat, D_cyc] = cyclostationaryDetect(x, alphaList, fs, tau) % x 接收采样序列 % alphaList 待扫描的循环频率集合 % fs 采样率 % tau 延迟采样点数 % feat 各循环频率对应的特征值 % D_cyc 判决结果 N = length(x); feat = zeros(size(alphaList)); for k = 1:length(alphaList) alpha = alphaList(k); R = x(1:end-tau) .* conj(x(1+tau:end)) ... .* exp(-1j*2*pi*alpha*(0:N-tau-1)' / fs); feat(k) = abs(mean(R)); end D_cyc = max(feat) > 3 * mean(feat); end这个函数里最耗时的就是那个 for 循环。alphaList 越长,计算量越大,所以扫描点要精不要多。对 BPSK 信号,alphaList = [2*fc, Rs, 2*fc+Rs]已经能覆盖主要特征。tau 的取值影响特征值的强度,取round(fs/Rs)就是延迟一个符号周期,这是工程上比较稳妥的起点。
3.5 蒙特卡洛主循环:统计检测概率
蒙特卡洛循环是整个仿真的引擎,在每一个信噪比点上重复 M 次实验,统计检测到信号的次数,再除以 M 得到检测概率估计值。
% 预分配结果矩阵 Pd = zeros(length(snrList), 3); % 三列分别是能量、匹配滤波、循环平稳 for idx = 1:length(snrList) snr = snrList(idx); hits = zeros(1, 3); for trial = 1:M % 生成噪声 noise = sqrt(0.5) * (randn(1, N) + 1j * randn(1, N)); noisePow = mean(abs(noise).^2); % 按信噪比换算信号幅度 amp = sqrt(10^(snr/10) * noisePow); rx = amp * tx + noise; % 能量检测 [~, D1] = energyDetect(rx, eta); hits(1) = hits(1) + D1; % 匹配滤波检测 [~, D2] = matchFilterDetect(rx, tx, noisePow, eta_mf); hits(2) = hits(2) + D2; % 循环平稳特征检测 [~, D3] = cyclostationaryDetect(rx, alphaList, fs, tau); hits(3) = hits(3) + D3; end Pd(idx, :) = hits / M; end注意noisePow是在循环内用mean(abs(noise).^2)实时算出来的。这样做比用理论值更贴近实际,因为有限长度的噪声样本功率会有波动,而这种波动正是噪声不确定性的一个来源。改用固定的理论功率值会让仿真结果偏乐观,和真实场景对不上。
M 的取值直接决定曲线平滑度。M=500 时单条曲线大概需要跑一两分钟,曲线还带一点抖动;M=2000 以上曲线平滑得多,但仿真时长按比例翻倍,适合最终出图时用,调参阶段没必要跑那么多。
4. 性能对比与参数灵敏度:从检测概率曲线到 ROC 的关键设置
检测器都跑通了,接下来是对比实验设计。这一阶段决定你的仿真结论是不是站得住脚。
4.1 对比维度与控制变量设置
三种检测器做对比,前提是控制变量。我一般固定以下条件:
| 参数 | 取值 | 说明 |
|---|---|---|
| 采样点数 N | 4096 | 三种检测器统一 |
| 目标虚警概率 | 0.01 | 三种检测器统一 |
| 信噪比范围 | -16 dB ~ 4 dB | 覆盖性能拐点区域 |
| 蒙特卡洛次数 | 500 ~ 2000 | 调参期 500,出图期 2000 |
| 信号类型 | BPSK | 保持调制方式一致 |
有一个容易被忽视的点是噪声功率。如果三种检测器分别在各自的代码里定义了不同的噪声功率,那对比结果没有任何意义。我习惯把noisePow设置为全局变量,在蒙特卡洛循环内部实时计算,确保每轮实验三种检测器看到的是同一批噪声样本。
4.2 检测概率与信噪比曲线解读
跑出来的 Pd 曲线通常呈现 S 形:低信噪比时贴近虚警概率,高信噪比时趋近 1,拐点位置是性能差距的直观体现。匹配滤波曲线的拐点出现得最早,能量检测次之,循环平稳特征检测在低信噪比区域通常比能量检测更好,但计算耗时也明显增加。
曲线出现异常的时候先别急着怀疑检测器,先看信号生成本身。比如amp的计算是否正确,tx是否已经归一化。我想强调的是,性能对比的原则是“看趋势更看拐点”,不要盯着某一两个点过度解读,蒙特卡洛本身就是随机实验,有波动是正常的。
4.3 ROC 曲线与门限权衡
检测概率和虚警概率是成对出现的。固定信噪比,把门限从低到高扫一遍,就能得到一条 ROC 曲线。ROC 曲线越靠近左上角,代表检测器性能越好。这个指标比单点 Pd 更全面,因为它展示了在不同工作点下的能力边界。
代码实现上,就是把前面固定门限的单次判决,改成门限扫描加重复统计:
% ROC 曲线扫描示例:固定 SNR = -8 dB snrFixed = -8; ampFixed = sqrt(10^(snrFixed/10) * noisePow); thresholdList = linspace(0.5, 2.5, 50); % 对能量统计量归一化后的门限扫描 Pd_roc = zeros(size(thresholdList)); Pfa_roc = zeros(size(thresholdList)); for ti = 1:length(thresholdList) th = thresholdList(ti); % 有信号情况 hits1 = 0; % 纯噪声情况 hits0 = 0; for trial = 1:M noise = sqrt(0.5) * (randn(1, N) + 1j * randn(1, N)); rx_sig = ampFixed * tx + noise; rx_noise = noise; T_sig = mean(abs(rx_sig).^2); T_noise = mean(abs(rx_noise).^2); hits1 = hits1 + (T_sig > th); hits0 = hits0 + (T_noise > th); end Pd_roc(ti) = hits1 / M; Pfa_roc(ti) = hits0 / M; end % 绘图时用 plot(Pfa_roc, Pd_roc)这段代码里的 thresholdList 需要多次实验来定范围。最稳的办法是先跑一次统计量分布,把纯噪声和有信号两种情况下统计量的取值范围打出来,再来选扫描区间。盲目扫描不但浪费计算时间,还可能在重叠区域画不出曲线的关键弧度。
4.4 参数灵敏度:采样点数和蒙特卡洛次数的影响
采样点数 N 是把双刃剑。N 越大,能量检测的统计量方差越小,门限越容易把噪声和信号分开;匹配滤波的相干积累增益也越大;循环平稳检测的特征值越稳定。但 N 同时意味着更长的感知时间和更高的计算负载,实际系统里 N 往往受限于感知时隙长度。
蒙特卡洛次数 M 影响的是统计精度。M 太小时,Pd 曲线毛刺很重,拐点位置不稳定;M 足够大之后,曲线基本稳定,再增加 M 收益开始递减。这是典型的投入产出取舍:跑 2000 次 M 能接受的等待时间,跑 5000 次就得考虑换台机器了。
5. 频谱感知仿真常见问题与排查:五个高发病例的处理记录
仿真跑多了会碰上各种奇怪的输出。这一章五个问题是我在调试中最常碰到的,每条按现象、原因、解决三段来写。
5.1 检测概率曲线平在地上,完全分不出来
现象是信号信噪比已经到 0 dB,能量检测和循环平稳检测的 Pd 仍然和 Pfa 重叠在一条线上。
原因往往是信号幅度没有按信噪比换算对。最常见的情况是amp计算错了,比如直接用sqrt(snr)而不是sqrt(10^(snr/10)),或者发送信号tx的功率没有归一化,导致实际注入的信噪比远低于设定值。另一个原因是噪声功率在循环内部被重复计算,但信号功率没有做同样处理,导致信噪比虚低。
解决方法是先加一组诊断代码:在蒙特卡洛循环里打印mean(abs(rx_sig).^2)和mean(abs(noise).^2),直接核对实际信噪比与设定值是否一致。这个简单的检查能排除一大半问题。
5.2 能量检测门限怎么调都对不上目标虚警概率
现象是设了 Pfa=0.01,跑出来的虚警概率是 0.03 或者 0.005,总是偏离。
原因是理论门限公式基于高斯近似,N=256 以下或者信号分布偏差大时,近似误差就很明显;另外,如果采用复噪声,统计量的自由度和实噪声不同,公式里 sqrt(2/N) 需要修正。
解决思路是放弃纯理论门限,改用标定法:先单独跑一段较长纯噪声,统计量分布的经验分位数作为门限基准。这个做法在工程中更可靠,也更容易解释给评审听。
5.3 匹配滤波检测概率虚高
现象是低信噪比下匹配滤波的 Pd 已经接近 1,而能量检测还在 0.2 附近,差距大得不像话。
原因是模板泄露。模板template直接用了接收序列rx,或者模板虽然是tx,但在生成tx时没有固定随机种子,导致每轮蒙特卡洛的发送波形都不一致,却被当作“已知模板”。真实场景里接收机不知道当前这帧信号的具体内容,只能依赖事先约定的发送波形,模板必须与发送端保持一致。
解决方法是固定随机种子,并且模板只取tx,同时做一次相关性检查,确保sum(rx .* conj(template))中噪声项不参与模板构造。
5.4 循环平稳检测慢到难以忍受
现象是蒙特卡洛跑 100 次,循环平稳检测就耗了十几分钟,而另外两个检测器几秒就完成。
原因是两个:alphaList 太长导致 for 循环迭代过多,以及特征值计算里用到了非矢量化的 exp 运算。比如把 alphaList 展开成几百个循环频率点,每次都要做复数乘法和指数计算,性能自然撑不住。
解决方法是缩小扫描范围到三五个峰值点即可,同时将 exp 部分的索引计算改造成矢量形式。另外,tau的选择也影响耗时,tau太小导致有效样本长度几乎等于 N,运算量反而更大,取一个符号周期就合适了。
5.5 对比曲线不公平:三种检测器的实验条件不一样
现象是最终画出的三条曲线上,能量检测明显不如另外两个,但仔细观察发现它的采样点数、虚警概率或信噪比定义和其他两个不一致。
原因是参数被分散在各个脚本里,有人改了一个脚本却忘了改另一个。这是仿真项目中非常常见的管理问题,不一定与技术原理有关。
解决方法是把公共参数集中到一个参数文件或脚本顶部,比如用第一节的全局参数区,三种检测器调用同一组 N、Pfa、snrList、M。我当时用这种写法重构了整套框架,之后很少再发生对比条件不一致的问题。
6. 仿真结果可信度校准:三个验证手段和一个习惯
拿到一组仿真曲线,第一件事不是急着截图写报告,而是先验证结果能不能站住脚。
验证手段一:把仿真检测概率曲线和理论曲线叠在一起。能量检测在有明确信噪比时是有理论检测概率公式的,虽然近似,但趋势可以参考。如果仿真曲线和理论曲线的拐点位置相差超过 2 dB,基本可以认定参数设置有误,先回头查信噪比换算和门限推导。
验证手段二:换一套随机种子重跑一遍。固定随机种子只能保证可复现,不能证明结果不依赖某个随机序列。换两个不同的种子,看曲线抖动是否在接受范围内。如果两条曲线的拐点漂移超过 1 dB,就把蒙特卡洛次数 M 加大再跑。
验证手段三:打印关键中间量,拒绝把仿真当黑匣子。每次蒙特卡洛循环内抽取几轮,打印实际噪声功率、信号功率、门限值和统计量,人工核对一下是否在合理区间。这个习惯帮我排掉过大量隐蔽问题。
说句实际的,我早期做频谱感知仿真时吃过亏:有一次拿到的匹配滤波结果无限完美,检测概率在任何信噪比下都是 1,我把结果做成图放进报告,直到导师看了一眼问模板是不是包含噪声了,我才意识到自己犯了数据泄漏。从那以后我每次拿到频谱感知类仿真,都会强制执行一套计算法校验:先跑纯噪声拿到虚警率,再跑强信号拿到收敛曲线,最后核对实际信噪比与设定值,三条不逐条通过不画最终图。这个流程虽然啰嗦,但帮我少踩了无数坑。希望帮到你。
本文还有配套的精品资源,点击获取