简介:本资源面向计算机视觉学习者与目标检测工程实践者,提供一套细分类型飞机、鸟类与无人机的YOLOv5检测训练方案,重点解决细粒度识别中机型区分难、样本组织繁琐的问题,适合具备一定深度学习基础、希望快速复现实验或搭建演示系统的开发者。压缩包共约2000个文件,以1994个txt标签文件为主体,另含3个Python脚本与3份PDF说明文档,整体约924.72MB,标签已按YOLO格式划分train、val、test并附data.yaml,可直接用于yolov5、yolov7、yolov8等框架训练。数据集规模超过一万张,覆盖多种飞机型号、鸟类与无人机目标,目录结构已配置完毕,省去清洗与划分环节。配套PyQt界面脚本与使用说明,便于将训练好的模型封装为可视化检测工具,同时提供环境配置教程,帮助读者打通从数据准备到界面部署的完整链路。目前已有467人学习下载,适合作为细粒度检测课程设计、毕业项目或算法对比实验的起点。
1. 从一堆飞机鸟群视频里,怎么把无人机和客机分清楚
上个月帮一个做低空安防的朋友看数据,他手里攒了 1 万多张从公开视频里抽帧的图,里面混着客机、螺旋桨小飞机、各种鸟,还有四旋翼无人机。他一开始想用 COCO 预训练的 YOLOv5 直接跑,结果模型把无人机和鸟全归成"bird",把远处的客机认成"airplane",根本没法用。问题不在模型,在标签——COCO 里压根没有"细分飞机型号"和"无人机"这两个类,你让它怎么分。
这份资源解决的就是这个事:一套已经标好、划分好、配好data.yaml的 YOLO 格式数据集,加上一个能直接跑起来的 PyQt5 检测界面。数据集覆盖飞机细分型号、鸟类、无人机三类目标,标签是 txt 格式,train/val/test 已经切分完毕,YOLOv5、YOLOv7、YOLOv8 都能直接拿去训练。适合两类人:一类是想练手目标检测但懒得自己标数据的,另一类是做低空监测、机场净空、生态观测这类场景、需要一个能区分"这是鸟还是无人机"的基线模型的。下面我按自己拆包复现的顺序,把环境、数据、训练、界面、坑一条条讲清楚。
2. 拆包先看目录:数据集结构与 YOLO 标签格式核对
拿到一个检测数据集,我第一件事不是急着训练,而是先把目录结构和标签格式核对一遍。这一步花十分钟,能省掉后面几个小时的报错排查。这份资源的目录组织是标准的 YOLO 检测格式,但有几个细节需要确认,尤其是类别索引和data.yaml的对应关系。
2.1 目录树与文件职责
解压后大致是这样一个结构(文件名以实际为准,这里按 YOLO 惯例还原):
dataset/ ├── images/ │ ├── train/ # 训练集图片 │ ├── val/ # 验证集图片 │ └── test/ # 测试集图片 ├── labels/ │ ├── train/ # 训练集标签,与图片同名 .txt │ ├── val/ │ └── test/ └── data.yaml # 数据集配置文件图片和标签是同名配对的:images/train/xxx.jpg对应labels/train/xxx.txt。YOLO 训练时靠文件名匹配,所以任何一张图缺了同名 txt,训练阶段会直接报No labels found或者静默跳过。我一般会先跑一段脚本核对配对情况:
import os img_dir = "dataset/images/train" lbl_dir = "dataset/labels/train" imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir)} lbls = {os.path.splitext(f)[0] for f in os.listdir(lbl_dir)} missing_lbl = imgs - lbls # 有图没标签 missing_img = lbls - imgs # 有标签没图 print("缺标签的图:", len(missing_lbl)) print("缺图的标签:", len(missing_img))这段逻辑很直白:用集合差集找出不配对的样本。missing_lbl不为空说明有图没标,训练时这些图会被忽略;missing_img不为空说明标签是孤儿,不影响训练但说明数据整理有残留。正常情况两个都应该是 0。
2.2 标签格式与类别索引
YOLO 的 txt 标签每行是class_id x_center y_center width height,后四个都是归一化到 0~1 的相对坐标。打开一个标签文件看一眼:
cat dataset/labels/train/Pexels-Videos-2068519_mp4-6_jpg.rf.152992a221524d15e4dc36a2ab9d647a.txt你会看到类似0 0.512 0.334 0.221 0.180这样的行。这里最容易翻车的是类别索引:class_id是从 0 开始的整数,它对应data.yaml里names列表的下标。如果data.yaml写的是['plane', 'bird', 'drone'],那0就是 plane,1是 bird,2是 drone。一旦标签生成时用的类别顺序和data.yaml不一致,模型学出来的就是错位的——把无人机当鸟,而且 loss 还降得很正常,属于典型的玄学问题。
核对方法很简单,统计一下所有标签里出现过的 class_id:
import glob from collections import Counter counter = Counter() for f in glob.glob("dataset/labels/train/*.txt"): with open(f) as fp: for line in fp: if line.strip(): counter[int(line.split()[0])] += 1 print(counter)如果输出里出现了3或更大的 id,而data.yaml只有 3 个类,说明标签和配置对不上,必须回去查生成脚本。这份资源号称"可区分具体飞机型号",意味着飞机类可能不止一个 id,具体几个类要以data.yaml的names长度为准,不要凭感觉假设。
2.3 data.yaml 的关键字段
data.yaml是训练入口,核心就四个字段:
path: ./dataset # 数据集根目录 train: images/train # 相对 path 的训练图路径 val: images/val test: images/test nc: 3 # 类别数 names: ['plane', 'bird', 'drone'] # 类别名,顺序即 class_idnc必须等于names的长度,也必须等于标签里最大 class_id + 1。这三个数任何一个对不上,训练要么报维度错误,要么静默学错。我见过有人改了names忘了改nc,YOLOv5 直接抛Label class X exceeds nc=Y,这种还算好的,至少报错了。真正坑的是nc对了但names顺序错了,训练全程无异常,推理时类别全乱。
提示:
path字段在 YOLOv5 里是相对当前工作目录解析的,不是相对 yaml 文件。所以训练命令在哪个目录下执行,path就要按那个目录来写,否则会报找不到图片。
3. 环境配置与训练:从 PDF 教程到实际跑通 YOLOv5
资源里附了两份环境配置 PDF(教程1、教程2),覆盖 YOLOv3 到 YOLOv8 的安装。PDF 适合照着装,但实际跑的时候版本冲突才是大头。这一章我按自己复现的路径,把环境、训练命令、参数含义和验证方法串一遍。
3.1 环境搭建与版本选择
YOLOv5 对 PyTorch 和 CUDA 版本比较敏感。我一般用 conda 隔离环境,避免污染主环境:
conda create -n yolov5 python=3.9 -y conda activate yolov5 # 按显卡 CUDA 版本装 PyTorch,这里以 CUDA 11.8 为例 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 克隆 YOLOv5 并装依赖 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt版本选择上,Python 3.9 是兼容性最稳的区间,3.11 以上有些依赖会编译失败。PyTorch 2.x 配 CUDA 11.8 是目前主流组合。如果你只有 CPU,把--index-url那行换成 CPU 版即可,但训练 1 万多张图会非常慢,建议至少有一张 8G 显存的卡。
装完验证一下 GPU 是否可用:
import torch print(torch.__version__) print(torch.cuda.is_available()) # 应为 True print(torch.cuda.get_device_name(0))cuda.is_available()返回 False 是最常见的翻车点,八成是 PyTorch 版本和驱动不匹配,重装对应 CUDA 版本的 torch 即可。
3.2 训练命令与关键参数
数据配好、环境通了,训练命令本身不复杂:
python train.py \ --data dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --project runs/train \ --name plane_bird_drone逐个说参数。--weights yolov5s.pt是预训练权重,从 COCO 迁移过来,比从头训收敛快得多;s是 small 版本,速度和精度平衡,显存不够就换n(nano),精度要求高换m或l。--img 640是输入分辨率,无人机和鸟在画面里往往很小,如果小目标漏检严重,可以提到 1280,但显存占用会翻几倍。--batch 16是批大小,8G 显存跑 640 分辨率大概能到 16,爆显存就往下调。--epochs 100对 1 万多张图通常够用,看验证集 mAP 是否还在涨,不涨就可以停。
训练过程中重点盯三个指标:box_loss、obj_loss、mAP@0.5。前两个持续下降是正常的,mAP@0.5是验证集上的平均精度,它才是判断模型好坏的依据。如果 loss 降但 mAP 不涨,多半是过拟合或者标签有问题。
3.3 用 test.py 验证与推理
资源里带了test.py,训练完可以直接拿它跑测试集:
python test.py \ --weights runs/train/plane_bird_drone/weights/best.pt \ --data dataset/data.yaml \ --img 640 \ --task test--task test会在 test 集上算 mAP 并输出每类的 AP。这里要注意,test 集必须和训练时用的 val 集分开,否则评估结果虚高。如果 test 的 mAP 比 val 低很多,说明模型泛化不行,可能是数据分布不均——比如无人机样本远少于鸟,模型就偏向鸟。
单张图推理看效果:
python detect.py \ --weights runs/train/plane_bird_drone/weights/best.pt \ --source dataset/images/test \ --img 640 \ --conf-thres 0.25 \ --save-txt--conf-thres 0.25是置信度阈值,低于它的框不输出。小目标场景可以降到 0.1 看召回,但误检会变多。--save-txt会把检测结果存成 YOLO 格式,方便和真值对比。我一般会挑几张无人机和鸟同框的图重点看,这类样本最能暴露模型到底有没有学会区分。
4. PyQt5 界面:把训练好的模型包成能点的工具
训练脚本跑通只是第一步,真正交付给非技术同事用,得有个界面。资源里的pyqt5使用说明.pdf和share.py就是干这个的。这一章讲界面怎么和模型对接、参数怎么暴露、以及打包时容易踩的坑。
4.1 界面与推理逻辑的对接
PyQt5 界面的核心是把"选图 → 推理 → 显示结果"这条链路串起来。share.py里通常是这样组织的:
import sys import cv2 import torch from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QFileDialog from PyQt5.QtGui import QPixmap, QImage class DetectWindow(QMainWindow): def __init__(self): super().__init__() self.model = torch.hub.load('ultralytics/yolov5', 'custom', path='best.pt', force_reload=False) self.model.conf = 0.25 # 置信度阈值 self.model.iou = 0.45 # NMS 的 IoU 阈值 self.init_ui() def init_ui(self): self.setWindowTitle("飞机-鸟类-无人机检测") self.btn = QPushButton("选择图片", self) self.btn.clicked.connect(self.load_image) self.label = QLabel(self) self.label.resize(640, 480) def load_image(self): path, _ = QFileDialog.getOpenFileName(self, "选图", "", "Images (*.jpg *.png)") if not path: return results = self.model(path) # 推理 results.render() # 画框 img = results.ims[0] # 拿到带框的 numpy 图 img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w, c = img.shape qimg = QImage(img.data, w, h, c * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg)) if __name__ == "__main__": app = QApplication(sys.argv) win = DetectWindow() win.show() sys.exit(app.exec_())逻辑说明:torch.hub.load直接加载本地best.pt,force_reload=False避免每次启动重新下载。self.model.conf和self.model.iou是两个最该暴露给用户的参数——前者控制灵敏度,后者控制重叠框的合并。results.render()会把检测框画到原图上,results.ims[0]拿到的是 BGR 格式的 numpy 数组,转成 RGB 才能给 Qt 显示。QImage那行的c * w是每行字节数(stride),写错会导致图像错位或花屏。
4.2 参数暴露与交互设计
界面好不好用,关键看参数有没有暴露出来。我一般会在界面上加两个滑块,分别控制conf和iou:
from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QSlider self.conf_slider = QSlider(Qt.Horizontal, self) self.conf_slider.setRange(1, 99) # 对应 0.01 ~ 0.99 self.conf_slider.setValue(25) self.conf_slider.valueChanged.connect(self.update_conf) def update_conf(self, value): self.model.conf = value / 100.0setRange(1, 99)是因为 Qt 滑块只接受整数,用 1~99 代表 0.01~0.99,回调里除以 100 还原。这样用户拖动滑块就能实时调灵敏度,不用改代码。iou同理。另外建议加一个"保存结果"按钮,把带框的图存下来,方便出报告。
4.3 打包成 exe 的注意事项
要发给别人用,通常用 PyInstaller 打包:
pyinstaller --noconfirm --windowed --name 检测工具 \ --add-data "best.pt;." \ --add-data "yolov5;yolov5" \ share.py--add-data是把模型权重和 yolov5 源码目录一起打进去,格式是源路径;目标路径(Windows 用分号,Linux 用冒号)。--windowed去掉控制台窗口。打包后最常见的坑是torch.hub.load找不到模型——因为打包后工作目录变了,得用sys._MEIPASS定位资源:
import sys, os def resource_path(rel): base = getattr(sys, '_MEIPASS', os.path.abspath(".")) return os.path.join(base, rel)然后path=resource_path('best.pt')。不处理这个,exe 在别人机器上必崩。
5. 避坑与排查:那些训练日志不会告诉你的问题
这一章是我自己复现和帮人排查时攒下来的,每条都按"现象 → 原因 → 解决"写,都是训练日志里看不出来的。
现象:训练 loss 正常下降,但推理时所有目标都标成同一个类。原因:data.yaml的names顺序和标签生成时的类别顺序不一致,模型学的是错位映射。这种情况 loss 不会异常,因为模型确实在拟合,只是拟合错了。 解决:用 2.2 节的统计脚本打印标签里实际出现的 class_id 分布,和data.yaml的names逐一对齐。如果发现某个 id 的样本数异常少或异常多,基本就是顺序错了。
现象:小目标(远处的鸟、小无人机)大量漏检,mAP 上不去。原因:输入分辨率 640 对小于 32×32 像素的目标不友好,下采样后特征几乎消失。 解决:把--img提到 1280,同时--batch减半防爆显存;或者在数据加载时开启 mosaic 增强(YOLOv5 默认开),让模型多见小目标拼接场景。如果还不行,考虑换l或x大模型,小目标检测对大模型更友好。
现象:PyQt5 界面点"选择图片"没反应,或者显示一片黑。原因:QImage的 stride 参数写错,或者 numpy 数组不是连续的(results.ims[0]经过 render 后可能不连续)。 解决:显示前加img = np.ascontiguousarray(img)保证内存连续,stride 用img.strides[0]而不是手算c * w。黑屏多半是通道顺序错了,确认转成了 RGB。
现象:打包成 exe 后报FileNotFoundError: best.pt。原因:PyInstaller 打包后资源被解压到临时目录sys._MEIPASS,代码里用的相对路径找不到文件。 解决:用 4.3 节的resource_path函数统一处理资源路径,所有--add-data加进去的文件都走这个函数定位。
现象:训练到一半显存溢出(CUDA out of memory)。原因:--batch太大,或者--img提太高,或者 dataloader 的workers太多导致内存碎片。 解决:先把--batch降到 8 或 4,再不行降--img。YOLOv5 支持--batch -1自动批大小,让它自己找上限。另外--workers在 Windows 上设太大反而慢,设 0 或 2 即可。
注意:改任何参数后重新训练,最好换个
--name,别覆盖上一次的runs/train/xxx,否则权重和日志混在一起,回头对比实验都分不清哪次是哪次。
6. 进阶技巧:用 test.py 做类别级误差分析,定位到底哪类拖后腿
训练完看一个总 mAP 是不够的,你根本不知道是飞机分不清型号,还是鸟和无人机混了。真正有用的做法是拿test.py的输出做类别级误差分析。YOLOv5 的test.py在--task test模式下会输出每类的 P、R、mAP@0.5、mAP@0.5:0.95,但默认只打印汇总。我一般会加--verbose让它逐类打印,然后把结果存下来对比。
具体操作:先跑一次基线,把每类指标记下来。
python test.py \ --weights runs/train/plane_bird_drone/weights/best.pt \ --data dataset/data.yaml \ --img 640 \ --task test \ --verbose \ --save-json--save-json会生成一个predictions.json,里面是每张图的检测框和置信度。有了这个文件,就能做更细的分析——比如专门统计"真值是 drone 但被预测成 bird"的样本有多少。这类混淆矩阵用confusion_matrix.png也能看,YOLOv5 训练结束会自动生成在runs/train/xxx/下,横轴真值纵轴预测,对角线越深越好,非对角线的亮块就是混淆重灾区。
我自己的习惯是:如果发现 drone 和 bird 混淆严重,先别急着调模型,回去看数据。十有八九是这两类的样本在视觉上确实像——比如远距离的四旋翼和展翅的鸟,轮廓接近。这时候要么补更多区分性强的样本(近距离无人机、飞行姿态明显的鸟),要么在推理时对这两类单独调高conf阈值,宁可漏检也别误判。低空安防场景里,把鸟误报成无人机带来的处置成本,远高于漏掉一只鸟。
另一个技巧是分分辨率测试。同一组权重,分别用--img 640和--img 1280跑 test,对比小目标类别的 mAP 变化。如果 1280 下无人机 mAP 明显涨,说明你的场景小目标多,部署时就该用高分辨率,哪怕推理慢一点。这个对比花不了多少时间,但能直接决定上线参数。
还有个容易被忽略的点:test.py的--task除了test还有val和speed。--task speed会测推理速度,输出预处理、推理、NMS 各阶段耗时。部署到边缘设备前,我一定先跑一遍 speed,看看在目标硬件上能不能达到实时。如果 NMS 耗时占比高,说明检测框太多,调高conf或iou能压下来。
从那以后我每次拿到新数据集,都强制先跑一遍类别级误差分析再决定要不要调模型——因为大部分"模型不行"的问题,根子都在数据和标签上,光调超参数是治标不治本。这套流程走下来,你手里就不只是一个能跑的模型,而是一个知道边界在哪、哪里会翻车的模型。希望帮到你。
本文还有配套的精品资源,点击获取