
简介这份资源围绕通信干扰的识别与时频分析展开面向通信工程、信号处理方向的学生与工程师帮助读者建立对常见干扰类型的直观认识并借助时频图理解其频率随时间的变化规律。压缩包内共5个文件均为m脚本文件整体约4KB分别对应单音干扰、多音干扰、线性扫频干扰、梳状干扰等典型干扰信号的生成与仿真便于在MATLAB环境中直接运行、观察波形与频谱特征。资源描述中系统梳理了单音干扰、多音干扰、射频噪声干扰、线性扫频干扰与梳状干扰的成因及在时频图上的表现并提及短时傅立叶变换、小波变换等时频分析手段在定位干扰源、评估干扰强度与制定抗干扰策略中的作用。目前已有510人学习适合作为通信抗干扰课程实验、毕业设计或相关课题的入门参考通过脚本复现可加深对干扰机理与抑制思路的理解。1. 通信干扰识别为什么你盯着频谱却看不出问题做无线通信的工程师大概都经历过这种场景现场信号质量突然恶化误码率飙升你打开频谱仪一看底噪正常没有明显的大信号压过来频谱上干干净净。但通信就是不通。这时候如果切到时频图问题往往一目了然——某个时间段内出现了一片规律性的能量块或者一条斜着扫过去的亮线又或者整个底噪被均匀抬高了几个dB但频谱上完全看不出来。这就是“常见的通信干扰及其时频图”这个方向要解决的核心问题单纯看频域或单纯看时域都会漏掉大量干扰信息。时频图把时间和频率两个维度同时展开干扰信号的调制方式、持续时间、周期规律、扫频斜率这些特征全都会在时频平面上留下独特的“指纹”。这篇文章面向的是需要做干扰排查、信号监测、通信链路保障的一线工程师。不管你是用USRP做采集、用RTL-SDR做监测还是在仿真环境里生成干扰样本训练分类模型下面要讲的东西都能直接落地。我会把常见干扰类型在时频图上的表现、怎么生成和采集数据、怎么做时频变换、怎么调参数、以及实际踩过的坑按可复现的路径讲清楚。2. 常见干扰类型在时频图上的指纹特征2.1 单音、多音与窄带干扰最容易被忽视的“简单”干扰单音干扰就是在某个频点上持续发射一个正弦波。在时频图上它表现为一条水平亮线频率位置固定时间上连续。看起来很简单但实际排查时有个坑如果单音功率不够大在频谱上可能只比底噪高几个dB你扫一眼就忽略了。但在时频图上因为时间维度的累积这条线会非常明显。多音干扰就是多个单音叠加时频图上出现多条平行水平线。窄带干扰则是单音附近有一定带宽的调制信号时频图上表现为一条有一定宽度的水平亮带。用Python生成一个单音加窄带干扰的时频图核心代码如下import numpy as np import matplotlib.pyplot as plt from scipy import signal # 参数设置 fs 1e6 # 采样率 1MHz duration 0.01 # 时长 10ms t np.arange(0, duration, 1/fs) # 生成单音干扰频率 200kHz tone 0.5 * np.exp(1j * 2 * np.pi * 200e3 * t) # 生成窄带干扰中心频率 500kHz带宽 20kHz narrowband 0.3 * np.exp(1j * 2 * np.pi * 500e3 * t) * \ np.exp(1j * 2 * np.pi * 10e3 * t) # 简化模型 # 合成信号 signal_composite tone narrowband 0.01 * (np.random.randn(len(t)) 1j*np.random.randn(len(t))) # 计算时频图STFT f, t_spec, Zxx signal.stft(signal_composite, fs, nperseg256, noverlap200) # 绘制时频图 plt.figure(figsize(10, 6)) plt.pcolormesh(t_spec*1000, f/1e3, 20*np.log10(np.abs(Zxx)1e-10), shadinggouraud) plt.xlabel(时间 (ms)) plt.ylabel(频率 (kHz)) plt.title(单音与窄带干扰时频图) plt.colorbar(label幅度 (dB)) plt.show()这段代码的关键参数是nperseg和noverlap。nperseg256意味着频率分辨率是fs/nperseg在1MHz采样率下约3.9kHz。如果你要分辨更窄的干扰需要增大nperseg但时间分辨率会下降。noverlap200让相邻窗重叠约78%时频图更平滑。实际排查中我一般先用nperseg1024看全局发现可疑区域后再用nperseg256局部放大。注意单音干扰在时频图上是一条水平线但如果你的STFT窗长太短频率分辨率不够这条线会糊成一片看起来像窄带干扰。窗长选择直接决定你能区分多细的频谱结构。2.2 扫频干扰时频图上的斜线为什么有时是弯的扫频干扰是现场最常见的恶意或无意干扰之一。它的瞬时频率随时间线性或非线性变化。在时频图上线性扫频表现为一条斜线斜率就是扫频速率。如果扫频速率很快比如1GHz/s而你的STFT时间窗又比较长那么在一个窗内频率已经变化了很多时频图上这条斜线会变粗甚至断裂。生成线性扫频干扰的代码# 线性扫频干扰从100kHz扫到900kHz周期5ms sweep_rate (900e3 - 100e3) / 5e-3 # 160 MHz/s f_start 100e3 phase 2 * np.pi * (f_start * t 0.5 * sweep_rate * t**2) sweep 0.4 * np.exp(1j * phase) # 注意这里t只取一个扫频周期内的数据 t_sweep np.arange(0, 5e-3, 1/fs) phase_sweep 2 * np.pi * (f_start * t_sweep 0.5 * sweep_rate * t_sweep**2) sweep_signal 0.4 * np.exp(1j * phase_sweep)这里有个血泪经验扫频干扰的时频图斜线斜率不仅取决于扫频速率还取决于你的STFT参数。如果扫频速率是160MHz/s而你的STFT时间分辨率是0.256msnperseg256fs1MHz那么在一个时间窗内频率变化了约41kHz。如果频率分辨率是3.9kHz这条斜线在时频图上会呈现为宽度约10个频率格的粗线。要得到清晰的细斜线要么提高采样率要么缩短窗长要么用更高级的时频分析方法。实际排查中我遇到过扫频干扰的时频图斜线是弯的——那是因为干扰源用的是VCO压控振荡器而不是DDS直接数字合成VCO的调谐电压和频率之间不是严格线性关系。这种非线性扫频在时频图上表现为一条弯曲的轨迹用线性拟合去预测下一时刻的频率会翻车。2.3 脉冲干扰与突发干扰时间维度上的“闪现”脉冲干扰的特点是持续时间极短可能只有几微秒到几毫秒但功率可能很大。在频谱上如果脉冲占空比很低你正好扫到它的概率很小频谱仪上看不到。但在时频图上因为时间轴是连续的脉冲会表现为垂直的亮线——在某个时间点整个频段或部分频段突然出现能量。突发干扰类似但持续时间稍长可能带有一定的调制特征。比如蓝牙的跳频信号在时频图上表现为一系列短促的亮块在多个频点之间跳跃。生成周期脉冲干扰# 脉冲干扰周期1ms脉宽100us pulse_period 1e-3 pulse_width 100e-6 pulse_train np.zeros_like(t) for i in range(int(duration / pulse_period)): start_idx int(i * pulse_period * fs) end_idx int((i * pulse_period pulse_width) * fs) if end_idx len(t): pulse_train[start_idx:end_idx] 1 # 脉冲调制到载波上 pulse_interference 0.6 * pulse_train * np.exp(1j * 2 * np.pi * 300e3 * t)脉冲干扰在时频图上的表现取决于脉冲宽度和STFT窗长的关系。如果脉冲宽度远小于窗长时频图上的亮线在时间轴上会被展宽到约一个窗长。这意味着你看到的脉冲宽度可能比实际宽。要准确测量脉冲宽度窗长必须小于脉冲宽度。但窗长太小频率分辨率又不够。这是一个需要权衡的参数选择问题。我一般会先用大窗长看全局确认脉冲的周期和大致时间位置然后用小窗长局部放大测量精确宽度。如果脉冲太窄比如只有几微秒那可能需要用更高采样率或者专门的脉冲检测算法而不是依赖STFT。3. 从采集到成像时频图生成的完整链路3.1 用USRP或RTL-SDR采集IQ数据采样率与中心频率怎么定做实际干扰排查第一步是采集IQ数据。用USRP B210或者RTL-SDR都可以区别在于带宽和动态范围。RTL-SDR便宜但只有8位量化动态范围约50dB如果干扰信号和有用信号功率差很大弱信号会被量化噪声淹没。USRP B210是12位动态范围好很多但价格也上去了。采集时两个关键参数采样率和中心频率。采样率决定了你能看到的瞬时带宽。如果干扰可能出现在1GHz到1.1GHz之间那采样率至少200MHz才能覆盖。但RTL-SDR最大采样率3.2MHzUSRP B210最大56MHz。如果干扰带宽超过采集带宽你需要多次采集或者用扫频方式拼接。中心频率的设置有个技巧不要设在你要观察的频段正中间。因为零中频架构在中心频率附近有DC offset和IQ不平衡时频图中间会出现一条亮线容易和单音干扰混淆。我一般把中心频率偏移几十kHz到几百kHz让目标频段落在时频图的一侧。用Python通过UHD采集USRP数据的示例import uhd import numpy as np # USRP采集参数 usrp uhd.usrp.MultiUSRP(typeb200) usrp.set_rx_rate(10e6) # 采样率 10MHz usrp.set_rx_freq(uhd.types.TuneRequest(915e6)) # 中心频率 915MHz usrp.set_rx_gain(40) # 增益 40dB # 采集10ms数据 samples usrp.recv_num_samps(100000, 915e6, 10e6, [0], 40) # samples是复数数组可以直接用于STFTset_rx_gain的设置很关键。增益太低弱干扰看不到增益太高强干扰会导致ADC饱和时频图上出现谐波和互调产物你会误以为有多个干扰源。我一般从30dB开始观察时频图如果底噪在-80dB以下且没有削顶就逐步增加增益直到干扰清晰但不过载。注意RTL-SDR的增益是分段可调的不同增益档位的噪声系数不同。如果发现时频图底噪忽高忽低可能是自动增益控制AGC在起作用。做干扰分析时一定要关掉AGC用手动增益。3.2 STFT参数选择窗函数、窗长、重叠率对时频图的影响STFT是生成时频图最常用的方法但参数选择直接决定你能不能看到想要的细节。核心参数有三个窗函数、窗长nperseg、重叠率noverlap。窗函数的选择矩形窗频率分辨率最高但频谱泄漏严重强干扰旁边会出现虚假的旁瓣亮线。汉宁窗Hann泄漏小但主瓣变宽频率分辨率下降。我一般用汉宁窗做通用观察如果两个干扰靠得很近需要分辨换矩形窗但要注意旁瓣。窗长的选择这是最关键的参数。窗长决定了频率分辨率fs/nperseg和时间分辨率nperseg/fs。两者乘积约等于1不可兼得。对于常见的通信干扰干扰类型建议窗长理由单音/窄带1024-4096需要高频率分辨率区分紧密间隔扫频128-512需要高时间分辨率跟踪频率变化脉冲64-256需要高时间分辨率测量脉冲宽度宽带噪声512-1024折中即可重叠率的选择重叠率越高时频图越平滑但计算量越大。一般50%-75%重叠就够了。如果重叠率太低时频图上会出现栅栏效应快速变化的干扰会被漏掉。用scipy.signal.stft时noverlap参数直接控制重叠点数。比如nperseg256, noverlap200就是约78%重叠。如果内存紧张可以降到noverlap12850%重叠。3.3 时频图后处理动态范围压缩与干扰增强原始STFT输出的幅度动态范围可能很大强干扰和弱干扰在同一个色阶下弱的根本看不见。这时候需要做动态范围压缩。最简单的方法是对幅度取对数dB然后设置色阶的上下限。# 计算STFT f, t_spec, Zxx signal.stft(signal_composite, fs, nperseg256, noverlap200) # 转换为dB Zxx_db 20 * np.log10(np.abs(Zxx) 1e-10) # 动态范围压缩只显示底噪以上30dB的范围 noise_floor np.median(Zxx_db) # 估计底噪 vmin noise_floor 3 # 低于底噪3dB的不显示 vmax noise_floor 30 # 高于底噪30dB的饱和 plt.pcolormesh(t_spec*1000, f/1e3, Zxx_db, shadinggouraud, vminvmin, vmaxvmax)vmin和vmax的设置是门艺术。vmin设得太低底噪的随机起伏会显示成一片雪花干扰反而看不清。vmax设得太高强干扰和弱干扰的对比度不够。我一般先用np.median估计底噪然后vmin底噪3dBvmax底噪25到30dB。如果干扰特别强可以适当提高vmax。还有一种干扰增强方法对时频图做二维CFAR检测。在时频平面上滑动一个窗口计算窗口内均值和方差如果中心点超过均值加若干倍标准差就判定为干扰像素。这样可以把干扰从背景中分割出来便于后续分类。4. 干扰排查避坑时频图上的假象与误判4.1 坑一时频图上的亮线不一定是干扰现象时频图上出现一条水平亮线频率固定看起来像单音干扰。但关闭发射机后这条线还在。原因可能是接收机自身的本振泄漏、DC offset、或者外部晶振的谐波。USRP和RTL-SDR在中心频率处都有DC offset表现为零频附近的一条亮线。另外如果附近有FM广播塔它的谐波可能落在你的观察频段内。解决先把中心频率偏移几百kHz看亮线是否跟着移动。如果亮线始终在零频位置那就是DC offset在时频图上把零频附近几个格屏蔽掉即可。如果亮线频率固定不随中心频率变化那可能是外部真实信号需要用定向天线测向确认。4.2 坑二扫频干扰的斜率在时频图上被“压缩”了现象已知干扰源扫频速率是100MHz/s但时频图上测出来的斜率只有50MHz/s。原因STFT的时间窗内频率已经变化时频图上的亮线是窗内平均效果。如果扫频速率是100MHz/s窗长0.256ms窗内频率变化25.6kHz。如果频率分辨率是3.9kHz这条斜线在频率方向被展宽了约6.5个格时间方向也被展宽了1个窗长。你测到的斜率是“模糊后”的斜率比真实值小。解决要准确测量扫频速率窗长必须满足窗内频率变化远小于频率分辨率。即扫频速率 × 窗长 fs/nperseg。对于100MHz/s的扫频如果fs1MHznperseg256窗长0.256ms窗内变化25.6kHz频率分辨率3.9kHz不满足条件。需要把nperseg降到64窗长0.064ms窗内变化6.4kHz频率分辨率15.6kHz勉强满足。或者提高采样率到10MHznperseg256频率分辨率39kHz窗内变化25.6kHz满足。4.3 坑三脉冲干扰的重复周期在时频图上“消失”了现象用频谱仪能看到脉冲干扰的频谱包络但在时频图上找不到规律的脉冲。原因脉冲的重复周期可能大于你的采集时长。比如脉冲每100ms出现一次但你只采集了10ms数据时频图上只有一条垂直亮线看不出周期。或者脉冲宽度极窄STFT窗长太大脉冲被平均到整个窗内时频图上只看到底噪略微抬高。解决先延长采集时间到至少覆盖几个脉冲周期。如果脉冲太窄用高采样率采集原始IQ然后在时域上做脉冲检测比如包络检波加阈值判决找到脉冲位置后再做STFT局部放大。4.4 坑四多径效应让单音干扰在时频图上“分裂”现象一个单音干扰在时频图上显示为多条间隔很近的水平线。原因多径传播导致同一信号以不同时延到达接收机在频域上表现为频率选择性衰落在时频图上就是多条平行线。这不是多个干扰源而是同一个信号的多径副本。解决观察这些线的频率间隔是否与多径时延的倒数一致。如果间隔是固定的且改变接收位置后间隔变化那就是多径。把接收机移动几米如果亮线合并或消失确认是多径。4.5 坑五ADC饱和产生的谐波被误判为多音干扰现象时频图上出现等间隔的多条水平线看起来像多音干扰。原因强干扰信号导致ADC饱和产生谐波和互调产物。这些谐波在时频图上就是等间隔的亮线。如果你把增益调低这些线会消失或减弱。解决降低接收增益观察这些等间隔亮线是否减弱。如果减弱就是ADC非线性产物。另外检查时域波形是否有削顶。做干扰分析时增益一定要留足余量确保最强信号不导致ADC饱和。5. 用CNN做干扰分类从时频图到自动识别5.1 数据集构建时频图样本的生成与标注要做自动干扰分类第一步是构建数据集。实际采集的干扰样本标注成本高我一般先用仿真生成大量样本再用少量实测数据做微调。仿真生成时每种干扰类型随机化参数频率位置、功率、持续时间、扫频速率、脉冲周期等。这样训练出来的模型泛化能力更好。生成时频图后保存为灰度图或伪彩色图标签就是干扰类型。import numpy as np from scipy import signal import cv2 def generate_tf_image(interference_type, fs1e6, duration0.01): 生成指定干扰类型的时频图 t np.arange(0, duration, 1/fs) if interference_type single_tone: freq np.random.uniform(100e3, 900e3) sig 0.5 * np.exp(1j * 2 * np.pi * freq * t) elif interference_type sweep: f_start np.random.uniform(100e3, 300e3) f_end np.random.uniform(700e3, 900e3) sweep_rate (f_end - f_start) / duration phase 2 * np.pi * (f_start * t 0.5 * sweep_rate * t**2) sig 0.4 * np.exp(1j * phase) elif interference_type pulse: period np.random.uniform(0.5e-3, 2e-3) width np.random.uniform(50e-6, 200e-6) pulse np.zeros_like(t) for i in range(int(duration / period)): s int(i * period * fs) e int((i * period width) * fs) if e len(t): pulse[s:e] 1 sig 0.6 * pulse * np.exp(1j * 2 * np.pi * 300e3 * t) else: sig 0.01 * (np.random.randn(len(t)) 1j*np.random.randn(len(t))) # 加噪声 sig 0.01 * (np.random.randn(len(t)) 1j*np.random.randn(len(t))) # STFT f, t_spec, Zxx signal.stft(sig, fs, nperseg256, noverlap200) Zxx_db 20 * np.log10(np.abs(Zxx) 1e-10) # 归一化到0-255 Zxx_norm (Zxx_db - Zxx_db.min()) / (Zxx_db.max() - Zxx_db.min()) * 255 return Zxx_norm.astype(np.uint8)这个函数每次调用生成一张时频图参数随机化。生成几千张后按8:1:1划分训练集、验证集、测试集。注意同一类干扰的不同样本要放在同一个集合里避免数据泄漏。比如单音干扰的样本1在训练集样本2在测试集但它们的频率可能很接近模型可能记住频率而不是学习干扰特征。我一般按频率范围划分训练集用100-400kHz验证集用400-600kHz测试集用600-900kHz。5.2 轻量级CNN模型在嵌入式设备上跑干扰识别时频图分类不需要ResNet那么大的模型。一个4层卷积加2层全连接的轻量级CNN就够了参数量控制在100k以内可以在树莓派或Jetson Nano上实时跑。import torch import torch.nn as nn class InterferenceCNN(nn.Module): def __init__(self, num_classes4): super().__init__() self.conv_layers nn.Sequential( nn.Conv2d(1, 16, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 64, 3, padding1), nn.ReLU(), nn.AdaptiveAvgPool2d((4,4)) ) self.fc nn.Sequential( nn.Linear(64*4*4, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, num_classes) ) def forward(self, x): x self.conv_layers(x) x x.view(x.size(0), -1) return self.fc(x)输入是单通道时频图尺寸建议128x128或256x256。太大推理慢太小细节丢失。训练时用交叉熵损失Adam优化器学习率1e-3batch size 32。训练20-30个epoch就够了因为时频图特征比较明显。注意实际部署时时频图的动态范围压缩参数要和训练时一致。如果训练时用底噪30dB的色阶部署时用底噪20dB模型准确率会下降。我一般把归一化参数固定下来不随环境变化。5.3 实测数据微调解决仿真与实际的域偏移仿真数据训练的模型直接用到实测数据上准确率可能掉20%-30%。原因是仿真没有考虑多径、接收机非线性、外部噪声等实际因素。解决办法是用少量实测数据做微调。微调时冻结卷积层只训练全连接层。学习率降到1e-4训练5-10个epoch。实测数据不需要太多每类50-100个样本就够了。标注时注意一个时频图里可能同时存在多种干扰这时候要么做多标签分类要么把主要干扰类型作为标签。我一般会保留一个“未知”类把不确定的样本扔进去。这样模型不会强行把未知干扰分到已知类里减少误报。5.4 推理加速从PyTorch到ONNX再到TensorRT如果要在嵌入式设备上实时推理PyTorch太慢。我一般导出为ONNX然后用TensorRT或OpenVINO加速。# 导出ONNX dummy_input torch.randn(1, 1, 128, 128) torch.onnx.export(model, dummy_input, interference_cnn.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}})导出后用ONNX Runtime测试推理速度。在Jetson Nano上128x128输入的推理时间约5-10ms可以做到每秒100帧以上。如果还不够快用TensorRT进一步优化FP16量化后可以到2-3ms。实际部署时STFT计算和CNN推理可以流水线并行。采集一段IQ数据做STFT送CNN推理同时采集下一段。这样整体延迟可以控制在20ms以内。6. 时频图分辨率与干扰检测的权衡一个具体技巧做干扰排查久了你会发现一个矛盾时频图的时间分辨率和频率分辨率永远不够用。扫频干扰需要高时间分辨率窄带干扰需要高频率分辨率脉冲干扰两者都要。但STFT的测不准原理决定了你不可能同时获得高时间分辨率和高频率分辨率。我的经验是不要试图用一张时频图解决所有问题。正确的做法是分层排查。第一层用大窗长nperseg4096生成全局时频图频率分辨率高能看到所有窄带和单音干扰。时间分辨率差但扫频和脉冲的大致位置能看到。第二层对可疑时间段用小窗长nperseg64局部放大时间分辨率高能看清扫频斜率和脉冲宽度。第三层如果两层都看不清用其他方法。比如对窄带干扰用高分辨率谱估计MUSIC、ESPRIT对脉冲干扰用时域包络检测对扫频干扰用分数阶傅里叶变换FRFT。这里给一个自适应窗长的具体实现思路def adaptive_stft(signal, fs, target_freq_res1e3, target_time_res1e-4): 根据目标频率分辨率和时间分辨率自动选择窗长 target_freq_res: 期望频率分辨率 (Hz) target_time_res: 期望时间分辨率 (s) nperseg_freq int(fs / target_freq_res) # 满足频率分辨率的窗长 nperseg_time int(target_time_res * fs) # 满足时间分辨率的窗长 # 取两者中较小的保证时间分辨率 nperseg min(nperseg_freq, nperseg_time) nperseg max(nperseg, 32) # 最小32点 nperseg min(nperseg, len(signal) // 4) # 不超过信号长度的1/4 noverlap int(nperseg * 0.75) f, t, Zxx signal.stft(signal, fs, npersegnperseg, noverlapnoverlap) return f, t, Zxx这个函数根据你期望的频率分辨率和时间分辨率自动选择窗长。实际使用时先设一个宽松的频率分辨率比如10kHz和时间分辨率比如0.1ms看全局。发现可疑区域后缩小时间范围重新调用这个函数设更严格的参数。还有一个技巧对时频图做多分辨率分析。用不同窗长生成多张时频图然后融合。比如大窗长的时频图用于检测窄带干扰小窗长的用于检测脉冲和扫频。融合时对每个像素取最大值或加权平均。这样一张图里既有高频率分辨率又有高时间分辨率。我最后悔的一次排查是现场有一个每200ms出现一次的脉冲干扰宽度只有10微秒。我用nperseg1024的窗长时频图上完全看不到脉冲只看到底噪偶尔抬高。折腾了一下午最后用nperseg64才看到。如果一开始就用自适应窗长十分钟就能定位。所以现在我的习惯是永远先用小窗长快速扫一遍确认没有窄脉冲再用大窗长细看频谱结构。这个顺序不能反。希望帮到你。本文还有配套的精品资源点击获取