ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YoloV5口罩识别项目实战:数据集制作、训练调参与GUI部署

YoloV5口罩识别项目实战:数据集制作、训练调参与GUI部署 简介这是一份基于YOLOv5的口罩识别完整项目覆盖模型训练、推理到图形界面交互全流程面向需要完成毕业设计、课程设计的计算机相关专业学生授课演示的老师以及希望进阶目标检测和界面开发的开发者。压缩包共1650个文件大小139.38MB其中779个python源码和778个python编译文件为项目主体支持直接运行和二次开发27个配置文件负责模型及训练参数设置3个权重文件保存训练结果另有20个可执行程序辅助环境与功能演示包含容器化和文本说明等资料便于快速复现部署。目前已有95人学习下载。项目获导师指导认可答辩评分95分代码经测试可稳定运行内含图形界面源码能直观展示口罩检测效果并配有详细文档既适合从零跑通YOLOv5流程也便于在此基础上扩展功能满足课设、毕设或项目演示需求。1. YoloV5口罩识别从数据集到可交付GUI的完整链路一个“口罩识别模型项目GUI源码详细文档”的打包项目真正拉开差距的往往不在模型代码本身而在三件事数据集质量、训练参数设置了没有、GUI有没有跟推理逻辑解耦。很多人在拿到类似 zip 后直接跑 train.py跑完用 detect.py 验证几张图就宣布完成结果放进 GUI 里要么卡死要么漏检频发最后只能靠调低置信度硬撑这其实是把问题推给了后处理。这篇文章按我实际做这类毕业设计和技术交付的路线来拆先讲口罩数据集怎么做才能让 YoloV5 训练自己的数据集时不走弯路再给一套可以直接套用的训练命令和超参配置接着把 GUI 源码里最容易被写坏的模型调用部分重构成可复用的 Detector 类最后聊验证指标和四类典型误检怎么排查。适合手里正好有一个口罩识别项目要交、或者入职后第一次接触 YoloV5 落地场景的工程师。2. 训练前的数据准备口罩数据集的采集、标注与格式转换2.1 口罩类别怎么定二分类还是三分类先定类别再找数据这是最容易返工的一步。常见的做法是with_mask和without_mask二分类但如果你的应用场景是闸机、考勤或公共区域监控最好一开始就做三分类with_mask、without_mask、mask_weared_incorrect。第三类专门覆盖口罩拉到下巴、只遮住嘴、口罩挂耳朵上这些“佩戴不规范”的情况实际使用中这类样本的漏检率远高于前两类。数据来源分两条路公开数据集和自采标注。公开方面可以找 RMFDReal-World Masked Face Dataset这一类专门做口罩识别的数据集注意它的目录结构和标注格式五花八门需要写脚本统一自采是保证场景覆盖的最可靠手段用手机或摄像头在不同角度、不同光线条件下拍人脸每个类别收集到 300 张以上对小型项目来说完全够用。这里要强调一个常见误判口罩识别模型的难点不是“有口罩没口罩”而是口罩边缘与肤色接近、半遮挡、暗光下的模糊目标所以采集时一定要加入逆光、侧脸、低头、多人同框这四类场景。标注工具我用 LabelImg保存格式选择 PascalVOC也就是 XML 文件。一个常见误区是 LabelImg 默认也能保存 YOLO 格式但你在标注过程中可能改了类别名或删过框容易造成 XML 与 txt 不一致所以我习惯统一存成 XML最后跑一次转换脚本批量生成 txt。2.2 把 VOC 的 XML 转成 YoloV5 需要的 txtYoloV5 的训练标签是每个图片一个 txt 文件每行内容为class_id x_center y_center width height坐标全部归一化到 0-1。而 LabelImg 输出的 XML 里存放的是左上角和右下角的绝对像素坐标所以转换这一步必须做。下面这个脚本可以直接放到datasets/mask/目录下运行import xml.etree.ElementTree as ET from pathlib import Path CLASS_NAMES [with_mask, without_mask, mask_weared_incorrect] def convert_voc_to_yolo(xml_path: Path, out_dir: Path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_NAMES: continue cls_id CLASS_NAMES.index(name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: out_path out_dir / (xml_path.stem .txt) out_path.write_text(\n.join(lines), encodingutf-8) xml_root Path(annotations) out_root Path(labels) out_root.mkdir(exist_okTrue) for xml_file in xml_root.glob(*.xml): convert_voc_to_yolo(xml_file, out_root)这段代码先读 XML 里的宽高再遍历每个object节点取出 XML 里记录的边界框坐标。注意x1x2除以 2 再除以图宽得到中心点归一化坐标宽高同理。忽略掉if lines判断里没有目标的情况如果你的标注文件里某个 XML 是空的转换后不生成 txt 文件反而更安全因为 YOLO 训练时如果某张图没有对应标签dataset.py会直接把这个样本过滤掉而生成一个内容为空的 txt 文件在某些版本里会触发“标签读取异常”的告警。2.3 目录结构、mask.yaml 和三个必调数据参数YoloV5 对数据目录的默认期望是images/train、images/val、labels/train、labels/val四件套。把转换出的 txt 文件按图片名对应放好目录结构长这样datasets/mask/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── mask.yaml然后在datasets/mask/下写数据配置文件mask.yamltrain: datasets/mask/images/train val: datasets/mask/images/val nc: 3 names: [with_mask, without_mask, mask_weared_incorrect]在 spawn 目录时需要留意路径写法如果你把 train.py 放在工程根目录而数据集放在工程外的某个盘符这里最好写绝对路径。我一般会在 yaml 里用相对于yolov5根目录的相对路径然后在命令行里统一从根目录发起训练这样其他小伙伴解压后不用改任何路径就能复现。三个必调数据参数要提前检查一是nc必须和类别数量严格一致类别顺序还要和转换脚本里的CLASS_NAMES保持一致否则 mAP 再看也白搭二是train和val的路径不能写反val 集合至少要保证每个类别有 30 个以上的实例否则验证集的 AP 波动很大三是确认训练集图片与标签文件名完全同名YoloV5 是按 stem 匹配的只要前缀不同就属于无效样本控制台训练日志里会直接看到图片数量缩水。3. 让 YoloV5 训练自己的数据集环境、命令与超参调整3.1 环境安装命里的两个坑以及模型怎么选先交代环境我用的是 Python 3.8 PyTorch 1.9 CUDA 11.1 的组合yolov5 源码建议直接拿较新的稳定分支这类项目代码改动频繁跟着 README 安装requirements.txt即可。最容易出问题的不是 torch而是pycocotoolsWindows 上 pip 安装经常编译失败建议用pip install pycocotools-windows替代。另一个是 opencv 版本如果之前装过 opencv-python-headlessGUI 里读取摄像头时会报错卸载干净后统一装 opencv-python。模型选型上yolov5s 是性价比最高的起步点CPU 上能跑到可接受的 fpsGPU 上训练一轮也就几分钟如果后面发现小目标或者遮挡场景漏检多再经 yaml 升级到 yolov5m。选 s 的另一个原因是它的预训练权重文件小跑通流程之后换 m 或 l 不需要改代码只改--weights参数即可。3.2 train.py 的最小训练命令和参数含义在yolov5源码根目录执行下面的命令将数据源指向刚刚配置好的mask.yamlpython train.py \ --data datasets/mask/mask.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 60 \ --project runs/train \ --name mask \ --device 0--img 640是输入分辨率口罩属于中等偏小的目标640 是底线显存允许就上 768--batch 16在 8G 显存上能跑低于 8G 就降到 8 并同步调低--workers--epochs 60是第一版建议值数据量小的场景甚至 30 轮就能收敛重点看后面日志里的 mAP 曲线是否走平--project和--name组合起来会把日志写到runs/train/mask/迭代记录里保留每个 epoch 的权重和很多指标曲线。第一次训练时--device 0要确认一下 CUDA 可见性没有独立显卡就改成cpu代价是训练时间大致翻十倍不是不能跑但建议直接换平台。3.3 超参数文件怎么调统计表里我常改的几个值YoloV5 的超参数集中在data/hyps/hyp.scratch.yaml里不需要全部理解真正值得手工干预的是下面这些参数默认值作用说明口罩场景建议lr00.01初始学习率过大会导致前几轮 loss 直接崩掉数据量大保持 0.01量小降到 0.005lrf0.1最终学习率相对于初始值的比例控制收尾精度默认即可不用动momentum0.937SGD 动量系数默认即可Momentum 太大对精度无提升反而震荡weight_decay0.0005正则化强度过拟合时调大自采数据只有几千张时建议提到 0.001mosaic1.0马赛克增强概率影响小目标学习保持 1.0最后 10 轮设置--close-mosaic 10mixup0.0图像混合增强概率面部语义较弱容易混出脏样本建议关掉保持 0cls_pw1.0类别权重失衡时提高少数类别的损失权重三分类中 incorrect 类别少时调到 1.5修改超参数有两条路直接改这个 yaml 文件或者用--hyp参数指定新文件。我习惯复制一份mask_hyp.yaml再改这样源码升级或重新拉分支时不丢配置。mosaic值得特别说一句它对口罩这种半遮挡目标很有效因为四张图拼在一起会让模型见过更多裁切和尺度变化。但马赛克数据分布和真实场景有差异所以在训练快结束时关闭它让模型在接近真实分布的样本上微调几条 epoch这一招对最终 mAP 的提升经常是肉眼可见的。3.4 训练日志里到底看什么以及断点续训怎么接训练开始后每一轮会打印epoch、GPU_mem、box_loss、obj_loss、cls_loss、mAP0.5等字段。新手容易只看 mAP我却建议先看三个 loss 的总趋势正常训练的 loss 应该是稳步下行、后期平坦如果某个 loss 在中途突然反弹大概率是学习率太大或者数据集里有坏标签。mAP 没到 0.8 之前不要轻易停先确认是不是数据量不够而不是反复调模型结构。训练中断是家常便饭。YoloV5 的断点续训用--resume参数不带路径时会自动从runs/train/mask/下找到最近一次last.pt权重恢复如果机器重启后目录结构变了就显式指定python train.py --resume runs/train/mask/weights/last.pt这里有个容易踩的坑--resume会继承原来的--img、--batch、--epochs参数如果你在中途换了显卡或者改了 batch size续训可能报“shape mismatch”保险的做法是删掉last.pt旁边 optimizer 状态文件只保留last.pt这样它会在新环境重新构建优化器状态代价是已调整过的学习率曲线会重置。4. GUI 源码怎么读把推理逻辑封装成 Detector 类4.1 GUI 需要四个模块以及“源代码为什么跑起来很卡”的真相正常交付项目的 GUI 源码至少包含四个模块模型加载与推理、输入源管理图片、视频、摄像头、界面绘制、结果导出。而相当一部分“高分项目”里 GUI 卡顿的根本原因是把模型加载和推理直接塞进了QMainWindow的构造函数和update_frame槽函数里。推理阻塞主线程时窗口的所有消息循环都等模型推理结束才继续表现就是画面长时间无响应、拖拽窗口会白屏。GUI 里真正应该持有的只有一个 Detector 实例它在窗口构造前初始化完毕之后所有按钮响应只调用Detector.detect()拿到检测结果。输入源管理和界面绘制可以放在同一个线程里如果还要做视频流处理那就把读取和解码移到 QThread 里只把结果帧通过 signal 传回主线程重绘。不要直接在主线程里跑cv2.imshow()PyQt5 的窗口体系和 OpenCV 的高层 GUI 会互相抢事件循环轻则闪烁重则崩溃。4.2 把 detect.py 的前向过程收敛成 20 行 DetectorYoloV5 的detect.py已经包含结果可视化、保存图片、输出 JSON 等多达几十个分支GUI 里复用它往往引入大量无关依赖。我会把它的核心前向逻辑抽成一个Detector类放在gui/detector.py中import cv2 import numpy as np import torch from pathlib import Path from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords from utils.augmentations import letterbox class Detector: def __init__(self, weights, device0, conf0.25, iou0.45, imgsz640): self.device torch.device(cuda if device ! cpu and torch.cuda.is_available() else cpu) self.model attempt_load(weights, map_locationself.device) self.stride int(self.model.stride.max()) self.conf conf self.iou iou self.imgsz imgsz self.names self.model.names def detect(self, bgr): h, w bgr.shape[:2] img letterbox(bgr, self.imgsz, strideself.stride)[0] img img[:, :, ::-1].transpose(2, 0, 1) # BGR - RGB, HWC - CHW img np.ascontiguousarray(img) img_tensor torch.from_numpy(img).to(self.device).float() / 255.0 img_tensor img_tensor.unsqueeze(0) with torch.no_grad(): pred self.model(img_tensor)[0] det non_max_suppression(pred, self.conf, self.iou)[0] if det is not None and len(det): det[:, :4] scale_coords(img_tensor.shape[2:], det[:, :4], (h, w)).round() return det.cpu().numpy().tolist() return []这个类把输入图片先做 letterbox 等比缩放转成 CHW 的归一化张量前向结束后用non_max_suppression完成同类别内去重再把预留在缩放图上的坐标映射回原始图片尺寸。返回的列表每个元素是[x1, y1, x2, y2, conf, class_id]GUI 拿到它之后只做一件事绘制矩形和标签。理解这段代码时需要注意几个参数conf是置信度阈值GUI 里建议默认 0.25低于 0.2 时会出现大量误报框高于 0.5 时遮挡场景漏检明显iou是 NMS 的 IoU 阈值人群密集的监控画面建议调到 0.35可以保留更多相互重叠的同类别框imgsz建议和训练时保持一致训练用 640推理就别改成 320否则小目标损失严重。4.3 PyQt5 主窗口的最小可跑骨架下面的代码是我通常会在main_window.py里搭出的最小骨架实现摄像头读取、检测框绘制和画面刷新import cv2 from PyQt5.QtCore import QTimer from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QMainWindow, QLabel from detector import Detector class MainWindow(QMainWindow): def __init__(self): super().__init__() self.detector Detector(weights/best.pt, device0, conf0.25, iou0.45) self.video_label QLabel(self) self.setCentralWidget(self.video_label) self.cap cv2.VideoCapture(0) self.timer QTimer(self) self.timer.timeout.connect(self.update_frame) self.timer.start(30) # 约 33 帧/秒 def update_frame(self): ret, frame self.cap.read() if not ret: return dets self.detector.detect(frame) for x1, y1, x2, y2, conf, cls_id in dets: label f{self.detector.names[int(cls_id)]} {conf:.2f} cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, label, (int(x1), int(y1) - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, _ rgb.shape qimg QImage(rgb.data, w, h, 3 * w, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(qimg))QTimer每隔 30 毫秒触发一次update_frame每次读取摄像头的一帧调用 Detector 获得全部预测框然后用 OpenCV 在原帧上画框和文字最后转换成QImage显示在QLabel上。这个流程是 GUI 源码里最常见的实现方式注意QImage构造时bytesPerLine必须写3 * w否则图像会倾斜。如果你的 GUI 项目里还要求“识别结果导出 Excel”在draw循环后把dets追加进列表再在关闭窗口的析构函数里统一写文件即可。如果模型推理导致卡顿仍然明显就把self.detector.detect(frame)放到 QThread 的 worker 线程里用 signal 把结果传回。具体做法是写一个DetectWorker(QThread)在run里循环读取摄像头并通过pyqtSignal发帧这里不再展开。5. 验证 mAP、排查四类误检、导出 TorchScript 交付5.1 用 val.py 生成客观指标而不是靠肉眼数框训练结束后我会跑一次完整的验证命令用测试集生成 mAP 和混淆矩阵而不是随便抽几张图用 GUI 截屏判断效果python val.py \ --data datasets/mask/mask.yaml \ --weights runs/train/mask/weights/best.pt \ --img 640 \ --conf-thres 0.001 \ --iou-thres 0.5 \ --project runs/val \ --name mask从runs/val/mask/生成的results.csv或终端输出里重点看三个指标mAP0.5应该达到 0.85 以上才算及格mAP0.5:0.95达到 0.5 以上说明模型对不同尺度目标都有一定泛化能力Precision和Recall这两个值要一起看只高一个往往意味着阈值没调对。混淆矩阵则能直接暴露出类别间互相误判的问题尤其是without_mask和mask_weared_incorrect的混淆。5.2 四个高频误检现象和对应的处理方向第一种现象是“口罩边缘和肤色接近时漏检”这是光照问题数据增强里把 HSV 的饱和度扰动范围调大即可或者直接采集更多正光场景的口罩照片。第二种是“戴在下巴上被识别成正常佩戴口罩”这是类别定义不清导致的训练噪声唯一的正解是统一标注标准把这类样本归入第三类。第三种是夜间或暗光下大面积漏检优先把输入分辨率从 640 提到 768并检查训练数据里是否缺少暗光样本。第四种是“人群密集互相遮挡时的框互相吞掉”把 NMS 的 IoU 阈值从 0.45 降到 0.35同时把置信度阈值从 0.25 下调到 0.2召回会明显改善代价是多出少量重复框。5.3 打包前最后一步导出 TorchScript 再放进 GUI交付 GUI 时不要直接让用户安装“GPU 版 PyTorch 全套 YoloV5 源码”。如果对方机器没有训练环境best.pt里的权重格式依赖原版源码的models包换路径或者版本不一致就报错。我通常会在项目交付前把模型导出成 TorchScriptpython export.py \ --weights runs/train/mask/weights/best.pt \ --include torchscript \ --img 640导出后得到best.torchscriptGUI 里不再需要attempt_load和non_max_suppression这些依赖模块直接torch.jit.load即可推理。如果最终产物不需要 PyTorch 环境下一步可以尝试 TensorRT 或 OpenVINO 导出把模型文件进一步固化成二进制这是把该 YoloV5 口罩识别项目往边缘设备上迁移的常见做法。我最后会把best.torchscript、mask.yaml和一个记录类别名称的label_map.json放进去GUI 启动时从这三个文件读取配置而不是硬编码权重路径。然后写好下面的run_gui.bat放进 GUI 目录让用户双击就能启动echo off python main.py pause把main.py里的设备参数改成devicecpu检测参数保持conf0.25, iou0.45确认best.torchscript和label_map.json在相对路径下能找到这样交付出去的 zip 解压后不会再出现“为什么双击报错找不到模型”这类问题。本文还有配套的精品资源点击获取
返回列表