简介面向计算机相关专业学生与实战开发者的YOLOv5口罩佩戴检测毕业设计项目经导师指导获得九十八分认可全部代码严格调试并可独立运行适合作为毕业设计、课程设计或期末大作业。压缩包共有一百四十九个文件大小约一百四十兆涵盖四十个Python源文件、四十四份YAML配置文件、三个已训练完成的模型权重、多张图片样本以及Docker环境、教程脚本和说明文档形成了从数据标注、模型训练到部署验证的完整链路。当前已有二百三十八人浏览学习可助读者快速搭建口罩佩戴检测系统。项目将标注好的数据、训练权重和系统源码完整整合无需额外采集数据即可复现检测效果也便于替换数据集、调整网络结构或拓展二次开发非常契合目标检测入门与进阶需求。1. 口罩佩戴检测毕设YOLOV5 数据集、模型与源码这条线怎么落地基于 YOLOV5 的口罩佩戴检测是计算机视觉方向毕业设计里选题最密集、也最容易被评委看一眼就判定工作量够不够的题目。理由很现实网上模板多图像素材和标注数据来源杂能跑通不一定能讲清楚能讲清楚不一定能过答辩。这套资源把标注好的数据、训练完成的权重、可运行的源码和推理入口打包在一起压缩包内部还带 Dockerfile、setup.cfg 和教程用的 notebook等于把「数据清洗 → 训练 → 出权重 → 部署演示」整条链路都铺好了剩下的事就是把环境跑通、把参数看懂、把结果讲明白。适合正在做毕设的计算机专业学生、需要课程设计或期末大作业的在校生以及想拿一个完整 CV 项目练手的从业者。这篇笔记按数据准备、训练调参、避坑、部署、验收五段拆开每个环节都给可复现的操作。2. 数据准备把 zip 里的标注数据吃透再补上自己的图2.1 先核对目录结构和标注格式别急着训练拿到压缩包第一件事不是解压就跑 train而是把目录结构整体翻一遍。这套资源里同时有 setup.cfg、Dockerfile、tutorial.ipynb 和一批样例图片up.jpeg、right.jpeg 这类命名就是摄像头抓拍的角度样本说明作者希望你二选一要么按 Docker 镜像把环境固定要么在本地按 notebook 一步步走。我一般先做本地核对因为答辩时要讲清楚每个文件是干什么的这一点说得越细评委越容易对工作量认可。典型目录结构长这样mask_yolov5/ ├── data.yaml ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 与图片同名的 txt 标注 │ └── val/ ├── runs/ │ └── train/exp0/ │ └── weights/ │ ├── best.pt │ └── last.pt ├── detect.py ├── train.py ├── requirements.txt ├── setup.cfg └── Dockerfile先做一次「图对标注」的基础体检确认数量能对得上find images/train -name *.jpg | wc -l find labels/train -name *.txt | wc -l for f in images/train/*.jpg; do base$(basename $f .jpg) if [ ! -f labels/train/${base}.txt ]; then echo 缺失标注: $base fi done第一、二行统计图片和标注各自的数目第三行的循环找出「有图没标注」的文件。图片数量和 txt 数量差得越多后面训练出来的 mAP 越不可信。口罩检测属于单类目标检测的简单场景基本都按两个类别处理with_mask口罩戴好和 without_mask没戴。如果标注文件里出现了第三个类别名说明这份数据从多个来源合并过需要先统一类别表再继续否则后面 data.yaml 怎么写都会错。提示标注格式检查最好在划分数据集之前完成。一旦按 8:1:1 切完再发现漏标注补救成本会翻倍。2.2 YOLO 标注格式从 txt 到可视化抽查YOLO 的 txt 标注是归一化坐标固定格式为 class_id x_center y_center width height四个框值全部是 0 到 1 的小数。随便打开一个标注看内容cat labels/train/00001.txt比如输出1 0.432 0.551 0.218 0.364含义是类别为 1without_mask目标中心点在图片横向 43.2%、纵向 55.1% 的位置框宽占 21.8%、高占 36.4%。这个格式最大的坑是很多人从网上下 VOC 格式的 XML 标注转 YOLO 时除错了宽高或忘了归一化训练时 loss 降不下去检测框全乱飘还以为是模型问题。我一般会写个小脚本把归一化坐标还原成像素坐标再对着原图肉眼抽查# check_label.py def inspect(label_file, img_w, img_h): with open(label_file) as f: for line in f.readlines(): parts line.strip().split() cls, cx, cy, bw, bh ( int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) ) x1 int((cx - bw / 2) * img_w) y1 int((cy - bh / 2) * img_h) x2 int((cx bw / 2) * img_w) y2 int((cy bh / 2) * img_h) print(cls, (x1, y1, x2, y2))这段脚本做的事就是还原像素坐标方便你画框核对。黑白口罩、深色口罩在暗光下的边界很模糊如果框明显比人脸大一圈多半是标注时手抖这类样本混在数据集里多了模型学出来的框就爱偏大。补标注时记住一个原则宁可框紧一点不要框松口罩目标边界简单框松比框紧带来更多误检。2.3 补数据与训练集划分8:1:1 之外还要看类别比例只用压缩包自带的样例图训练模型泛化能力有限因为样例图的场景和角度都比较单一。毕设想加工作量最实在的做法是拿手机或摄像头补 200 到 300 张不同场景的图室内、户外、侧脸、低头、口罩挂在下巴上。补完的图用标注工具按 YOLO 格式导出 txt再按 8:1:1 划分 train/val/test# split_data.py import os, random, shutil images os.listdir(images/train_all) random.seed(42) random.shuffle(images) train_n int(len(images) * 0.8) val_n int(len(images) * 0.1) for sp, imgs in [(train, images[:train_n]), (val, images[train_n:train_n val_n]), (test, images[train_n val_n:])]: os.makedirs(fimages/{sp}, exist_okTrue) os.makedirs(flabels/{sp}, exist_okTrue) for img in imgs: base os.path.splitext(img)[0] shutil.copy(fimages/train_all/{img}, fimages/{sp}/{img}) if os.path.exists(flabels/train_all/{base}.txt): shutil.copy(flabels/train_all/{base}.txt, flabels/{sp}/{base}.txt)这个脚本把 8 成数据给训练、1 成给验证、1 成给测试random.seed(42) 固定随机种子保证每次跑的划分结果一致答辩复盘时能完整复现。注意它只复制有标注的 txt有图无标的文件会被自动丢出训练集这比训练时报错友好得多。划分完再统计类别比例echo with_mask 数量:; grep -h ^0 labels/train/*.txt | wc -l echo without_mask 数量:; grep -h ^1 labels/train/*.txt | wc -l两个类别数量差距超过 3 倍时优先补少数类。口罩场景里常见的失衡是 with_mask 远多于 without_mask结果模型对「没戴口罩」召回率低而系统里最需要报警的场景恰好就是漏检。另外建议在数据集里放一批难例距离远、人脸占比小、口罩挂在下巴上的图。口罩检测的难点不在「戴 vs 不戴」的清晰区分而在这些中间状态训练数据里没有难例现场演示就容易露怯。3. 训练与调参让 YOLOV5 在本地跑完一百个 epoch3.1 环境搭建优先本地装包Docker 作为后悔药资源包同时给了 Dockerfile 和 requirements.txt这是有道理的YOLOV5 对 PyTorch、CUDA 的版本组合很敏感本地裸装翻车率不低。如果你机器上有显卡且驱动正常我建议先试本地环境因为答辩演示跑在宿主机上更灵活如果驱动版本搞不定再退回 Docker 路线。docker build -t mask-yolov5 . docker run --gpus all -v $(pwd):/workspace -it mask-yolov5 /bin/bashDocker 路线的好处是一次构建、到处复现缺点是每次改代码都要挂载目录新手容易把容器里的路径和宿主机路径搞混日志和权重常常不知道落在哪。本地路线则直接创建虚拟环境装依赖python -m venv venv source venv/bin/activate pip install -U pip pip install -r requirements.txt python -c import torch; print(torch.__version__, torch.cuda.is_available())最后一行是分水岭。我经手过不少机器碰到最多的「训练失败」根本不是代码问题而是装到了 CPU 版 PyTorch模型能跑但速度慢几十倍一个 100 epoch 的训练能拖一整天。先验这一行再谈训练环境问题优先于一切调参。3.2 data.yaml 与超参数顺序、尺寸、batchYOLOV5 的训练入口是 train.py数据描述文件是 data.yaml。项目里的 data.yaml 大致如下train: images/train val: images/val test: images/test nc: 2 names: [with_mask, without_mask]这里 nc 为 2names 的顺序必须和第 2 章的标注文件 class_id 完全对齐。如果标注里 0 是 without_mask、1 是 with_mask而 data.yaml 里 names 反了训练不会报任何错误推理结果却类别互换这个错我在一次项目里踩过排查花了一整天。训练命令python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --workers 4 \ --device 0各参数在这个场景下的选择参数作用口罩场景建议--img输入分辨率640 够用追求速度可降到 416--batch每批样本数8G 显存用 1612G 以上用 32--epochs迭代轮数100 轮起步看曲线再决定--weights预训练权重用 yolov5s.pt 迁移学习收敛快选 yolov5s 而不是更大模型的原因口罩检测类别少、目标大s 级模型的容量已经足够m 或 l 级只会让训练变慢精度提升非常有限。迁移学习比从零训练收敛快得多因为公开目标检测预训练模型里已经包含大量人脸、人体特征口罩检测只是在这个基础上做微调。如果你是第一次跑这个项目建议先 --epochs 30 把全链路跑通确认数据路径、标注格式都没问题再放完整训练否则跑到第 80 轮才发现 data.yaml 里路径写错重跑的时间成本很高。3.3 训练监控、断点续训与曲线判断train.py 每轮结束会往 runs/train/exp 里写 results.csv一行对应一轮的 box_loss、cls_loss、precision、mAP 等指标。盯模型状态我只看两个数验证集 mAP0.5口罩检测里能到 0.95 以上说明数据和标注都比较干净cls_loss 在第 30 轮之后是否还在明显下降如果从第 20 轮到第 70 轮几乎是一条横线说明已经收敛继续加 epoch 只是烧电。训练中断是常事机器休眠、显存被占、SSH 断线都会打断。恢复手段是断点续训python train.py --data data.yaml --weights runs/train/exp/weights/last.pt --resume这里有一个容易翻车的细节resume 会在之前的训练配置基础上继续如果你中途改过 batch 或 img就会报 shape 不匹配。此时不要硬续把 last.pt 当预训练权重重新起跑更省事。另外我习惯训练开始后顺手看一眼 runs/train/exp 目录里的 opt.yaml确认落盘的超参数和你敲的命令一致这个文件是答辩时解释「为什么这么调」的直接证据。注意resume 时不要改动 batch 和 img要续就原参数续要改就重跑。4. 避坑清单口罩数据集训练里最常见的五个翻车点4.1 标签与配置不对齐mAP 看起来像故障现象训练 loss 正常下降但验证集 mAP 在 0.5 上下徘徊推理结果张冠李戴把戴了口罩的人标成 without_mask。原因labels 里的 class_id 和 data.yaml 的 names 顺序不一致。多人协作标注后合并数据集时标注工具里的类别顺序和最终 data.yaml 各不相同没人检查就直接开训。解决先统计 labels 里每个类别数量再与 data.yaml 比对。然后抽 10 张图做可视化确认框的位置和类别语义一致。可视化这步不要省框对了但类别反了是一个隐蔽的对齐问题肉眼能抓出来光看指标抓不出来。4.2 显存不足与训练进程被杀现象跑到第 40 个 epoch 突然报 CUDA out of memory或者远程训练时终端断开回来发现进程没了runs 里只有断掉的残留文件。原因训练后期显存碎片化叠加缓存累积batch 16 在低显存卡上不够用终端断开时训练进程收到挂断信号直接退出没有守护机制。解决显存不足时把 batch 减半或改成 --batch 8也可以先停掉占显存的其他进程再训。进程被杀的问题用 tmux 包一层最省心tmux new -s train python train.py ... # 在 tmux 会话里执行 # 断线重连后 tmux attach -t train # 进程还在跑这是我踩过最多次的坑不是代码问题纯粹是运维问题但直接导致一个完整训练白跑。从那以后凡是超过 30 epoch 的训练任务我都强制套 tmux。4.3 手机照片的 EXIF 方向导致标注偏移现象自采图片训练时 loss 能降但检测框整体偏移而且只偏移竖屏拍摄的那批图。原因手机竖拍的照片会写入 EXIF 旋转信息图片查看器自动转正但 OpenCV 的 imread 不处理 EXIF模型看到的像素和标注时看到的像素差了 90 度。标注工具一般会自动转正于是标注跟着转正后的图训练吃的却是原始像素。解决训练前批量转正并清掉 EXIF 信息from PIL import Image, ImageOps import os os.makedirs(images_norm, exist_okTrue) for fn in os.listdir(images): img Image.open(fimages/{fn}) img ImageOps.exif_transpose(img).convert(RGB) img.save(fimages_norm/{fn}, exifb)这步要在划分数据集之前做否则一边转正一边划分文件对应关系容易乱。我现在自采数据后的固定流程是先转正、再标注、再划分一次到位。4.4 类别极端不平衡口罩多、没口罩的少现象训练 100 轮后 recall 高但 precision 低误报多总把背景里的深色物体当成口罩。原因with_mask 样本占比 90% 以上without_mask 太少模型的决策边界被多数类带偏对没戴口罩的场景过度敏感。解决优先补拍不戴口罩的场景能补到 100 张以上比任何调参都有效。补不到就降低少数类 loss 权重或把已有的 without_mask 目标复制粘贴合成到其他背景图里做数据层面的增强。核心原则是让两个类别在 batch 里出现的频率接近模型才学得稳。4.5 验证集与训练集数据泄漏评估虚高现象验证集 mAP 0.98感觉稳了但换一批新图实测检测效果明显崩盘漏检一堆。原因划分数据时按视频连续帧切片同一场景相邻帧几乎一样训练集和验证集高度重叠评估结果虚高。这类泄漏在答辩现场最容易露馅因为评审用的新图和你训练分布完全不同。解决按场景而不是按帧划分。拍视频时按时间段分段同一段内的帧全部放进同一个集合如果图片来自不同摄像头按摄像头编号分组后再随机划分。这比任何数据增强都能保证评估可信也是我每次换数据都强制重走一遍的环节。5. 推理与部署把训练好的权重接进检测系统5.1 单图与图片目录推理训练产物是 runs/train/exp/weights/best.pt。验证模型的最快方式是跑 detect.py 对测试集目录推理python detect.py --weights runs/train/exp/weights/best.pt \ --source data/images/test --img 640 --conf-thres 0.4--conf-thres 是置信度阈值默认 0.25 对口罩场景偏低。口罩目标不小、框稳定用低阈值只会带来一堆背景误报我一般定在 0.4 到 0.5 之间。推理输出在 runs/detect/exp 里每张图都画好了框和置信度答辩 PPT 里直接放这批图就很有说服力比贴训练曲线直观得多。5.2 摄像头实时检测现场演示最常见的需求是 USB 摄像头实时检测python detect.py --weights best.pt --source 0 --img 640 --conf-thres 0.4--source 0 表示第 0 路摄像头。如果插了 USB 摄像头但画面是黑的先试 1 或 2大概率是设备号不对。帧率方面yolov5s 在普通笔记本 GPU 上单帧推理大约 25 到 40 毫秒配合摄像头 30 帧输入足够流畅如果卡顿明显先确认是不是误用了 CPU 推理再看 --img 是不是设成了 640 以上。5.3 输出解析与业务后处理报警逻辑怎么写detect.py 只负责画框毕设系统通常要的是「有人没戴口罩就提醒」。这时不调 detect.py而是把模型当库一样 importimport torch model torch.hub.load(yolov5, custom, pathbest.pt, sourcelocal) model.conf 0.4 results model(frame) det results.pandas().xyxy[0] # 一行一个目标x1,y1,x2,y2,confidence,class no_mask det[det[class] 1] # 1 without_mask需与 data.yaml 对齐 if not no_mask.empty: alert(f检测到 {len(no_mask)} 人未戴口罩) # 后续可接: 截图留存、声音报警、写记录到本地文件这段逻辑的关键点有两个model.conf 设的是推理置信度阈值与命令行的 --conf-thres 对应class 数字必须和 data.yaml 的 names 一致这个错我在文中反复强调因为出错率实在太高。业务侧可以把违规截图存到单独目录答辩时统计每天触发次数这就从「目标检测」延伸到了「检测系统」工作量维度立刻不一样。演示时建议准备一张没戴口罩的图和一个实时摄像头场景各跑一遍两条路都能走通评委基本没理由质疑。6. 模型验收用 mAP 和混淆矩阵决定要不要重训训练结束后不要只看一张示例图就收工。标准流程是跑 val.py 拿到指标表python val.py --data data.yaml --weights runs/train/exp/weights/best.pt \ --task test --conf-thres 0.001 --iou-thres 0.6 --save-json--task test 指定在没参与训练的测试集上评估--conf-thres 0.001 是为了画出完整的 PR 曲线评估时用低阈值实际推理时再卡高阈值。看结果里 P、R、mAP50 三个数字口罩检测的合理水位是 Precision 和 Recall 都过 0.93mAP50 过 0.95。然后打开 runs 里的混淆矩阵图看一眼主对角线两个格子越亮越好。如果 without_mask 那格的召回明显偏低说明漏检多优先补数据如果是 with_mask 那格被误判说明多数类把少数类压过去了。看混淆矩阵能直接告诉你该往哪个方向补数据比瞎调参数强得多。训练好之后如果想界面和按钮更友好可以顺手把模型导出成 ONNX 格式放后台服务里跑但这属于加分项毕设答辩阶段把 best.pt 跑通检测链路已经足够。我的个人习惯是每一次换数据、改标注、调超参都强制走一遍「转正 → 划分 → 训练 → val.py → 看混淆矩阵」的完整流程哪怕只是改了 10 张图。因为小型数据集上任何一次标注修正都可能让指标从 0.92 跳到 0.97而不走验收入口你根本不知道这 10 张图的改动带来的是提升还是灾难。这套流程走顺之后口罩检测这件事就从「全凭玄学」变成了「每一步都有据可查」。希望帮到你。本文还有配套的精品资源点击获取