YOLOv5三分类实战:飞机鸟类无人机检测与PyQt界面部署
发布时间:2026/10/2 2:44:04来源:尧图网络
简介本资源面向计算机视觉学习者与目标检测工程实践者提供一套细分类型飞机、鸟类与无人机检测的完整训练方案可区分具体飞机型号适合课程设计、科研实验与算法对比等场景。压缩包共约2000个文件以1994个YOLO格式txt标签为主另含3个Python脚本与3份PDF教程整体约924.72MB数据集已划分train、val、test并配置data.yamlyolov5、yolov7、yolov8等算法可直接训练。资源同时附带PyQt界面相关脚本与使用说明便于将训练好的模型封装为可视化检测工具。目前已有467人学习下载读者可据此快速复现多类别目标检测流程掌握数据组织、模型训练与界面部署的完整链路。1. 飞机、鸟类、无人机混检为什么通用 YOLOv5 模型一上线就翻车机场净空区最怕的不是鸟是分不清鸟和无人机。我做过一个真实场景摄像头架在跑道外侧画面里同时出现远处客机、低空盘旋的鸟群、还有一架四旋翼无人机。拿 COCO 预训练的 YOLOv5s 直接推理结果三类目标全被归到“bird”或“airplane”里无人机要么漏检要么被当成鸟。原因很直接——通用模型没见过“细分类型”的标注体系它的分类头只认 COCO 的 80 类而我们要的是飞机、鸟类、无人机三个互斥类别且鸟类内部还分大型猛禽和小型群鸟。这个标题讲的就是一套完整落地链路用 YOLOv5 训练一个三分类检测模型配一份自建数据集再套一个 PyQt 界面做本地推理。它解决的是“通用检测模型在细分场景下不可用”的问题适合做安防监控、机场净空、生态监测的工程师也适合想跑通“训练界面”全流程的学生。数据集不是公开的 COCO 子集而是按飞机、鸟类、无人机三类重新标注的图片数量在几千张量级覆盖不同光照、遮挡和背景。PyQt 界面不是玩具它要能加载权重、选图片或视频、实时画框、显示置信度并且能在没有 GPU 的笔记本上跑起来。我踩过的最大坑是拿 COCO 里 airplane 和 bird 的标注直接训模型会把无人机当成鸟因为 COCO 没有无人机类。后来重新标注了 1200 张无人机图混入 800 张飞机和 1500 张鸟类才把三类的混淆矩阵压下来。这一章先立住认知细分类型检测不是改个类别数就完事数据分布、锚框尺寸、后处理阈值都要跟着调。2. 数据集怎么攒从 COCO 子集到三分类标注的完整流程2.1 为什么不能直接用 COCO 的 airplane 和 bird 类COCO 里 airplane 类大约 5000 张bird 类约 9000 张但这两个类的标注框普遍偏大飞机多是停机坪或空中远景鸟多是近景特写。而我们的场景是远距离小目标飞机在画面里可能只有 40×30 像素鸟群更小无人机在 100 米外只有 20×15 像素。直接拿 COCO 训模型会偏向大目标小目标召回率极低。更致命的是 COCO 没有无人机类你硬把无人机标成 bird模型学到的特征就是“鸟无人机”上线必翻车。我的做法是从 COCO 里只取 airplane 和 bird 中“小目标占比高”的子集再用自有无人机图补齐。具体筛选逻辑是标注框面积小于 32×32 的保留大于 96×96 的丢弃。这样飞机保留约 1800 张鸟类保留约 2600 张再混入 1200 张无人机图总数据量 5600 张左右。无人机图来自公开航拍数据集和自采视频抽帧标注时统一用 labelImg 画框类别名写成plane、bird、drone。2.2 用脚本把 COCO 标注转成 YOLO 格式COCO 的标注是 JSONYOLO 要的是每张图一个 txt每行class_id x_center y_center width height且坐标归一化到 0~1。下面这个脚本我用了三年处理过十几个数据集稳定。import json import os from PIL import Image # 类别映射COCO 的 airplane5, bird16 映射到我们的 0,1 COCO_TO_CUSTOM {5: 0, 16: 1} # 无人机图片单独放一个文件夹标注文件已经手动写成 YOLO 格式 DRONE_DIR drone_images def coco_to_yolo(coco_json, image_dir, output_dir): with open(coco_json, r) as f: data json.load(f) # 建立 image_id 到文件名的映射 img_id_to_info {img[id]: img for img in data[images]} # 建立 image_id 到标注列表的映射 img_id_to_anns {} for ann in data[annotations]: img_id_to_anns.setdefault(ann[image_id], []).append(ann) os.makedirs(output_dir, exist_okTrue) for img_id, info in img_id_to_info.items(): file_name info[file_name] w, h info[width], info[height] anns img_id_to_anns.get(img_id, []) lines [] for ann in anns: cat_id ann[category_id] if cat_id not in COCO_TO_CUSTOM: continue # COCO 框格式 [x_min, y_min, width, height] x, y, bw, bh ann[bbox] # 过滤掉太小的框面积小于 32*32 if bw * bh 1024: continue # 转成 YOLO 中心点归一化坐标 x_center (x bw / 2) / w y_center (y bh / 2) / h nw bw / w nh bh / h cls_id COCO_TO_CUSTOM[cat_id] lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {nw:.6f} {nh:.6f}) if lines: txt_name os.path.splitext(file_name)[0] .txt with open(os.path.join(output_dir, txt_name), w) as f: f.write(\n.join(lines)) if __name__ __main__: coco_to_yolo(annotations/instances_train2017.json, train2017, labels/train)逻辑说明先读 COCO JSON按image_id聚合标注只保留category_id为 5 和 16 的框面积小于 1024 像素的框直接丢弃这是为了聚焦小目标坐标归一化用图像宽高不能搞反。参数上COCO_TO_CUSTOM可以根据你的类别体系改比如你想把鸟再分成bird_large和bird_small就加映射。跑完脚本后labels/train里每张图对应一个 txt空 txt 表示该图没有目标训练时会被忽略。2.3 无人机数据单独处理与合并无人机图片我单独放drone_images标注文件已经写成 YOLO 格式类别 id 固定为 2。合并时直接复制图片和标签到统一目录注意文件名不能冲突。我一般用plane_、bird_、drone_前缀重命名。合并后统计一下三类框的数量飞机约 3200 个鸟类约 5800 个无人机约 2100 个。鸟类框最多因为群鸟一张图能出几十个框。这个分布不算均衡但 YOLOv5 对类别不平衡有一定容忍度后面可以在损失函数里调cls权重。注意标注时一定要统一框的松紧度。飞机框要贴紧机身鸟框要包住整个鸟身包括翅膀无人机框要包含旋翼。松紧不一致会让模型学歪mAP 掉 5 个点很正常。3. YOLOv5 训练三分类模型超参数怎么设、什么时候停3.1 环境配置与数据 YAML 写法环境用 conda 建一个 Python 3.8 的虚拟环境装 PyTorch 1.8 和 YOLOv5 的 requirements。我一般直接 clone 官方仓库但不要用最新版用 v6.0 或 v7.0 这种稳定 tag。数据配置文件data/plane_bird_drone.yaml长这样path: ../datasets/plane_bird_drone # 数据集根目录 train: images/train # 训练图片相对路径 val: images/val # 验证图片相对路径 nc: 3 # 类别数 names: [plane, bird, drone] # 类别名顺序必须和标注 id 一致path写绝对路径也行但相对路径更方便迁移。train和val是相对于path的目录。图片和标签的目录结构要对应images/train/xxx.jpg对应labels/train/xxx.txt。YOLOv5 会自动去找同名 txt找不到就报错。3.2 训练命令与关键超参数我用的训练命令基于 YOLOv5s因为要在 PyQt 界面里跑模型不能太大。命令如下python train.py \ --img 640 \ --batch 16 \ --epochs 150 \ --data data/plane_bird_drone.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ --name plane_bird_drone_v1 \ --cache参数逐个说--img 640是输入分辨率小目标多的话可以提到 1280但显存翻倍我 8G 显存只能跑 640。--batch 16是批次大小显存不够就降到 8但 batch 太小 BN 层统计不准mAP 会抖。--epochs 150是我试出来的100 轮时验证集 mAP 还在涨150 轮基本平了再训就过拟合。--weights yolov5s.pt加载 COCO 预训练权重这是必须的从头训收敛太慢。--hyp用 low 增强配置因为我们的数据量不大增强太猛反而学不到真实特征。--cache把图片缓存到内存加速训练但内存小于 16G 别开。训练过程中重点看三个指标mAP0.5、mAP0.5:0.95、val/obj_loss。mAP0.5 到 0.85 以上算可用0.9 以上算好。obj_loss 如果一直不降说明锚框和你的目标尺寸不匹配需要重新聚类锚框。3.3 锚框重聚类小目标检测的后悔药YOLOv5 默认锚框是基于 COCO 的最小锚框是 10×13对应 640 分辨率下约 10 像素的目标。但我们的无人机在画面里可能只有 15×15 像素鸟群更小。默认锚框召回率低必须重聚类。用 YOLOv5 自带的utils/autoanchor.py命令python utils/autoanchor.py --data data/plane_bird_drone.yaml --img 640 --thr 4.0它会输出新的锚框尺寸比如[8,10, 15,18, 25,30]这种更小的框。把结果替换到模型配置里或者直接在训练命令加--anchor参数。我实测重聚类后小目标召回率提升 12%尤其是无人机类。提示重聚类前先把数据集里所有标注框的宽高导出来画个散点图看看分布。如果大部分框都小于 32×32那默认锚框肯定不行。4. PyQt 界面集成从权重加载到实时画框4.1 界面布局与核心控件PyQt 界面我一般用 Qt Designer 拖一个主窗口左边放QLabel显示图片右边放按钮选择图片、选择视频、开始检测、停止下面放QTextEdit显示日志。核心控件就这几个不要搞太花。关键是要把 YOLOv5 的推理封装成一个类界面只负责调它。import sys import cv2 import torch from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget, QFileDialog from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtCore import Qt, QTimer class Detector: def __init__(self, weights, devicecpu): self.model torch.hub.load(ultralytics/yolov5, custom, pathweights, force_reloadFalse) self.model.conf 0.4 # 置信度阈值 self.model.iou 0.45 # NMS IoU 阈值 self.model.classes [0, 1, 2] # 只检测三类 self.device device self.model.to(device) def detect(self, img): # img 是 BGR numpy 数组 results self.model(img) return results class MainWindow(QMainWindow): def __init__(self): super().__init__() self.detector Detector(best.pt, devicecpu) self.init_ui() def init_ui(self): self.setWindowTitle(飞机鸟类无人机检测) self.label QLabel(等待图片...) self.label.setAlignment(Qt.AlignCenter) self.btn_img QPushButton(选择图片) self.btn_img.clicked.connect(self.load_image) self.btn_video QPushButton(选择视频) self.btn_video.clicked.connect(self.load_video) layout QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.btn_img) layout.addWidget(self.btn_video) container QWidget() container.setLayout(layout) self.setCentralWidget(container) def load_image(self): path, _ QFileDialog.getOpenFileName(self, 选图片, , Images (*.jpg *.png)) if not path: return img cv2.imread(path) results self.detector.detect(img) # 把结果画到图上 rendered results.render()[0] # 返回 BGR 数组 self.show_image(rendered) def show_image(self, img): rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg).scaled(self.label.size(), Qt.KeepAspectRatio))逻辑说明Detector类用torch.hub.load加载本地权重conf和iou是后处理参数classes限定只输出三类。detect返回 YOLOv5 的 Results 对象render()直接画好框和标签。界面部分load_image读图后调检测再把渲染结果转成 QImage 显示。注意QImage构造时数据要连续rgb.data在 numpy 数组里是连续的但缩放后可能不连续稳妥做法是rgb.copy()。4.2 视频流检测与线程安全视频检测不能直接在 UI 线程里跑否则界面卡死。我用QTimer定时读帧每 30ms 读一帧检测完再显示。但检测本身耗时如果一帧要 200ms定时器会堆积。更好的做法是开一个QThread在子线程里循环读帧、检测通过信号把画好的图发给主线程显示。from PyQt5.QtCore import QThread, pyqtSignal class VideoThread(QThread): frame_ready pyqtSignal(object) def __init__(self, detector, video_path): super().__init__() self.detector detector self.video_path video_path self.running True def run(self): cap cv2.VideoCapture(self.video_path) while self.running and cap.isOpened(): ret, frame cap.read() if not ret: break results self.detector.detect(frame) rendered results.render()[0] self.frame_ready.emit(rendered) cap.release() def stop(self): self.running False self.wait()参数说明frame_ready信号传 numpy 数组主线程接到后转 QImage 显示。running标志控制循环退出。注意results.render()返回的是列表取第一个元素。如果视频帧率很高可以在run里加self.msleep(10)控制速度。注意PyQt 界面里不要用cv2.imshow会和 Qt 事件循环冲突导致窗口无响应。所有显示都走 QLabel。5. 避坑与排查三类混淆、漏检、界面卡死的血泪经验5.1 无人机被识别成鸟类别混淆的排查现象验证集上无人机类的 mAP 只有 0.6混淆矩阵显示 30% 的无人机被预测成鸟。原因无人机和鸟在远距离下都是小目标形状特征模糊且训练数据里无人机样本太少。解决一是增加无人机样本到 2000 张以上二是把无人机和鸟的标注框严格区分——无人机框要包含旋翼鸟框不要包含背景树枝。三是在损失函数里给无人机类加权YOLOv5 的hyp文件里cls_pw参数可以调默认 1.0我改成 1.5 后无人机召回提升 8%。5.2 小目标漏检严重锚框和分辨率的双重问题现象画面里 20×20 像素的鸟完全检测不到置信度低于 0.1。原因默认锚框最小 10×13但经过 32 倍下采样后特征图上的感受野覆盖不了这么小的目标。解决重聚类锚框把最小锚框降到 6×8同时把输入分辨率从 640 提到 1024但显存不够就只提验证集分辨率训练时用 mosaic 增强把小目标拼到大图上。我实测 1024 分辨率下小目标召回从 0.45 提到 0.72。5.3 PyQt 界面点“开始检测”就卡死现象点击按钮后界面无响应日志也不输出。原因检测代码直接写在按钮回调里阻塞了 Qt 事件循环。解决把检测放到QThread子线程通过信号更新 UI。另外torch.hub.load第一次加载模型会下载权重如果网络不通会卡住建议提前把best.pt放到本地用path参数指定绝对路径。5.4 训练 loss 震荡不收敛学习率和 batch 的坑现象训练前 20 轮 loss 从 0.8 降到 0.3然后突然跳到 0.9来回震荡。原因学习率太大或者 batch 太小导致 BN 层统计量不稳定。解决用--hyp里的lr0从 0.01 降到 0.001--batch从 8 提到 16。如果显存不够用梯度累积--accumulate 2模拟大 batch。另外--cache在内存不足时会频繁换页反而拖慢训练关掉就好。5.5 验证集 mAP 很高但实际推理很差数据泄露的坑现象验证集 mAP0.5 到 0.92但拿新视频测试漏检一半。原因训练集和验证集有同一段视频抽的帧画面几乎一样模型过拟合了。解决按视频源划分数据集同一段视频的帧只能出现在训练集或验证集之一。我一般用 7:2:1 划分训练 70%验证 20%测试 10%且测试集完全不参与训练和调参。6. 把模型塞进树莓派5量化、加速与一个取巧的验证方法训练完的best.pt在 PC 上跑得欢但真要落地到边缘设备比如树莓派5就得考虑算力。树莓派5 的 CPU 是四核 A76没有 GPU直接跑 PyTorch 版 YOLOv5s一张 640 图要 1.2 秒视频根本没法看。我的做法是导出 ONNX再用 ONNX Runtime 推理速度能到 300ms 一张。如果还嫌慢就上 NCNN但 NCNN 对 PyQt 不友好得用 C 重写界面成本太高。导出 ONNX 的命令python export.py --weights best.pt --include onnx --img 640 --batch 1导出后得到一个best.onnx在树莓派上装onnxruntime推理代码import onnxruntime as ort import numpy as np import cv2 sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name sess.get_inputs()[0].name def preprocess(img, img_size640): # 保持长宽比缩放填充灰边 h, w img.shape[:2] scale img_size / max(h, w) nh, nw int(h * scale), int(w * scale) resized cv2.resize(img, (nw, nh)) canvas np.full((img_size, img_size, 3), 114, dtypenp.uint8) canvas[:nh, :nw] resized # 转 RGB、归一化、HWC 转 CHW、加 batch 维 blob canvas[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 blob np.expand_dims(blob, axis0) return blob, scale, (nw, nh) def infer(img): blob, scale, (nw, nh) preprocess(img) outputs sess.run(None, {input_name: blob}) # outputs[0] 形状 [1, 25200, 7]7 4 框 1 obj 3 类 preds outputs[0][0] # 过滤置信度 conf_thres 0.4 boxes [] for pred in preds: obj_conf pred[4] if obj_conf conf_thres: continue cls_scores pred[5:] cls_id np.argmax(cls_scores) score obj_conf * cls_scores[cls_id] if score conf_thres: continue # 框坐标是中心点宽高归一化到 0~1 x, y, w, h pred[:4] # 还原到原图 x1 (x - w / 2) / scale y1 (y - h / 2) / scale x2 (x w / 2) / scale y2 (y h / 2) / scale boxes.append([x1, y1, x2, y2, score, cls_id]) # NMS 略可以用 cv2.dnn.NMSBoxes return boxes参数说明img_size640要和导出时一致114是 YOLOv5 默认的填充灰度值conf_thres可以按场景调安防场景宁可误报不可漏报就降到 0.3。NMS 用 OpenCV 的cv2.dnn.NMSBoxesIoU 阈值设 0.45。验证量化效果有个取巧办法不要只看 mAP把测试集里所有小目标面积小于 32×32单独拎出来算召回率。如果量化后小目标召回掉超过 10%说明量化对浅层特征伤害大得用混合量化或者跳过第一层。我一般用 ONNX Runtime 的quantize_static做 INT8 量化校准集用 200 张训练图量化后模型大小从 14MB 降到 4MB树莓派上推理速度提到 180ms。最后说个习惯每次训完模型我都会拿同一段 30 秒的测试视频跑三遍——原版 PyTorch、ONNX FP32、ONNX INT8把三者的检测框数量画成折线图。如果 INT8 的框数量比 FP32 少 15% 以上我就放弃量化宁可多花点算力。这个习惯帮我省了至少三次返工。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网