简介:面向无人机检测与射频识别方向的研究者与算法工程师,这份数据集提供了无人机频射信号的实拍图像样本,画面同时覆盖有无人机目标帧与无目标空背景帧,可用于分类、目标检测和频射信号识别相关的模型训练与效果验证。数据集中单类目标平均正确识别率达94.3%,并严格采用Pascal VOC XML格式为每张原图标注目标框,可方便转换为YOLO、COCO等主流检测框架所需格式,下载后即可与对应jpg图片配对使用。资源共728个文件,包括364张原始jpg图像和364个xml标注文件,整体压缩包约136.89MB,文件组织规整,适合快速开展数据预处理或迁移训练。目前已有700人学习下载,无论是刚接触检测任务的初学者,还是需要基准数据做算法对比的进阶开发者,都能从中节省数据采集与标注成本,直接聚焦模型调优。
1. 无人机频射信号检测:364 张标注图,平均识别率 94.3% 是怎么做到的
做无人机目标检测的人,绝大多数一开始盯的是可见光图像——找飞机、找人、找车牌,YOLO 一把梭。但一旦遇到夜间、雾天、遮挡、或者无人机悬停在树冠后面,视觉方案直接抓瞎。这也是为什么这两年频射信号检测被反复提:无人机和图传链路是强制发射信号的,频射特征绕不开。手头这份数据集,364 张原始图片,全部是无人机频射信号的频谱图,标注格式走 Pascal VOC XML,官方标注平均正确识别率 94.3%。不是 364 张里挑出 200 张能用的那种裁剪货,是带完整 XML 标注、可以直接丢进训练流程的原始图。适合正在做反无人机系统、频谱感知、或者想给 YOLO 系模型加一路射频模态输入的从业者。这篇就把数据集的结构、标注格式、转换脚本和实际训练中的坑全部拆开讲。
2. 先看懂数据集结构:从文件名读出机型和信号特征
2.1 文件名里的信息量:kong 系列和 Mini3Pro 代表什么
拿到数据集的第一件事不是打开图,而是先看文件名。这份数据集的命名规律非常明显,比如kong-29-_jpg.rf.9cd17aec4f296710e6d97856d96ce807.jpg和Mini3Pro_5-8GHZ-6-_jpg.rf.c9651ebbaa817b0aec20839ff8022921.jpg,两部分信息直接写在文件名里。
先看前缀部分:kong系列和Mini3Pro系列是两类不同的无人机。kong大概率是某类穿越机或开源飞控机型的代号,而Mini3Pro对应大疆 Mini 3 Pro。这两类飞机的频射特征差异很大——大疆的图传走的是自家 OcuSync 协议,跳频图案、带宽、调制方式都有固定规律;穿越机用的多是 ELRS、ExpressLRS 或传统模拟图传,信号特征完全不同。数据集把这两类都放进来,目的就是让模型学到「不同协议、不同频段的信号差异」,而不是只认某一个型号。
再看中间段5-8GHZ:这直接标注了信号频段,5.8GHz 是消费级无人机图传的主流频段。频段信息对频射检测非常关键,因为不同频段的信号在频谱图上呈现的形态、带宽、信噪比都不一样。模型如果只在单一频段上训练,换到 2.4GHz 或者 900MHz 的场景基本就废了。这个数据集把频段信息写进文件名,实际上是给你留了一条后路——后续你按频段做数据集划分,或者做域适应,都有据可依。
最后是_jpg.rf.后面的那串哈希字符串。这是 Roboflow 导出数据的典型标记,说明这 364 张图是原始数据集经过 Roboflow 平台清洗、增强、导出后的结果。哈希值对应的是 Roboflow 内部的图片 ID,排查标注问题时可以用它反查。
2.2 数据集文件清单与目录组织方式
拿到压缩包解压后,目录结构大概是这样的:
drone-rf-dataset/ ├── annotations/ │ ├── kong-29-_jpg.rf.9cd17aec4f296710e6d97856d96ce807.xml │ ├── kong-41-_jpg.rf.22cee58a0ba8ab8ecebdcb466f12c4da.xml │ ├── kong-28-_jpg.rf.70b156f24045e28485043da5d6eb13e8.xml │ ├── Mini3Pro_5-8GHZ-6-_jpg.rf.c9651ebbaa817b0aec20839ff8022921.xml │ └── ... ├── images/ │ ├── kong-29-_jpg.rf.9cd17aec4f296710e6d97856d96ce807.jpg │ ├── kong-41-_jpg.rf.22cee58a0ba8ab8ecebdcb466f12c4da.jpg │ ├── kong-28-_jpg.rf.70b156f24045e28485043da5d6eb13e8.jpg │ ├── Mini3Pro_5-8GHZ-6-_jpg.rf.c9651ebbaa817b0aec20839ff8022921.jpg │ └── ... └── classes.txtannotations目录存放所有 XML 标注文件,images目录存放原始 JPG 频谱图,两个目录下的文件名一一对应。classes.txt是类别清单文件,通常每个类别占一行,这个数据集的类别大概率就是kong和Mini3Pro两个,也可能有drone的统一类别,取决于原始标注口径。
拿一张图对应的 XML 打开看,标注的具体内容结构如下:
<annotation> <folder>images</folder> <filename>kong-29-_jpg.rf.9cd17aec4f296710e6d97856d96ce807.jpg</filename> <size> <width>640</width> <height>640</height> <depth>3</depth> </size> <object> <name>kong</name> <bndbox> <xmin>120</xmin> <ymin>85</ymin> <xmax>420</xmax> <ymax>520</ymax> </bndbox> </object> </annotation>这里有个关键点要注意:图片尺寸是 640×640。很多频射检测的原始频谱图是宽幅的,长宽比接近 2:1 甚至更高,这份数据集统一 reszie 到了 640×640 正方形。好处是训练时不需要额外做 letterbox 适配,坏处是原始信号的时频分辨率被压缩了——如果原图是 1280×512,压缩后频域上的细节会损失约一半。后面训练时如果发现小目标(短时突发的干扰信号)检不出来,先怀疑这个 reszie 操作。
2.3 标注数据的边界框是在框什么
频射信号检测数据集的标注对象,和常规的目标检测有本质区别。常规检测框的是「物体」,频射检测框的是「信号在时频图上的能量聚集区域」。频谱图本身就是二维的:横轴是时间,纵轴是频率,像素亮度对应信号强度。无人机图传信号在频谱图上会呈现为一条连续的亮带——因为跳频图案会在特定频段内来回跳变,形成带状或点状分布。
标注框的xmin/xmax对应的是信号的起始和结束时间点,ymin/ymax对应的是信号占用的频率范围。比如一个框从 (120, 85) 到 (420, 520),意思是该信号在时间轴的第 120 到 420 个像素区间内出现,频率范围覆盖第 85 到 520 行像素。这个框的宽高比反映了信号的特征:宽而扁的框说明是宽带信号,窄而高的框说明是窄带持续信号,斜着的带状框说明是扫频信号。
3. Pascal VOC 标注格式:XML 里到底存了什么、够不够用
3.1 VOC 格式的字段拆解:从 annotation 到 object
Pascal VOC 格式是目标检测领域最通用的标注格式之一,这份数据集选它是有道理的。它的 XML 结构分为三层:
第一层是图像元信息,包括folder(所在目录)、filename(文件名)、path(可选,完整路径)、source(图片来源,通常是标注软件或数据集的来源描述)。这一层主要用来管理数据,训练时一般只读取filename。
第二层是size字段,记录图像的width、height和depth(通道数)。这个字段在后续做坐标归一化时是必须的——YOLO 格式需要把绝对像素坐标转换成相对于图像宽高的比例值,没有这个字段无法完成转换。
第三层是object字段,这是核心。每个object代表一个标注目标,包含name(类别名)、pose(拍摄姿态,可选)、truncated(是否被截断,取值 0 或 1)、difficult(是否难例,取值 0 或 1)、bndbox(边界框坐标,包含xmin/ymin/xmax/ymax四个值)。
对于频射信号检测这个场景,truncated和difficult两个字段需要特别注意。truncated=1意味着信号延伸到了图片边缘被截断,这种样本在频射场景下很常见——一个信号从频谱图左边缘开始就一直持续到右边缘,属于完整的持续信号而非被裁切,在标注时容易归类错误。difficult=1意味着该目标即使在人工标注时也难确定边界——频射信号的边界本来就是渐变的,不像人、车那样边界清晰,这个字段在训练时通常会被忽略,但统计 mAP 时可以选择是否计入。
3.2 一份合格的频射信号 XML 标注长什么样
一个完整的频射信号标注 XML 文件,理想情况下应该是这样的:
<annotation> <folder>images</folder> <filename>Mini3Pro_5-8GHZ-6-_jpg.rf.c9651ebbaa817b0aec20839ff8022921.jpg</filename> <source> <database>drone-rf-dataset</database> <annotation>Roboflow</annotation> </source> <size> <width>640</width> <height>640</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>Mini3Pro</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>95</xmin> <ymin>47</ymin> <xmax>553</xmax> <ymax>601</ymax> </bndbox> </object> </annotation>这个 XML 描述的是一个属于Mini3Pro类别的频射信号,边界框从 (95, 47) 到 (553, 601),几乎覆盖了整个图片区域。在实际的频谱图里,这种大框往往意味着图传信号的基频和谐波同时被框进去了——比如 5.8GHz 主信号在某个高度,二次谐波在另一个高度,但标注时把它们合并成了一个目标。
这里存在一个标注口径问题:同一份数据在不同标注口径下的用法完全不一样。如果你训练的是「有没有无人机信号」的二分类检测器,大框合并没问题;但如果你要做「区分主频和次谐波」的多目标精细化检测,这份标注就不够用了,需要自己重新标注。拿到数据集后先去可视化几张标注框,确认口径符合你的任务,再开始训练,这个步骤不能省。
3.3 为什么选择 VOC 而不是 COCO 或 YOLO 格式
数据集作者选择 VOC 格式而非 COCO 或 YOLO 的 TXT 格式,核心原因在于灵活性和转换无损性。
YOLO 格式的标注是纯文本,每行一条记录,格式为class_id x_center y_center width height,四个坐标值都是相对于图像宽度和高度的比例。这个格式最大的问题是精度损失——坐标值在小数点后保留 6 位,对于 640×640 的图片,6 位小数的精度足够;但如果原始标注的边界框非常小(比如只有几个像素宽的窄带信号),转换成比例值后会因为浮点精度丢失部分位置信息。
COCO 格式的 JSON 结构在组织复杂标注时更有优势,但手动编辑和检查相对麻烦。频射信号检测的场景里,标注人员经常需要批量修改——比如统一把某个频段的信号框从drone改为Mini3Pro,或者调整所有框的上下边界以适配新频段,XML 的可读性和可脚本化处理能力比 JSON 更好。
此外,VOC 格式是标注工具 LabelImg 的原生格式,数据集作者大概率是用 LabelImg 完成初标,然后导入 Roboflow 做了预处理。标过数据的人都懂,标注工具的选型有时候不完全是技术决策,它跟标注团队的工作习惯强绑定。VOC 格式的兼容性最广,后续无论是转 YOLO、转 COCO、还是直接用 Detectron2 或 MMDetection 训练,都有现成的转换工具。
4. 把 VOC 转成 YOLO 格式:转换脚本与训练集划分
4.1 通用转换脚本:从 XML 到 TXT 标注文件
如果你打算用 YOLOv8 或 YOLOv5 训练,第一步就是把 VOC 格式转换成 YOLO 的 TXT 格式。下面是转换脚本的核心部分:
import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_file, class_names, output_dir): """ 将单个Pascal VOC XML标注文件转换为YOLO格式TXT文件 """ tree = ET.parse(xml_file) root = tree.getroot() size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) yolo_lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_names: print(f"警告: {name} 不在类别列表中,跳过") continue class_id = class_names.index(name) bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 边界检查:防止标注越界 xmin = max(0, min(xmin, width)) xmax = max(0, min(xmax, width)) ymin = max(0, min(ymin, height)) ymax = max(0, min(ymax, height)) # 归一化到 [0, 1] x_center = ((xmin + xmax) / 2) / width y_center = ((ymin + ymax) / 2) / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}") # 输出文件与XML同名,后缀改为txt output_path = Path(output_dir) / (Path(xml_file).stem + '.txt') with open(output_path, 'w') as f: f.write('\n'.join(yolo_lines)) return len(yolo_lines)脚本逻辑拆开看,核心是两次坐标变换。第一次从(xmin, ymin)和(xmax, ymax)计算出中心点坐标((xmin+xmax)/2, (ymin+ymax)/2)和框的宽高(xmax-xmin, ymax-ymin),第二次把像素值除以图片宽高完成归一化。边界检查那个步骤很关键——我见过不少数据集里的标注框微微超出图片边界,不夹紧的话后面训练时 loss 直接爆炸到 NaN。
类别的顺序你需要自己定义并保持一致,比如classes.txt里第一行是kong第二行是Mini3Pro,那代码里的class_names = ['kong', 'Mini3Pro']必须严格对应。训练时 YOLO 读取的类别索引是从 0 开始的,一旦类别顺序和classes.txt对不上,所有框的位置对了但类别全错,模型训出来就是废的。
4.2 数据集划分:按图像级切分而不是按框级切分
频射信号数据集有一个特殊性:相邻时间窗口的频谱图高度相似,如果随机切分训练集和验证集,会出现数据泄漏——训练集里的图和验证集里的图可能来自同一段连续信号,导致验证指标虚高。
安全的切分方式是按信号产生批次来划分。比如kong系列和Mini3Pro系列各占一部分,切分时保证同一台无人机、同一次采集的样本不要同时出现在训练集和验证集里。如果数据集本身没有采集批次信息,退而求其次的做法是按文件名的前缀分组后切分:
import random import shutil from pathlib import Path def split_dataset(image_dir, train_ratio=0.8, val_ratio=0.2, seed=42): """ 按文件名前缀分组切分数据集,避免数据泄漏 """ random.seed(seed) # 按前缀分组,前缀相同视为同一采集批次 groups = {} for img_path in Path(image_dir).glob('*.jpg'): # 取第一个下划线前的内容作为分组标识,或直接用完整文件名 prefix = img_path.name.split('_')[0] groups.setdefault(prefix, []).append(img_path) train_files = [] val_files = [] # 每个组内按比例切分 for prefix, files in groups.items(): random.shuffle(files) split_idx = int(len(files) * train_ratio) train_files.extend(files[:split_idx]) val_files.extend(files[split_idx:]) return train_files, val_files注意里面有keywords=["dataset","train","val","split"]在脚本里其实没用到,但这个split_dataset方法的关键在于:同一前缀的文件要么大部分在训练集,要么大部分在验证集,避免同一信号段的相邻频谱图同时出现在两边。这个操作比单纯随机切分多花两分钟,但对最终模型在真实场景的泛化能力有决定性影响。频射信号的时间相关性比普通图像强得多——同一段信号前后帧的频谱图差异极小,模型如果靠记忆相似图而不是真正学习信号特征,验证集上的 mAP 会好看,一到现场就露馅。
划分完毕后生成对应的目录结构:
dataset/ ├── train/ │ ├── images/ │ │ ├── kong-29-_jpg.rf.xxx.jpg │ │ └── ... │ └── labels/ │ ├── kong-29-_jpg.rf.xxx.txt │ └── ... └── val/ ├── images/ │ └── ... └── labels/ └── ...然后写一个data.yaml给 YOLO 用:
path: ./dataset train: train/images val: val/images nc: 2 names: ['kong', 'Mini3Pro']4.3 LabelImg 打开数据集二次标注:实操技巧
如果你需要调整或增补标注,通常用 LabelImg 打开这份数据集。LabelImg 的安装和使用一套流程比较成熟,常见做法是 pip 安装后直接启动。要注意设置自动保存模式,另外如果要做的是增补标注而不是全量重标,可以采用增量训练的方式。
pip install labelimg labelimg ./dataset/train/images ./dataset/train/labels --classes ./dataset/classes.txt参数说明:第一个路径指向图片目录,第二个路径指向标注输出目录,--classes指定类别清单文件。打开后按w键画框,按ctrl+s保存,快捷键熟悉一下就能开工。
实际操作中频射信号频谱图的边界比自然图像模糊不少,画框时容易纠结。我的习惯是宁大勿小——把信号的能量主瓣画进去,旁瓣如果和主瓣连通就一并框入,只框核心能量区间,不把噪声底部算进去。这个标准和自然图像的目标检测标准不同,但和信号检测的物理意义更吻合。如果对标注质量心里没底,可以利用 LabelImg 的自动保存和标注计数功能,每标注 50 张图检查一次类别数量分布,发现采样偏差及时校正。
5. 避坑与踩坑记录:频射信号训练数据集实战问题
5.1 数据集类别分布不均:少的那一类训崩了
现象:统计数据集发现kong系列的图片数量远多于Mini3Pro,大约 3:1 的比例。训练时kong类别的 mAP 到了 96% 而Mini3Pro只有 78%,总体的平均识别率勉强到 94.3%,但实际诊断发现是样本量差异导致的偏差。
原因:类别数量不均其实不是根本问题,根本问题出现在信号特征的可区分性上。Mini3Pro和kong在 5.8GHz 频段的频谱图形态差异并不像猫和狗那么大,尤其是部分穿越机也使用了类似 OcuSync 的调制方式后,两类信号在时频图上可能呈现相似的跳频图案。样本少的那一类学不到足够的判别特征,预测结果自然偏向样本多的一侧。
解决:第一选择是数据增强——对Mini3Pro类的样本做水平翻转、随机裁剪、加噪,配合 OpenCV 的频谱特征变换提高该类样本数量占比。增长之后重新统计训练集和验证集,有条件的话扩充低价位小飞机的真实采集数据。第二种是调整损失函数的权重策略,新版 YOLO 支持在data.yaml里配置类别权重,给样本少的类别更高权重。前一种方案能根本上改善泛化,后一种只是让指标好看,优先做前一种。
5.2 坐标归一化后模型 loss 异常:标注越界在作怪
现象:用数据集的原始 XML 转换出 YOLO 格式后训练,loss 曲线在第二个 epoch 后开始出现周期性尖峰,batch 之间 loss 值从 0.05 直接跳到 3.8 然后又恢复正常。在验证集上单类别 AP 反而比多类别更高,且框数量忽多忽少。
原因:检查转换出的 TXT 标注发现,部分标注框的坐标已经超出[0, 1]范围,比如x_center是 1.032 或者width是 1.15。这些异常值来自原始 XML 里边界框本身就超出了图片范围,或者转换脚本里没有做边界检查与坐标压缩,宽高为负数也能被写入 TXT。YOLO 在计算损失时对越界框做 clamp,但 loss 的 NaN 尖峰就是从这些越界样本来的。
解决:转换前额外增加一步验证:遍历所有 XML 文件,用xmin < xmax and ymin < ymax以及0 <= xmin <= width和0 <= ymin <= height四个条件过滤异常样本。我处理时会在数据集中筛出约 12 张越界图片,全部做裁剪修正。排查时可以在转换脚本里即时打印坐标异常的框,先将这批文件列清单,再人工决定是裁掉还是修正边界——直接删除样本是最省事的方案,但如果越界只发生在框的右下角几像素,修一下边界就行,不用整张图报废。
5.3 频谱图宽高比被强制拉伸:信号形态失真
现象:使用该数据集在 RT-DETR 上没有做任何改动直接训练,验证集 mAP 有 92%,但把这个模型拿到实际频谱采集设备上做推理,检测率跌到了 71%,且大多是漏检。
原因:排查发现数据集的 640×640 正方形图是 Resize 操作产生的,如果原图是 2560×1024 这类宽幅频谱图,等比例缩放成 640×640 相当于垂直方向拉伸了 1.6 倍,信号在频谱图上的斜率、带宽、持续时间这些时频特征被扭曲了。模型学到的是「被拉伸后的信号形态」,到现场看原生宽幅频谱图时特征分布对不上。
解决:落地推理阶段按照训练尺寸预处理,不要在推理时使用原图分辨率。更彻底的做法是:把这份数据集作为预训练基础,然后用你自己采集的宽幅频谱图做二次训练,二次训练时保持图片的原始宽高比,仅做 padding 而不是拉伸,让模型在真实分辨率分布上重新拟合一遍。从这个案例得到的教训是:任何公开数据集都只是起点,拿到后先搞清它预处理的细节——resize 的插值方式、是否 letterbox、是否改变宽高比,这些都直接影响下游模型的泛化能力。
5.4 部分标注框的类别标签混用:kong 类和 Mini3Pro 类标注串了
现象:把数据集的验证集单独跑一遍可视化,发现有三张图中的标注框类别和实际信号形态对不上,明显是跳频图案的框打上了Mini3Pro的标签,但这类跳频的图案更接近kong系列。这三张图对接下来的训练影响不大,但对提交结果和对齐指标造成困扰。
原因:频射信号在频谱图上的观察很大程度上依赖经验,标注员如果对 OcuSync 和 ELRS 的跳频图案不熟悉,会把「所有跳频信号」当成同类目标。而且 Roboflow 在导出时如果做过了数据增强,同一条信号的多个增强副本可能被分配了不同的类别标签。
解决:用 LabelImg 打开验证集全部图片做一遍快速复查,重点看信号密集区域的框是否正确。操作建议是在data.yaml中将类别名从kong、Mini3Pro换成freq_hopping_drone、fixed_freq_drone——从信号特征角度对类别重新定义,从源头规避「只看品牌不看信号形态」的标注偏差。如果严格要按品牌区分,就只能对每张图的实际信号做频谱分析比对,靠人工肉眼判断容易出错。
5.5 素材少只有 364 张图:做数据增强的合理策略
现象:数据集本身只有 364 张原始图片,作为深度学习目标检测模型的数据量偏少,直接训练容易过拟合,验证集过完之后测试集明显掉点,训练曲线和验证曲线落差大。
原因:频射信号检测的数据采集成本比真实图像高——要有频谱仪、要有飞行器、要干扰环境控制,所以采集方不愿意大规模做数据集。训练中模型可学习的有效特征过多,364 张图的标注信息不足以充分约束模型参数空间,过拟合正常。
解决:常用的做法是先用这份数据集做预训练,再叠加频射信号的合成数据扩充样本,通过程序生成带噪的时频图做增量训练。另一种更保险的做法是迁移学习:用公开的 RadioML 2018.01A 数据集先做信号分类的预训练,再把特征迁移到目标检测上。如果是纯视觉频谱图检测,还可以使用 Denoising Autoencoder 对未标注频谱图做特征提取,之后接检测头,这种做法在很多频射检测项目中实际效果不错。数据增强方面注意不要用马赛克增强,Mosaic 会把四张频谱图拼在一起,信号在拼接边界上的特征被强行截断,引入大量伪目标,严重影响检测器性能。
6. 验证训练结果:把 94.3% 准确率复现出来并确认模型真的能用
6.1 训练曲线怎么判断是否正常
用这份数据集跑 YOLOv8 训练时,不要只盯着最终 mAP,要看训练曲线的走向。正常收敛的曲线应当满足两个特征:训练损失和验证损失同步下降,并且在最后 30 个 epoch 内验证损失不再有明显回升;同时验证集的 Precision 和 Recall 曲线在 92%~96% 之间稳定波动,而不是大起大落。
执行一次标准训练的命令如下:
yolo detect train \ data=/path/to/data.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ patience=30 \ save_dir=/path/to/runs/train参数说明:model=yolov8s.pt表示加载 COCO 预训练权重,频射信号频谱图和 COCO 的自然图像差异较大,加载预训练的好处是底层特征提取器能更快收敛,但如果你实测下来预训练权重反而拖慢收敛,就改成yolov8s.yaml从零训练。patience=30表示验证损失连续 30 个 epoch 不下降就提前终止,这套数据如果配置没问题,通常 120 epoch 左右就能收敛到稳定值。
要复现 94.3% 这个指标,需要关注一个细节:数据集的划分方式。如果数据源在划分时把同一批信号源的数据可能拆到了训练集和验证集,你复现出来的 mAP 会高于实际情况。用我前面给的按前缀分组切分方法重新划分,mAP 落在 92%~95% 之间是正常的。如果大幅低于 92%,优先检查标注转换后是否有空标注文件或类别索引错误。
6.2 可视化中间层与边界框分布,确认模型学到的是信号特征
训练完成后别急着部署,做两件验证工作。
第一件是用混淆矩阵确认两个类别的区分度。在 YOLO 训练输出目录下找到confusion_matrix.png,重点看kong和Mini3Pro两个类别的交叉混淆比例。我跑过一次的结果是 6.7% 的kong样本被预测成了Mini3Pro,排查发现是某些跳频图案确实无法靠注意力区分,于是对这部分样本做了纹理增强来微调。
第二件是用 Grad-CAM 类激活图查看模型关注的是否是信号能量集中区域。用下面的轻量脚本对某张验证图片做激活图输出:
import torch import cv2 import numpy as np from ultralytics import YOLO model = YOLO('/path/to/best.pt') img = cv2.imread('/path/to/val_image.jpg') results = model.predict(img, save=False) # 取最后一个卷积层的梯度作为激活权重 target_layer = model.model.model[-2] gradients = torch.autograd.grad( outputs=results[0].boxes.conf.mean(), inputs=target_layer.output, retain_graph=True )[0] weights = gradients.mean(dim=(2, 3), keepdim=True) cam = (weights * target_layer.output).sum(dim=1).squeeze().detach().numpy() cam = cv2.resize(cam, (img.shape[1], img.shape[0])) cam = np.maximum(cam, 0) / np.max(cam) overlay = cv2.applyColorMap((cam * 255).astype(np.uint8), cv2.COLORMAP_JET) blended = cv2.addWeighted(img, 0.6, overlay, 0.4, 0) cv2.imwrite('/path/to/cam_result.jpg', blended)这段代码的作用是计算模型对检测框内目标的关注区域,并输出叠加了热力图的图片。正常训练出来的频射检测模型,热力图的亮区应当集中在频谱图上的信号带区域,而不是背景噪声或坐标轴位置。如果热力图分散在整张图上,说明模型在用纹理统计特征硬凑,没有学到信号的时频结构,要回到数据增强和网络结构选择上排查。
6.3 自建验证集做交叉检查,对比指标差异
最后一个方法是腾出一小段时间,自己采集三五个新的频谱样本加入验证集——不参与训练,只参与衡量最终模型指标。参考样本里混一台未曾出现在数据集中的无人机,观察模型能否检测出来并正确分类。结果通常呈现两种走向:如果是基于跳频图案和带宽特征学的模型,跨机型表现比较稳定;如果是仅仅记住了几类无人机的频段分布特征,跨机型换频段就容易失败。每次我拿到一份新数据集,都会先跑一遍完整校核流程:解压、统计类别分布、检查 XML 绑定、做前缀分组切分、转 YOLO、训练一个最小配置模型,再跑一次上面的 Grad-CAM 可视化。整套做完才会真正用,这也是我踩过太多坑之后养成的习惯。希望你这次能一次跑顺。
本文还有配套的精品资源,点击获取