简介:该资源聚焦OFDM信号频谱感知与协作频谱感知方向,面向通信工程、信号处理专业的学生及研究人员,尤其适合正在学习循环平稳检测、能量检测等经典感知算法并希望动手复现的读者。资源包共6个文件,包含4个m脚本与2个txt说明文件,整体约6KB,其中m文件承载OFDM频谱感知与瑞利信道相关的MATLAB仿真代码,txt文件则提供配套说明与参考信息,便于快速理解代码结构与实验背景。内容涉及匹配滤波器检测、能量检测、循环平稳特征检测及多分辨率频谱感知等单节点方法,并延伸至阴影与深度衰落场景下的协作感知融合,涵盖“或”准则、D-S证据理论及似然比检测软判决等思路,可帮助读者对比不同融合策略的检测可靠性。目前已有234人学习,适合作为频谱感知算法入门复现与协作感知方案对比的参考素材。
1. OFDM 频谱感知:从历史频谱到循环平稳检测的工程落地
第一次接触 OFDM 信号频谱感知,很多人会以为这只是“能量检测换个名字”。真到工程里跑一遍就会发现,能量检测在低信噪比下几乎不可用,噪声一波动就误判。OFDM 信号本身有循环前缀、子载波正交结构,这些结构在统计上留下周期性的痕迹,循环平稳检测就是抓这个痕迹。标题里的“HistoricalSpectrum”可以理解成把历史频谱数据积累起来做趋势判断,而不是只看当前一帧。协作频谱感知则是多个节点把各自的判决汇总,弥补单点衰落。这套组合适合做频谱监测、认知无线电、无线资源管理的从业者,尤其是需要在 -10 dB 以下信噪比仍能区分信号有无的场景。下面按“信号怎么建模 → 循环特征怎么算 → 协作怎么融合 → 坑在哪”的顺序拆开讲。
2. OFDM 循环平稳特征:为什么自相关函数能暴露信号存在
2.1 从 OFDM 符号结构推导循环频率
OFDM 符号由有用部分和循环前缀组成,循环前缀是把有用部分尾部复制到前面。这个复制操作让信号在时域上具有周期性,具体表现为自相关函数在延迟等于有用符号长度时出现峰值。对循环平稳过程,自相关函数是周期性的,展开成傅里叶级数后,非零系数对应的频率就是循环频率。
设接收信号为 (x(t)),其循环自相关函数定义为:
[ R_x^\alpha(\tau) = \lim_{T \to \infty} \frac{1}{T} \int_{-T/2}^{T/2} x(t+\tau/2) x^*(t-\tau/2) e^{-j2\pi \alpha t} dt ]
其中 (\alpha) 是循环频率。对 OFDM 信号,当 (\alpha = k/T_u)((T_u) 为有用符号长度,(k) 为整数)时,(R_x^\alpha(\tau)) 在 (\tau = T_u) 处出现明显峰值。噪声是平稳的,其循环自相关在非零循环频率处趋近于零。这就是循环平稳检测能抗低信噪比的根源。
实际实现时不会直接算积分,而是用有限长度的采样做估计。常见做法是分段计算然后平均,段数越多估计越稳,但时间分辨率下降。我一般取 64 到 256 段,每段长度至少覆盖两个 OFDM 符号。
2.2 用 Python 计算循环自相关谱
下面这段代码演示如何从接收采样中估计循环自相关,并画出循环频率谱。输入是复数基带采样,已知 OFDM 有用符号长度和循环前缀长度。
import numpy as np import matplotlib.pyplot as plt def cyclic_autocorrelation(x, alpha, tau, fs): """ 估计循环自相关 R_x^alpha(tau) x: 复数基带采样,一维数组 alpha: 循环频率,单位 Hz tau: 延迟,单位秒 fs: 采样率,单位 Hz """ tau_samples = int(round(tau * fs)) if tau_samples == 0: tau_samples = 1 N = len(x) # 截断保证索引有效 x1 = x[tau_samples//2 : N - tau_samples//2] x2 = x[: N - tau_samples] if tau_samples > 0 else x # 对齐长度 L = min(len(x1), len(x2)) x1 = x1[:L] x2 = x2[:L] # 分段平均,降低方差 seg_len = 1024 num_segs = L // seg_len if num_segs < 1: raise ValueError("采样长度不足,至少需要 %d 点" % seg_len) acc = 0j t = np.arange(seg_len) / fs for i in range(num_segs): s1 = x1[i*seg_len : (i+1)*seg_len] s2 = x2[i*seg_len : (i+1)*seg_len] acc += np.sum(s1 * np.conj(s2) * np.exp(-1j * 2 * np.pi * alpha * t)) return acc / num_segs def cyclic_spectrum(x, fs, f_range, tau): """ 在给定循环频率范围内扫描,返回循环频率谱 """ alphas = np.linspace(f_range[0], f_range[1], 200) mags = [] for a in alphas: val = cyclic_autocorrelation(x, a, tau, fs) mags.append(np.abs(val)) return alphas, np.array(mags) # 模拟参数 fs = 10e6 # 采样率 10 MHz Tu = 64e-6 # 有用符号长度 64 us Tcp = 16e-6 # 循环前缀 16 us N_sym = 200 # 符号数 # 生成 OFDM 信号(简化:仅用随机 QPSK 子载波) num_sub = 64 ofdm_syms = [] for _ in range(N_sym): data = np.random.randn(num_sub) + 1j * np.random.randn(num_sub) useful = np.fft.ifft(data) cp = useful[-int(Tcp*fs):] ofdm_syms.append(np.concatenate([cp, useful])) tx = np.concatenate(ofdm_syms) # 加噪声,信噪比 -8 dB noise = (np.random.randn(len(tx)) + 1j * np.random.randn(len(tx))) / np.sqrt(2) signal_power = np.mean(np.abs(tx)**2) noise_power = np.mean(np.abs(noise)**2) scale = np.sqrt(signal_power / (10**(-8/10)) / noise_power) rx = tx + scale * noise # 计算循环谱,延迟取 Tu alphas, mags = cyclic_spectrum(rx, fs, (-2e5, 2e5), Tu) plt.plot(alphas, mags) plt.xlabel('循环频率 (Hz)') plt.ylabel('|R_x^alpha(Tu)|') plt.title('OFDM 循环自相关谱 (SNR=-8 dB)') plt.grid(True) plt.show()这段代码的关键点有三个。第一,tau_samples必须对应有用符号长度,如果设错,峰值位置会偏移甚至消失。第二,分段平均的段长seg_len要能覆盖至少一个完整 OFDM 符号,否则循环特征被截断。第三,循环频率扫描范围不用太宽,OFDM 的循环频率集中在子载波间隔的整数倍附近,扫 ±200 kHz 足够。跑完图能看到在 (\alpha = 0) 附近有主峰,在 (\alpha = \pm 1/T_u) 处有次峰,噪声底在 -8 dB 下仍然低于峰值 10 dB 以上。
2.3 循环频率的选择与采样率约束
循环频率不是随便选的。对 OFDM 信号,最强的循环频率出现在 (\alpha = 1/T_u) 和 (\alpha = 1/T_{cp}) 附近,其中 (T_{cp}) 是循环前缀长度。如果采样率不够,这些循环频率会混叠。工程上要求 (f_s > 2 \times \alpha_{max}),而 (\alpha_{max}) 通常取 (1/T_{cp})。比如 (T_{cp} = 16 \mu s),(\alpha_{max} = 62.5 kHz),采样率至少 125 kHz。实际系统采样率远高于此,所以不是瓶颈,但做降采样优化时要留意。
另一个容易翻车的点是循环频率的分辨率。扫描步长太粗会漏掉峰值,太细计算量爆炸。我一般用 (f_s / (N \times L)) 作为步长下限,其中 (N) 是 FFT 点数,(L) 是分段数。实际取 100 Hz 到 1 kHz 之间,根据实时性要求调整。
3. 协作频谱感知:多节点判决融合与历史频谱积累
3.1 硬判决融合与软判决融合的选型
单节点循环平稳检测在深衰落里仍然会失效,协作感知就是把多个地理分离的节点结果汇总。融合方式分两类:硬判决融合和软判决融合。硬判决是每个节点先做二值判断(有信号/无信号),然后中心节点按多数投票或 K 秩准则融合。软判决是每个节点上传循环谱的峰值或检验统计量,中心节点加权合并。
硬判决实现简单,对回传带宽要求低,但量化损失大。软判决性能更好,尤其在低信噪比下,但需要回传浮点数或高比特量化值。我一般建议:如果回传链路带宽受限(比如无线传感器网络),用硬判决加 3 到 5 个节点;如果回传是光纤或高速链路,用软判决,节点数可以到 10 个以上。
下面是一个软判决融合的 Python 实现,假设每个节点上传循环谱在候选循环频率处的幅度。
import numpy as np def soft_fusion(node_stats, weights=None): """ 软判决融合:加权合并各节点的检验统计量 node_stats: 二维数组,形状 (节点数, 候选循环频率数) weights: 各节点权重,一维数组,长度等于节点数;None 则等权 返回:融合后的统计量,一维数组 """ node_stats = np.asarray(node_stats) if weights is None: weights = np.ones(node_stats.shape[0]) / node_stats.shape[0] else: weights = np.asarray(weights) weights = weights / np.sum(weights) # 加权求和 fused = np.sum(node_stats * weights[:, np.newaxis], axis=0) return fused def hard_fusion(node_decisions, rule='majority'): """ 硬判决融合 node_decisions: 二维数组,形状 (节点数, 候选循环频率数),元素为 0/1 rule: 'majority' 多数投票;'k_rank' K 秩准则,需要额外参数 返回:融合判决,一维数组 """ node_decisions = np.asarray(node_decisions) if rule == 'majority': votes = np.sum(node_decisions, axis=0) return (votes > node_decisions.shape[0] / 2).astype(int) else: raise ValueError("暂不支持该规则") # 模拟 5 个节点,每个节点在 3 个候选循环频率上的统计量 np.random.seed(42) num_nodes = 5 num_candidates = 3 # 有信号时统计量偏高,无信号时偏低 node_stats = np.random.randn(num_nodes, num_candidates) + 2.0 # 假设有信号 fused_soft = soft_fusion(node_stats) print("软判决融合统计量:", fused_soft) # 硬判决:统计量大于阈值判为 1 threshold = 1.5 node_dec = (node_stats > threshold).astype(int) fused_hard = hard_fusion(node_dec, rule='majority') print("硬判决融合结果:", fused_hard)软判决融合里权重可以按节点信噪比设定,信噪比高的节点权重大。实际中节点信噪比未知,可以用循环谱峰值的信噪比作为权重,或者用历史准确率动态调整。硬判决的阈值需要根据噪声分布设定,通常用恒虚警率准则,先估计噪声功率,再定阈值。
3.2 历史频谱数据的滑动窗口积累
标题里的“HistoricalSpectrum”不是噱头。单次检测受瞬时衰落影响大,把过去若干帧的判决或统计量存下来做滑动窗口平均,能显著降低方差。常见做法是维护一个长度为 (W) 的环形缓冲区,每次新检测结果入队,输出窗口内均值或中位数。
窗口长度 (W) 的选择是权衡:(W) 越大,平滑效果越好,但对信号出现的响应越慢。如果信号是突发性的,(W) 取 5 到 10 帧;如果是持续信号,(W) 可以取 20 到 50 帧。我一般先用 10 帧起步,根据虚警和漏检曲线再调。
from collections import deque class HistoricalSpectrum: def __init__(self, window_size=10): self.window = deque(maxlen=window_size) self.window_size = window_size def update(self, stat): """输入当前帧的检验统计量,返回窗口内均值""" self.window.append(stat) return np.mean(self.window) def reset(self): self.window.clear() # 使用示例 hs = HistoricalSpectrum(window_size=10) for i in range(20): # 模拟统计量:前 10 帧无信号,后 10 帧有信号 if i < 10: stat = np.random.randn() * 0.5 else: stat = np.random.randn() * 0.5 + 3.0 avg = hs.update(stat) print(f"帧 {i}: 当前统计量 {stat:.2f}, 窗口均值 {avg:.2f}")这个类很简单,但要注意:如果窗口内同时包含有信号和无信号帧,均值会被拉低,可能漏检。改进方法是用中位数代替均值,或者用双窗口检测——短窗口做快速响应,长窗口做确认。实际工程里我倾向于中位数,对突发信号更稳。
3.3 协作节点的空间分集增益评估
协作感知的性能提升来自空间分集。假设单节点检测概率为 (P_d),虚警概率为 (P_f),(N) 个独立节点做多数投票融合,融合后的检测概率和虚警概率可以用二项分布计算。但节点间如果距离太近,信道相关性高,分集增益会打折扣。工程上要求节点间距至少半个波长,最好几个波长以上。
评估分集增益的简单方法:固定虚警概率,画检测概率随信噪比变化的曲线,对比单节点和多节点。如果多节点曲线在低信噪比区明显左移,说明分集有效。如果两条曲线几乎重合,检查节点间是否相关,或者融合权重是否合理。
4. 避坑与排查:循环平稳检测协作感知的五个血泪教训
4.1 循环频率估计偏差导致峰值消失
现象:循环谱图上该有峰值的位置只有噪声,换一个循环频率又能看到峰。原因:OFDM 有用符号长度估计不准,或者采样时钟有频偏,导致实际循环频率偏离理论值。解决:先用已知前导或导频估计符号长度和频偏,做补偿后再算循环谱。如果无法估计,就在理论值附近做小范围扫描,取最大值。
4.2 分段平均段数不足导致方差过大
现象:每次运行循环谱幅度波动很大,判决结果跳变。原因:分段数太少,循环自相关估计的方差没有压下来。解决:增加分段数,或者加长采样时间。经验值是分段数至少 64,最好 128 以上。如果实时性不允许,改用指数加权滑动平均,牺牲一点方差换响应速度。
4.3 协作节点时钟不同步导致融合失效
现象:多节点软判决融合后性能反而比单节点差。原因:各节点采样时钟独立,循环频率有微小偏差,直接加权合并时峰值对不齐,互相抵消。解决:融合前先做循环频率对齐,以某个参考节点的峰值为准,其他节点在附近搜索最大值再上传。或者改用硬判决融合,对时钟偏差不敏感。
4.4 历史窗口长度设置不当导致响应迟滞
现象:信号已经消失,但融合结果仍然判为有信号,持续好几帧。原因:历史窗口太长,旧数据权重过高。解决:改用中位数或截尾均值,或者设置双阈值——进入用高阈值,退出用低阈值,避免窗口拖尾。窗口长度根据信号持续时间调整,一般不超过信号持续帧数的两倍。
4.5 噪声功率估计错误导致恒虚警阈值失效
现象:虚警率远高于设计值,或者漏检严重。原因:噪声功率估计用了包含信号的样本,或者噪声非平稳。解决:用无信号段的样本估计噪声功率,并且定期更新。如果噪声非平稳,改用自适应阈值,比如取循环谱幅度的中位数乘以一个系数作为门限。
5. 进阶技巧:用循环谱相干函数提升低信噪比检测概率
循环谱相干函数是循环自相关的归一化版本,定义为:
[ C_x^\alpha(\tau) = \frac{R_x^\alpha(\tau)}{\sqrt{R_x^0(0) R_x^0(\tau)}} ]
分母是常规自相关,归一化后消除了信号功率的影响,只保留循环结构的纯度。在低信噪比下,相干函数的峰值比原始循环自相关更突出,因为噪声的循环自相关被分母抑制。
实现时,先算 (R_x^\alpha(\tau)),再算 (R_x^0(0)) 和 (R_x^0(\tau)),然后逐点相除。注意分母不能为零,加一个小正则项。我一般用 1e-12 乘以信号功率作为正则。
def cyclic_coherence(x, alpha, tau, fs): """计算循环谱相干函数""" r_alpha = cyclic_autocorrelation(x, alpha, tau, fs) r0_0 = cyclic_autocorrelation(x, 0, 0, fs) r0_tau = cyclic_autocorrelation(x, 0, tau, fs) denom = np.sqrt(np.abs(r0_0) * np.abs(r0_tau)) + 1e-12 return r_alpha / denom # 在之前的循环谱扫描中替换为相干函数 alphas, mags_coh = [], [] for a in np.linspace(-2e5, 2e5, 200): val = cyclic_coherence(rx, a, Tu, fs) alphas.append(a) mags_coh.append(np.abs(val))对比原始循环谱和相干函数谱,在 -8 dB 下相干函数的峰值旁瓣比更高,门限更容易设定。代价是计算量增加约一倍,因为要额外算两个自相关。如果实时性要求高,可以只在候选循环频率处算相干函数,不用全扫描。
另一个进阶方向是结合历史频谱做序贯检测。不是每帧都做完整循环谱计算,而是先用低复杂度能量检测快速筛,只有能量超过粗门限时才启动循环平稳检测。这样在信号稀疏的场景下能大幅降低平均计算量。序贯检测的阈值用 Wald 序贯概率比检验设定,这里不展开,但思路值得一试。
最后说个我自己的习惯:每次调循环平稳检测参数,先固定虚警率,再扫信噪比画检测概率曲线,别反过来。虚警率是系统设计指标,检测概率是结果。反过来调容易陷入局部最优,换一个场景就翻车。希望帮到你。
本文还有配套的精品资源,点击获取