前言
在上一篇文章中,我们详细介绍了目标检测的基本概念、YOLO的核心思想以及YOLOv5的网络结构。本文正式进入完整实战阶段------使用一个已经标注好的口罩数据集训练YOLOv5s模型,实现mask与no-mask两类目标的检测,并最终在图片和摄像头实时画面上验证模型效果。
真实的目标检测项目往往面临一个核心问题:数据集的目录结构、标签格式、配置文件三者之间必须严格对应,任何一处不匹配都会导致训练失败。本文将从数据集检查开始,逐步完成环境配置、模型训练、结果评估和摄像头部署的全流程。所有命令均按照实际工程支持的参数编写,确保代码可直接运行。
本文使用的是较早期的YOLOv5 2.0工程版代码,它与当前通过ultralytics包调用的新版接口不同,但目标检测的核心数据格式和训练逻辑完全一致,学习价值不受影响。
整篇代码严格遵循工程化规范,超参数统一配置,训练流程清晰可复现,可直接迁移至其他目标检测数据集的训练任务中。文章适合具备Python基础、希望系统掌握YOLOv5训练流程的开发者。
项目效果展示
运行camera_mask.py后,摄像头画面中每张人脸会被矩形框标注,戴口罩的人脸显示绿色边框和mask标签,未戴口罩的人脸显示红色边框和no-mask标签,画面流畅实时。按q键即可退出程序。
一、完整工程代码
项目目录结构

训练命令
在yolov5_2_0目录下执行:
cmd
python train.py --data ../MaskDataSet/data.yaml --cfg models/yolov5s.yaml --weights weights/yolov5s.pt --hyp hyp.yaml --epochs 200 --batch-size 4 --img-size 640 640 --device cpu
摄像头推理命令
cmd
python camera_mask.py
二、技术原理概述
YOLOv5目标检测流程
YOLOv5从原始图片到检测结果经历以下步骤:

一次前向传播即可完成所有目标的定位和分类,这是YOLO能够实现实时检测的核心原因。
三、代码逐模块详细解析
3.1 依赖库与环境配置
python
import numpy as np
import torch
import cv2
import os
import yaml
功能说明:导入项目所需的基础依赖库,这些库在后续的数据加载、模型训练、图像处理和配置解析中都会使用。
逐库解析:
numpy:Python科学计算核心库,在YOLOv5中承担数组运算、坐标变换、损失计算等任务。训练过程中的数据加载和预处理大量依赖其高效的向量化操作。torch:PyTorch深度学习框架的核心模块,提供了张量运算、自动求导、神经网络层定义等基础能力。YOLOv5的模型定义、前向传播和反向传播均基于PyTorch实现。cv2(OpenCV):图像处理与可视化库,负责图片读取、视频帧捕获、图像预处理(缩放、色彩空间转换)以及检测结果的绘制(矩形框、文字标注)。os:操作系统接口模块,用于文件路径拼接、目录遍历和文件存在性检查,在数据加载和结果保存中发挥重要作用。yaml:YAML格式解析库,用于读取data.yaml和hyp.yaml配置文件,将训练参数和数据集路径加载为Python字典。
调试提示 :在import torch后设置断点,执行torch.__version__查看PyTorch版本。若报错ModuleNotFoundError,说明当前Python环境中未安装PyTorch,需先激活正确的Conda环境或执行pip install torch。
3.2 数据配置文件(data.yaml)
yaml
train: ../MaskDataSet/train/images
val: ../MaskDataSet/valid/images
nc: 2
names: ['mask', 'no-mask']
功能说明 :data.yaml是连接数据集与训练脚本的桥梁,定义了训练集、验证集的图片路径、类别数量和类别名称。训练脚本启动时首先读取此文件。
参数说明:
train:字符串类型,训练集图片目录的路径,相对于训练脚本运行时的工作目录。由于训练时在yolov5_2_0目录下执行命令,../MaskDataSet表示"上一级目录中的MaskDataSet文件夹"。val:字符串类型,验证集图片目录的路径。nc:整数类型,类别数量(number of classes)。本项目为2类:戴口罩和未戴口罩。names:字符串列表,类别名称。顺序必须与标签文件中的class_id完全一致 ------标签中0对应列表第0项mask,1对应第1项no-mask。
调试提示 :若训练时出现No labels found或Dataset not found错误,首先检查当前工作目录是否为yolov5_2_0。执行cd命令查看当前路径,若不是则切换到正确目录。
3.3 YOLO标签文件格式
每张图片对应一个同名的标签文件。例如train/images/person_001.jpg对应train/labels/person_001.txt。
标签文件中每一行表示一个目标物体:
text
class_id x_center y_center width height
参数说明:
class_id:整数类型,类别编号,从0开始。本项目规定0=mask,1=no-mask。x_center:浮点数,目标中心点的x坐标,归一化到0~1(实际像素坐标除以图片宽度)。y_center:浮点数,目标中心点的y坐标,归一化到0~1(实际像素坐标除以图片高度)。width:浮点数,目标宽度,归一化到0~1(实际像素宽度除以图片宽度)。height:浮点数,目标高度,归一化到0~1(实际像素高度除以图片高度)。
示例标签文件内容:
text
0 0.515625 0.462500 0.187500 0.325000
1 0.243750 0.510000 0.162500 0.290000
第一行表示类别0(mask),第二行表示类别1(no-mask)。一张图中有多个人脸就有多少行。
为什么坐标必须归一化:YOLO模型在训练时会动态调整输入图片的尺寸(通过Letterbox保持宽高比)。若坐标是绝对值(像素值),缩放后坐标将完全错位。归一化坐标使得标签与图片尺寸解耦,无论输入尺寸如何变化,坐标始终在0~1范围内有效,模型可直接使用。
3.4 训练命令逐项详解
cmd
python train.py ^
--data ../MaskDataSet/data.yaml ^
--cfg models/yolov5s.yaml ^
--weights weights/yolov5s.pt ^
--hyp hyp.yaml ^
--epochs 200 ^
--batch-size 4 ^
--img-size 640 640 ^
--device cpu
功能说明 :train.py是YOLOv5的训练脚本,通过命令行参数控制训练的全部配置。以下逐项解释每个参数的作用和选择依据。
参数详解:
--data:数据类型为字符串,无默认值(必填)。指定数据集配置文件的路径,脚本从此文件读取训练/验证目录、类别数和类别名。路径相对于运行命令时的工作目录。--cfg:数据类型为字符串,无默认值(必填)。指定模型结构配置文件的路径,定义网络的层数、通道数等架构参数。本项目选择yolov5s.yaml------YOLOv5s是系列中参数最少、速度最快的版本。--weights:数据类型为字符串,无默认值(必填)。指定初始化权重的路径。填写weights/yolov5s.pt表示从COCO预训练权重开始迁移学习,而非随机初始化。--hyp:数据类型为字符串,默认hyp.yaml。超参数配置文件路径,控制学习率、动量、数据增强强度等训练细节。--epochs:数据类型为整数,默认300。完整遍历训练集的次数。200个epoch意味着模型会200次看到全部训练图片,每次经过随机数据增强,相当于看到200倍的数据量。--batch-size:数据类型为整数,默认16。每次迭代送入模型的图片数量。CPU环境下建议使用较小的值(如4),避免内存溢出。batch越大,每个epoch的迭代次数越少,但单次迭代内存占用越高。--img-size:两个整数,默认640 640。分别指定训练和验证时的输入尺寸。640×640是YOLOv5的默认尺寸,在速度和精度之间取得平衡。--device:数据类型为字符串,默认cpu。指定运算设备:cpu表示使用CPU,0表示使用第一块GPU,0,1表示使用多块GPU。
为什么选择YOLOv5s:YOLOv5s参数量最小,推理速度最快,适合在CPU上运行。对于仅有149张图片的小数据集,大模型(如YOLOv5x)更容易过拟合,且训练时间显著增加。
为什么从预训练权重开始:COCO数据集包含80个类别、超过30万张图片,模型已学会通用的边缘、纹理和物体结构特征。在小数据集上从零训练(随机初始化)几乎必然过拟合。迁移学习只需在口罩数据上微调少量参数,即可获得良好的泛化性能。
3.5 验证模型与分类别指标
cmd
python test.py ^
--weights runs\exp17\weights\best.pt ^
--data ..\MaskDataSet\data.yaml ^
--batch-size 4 ^
--img-size 640 ^
--conf-thres 0.001 ^
--iou-thres 0.5 ^
--device cpu ^
--verbose
参数说明:
--conf-thres 0.001:评估时使用极低的置信度阈值,目的是收集足够多的候选框来绘制完整的Precision-Recall曲线。这与实际展示结果时常用的0.25不同------评估需要覆盖所有可能的置信度水平。--verbose:输出每个类别的独立指标,而非仅输出汇总的all行。在类别失衡时,汇总行会掩盖少数类的问题。
本项目的实际验证结果(exp17,200 epochs):
| 类别 | Images | Targets | Precision | Recall | mAP@0.5 | mAP@0.5:0.95 |
|---|---|---|---|---|---|---|
| all | 29 | 162 | 0.671 | 0.905 | 0.872 | 0.557 |
| mask | 29 | 142 | 0.819 | 0.880 | 0.913 | 0.588 |
| no-mask | 29 | 20 | 0.522 | 0.930 | 0.830 | 0.526 |
结果解读:
总体mAP@0.5=0.872,表面上看效果不错,但不能因此认为模型在所有场景都表现良好。
no-mask的Recall为0.930,说明验证集中93%的未戴口罩目标被模型找到;但Precision仅0.522,说明该类别预测不够稳定,存在较多误报。验证集中只有20个no-mask框,样本规模不足以全面代表真实摄像头环境------这是当前模型的主要瓶颈。
图7 分类别指标输出截图(此处建议放置终端中test.py --verbose输出的表格截图,高亮显示no-mask行的Precision和Recall)
3.6 摄像头实时推理代码解析
python
# 加载模型
model = attempt_load(str(WEIGHTS_PATH), map_location=DEVICE)
model.eval()
# 打开摄像头
cap = cv2.VideoCapture(0)
while True:
ok, frame = cap.read()
if not ok:
break
# Letterbox保持宽高比缩放
img = letterbox(frame, new_shape=IMG_SIZE)[0]
# BGR→RGB,HWC→CHW
img = img[:, :, ::-1].transpose(2, 0, 1)
img = np.ascontiguousarray(img)
# 转为张量并归一化
img_tensor = torch.from_numpy(img).to(DEVICE).float() / 255.0
img_tensor = img_tensor.unsqueeze(0)
# 推理
with torch.no_grad():
prediction = model(img_tensor)[0]
# NMS去重
prediction = non_max_suppression(prediction, conf_thres=NMS_CONF, iou_thres=IOU_THRESH)
# 缩放坐标回原图并绘制
# ...(绘制逻辑)
cv2.imshow("Camera Face Landmark", frame)
if cv2.waitKey(1) == 27:
break
功能说明 :camera_mask.py读取摄像头每一帧,经过预处理后送入模型推理,最后在画面上绘制检测结果。
关键步骤解析:
model.eval():将模型切换到推理模式,调整BatchNorm和Dropout层的行为------BatchNorm使用训练时的全局统计量而非当前batch的统计量,Dropout层则完全关闭。这一步虽小,但直接影响推理结果的稳定性。letterbox(frame, new_shape=IMG_SIZE)[0]:保持宽高比缩放并补边,避免直接拉伸导致人脸比例失真。旧版YOLOv5 2.0的letterbox()不接受stride参数,这是旧版与新版的区别之一。img[:, :, ::-1].transpose(2, 0, 1):OpenCV读取的是BGR格式,模型训练时使用RGB格式,因此通过::-1反转通道顺序;同时将[H,W,C]转为[C,H,W]以满足PyTorch的输入要求。torch.no_grad():关闭梯度记录,大幅减少内存占用并加速推理。推理时不需要反向传播,因此无需记录中间变量的梯度。non_max_suppression:NMS去重,删除高重叠度的重复框,保留置信度最高的那个。
调试提示 :在prediction = model(img_tensor)[0]后设置断点,查看prediction的形状为[N, 6],其中6列分别为[x1, y1, x2, y2, conf, cls],N为检测到的目标数。若N=0,说明模型未检测到任何目标,可能是置信度阈值过高或画面中无人脸。
图8 摄像头实时检测运行截图(此处建议放置摄像头运行时终端和图像窗口的并列截图,显示检测结果和终端输出的帧率信息)
四、完整运行流程
第一步:安装依赖
bash
pip install numpy torch opencv-python pyyaml
第二步:下载YOLOv5工程代码
从GitHub克隆YOLOv5 2.0版本或使用已有工程文件。
第三步:准备数据集
将标注好的口罩数据集按以下结构放置:
text
MaskDataSet/
├── train/
│ ├── images/ # 训练图片
│ └── labels/ # 训练标签
├── valid/
│ ├── images/ # 验证图片
│ └── labels/ # 验证标签
└── data.yaml # 配置文件
第四步:下载预训练权重
将yolov5s.pt放入yolov5_2_0/weights/目录。
第五步:激活Conda环境(如使用Conda)
cmd
conda activate pytorch
第六步:切换到工程目录并开始训练
cmd
cd /d D:\Mask_YOLO\yolov5_2_0
python train.py --data ../MaskDataSet/data.yaml --cfg models/yolov5s.yaml --weights weights/yolov5s.pt --hyp hyp.yaml --epochs 200 --batch-size 4 --img-size 640 640 --device cpu
第七步:验证模型
cmd
python test.py --weights runs/exp17/weights/best.pt --data ../MaskDataSet/data.yaml --verbose --device cpu
第八步:摄像头实时推理
cmd
python camera_mask.py
按q键退出程序。
五、常见问题与解决
问题1:ModuleNotFoundError: No module named 'torch'
原因:当前Python环境中未安装PyTorch。
解决方法 :先执行conda activate pytorch激活正确环境,或执行pip install torch安装PyTorch。
问题2:训练时报错No labels found
原因:数据集目录结构不正确,或图片与标签文件名不一致。
解决方法:
- 确认目录名为
images和labels,拼写无误 - 确认每张图片都有同名(不含扩展名)的
.txt标签文件 - 确认从
yolov5_2_0目录启动训练
问题3:训练时报错Dataset not found
原因 :data.yaml中的路径无法正确解析。
解决方法:
- 执行
cd确认当前工作目录是否为yolov5_2_0 - 检查
data.yaml中的路径是否写为../MaskDataSet/train/images(相对路径)
问题4:类别名称显示反了
原因 :标签文件中的class_id与data.yaml中的names列表顺序不对应。
解决方法 :检查标签文件中0代表mask还是no-mask,确保names: ['mask', 'no-mask']中索引0对应mask。若标签中0实际为no-mask,应改为names: ['no-mask', 'mask']。
问题5:摄像头无法打开
原因:摄像头被其他程序占用,或设备ID不正确。
解决方法:
- 关闭微信、会议软件等可能占用摄像头的程序
- 尝试将
cv2.VideoCapture(0)改为cv2.VideoCapture(1) - 检查Windows摄像头权限设置
问题6:CPU训练极慢
原因:CPU算力有限,200个epoch耗时数天。
解决方法:
- 安装GPU版PyTorch并使用
--device 0 - 减少epochs(如100)先验证流程
- 减小
--img-size(如416) - 减小
--batch-size减少内存压力
问题7:训练日志显示0G
原因 :日志中的0G表示未使用GPU显存,说明当前运行在CPU上。
解决方法 :这不是错误。若想使用GPU,需安装CUDA版PyTorch并指定--device 0。
问题8:运行test.py时报错np.int不存在
原因 :旧版YOLOv5代码使用了新版NumPy已移除的np.int。
解决方法 :在test.py中找到np.int并替换为内置int。
问题9:摄像头检测全部识别为mask
原因:类别失衡,no-mask样本太少;或训练数据与摄像头场景差异过大。
解决方法:
- 补充实际摄像头拍摄的no-mask样本重新训练
- 验证集指标不代表真实场景效果,需实际测试
问题10:运行camera_mask.py报错letterbox()参数错误
原因 :新版教程中letterbox有stride参数,但YOLOv5 2.0的letterbox()没有该参数。
解决方法 :将letterbox(frame, new_shape=IMG_SIZE, stride=stride)改为letterbox(frame, new_shape=IMG_SIZE)[0]。
六、性能优化建议
CPU推理优化:
- 降低输入尺寸:
IMG_SIZE从640降至416,可提升约40%推理速度,但可能降低小目标检测精度 - 跳帧处理:每2~3帧执行一次检测,中间帧复用前一帧结果
- 降低摄像头分辨率:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)减少预处理计算量
GPU推理优化:
- 安装CUDA版PyTorch并指定
--device 0 - 使用半精度推理:
model.half()+img_tensor.half() - 批量推理:多张图片合并为一个batch同时处理
训练优化:
- 使用GPU训练可大幅缩短训练时间(从数天缩短至数小时)
- 适当增大batch size(如16或32)提升GPU利用率
- 使用更小的模型(YOLOv5n)进一步加速
七、项目扩展方向
- 增加错误佩戴检测 :新增
incorrect-mask类别,检测口罩未遮住鼻子等错误佩戴情况 - 多目标扩展:在口罩检测基础上增加人脸识别、情绪识别等模块,构建综合人脸分析系统
- 批量处理:改造代码遍历文件夹内所有图片,实现批量自动判分
- 导出ONNX加速:将PyTorch模型导出为ONNX格式,使用ONNX Runtime或TensorRT加速推理
- 部署到边缘设备:将模型转换为TensorFlow Lite或NCNN格式,部署到手机或嵌入式设备
- Web可视化:接入Flask或FastAPI,提供网页端上传图片进行检测的接口
八、总结
本文基于YOLOv5s和口罩数据集,完整实现了目标检测项目的全流程------从数据集准备、环境配置、模型训练到结果评估和摄像头实时部署。核心要点总结如下:
- 数据集规范 :图片、标签、
data.yaml三者必须严格对应,标签使用从0开始的类别编号和归一化xywh坐标,这是训练能够正常启动的前提。 - 训练策略:小数据集必须从预训练权重开始迁移学习,而非随机初始化;使用YOLOv5s等小模型避免过拟合。
- 指标解读:不能只看总体mAP,必须查看每个类别的Precision和Recall------类别失衡时总体指标会隐藏少数类的问题。
- 推理一致性:训练和推理的预处理必须保持一致(都用Letterbox而非直接resize),否则目标形状改变会导致检测失败。
- 持续迭代:模型指标好不等于摄像头效果好,实际部署后需收集失败样本重新训练,形成"训练→部署→采集→再训练"的闭环。
读者可根据本文的工程框架,将自己的数据集替换进来,快速训练出适用于其他目标检测场景(如车辆检测、行人检测)的模型。