☰
YOLOv5口罩检测实战:从数据标注到迁移学习训练与部署
2026/9/28 17:10:11 网站建设 项目流程

简介:一份基于YOLOv5的口罩佩戴检测完整项目,主要面向计算机相关专业正在做毕业设计的学生,以及需要项目实战练习的学习者,也可直接用于课程设计或期末大作业。压缩包共149个文件,大小约139.76MB,内含40个Python源码、44个YAML标注/配置文件、训练好的.pt模型权重、Shell训练脚本、Dockerfile、Jupyter教程、说明文档及示例图片,覆盖数据准备、模型训练、推理测试全流程。目前已有238人学习下载。项目自带训练好的模型与标注好的数据集,经过严格调试可直接运行;提供tutorial.ipynb便于逐步操作复现,Dockerfile可快速搭建统一环境,适合作为毕设演示、二次开发或算法对比实验的基座。代码结构清晰,按功能划分模块,标注数据可进一步扩充类别,便于读者深入理解YOLOv5的迁移学习与目标检测实践。

1. 先拆开这个zip:YOLOv5口罩检测项目包的四份家底

打开这个压缩包,你会看到四类东西:标注好的数据、整理好的数据集、可运行的YOLOv5源码、训练好的模型权重。之所以用压缩包一次给全,是因为口罩佩戴检测这个场景的落地痛点不在算法本身,而在数据准备和训练环境的衔接——模型大家都跑得通,合不合格看的是标注质量和复训参数。这篇笔记就顺着这个包的文件结构,讲清楚从环境配置、标签解析、迁移学习到部署验证整条链路怎么做。适合三类人:用YOLOv5做目标检测毕设的学生、要做安防或门禁原型的工程师,以及想把手头标注数据快速复训成自定义模型的开发者。下文所有路径都以包内目录为准,运行命令默认在项目根目录执行。

2. 把YOLOv5环境配起来:从零到跑通 detect.py 的最小命令

2.1 环境隔离与依赖安装:conda 和 requirements.txt 的搭配

常见做法是先建一个独立的 conda 环境,再把 requirements.txt 装进去。mask检测这个项目用到的依赖和通用 YOLOv5 一致,核心是 torch、opencv-python、matplotlib、pyyaml 这几个。先建环境:

conda create -n maskdet python=3.8 conda activate maskdet cd yolov5-mask # 进入项目代码目录 pip install -r requirements.txt

requirements.txt 里锁定了 torch 的版本范围,直接装一般没问题。如果在本机装过其他深度学习项目,建议先确认 torch 版本,避免和本地 CUDA 不匹配——很多翻车都发生在这一步,检测的时候模型能加载,推理却全程跑 CPU,速度慢到没法用。没有 GPU 的机器也能跑,把后面命令里的 device 参数改成 cpu 就行,只是单张图的推理时间会长不少。

依赖装完先别急着跑训练,用一条最简单的推理命令验证环境是否真的通了,能输出结果图说明环境没问题,后面数据、训练的问题才能单独排查。

参数说明:conda create 指定 python=3.8 是因为 YOLOv5 官方对 3.7~3.9 支持最好;pip install 不指定镜像源时,第一次下载 torch 可能比较慢,可以用 -i 指定国内 PyPI 镜像提速,注意这只是加速包下载,和模型权重文件是两回事,Pytorch 权重还是要从包内或官方 release 里拿。

2.2 第一次推理:用训练好的模型权重跑一张测试图

环境装好后,直接用包里给好的权重做推理,这是最快确认模型是否有用的方式:

python detect.py --weights runs/train/exp/weights/best.pt --source data/images/mask_test.jpg --conf-thres 0.5

默认跑完图片会输出到 runs/detect/exp 目录,打开就能看到画了检测框和置信度的结果图。这里有两个超参数值得先明白:conf-thres 是置信度阈值,低于 0.5 的框会被丢弃;iou-thres 是 NMS(非极大值抑制)的 IoU 阈值,控制两个重叠框是否合并。做口罩检测时 conf-thres 不建议设到 0.7 以上,因为小脸、侧脸、口罩遮半边的样本置信度天然不高,阈值太高会漏检。

detect.py 还会自动读取 data.yaml 里的类别名,在框的左上角标出 mask 或 nomask。如果跑出来的图标签乱掉,先看 data.yaml 里 names 的顺序是否和标注文件里的 class id 一一对应,这是最容易被忽略的问题。后面数据章节会专门讲这个对应关系。

参数说明:--weights 指定训练好的权重文件,优先用 best.pt 而不是 last.pt,best 是按验证集指标保存的最优模型;--source 可以接图片路径、文件夹路径、摄像头编号或视频文件路径,做实时演示时可以传 0 调用本地摄像头。

2.3 项目目录结构:哪块是代码、哪块是数据

包的根目录一般长这样:yolov5 官方代码为主体,data 下放图片和标签,weights 或 runs 下放训练产物。代码和数据分开的好处是,训练时改配置不影响源码,换数据集时也只用动 data 目录和 yaml 配置。

常见目录结构如下(不依赖具体版本号):

  • data/images:按 train、val 分好类的图片
  • data/labels:对应图片同文件名的 txt 标注
  • data/mask.yaml:数据集配置,声明类别和路径
  • runs/train:训练输出的权重和日志
  • runs/detect:推理输出结果图
  • models:模型结构定义,训练时选用的 yaml 在这里

我个人习惯在拿到包后先跑一遍find或tree命令,把目录结构打印出来,对照文档确认每个路径的存在性。这样后面训模型时,data yaml 里的路径写错了能一眼看出来——路径问题占训练报错的很大一部分。

3. 数据集与标注:mask 和 nomask 两类样本的后台结构

3.1 数据集目录与训练配置:yaml 怎么告诉 YOLOv5 数据在哪

YOLOv5 的数据集不看后缀,只看 yaml 配置。一个口罩检测的 mask.yaml 大致是下面这样:

train: data/images/train val: data/images/val nc: 2 names: ['mask', 'nomask']

先解释三个关键点的含义。train 和 val 指向的是存放图片的目录路径,YOLOv5 会在对应目录的同级 labels 目录下自动找同名 txt,所以 labels 目录不需要在 yaml 里显式声明;nc 是类别数量,等于 2,对应 mask 和 nomask;names 里第一个名字对应标注 class id 0,第二个对应 1,顺序不能随意调,否则推理时标签名会错位。

如果要自己组织数据集,常见规范是 images/train、images/val、labels/train、labels/val 这四个目录并排,图片和标签同名不同后缀。目录名千万别写成 image 或 lables,YOLOv5 对这两个固定目录名有硬编码逻辑,写错了找不到标签文件,训练出来的模型会变成什么也学不到的"瞎猜模型"。

还有一个细节需要特别提醒:yaml 里的路径是相对于项目根目录的,不要在路径前加斜杠写成绝对路径,也不要加../跳到项目外。换机器跑的时候,只需要改这个 yaml 的路径和 weights 路径,其他文件不用动。

3.2 标注格式与数据产生:归一化的 class x y w h 到底怎么写

YOLO 格式的标注文件是 txt,每行表示一个目标,格式如下:

0 0.5 0.4 0.3 0.25 1 0.7 0.6 0.2 0.3

每个数字的含义是:类别编号、目标的中心点 x 坐标、中心点 y 坐标、目标宽度 w、目标高度 h,其中 x y w h 全部相对原图尺寸做了归一化。比如第一行的 0.5 0.4 意味着 mask 目标的中心在图片宽度的 50%、高度的 40% 处,宽度占图片宽度的 30%、高度占 25%。注意是中心点坐标,不是左上角坐标,用 labelimg 标注时它显示的是左上角右下角,导出成 YOLO 时会自动换算,但如果自己写脚本处理数据,这个换算关系很容易搞错。

如果你手头拿到的是 VOC 格式的 XML 标注,想转成 YOLO 的 txt,常见做法是写一个 Python 转换脚本,核心逻辑如下:

import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt, class_list): root = ET.parse(xml_path).getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.findall('object'): cls = obj.find('name').text if cls not in class_list: continue class_id = class_list.index(cls) 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) x_center = (x1 + x2) / 2.0 / img_w y_center = (y1 + y2) / 2.0 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_txt, 'w') as f: f.write('\n'.join(lines)) class_list = ['mask', 'nomask'] voc_to_yolo('data/annotations/00001.xml', 'data/labels/00001.txt', class_list)

代码逻辑说明:先读 XML 里的图片宽高,再把每个目标的 xmin/ymin/xmax/ymax 从左上角右下角表示换算成中心点加宽高的表示。这里最容易出的问题有两个,一个是忘了除以图片宽高做归一化,直接存像素值,导致训练时目标框全部超出 0~1 区间,loss 永远不收敛;另一个是 class_list 的顺序和 mask.yaml 的 names 顺序不一致,比如 XML 里是 "with_mask",而 names 里写的是 "mask",转换脚本会静默跳过这些目标,最终标签文件比图片数量少很多,训练集被悄悄削弱。

实际标注时大家一般用 labelimg 可视化标注,它支持框选后按数字键标类别,导出的就是 YOLO 格式 txt。注意 labelimg 没有撤销热键,标错只能删掉重框,这个稍显磨人,但够用。想要更省事的话,可以用 labelstudio 这类工具做在线标注,它的好处是多人在线协作和自动标注预标记,但配置成本比本地工具高。数据标注这个环节在整个项目里最耗时,包里的标注数据可以直接复用,省掉了最枯燥的一步。

3.3 训练集划分与样本均衡:多少图片够用、两类样本怎么配

模型好不好,数据占比影响很大。口罩检测是个二分类目标检测任务,我的经验是:每个类别至少要有 300 到 500 个实例目标,图片总数在 800 到 1500 张之间比较稳。如果包里数据偏少,宁可先跑一个过拟合实验确认代码链路正确,再着手扩数据,不要直接堆 epoch 数期待奇迹。

训练集和验证集划分比例一般是 8:2 或 9:1。划分时注意两个容易翻车的点:一是同一个场景的连续帧图片要一起划到同一侧,否则训练集和验证集出现重叠,验证指标虚高;二是 mask 和 nomask 两类目标的数量差距不要太大,如果 nomask 样本明显偏多,模型倾向于把所有脸都判成没戴口罩,表现为精度可以但召回很低。常见处理办法是给样本少的一类做数据增强,或是在训练时调整类别权重。

划分脚本可以用一行 shuffle 命令加文件移动实现,注意保持图片和标签文件同名同步移动,只移图片不移标签是新手最常见的低级错误。

4. 迁移学习与参数选择:用自带权重复训出自己的模型

4.1 训练命令和权重初始化:为什么从 best.pt 开始而不是从零训

拿到一个带训练好模型权的包,直接训练是典型操作。命令如下:

python train.py --data data/mask.yaml --weights yolov5s.pt --epochs 100 --batch-size 16 --img 640

这条命令的含义是:用 yolov5s.pt 做预训练权重初始化,在 mask.yaml 配置的数据上迭代 100 轮,每批 16 张图,输入分辨率 640×640。如果机器显存不足,可以把 batch-size 降到 8 或 4,img 降到 416,效果会略有下降但模型能跑起来。

迁移学习是这个项目包最有价值的地方。YOLOv5 官方在 COCO 上训练的权重已经学到区分形状、边缘、纹理的通用能力,口罩检测和 COCO 的类别差异大,但从预训练开始微调,收敛速度仍然远快于随机初始化。数据量只有几百张时,随机初始化训练 100 轮很可能不收敛,而迁移学习 30 轮就能看到可用效果。选择 weights 时,CPU 机器建议用 yolov5s.pt,参数量小推理快;显存充足且想要更高精度,可以换成 yolov5m.pt,代价是训练时间几乎翻倍。

训练过程中注意看两个输出:一个是每个 epoch 结束打印的 mAP@0.5 和 Precision/Recall 指标,另一个是 runs/train/exp 里的曲线图。mAP@0.5 在 0.8 以上说明模型基本可用,0.9 以上是优秀的口罩检测水平。

4.2 三个必调参数:lr、batch-size 和 img 之间的关系

YOLOv5 超参数里,最影响口罩检测效果的是这三个:batch-size、img-size、初始学习率 lr0。batch-size 决定每个 step 看到的图片数量,显存允许范围内尽量大,它既影响梯度稳定性也影响一批数据里正负样本的比例;img-size 决定输入分辨率,口罩是小目标物体,分辨率太低框会偏大偏糊,分辨率太高又吃显存;lr0 默认 0.01,在迁移学习场景下可以做适当调整。

python train.py --data data/mask.yaml --weights yolov5s.pt --epochs 100 --batch-size 8 --img 640 --hyp data/hyps/hyp.scratch-low.yaml

hyp 参数文件的含义是:训练策略总配置,里面包含 lr0、lrf、momentum、weight_decay、数据增强系数等十几项参数。对口罩检测来说,迁移学习场景下 lr0 设为 0.005 比较稳,官方默认 0.01 在新的小数据集上容易出现训练初期 loss 跳变。改动时只改 lr0 一项,其他增强参数先不动。这批"超参数"跟模型结构里的参数不是一回事,调的是训练过程的行为,新手最容易把它们混为一谈,结果到处乱改,模型越训越差。

img-size 和 batch-size 的组合有一个硬关系:两者同时增大时显存开销接近线性增长。显存 6G 的卡跑 640 分辨率建议 batch-size 不超过 8;如果坚持用 16,要么降到 416 分辨率,要么开梯度累积(--accumulate 参数)来模拟大批次效果。梯度累积的本质是把多个小 batch 的梯度叠加后再更新权重,能缓解显存压力,但会拖慢训练速度,迭代次数少的数据集上不明显。

4.3 低显存运行模型:小卡也能练完 100 轮

很多用户的显卡是 4G、6G 的入门卡,跑 YOLOv5s 训练确实勉强。我的建议是组合拳:分辨率降到 416,batch-size 取 4,workers 取 0,关闭 mosaic 增强(用 --no-mosaic)。这套配置可以让低显存环境跑通 416 分辨率的训练,实测单轮速度慢但稳定不崩。

python train.py --data data/mask.yaml --weights yolov5s.pt --epochs 100 --batch-size 4 --img 416 --workers 0 --no-mosaic

参数说明:workers 是数据加载的并行进程数,Linux 下取 4 或 8 提速,Windows 下经常因多进程问题卡死,取 0 最保险;--no-mosaic 关闭马赛克增强,这是一个省显存且降低复杂度的措施,缺点是模型对遮挡和小目标鲁棒性变差,数据量充足时建议不要长期关闭;--device 0 指定用第一张 GPU,不写也是默认行为。

另外有个容易被忽略的点:训练时显存占用是动态的,前几个 epoch 因为图片没有被 cache,占用较小,跑一段时间后如果开了 cache 特性,显存占用会上涨。如果中途 OOM,不代表配置不行,先关掉 cache 相关参数再试。实际经验里,低显存机器把 cache 关掉、workers 调 0、mosaic 关闭后,5G 显存卡训练 640 分辨率 batch-size 4 是可以完成的,只是时间会长一些,胜在稳定。

5. 避坑与排查:口罩检测项目里最容易翻车的 5 个点

5.1 训练 loss 不降:先怀疑标签文件,别急着调模型

现象:训练跑了几十个 epoch,loss 基本不动,验证集 mAP 停在 0.1 以下。原因排查了一圈,最常见的是标签文件里类别 id 越界(比如类别总数是 2,标注里却出现了 id 2)或者标签文件为空。解决:脚本批量扫描所有 txt,统计每行的 class id 最大值,同时检查是否有 0 字节的空标签文件。给一个快速检查命令:

# 统计 labels 目录里所有 txt 的首列取值分布 awk '{print $1}' data/labels/train/*.txt | sort | uniq -c

如果出现大于等于 nc 的数字,回标注工具改正;如果空文件很多,重新导出标注。其实训练日志里 Epoch 那行如果 box loss 和 val loss 同时震荡不降,先跑这个命令,比改学习率靠玄学调试靠谱得多。

5.2 推理框错位:归一化坐标和像素坐标混用

现象:训练正常,验证指标不错,但 detect.py 推理出来的框位置偏了,或者框的大小完全不对。原因:标注文件里的 x y w h 是 0~1 的归一化值,YOLOv5 内部会按输入尺寸换算回像素;如果数据准备阶段误用了 VOC 的像素坐标直接存成 txt,那训练时模型学到的"目标位置"全是错的。解决:检查任意一个 txt 文件,看数值是否都在 0~1 之间,超出就回到标注转换脚本,把除以宽高那一步补上。口罩这类小目标对位置精度敏感,坐标错位即使训练指标好看,实际推理也会漏检或框到背景上。

5.3 精度上不去:验证集与训练集重叠

现象:训练集 loss 很低,验证集 mAP 卡在 0.6 上不去,怀疑是数据量不够。原因:数据划分时没有按来源分组,同一个视频连续帧被随机分到了 train 和 val,导致验证时看到的图片是训练时见过的,真实泛化能力被高估,一旦换新场景就翻车。解决:先按图片文件名前缀做分组,再对组做随机划分。比如 camera_A_0001.jpg 到 camera_A_0100.jpg 是一组,整组划入 train 或 val,不要在组内切散。

5.4 显存 OOM:batch-size 和分辨率是一对共犯

现象:训练跑到一半报 CUDA out of memory,程序退出。原因:GPU 显存被中间特征图占满,尤以 mosaic 增强和 cache 开启时明显。解决:先关 cache 和 mosaic,把 batch-size 降一半,若还 OOM 就把 img 从 640 降到 416。注意不要为了省显存把 img 降得太低,低于 320 时口罩目标通常只剩十几个像素,几乎不可检测,这会影响后续模型质量。

5.5 导出模型效果和 PyTorch 不一致:关注 opset 版本

现象:用 best.pt 推理效果不错,导出 ONNX 后部署到其他框架,同一张图结果差异很大甚至不出框。原因:导出时的 opset 版本和部署侧算子支持不一致,或者模型里含某些动态尺寸操作,部署时输入尺寸没有保持训练时的 640×640 导致 letterbox 处理不一致。解决:导出命令显式指定 opset:

python export.py --weights runs/train/exp/weights/best.pt --include onnx --opset 12

导出后先用 onnxruntime 加载同一个模型和同一张图,对比输出框坐标和置信度,差异在可接受范围再交给部署端。这一步是为数不多能够提前发现部署坑的后悔药,值得在项目初期就走一遍。

6. 再用 val.py 量化模型,决定要不要微调

做口罩检测做到能跑、能出框,只算完成了前半段。后半段是怎么知道这个模型到底行不行、够不够上线。val.py 是验证模型在测试集上真实水平的工具,和 detect.py 的定性看效果不是一个层次:

python val.py --data data/mask.yaml --weights runs/train/exp/weights/best.pt --img 640 --conf-thres 0.001

conf-thres 在验证时设得非常低,是为了完整计算 PR 曲线,让 mAP 指标覆盖低置信度区间,真实反映模型的召回能力。验证脚本输出一张 result.png,里面有 P、R、mAP@0.5、mAP@0.5:0.95 四组曲线。口罩检测场景下,我一般以 mAP@0.5 为准:0.85 以上可以用于演示和原型;0.90 以上才有底气做门禁或者闸机集成。如果验证分数不理想,回到数据上补样本,不要盲目调超参数靠玄学调参,补几十张最难的正脸侧面图往往比折腾学习率更有效。

验证通过后,部署方向也要想清楚。要做成桌面演示,用 PyTorch 权重直接跑即可;要做网络服务或嵌入设备,导出 ONNX 再配合 onnxruntime 或 TensorRT 推理,比直接调 Python 稳定得多。导出命令里的 opset 版本和静态尺寸这两项,建议固定,部署侧代码里写死输入为 640×640,避免动态尺寸带来额外复杂度。

最后分享一个我的习惯:模型训练完成后,不急着部署,先把 val.py 的输出文件保留在 runs 目录里,连同 best.pt 和 data.yaml 一起打包归档。三个月后回看项目时,你能记得的不只是"模型跑通了",还有当时的精度水平、验证参数和数据集范围。这套流程里,我最大的教训是"先量化、再优化"——用指标代替感觉,能少走很多弯路。希望这份笔记能帮你在口罩检测和后续自定义目标检测项目里,把时间花在数据质量和部署验证上,而不是反复踩别人踩过的坑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询