简介这份无人机频射信号检测数据集面向从事无人机侦测、频谱识别与目标检测的算法工程师及高校研究者可用于训练和评估基于视觉化射频信号的无人机识别模型。数据集包含364张原始图片每张图片均配有对应的YOLOv5格式标注文件另附1个yaml配置文件压缩包共729个文件、约136.82MB涵盖jpg图像、txt标注与yaml配置三类内容开箱即可接入YOLOv5训练流程。据描述该数据集在相关任务上的平均正确识别率可达94.3%覆盖多类无人机频射信号样本适合用于模型微调、对比实验与检测精度验证。目前已有451人学习下载具备一定的社区验证基础。对于需要快速构建无人机频射信号检测基线、验证YOLOv5训练链路或开展频谱图像识别研究的读者这份数据可节省数据采集与标注成本直接投入实验迭代。1. 无人机频射信号检测数据集364 张原始图片凭什么做到 94.3% 平均正确识别率拿到这个标题很多人第一反应是「364 张图片也能叫数据集」。我一开始也这么想直到自己用频谱仪采了一周数据才明白频射信号检测的样本和图像目标检测的样本压根不是一个量级的东西。一张 640×640 的可见光图里可能塞几十个目标而一张频射瀑布图里一个信号占的像素块往往只有十几乘几十背景噪声却铺满全图。364 张原始图片如果每张都覆盖了跳频、突发、连续波等不同调制样式再叠加信噪比梯度实际可用的信号实例数远不止 364。这个数据集真正解决的是「把射频信号检测从传统能量门限拉进深度学习流水线」的问题。传统做法靠 CFAR 恒虚警检测加人工特征遇到低信噪比、多径、同频干扰就集体翻车。而标注成 YOLOv5 格式之后你可以直接复用目标检测那一整套训练、验证、部署链路把信号在时频图上的位置当成目标框来回归。适合谁做无人机侦测、频谱监测、电子对抗的算法工程师以及想用 YOLOv5 快速验证频射信号检测可行性的学生和爱好者。平均正确识别率 94.3% 这个数字放在 364 张的规模上说明数据质量比数量更关键——每一张都标注得干净比一万张脏数据管用。2. 频射信号检测为什么能套 YOLOv5从时频图到标注框的映射逻辑2.1 射频信号转时频图STFT 是绕不开的第一步射频信号本身是一维时序YOLOv5 吃的是二维图像中间必须做一次时频变换。最常见也最稳的做法是短时傅里叶变换STFT把 IQ 采样切成短帧每帧做 FFT按时间轴堆叠成二维矩阵再映射成灰度或伪彩色图。这里有个血泪经验窗长和重叠率直接决定信号在图上「长什么样」。窗太长跳频信号的频率跳变会被抹平窗太短频率分辨率不够窄带信号糊成一团。我一般会先用一段已知信号做参数扫描固定采样率后窗长从 256 到 2048 试一遍看哪种设置下信号在时频图上的能量团最集中、背景最干净。下面是一个最小可复现的 STFT 转图脚本输入是复数 IQ 数组输出是归一化后的灰度 PNG。import numpy as np import matplotlib.pyplot as plt from scipy.signal import stft def iq_to_spectrogram(iq_data, fs, nperseg512, noverlap384, save_pathNone): iq_data: 复数IQ采样数组 fs: 采样率 Hz nperseg: STFT窗长决定频率分辨率 noverlap: 帧间重叠决定时间轴平滑度 # 做STFT返回频率轴、时间轴、复数谱 f, t, Zxx stft(iq_data, fsfs, npersegnperseg, noverlapnoverlap, return_onesidedFalse) # 取幅度并转dB加1e-12防止log0 mag_db 20 * np.log10(np.abs(Zxx) 1e-12) # 频率轴搬移让零频在中心 mag_db np.fft.fftshift(mag_db, axes0) # 归一化到0-255方便存成灰度图 mag_norm (mag_db - mag_db.min()) / (mag_db.max() - mag_db.min() 1e-12) img (mag_norm * 255).astype(np.uint8) if save_path: plt.imsave(save_path, img, cmapgray) return img # 示例生成一段含跳频的仿真IQ fs 1e6 t np.arange(0, 0.01, 1/fs) sig np.exp(1j*2*np.pi*1e5*t) 0.5*np.exp(1j*2*np.pi*2e5*t) noise (np.random.randn(len(t)) 1j*np.random.randn(len(t))) * 0.3 img iq_to_spectrogram(sig noise, fs, save_pathspec.png) print(输出图像尺寸:, img.shape)逻辑说明stft返回的复数谱取模转 dB 后用fftshift把零频搬到中心这样正负频率对称信号在图上不会偏在一侧。归一化用 min-max 而不是固定阈值是为了让不同信噪比的样本都能拉开对比度。参数方面nperseg512在 1 MHz 采样率下对应约 2 kHz 频率分辨率noverlap384即 75% 重叠时间轴足够密跳频的瞬态不会被漏掉。如果你处理的信号带宽更宽把nperseg提到 1024 或 2048但注意图像尺寸会变大训练显存也跟着涨。2.2 YOLOv5 标注格式拆解一行文本背后的五个数YOLOv5 的标注文件是每张图对应一个.txt每行五个数class_id x_center y_center width height全部归一化到 0-1。频射信号检测通常只有一个类所以class_id恒为 0。关键在于框怎么画。信号在时频图上的能量团往往不是矩形而是斜线、L 形、甚至多个离散点。我的做法是用标注工具拉一个能包住主要能量团的最小外接矩形允许框内包含少量背景噪声但不要把相邻信号框进去。这里有个容易踩的坑时频图的坐标原点和普通图像不一样。STFT 输出的矩阵行是频率、列是时间存成图片后纵轴是频率、横轴是时间。标注时x_center对应时间轴归一化位置y_center对应频率轴归一化位置。如果你用 LabelImg 这类工具直接标它默认左上角是原点而频率轴通常低频在下、高频在上所以标出来的y需要做一次翻转才能和训练时的图像对齐。我一般会在转图阶段就把频率轴翻转好让低频在上这样标注工具和 YOLOv5 的默认坐标系一致省得后面换算。def yolo_label_from_box(img_w, img_h, xmin, ymin, xmax, ymax, class_id0): 把像素坐标框转成YOLOv5归一化格式 img_w, img_h: 图像宽高 xmin,ymin,xmax,ymax: 框的像素坐标原点左上 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h # 裁剪到0-1防止越界 x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) width min(max(width, 0), 1) height min(max(height, 0), 1) return f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f} # 示例一张640x480的时频图信号框在(100,200)到(300,260) line yolo_label_from_box(640, 480, 100, 200, 300, 260) print(line)逻辑说明归一化用图像宽高做分母x_center和width除以宽y_center和height除以高。保留六位小数是为了避免小框在归一化后精度丢失。裁剪到 0-1 是防止标注时手抖拉出界训练时 YOLOv5 对越界坐标会直接报错或静默截断不如在生成阶段就处理干净。参数上class_id如果你要做信号类型细分比如跳频、连续波、脉冲各一类就改成 0、1、2但类别数一多364 张图的类别平衡就会成问题建议先做单类检测跑通再扩展。2.3 数据集划分与 YOLOv5 目录结构对齐364 张图按 8:1:1 分训练集 291 张、验证集 36 张、测试集 37 张。这个量级下验证集和测试集各三十多张指标波动会比较大所以 94.3% 这个平均正确识别率大概率是多次交叉验证或者多轮取平均的结果单次划分跑出来的数字可能上下浮动几个点。目录结构必须按 YOLOv5 的要求来dataset/ ├── images/ │ ├── train/ # 291张 │ ├── val/ # 36张 │ └── test/ # 37张 └── labels/ ├── train/ # 与images/train一一对应的.txt ├── val/ └── test/图片和标签文件名必须一致只是扩展名不同。比如images/train/sig_001.png对应labels/train/sig_001.txt。我见过有人把标签全塞一个文件夹训练时 YOLOv5 找不到对应文件直接报No labels found排查半天以为是路径写错其实是结构没对齐。另外如果原始图片是 364 张转时频图时可能会因为参数不同生成多版本建议固定一套 STFT 参数不要混用否则同一信号在不同图上的外观差异会让模型学乱。3. 用 YOLOv5 训练频射信号检测模型从环境配置到 94.3% 的复现路径3.1 环境配置与数据配置文件YOLOv5 对环境不算挑剔但版本要对齐。我一般用 Python 3.8 以上PyTorch 1.8 到 1.12 之间都稳太新的版本有时和torchvision的算子对不上。安装走官方 requirements 最省事git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt数据配置文件rf_signal.yaml放在data/下内容如下path: ../dataset # 数据集根目录 train: images/train val: images/val test: images/test nc: 1 # 类别数频射信号单类 names: [signal] # 类别名逻辑说明path是根目录train/val/test是相对路径。nc必须和标注文件里的class_id最大值加一一致单类就是 1。names列表长度也要等于nc否则训练时打印类别名会索引越界。这个文件是 YOLOv5 数据加载的入口写错一个字段就是AssertionError别问我是怎么知道的。3.2 关键训练参数为什么我不用默认的 hyp.scratch.yamlYOLOv5 默认超参是针对 COCO 那种自然图像调的频射信号时频图和自然图像分布差很远。默认的 HSV 增强、随机翻转、马赛克增强在时频图上会引入物理上不存在的信号形态。比如水平翻转会让时间轴倒流跳频顺序反了垂直翻转会让频率高低颠倒低频信号跑到高频去。这些增强在自然图像里是合理的在频射信号里就是制造假样本。我一般会改hyp.scratch.yaml里几个关键项lr0: 0.01 # 初始学习率364张小数据集用0.01够了太大容易震荡 lrf: 0.1 # 最终学习率系数余弦退火到0.001 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3.0 box: 0.05 # 框回归损失权重信号框通常较小适当降低 cls: 0.5 # 分类损失权重单类可以高一点 obj: 1.0 # 目标置信度损失保持默认 hsv_h: 0.0 # 关闭色相增强时频图颜色是伪彩色色相无意义 hsv_s: 0.0 # 关闭饱和度增强 hsv_v: 0.3 # 保留亮度增强模拟不同信噪比 flipud: 0.0 # 关闭垂直翻转频率轴不能颠倒 fliplr: 0.0 # 关闭水平翻转时间轴不能倒流 mosaic: 0.5 # 马赛克增强降到0.5小数据集上太强会过拟合 mixup: 0.0 # 关闭mixup信号叠加会制造不存在的调制逻辑说明hsv_h和hsv_s关掉是因为时频图的颜色映射是人为选的色相和饱和度变化不改变信号本质反而增加无关变异。hsv_v保留 0.3 是为了模拟不同信噪比下的亮度差异这个对频射信号检测是有意义的。flipud和fliplr必须关这是频射信号和自然图像最大的区别。mosaic降到 0.5 是因为 364 张图本身就不多四图拼接后每张图的有效信号区域更小模型容易学到拼接边缘的伪影。mixup关掉是因为两个信号叠加在物理上可能产生互调但标注框不会体现互调产物模型会困惑。3.3 启动训练与指标观察训练命令python train.py --img 640 --batch 16 --epochs 200 --data data/rf_signal.yaml --weights yolov5s.pt --hyp data/hyp.scratch.yaml --name rf_signal_exp逻辑说明--img 640是输入尺寸时频图如果原始尺寸不是 640YOLOv5 会自动缩放。--batch 16在 364 张图上每个 epoch 约 18 个 batch显存占用不大6G 显存够用。--epochs 200是小数据集常见轮数配合早停。--weights yolov5s.pt用预训练权重虽然 COCO 和时频图差异大但浅层边缘特征还是能迁移的比从头训收敛快。--name指定输出目录日志和权重都存这里。训练过程中重点看三个指标mAP0.5、precision、recall。频射信号检测里recall比precision更关键漏检一个信号可能比误报一个更严重。如果recall上不去先检查标注框是不是太小或者太松。如果mAP震荡大把lr0降到 0.005 再试。94.3% 的平均正确识别率在mAP0.5上通常对应 0.92 到 0.95 之间具体看测试集划分。3.4 推理与部署从测试集到实际频谱流训练完用detect.py跑测试集python detect.py --weights runs/train/rf_signal_exp/weights/best.pt --source dataset/images/test --img 640 --conf 0.25 --iou 0.45 --save-txt逻辑说明--conf 0.25是置信度阈值频射信号检测里可以降到 0.2 提高召回。--iou 0.45是 NMS 的 IoU 阈值信号框重叠不多0.45 够用。--save-txt会把检测结果存成 YOLO 格式方便和真值对比算指标。如果要部署到实际频谱流把detect.py里的推理循环抽出来每来一帧 IQ 就转时频图、送模型、拿框再映射回频率和时间坐标。树莓派 5 上跑 YOLOv5s用 ONNX Runtime 或 OpenVINO 量化后单帧推理能压到几十毫秒但 364 张训练出来的模型泛化到新场景建议至少再采几百张做微调。4. 频射信号检测数据集避坑364 张图训练时最容易翻车的五个地方4.1 现象训练 loss 正常下降但验证集 mAP 始终在 0.3 以下原因时频图归一化方式不一致。训练集图片用 min-max 归一化到 0-255验证集图片用了固定阈值截断导致同一信号在训练和验证时的灰度分布不同。模型学到的是训练集的灰度统计换到验证集就失效。解决把归一化逻辑写成一个函数训练、验证、测试、推理全部调用同一个函数。归一化参数min 和 max要么从训练集全局统计要么每张图独立做但保证方式一致。我一般每张图独立 min-max因为不同信噪比的样本动态范围差异大全局归一化会让弱信号被强信号压掉。4.2 现象模型把背景噪声里的亮斑当成信号误报率高原因标注时把一些噪声尖峰也框进去了或者框画得太大包含了大量背景。YOLOv5 对框内像素一视同仁框太大等于告诉模型「这些背景也是信号的一部分」。解决重新检查标注框只包住信号能量团的主瓣旁瓣和噪声尖峰不要框。如果信号是斜线用最小外接矩形不要为了包全斜线而拉一个巨大的框。另外可以在训练时把obj损失权重调高让模型更关注「有没有目标」而不是「框得准不准」。4.3 现象训练到 50 epoch 后 mAP 突然掉下去再也回不来原因学习率太大导致跳出最优解或者数据增强太强让模型学偏了。364 张小数据集上默认的mosaic1.0和mixup0.5会制造大量不真实的信号组合模型在后期被这些假样本带偏。解决把mosaic降到 0.5 以下mixup关掉。学习率用余弦退火lr0从 0.01 降到 0.005。加早停patience30连续 30 epoch 验证集 mAP 不升就停。如果已经掉下去了从best.pt恢复用更低学习率再训 50 epoch。4.4 现象推理时同一信号被多个框重复检测原因NMS 的 IoU 阈值设太高或者信号在时频图上有多个能量峰模型在每个峰上都输出了一个框。频射信号的旁瓣、谐波都可能被当成独立目标。解决降低--iou到 0.3 或 0.35让 NMS 更激进地合并重叠框。如果信号本身有多个离散峰考虑在标注阶段就把它们标成一个框或者改成多类检测把主瓣和旁瓣分成不同类让模型学会区分。我一般先试降 IoU不行再改标注策略。4.5 现象换一台设备采的数据模型完全失效原因不同设备的采样率、中心频率、噪声基底不同时频图的外观差异巨大。364 张图如果只来自一台设备模型学到的特征和设备强相关换设备就翻车。解决训练时加入设备相关的数据增强比如随机频率偏移、随机增益、加不同功率的噪声。更彻底的做法是采集多设备数据哪怕每台设备只采几十张混合训练后泛化能力会明显提升。如果没法采新数据至少在推理前做一次频谱对齐把输入信号的噪声基底和训练集对齐。5. 把 94.3% 变成可复现的基线频射信号检测的验证技巧与进阶习惯94.3% 这个数字如果你直接拿 364 张图跑一次训练大概率复现不出来。不是数据有问题而是小数据集的指标对随机种子、划分方式、超参太敏感。我一般会做三件事来确认一个基线是否可信。第一固定随机种子把torch.manual_seed、np.random.seed、random.seed全设成同一个值跑三次训练看 mAP 的方差。如果方差超过 2 个点说明模型不稳定需要调参或加数据。第二用 K 折交叉验证代替单次划分。364 张图做 5 折每折训练集 291 张、验证集 73 张五折的 mAP 取平均这个平均值比单次划分可靠得多。第三把测试集完全隔离训练和调参只看验证集最后跑一次测试集出最终数字避免信息泄露。进阶用法上频射信号检测和自然图像检测最大的不同是「物理约束可以当正则化用」。比如你知道信号的频率不会突变超过某个斜率可以在损失函数里加一项惩罚预测框在频率轴上的跳变。或者你知道同一时刻最多只有一个信号可以在后处理里加约束同一时间窗口内只保留置信度最高的框。这些约束在自然图像里不好加在频射信号里是天然的领域知识。# 示例在NMS后加时间轴约束同一时间窗口只保留最高置信度框 def temporal_nms(boxes, scores, time_window0.05): boxes: Nx4 tensor, x_center,y_center,w,h 归一化 scores: N time_window: 时间轴归一化窗口0.05对应图像宽度的5% order scores.argsort(descendingTrue) keep [] while order.numel() 0: i order[0].item() keep.append(i) if order.numel() 1: break # 计算当前框与剩余框在时间轴上的中心距离 time_diff torch.abs(boxes[order[1:], 0] - boxes[i, 0]) # 时间距离小于窗口的框抑制掉 mask time_diff time_window order order[1:][mask] return keep逻辑说明这个函数在标准 NMS 之后再做一次时间轴上的抑制time_window控制多近算「同一时刻」。参数 0.05 是经验值对应 640 像素图上的 32 像素大概是一个信号持续时间的量级。如果你的信号持续时间更长把窗口调大。这个约束能有效减少同一信号被重复检测的情况尤其是信号有多个能量峰时。我自己的习惯是每拿到一个新场景的频射数据先不急着训模型而是花半天时间把时频图参数扫一遍确认信号在图上「长得清楚」再开始标注。标注完先拿 10 张图过一遍 YOLOv5 的train.py看 loss 能不能正常降不能降就说明标注格式或数据配置有问题别等到标完 364 张才发现。这个习惯帮我省过至少两次返工。希望帮到你。本文还有配套的精品资源点击获取
