YOLOv5口罩检测完整实战:训练、评估与摄像头识别

发布时间:2026/7/18 23:10:14
YOLOv5口罩检测完整实战:训练、评估与摄像头识别 前言上一篇介绍了目标检测和YOLOv5的基本原理本文进入完整实战。我们将使用一个已经标注好的口罩数据集训练YOLOv5s实现mask与no-mask两类目标检测并使用图片和摄像头验证模型。本文不是基于当前最新版ultralyticsPython包而是基于本地yolov5_2_0旧版工程代码。很多网络教程使用新版参数例如--img、runs/train或from ultralytics import YOLO不能直接照搬到这个项目。本文所有命令均按照当前工程实际支持的参数编写。本项目已经具备现成数据集因此不会展开标注软件操作只说明YOLOv5数据集必须包含哪些内容、目录如何对应、标签如何检查。整篇代码严格遵循工程化规范所有训练命令按实际支持的参数编写环境配置清晰可复现可直接迁移至其他目标检测数据集的训练任务中。文章目录前言一、项目目标与最终效果二、项目目录结构三、Python与Conda环境四、数据集必须包含哪些内容五、YOLO标签文件格式六、数据集目录为什么必须对应七、编写data.yaml八、检查类别编号和样本分布九、模型配置与预训练权重十、训练命令逐项解释十一、正式开始训练十二、训练过程中观察什么十三、训练输出文件说明十四、best.pt与last.pt的区别十五、验证模型并查看分类别指标十六、本项目的实际训练结果十七、使用图片测试模型十八、使用摄像头实时识别十九、实时推理代码逐步解释二十、关键推理参数如何调整二十一、为什么全部识别成戴口罩二十二、如何改善no-mask类别二十三、旧版YOLOv5与新环境兼容问题二十四、常见报错与处理方法二十五、一套规范的实验流程二十六、总结一、项目目标与最终效果项目需要在图片、视频或摄像头中检测人脸区域并输出两个类别0mask戴口罩 1no-mask未戴口罩最终效果如下绿色框显示戴口罩目标红色框显示未戴口罩目标画面中出现no-mask时显示警告文字。训练完成后核心产物不是训练脚本本身而是权重文件runs/exp17/weights/best.pt以后进行图片或摄像头推理时主要加载这个文件。二、项目目录结构当前项目中数据集和YOLOv5工程是同级目录Mask_YOLO/ ├─ MaskDataSet/ │ ├─ train/ │ │ ├─ images/ │ │ └─ labels/ │ ├─ valid/ │ │ ├─ images/ │ │ └─ labels/ │ ├─ test/ │ │ ├─ images/ │ │ └─ labels/ │ └─ data.yaml └─ yolov5_2_0/ ├─ models/ ├─ utils/ ├─ weights/ ├─ runs/ ├─ train.py ├─ test.py ├─ detect.py ├─ camera_mask.py └─ hyp.yaml“同级目录”意味着从yolov5_2_0出发先使用..返回上一级再进入MaskDataSet../MaskDataSetWindows命令中的反斜杠和YAML中常用的正斜杠通常都能被Python处理但配置文件中使用/更容易跨平台。三、Python与Conda环境同一台电脑可以同时存在Python 3.9、3.10、3.11和3.13。不同解释器的第三方库默认不互通因此不能只看电脑上“安装了PyTorch”还要看它安装在哪个环境中。本项目已经验证可用的环境是Conda环境名pytorch Python3.10.20 PyTorch2.5.1 CPU版在CMD中激活环境conda activate pytorch确认解释器where python python --version python -c import sys; print(sys.executable) python -c import torch; print(torch.__version__, torch.cuda.is_available())正确路径应类似D:\Anaconda3\envs\pytorch\python.exe如果torch.cuda.is_available()返回False表示当前使用CPU版PyTorch。程序仍然可以运行只是训练和推理速度较慢。为了长期管理可以将环境克隆为项目专属名称conda create -n mask-yolo --clone pytorch conda activate mask-yolo四、数据集必须包含哪些内容现成的YOLOv5目标检测数据集至少需要以下内容训练图片与训练图片对应的YOLO TXT标签验证图片与验证图片对应的YOLO TXT标签数据配置文件data.yaml明确且固定的类别编号与类别名称。测试集不是训练的绝对必需项。训练时必须有训练集通常也应有验证集测试集用于最终独立评价。图片支持的常见格式包括.jpg、.jpeg和.png。标签必须是纯文本.txt文件。数据质量比单纯数量更重要。图片应尽量覆盖不同人物正脸和侧脸不同距离和人脸大小室内与室外光线明亮、背光和低照度情况不同颜色、形状的口罩未戴口罩、错误佩戴、遮挡脸部等困难情况最终实际使用的摄像头画面。五、YOLO标签文件格式每张图片对应一个同名标签文件。例如images/person_001.jpg labels/person_001.txtTXT中每一行表示一个目标class_id x_center y_center width height示例0 0.515625 0.462500 0.187500 0.325000 1 0.243750 0.510000 0.162500 0.290000第一行表示类别0第二行表示类别1。一张图有多少个目标就有多少行。后四个坐标必须归一化到01。如果标注工具导出的坐标是像素值需要转换x_center_normalized x_center_pixel / image_width y_center_normalized y_center_pixel / image_height width_normalized box_width_pixel / image_width height_normalized box_height_pixel / image_height类别编号必须从0开始。本项目规定0 mask 1 no-mask如果标签中的0实际代表未戴口罩而YAML却写成mask模型训练本身可能正常但最终显示名称会完全相反。调试提示在训练前可编写一个简单的标签检查脚本在PyCharm中设置断点查看标签是否正确加载importcv2importnumpyasnp img_path../MaskDataSet/train/images/000001.jpglabel_path../MaskDataSet/train/labels/000001.txtimgcv2.imread(img_path)h,wimg.shape[:2]withopen(label_path,r)asf:linesf.readlines()forlineinlines:cls,xc,yc,bw,bhmap(float,line.strip().split())x1int((xc-bw/2)*w)y1int((yc-bh/2)*h)x2int((xcbw/2)*w)y2int((ycbh/2)*h)cv2.rectangle(img,(x1,y1),(x2,y2),(0,255,0),2)cv2.putText(img,str(int(cls)),(x1,y1-5),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,255,0),1)cv2.imshow(check,img)cv2.waitKey(0)通过断点观察lines的内容确认每行恰好包含5个数值且坐标都在0~1之间。六、数据集目录为什么必须对应YOLOv5会根据图片路径自动寻找标签。在这个旧版工程中它通常把路径中的images替换成labels并把图片后缀替换成.txt。例如MaskDataSet/train/images/abc.jpg对应MaskDataSet/train/labels/abc.txt因此以下情况会导致标签无法加载图片和标签文件名不同images或labels目录拼写错误标签扩展名不是.txt图片被重新命名但标签没有同步改名。本项目训练集105张图片与105个标签能够对应验证集29张图片与29个标签也能对应。测试集中的15张图片曾被改名为1.jpg到15.jpg标签仍保留原名所以测试集当前不能可靠用于评价。七、编写data.yaml本项目使用的MaskDataSet/data.yaml为train:../MaskDataSet/train/imagesval:../MaskDataSet/valid/imagesnc:2names:[mask,no-mask]字段含义如下train训练图片目录val验证图片目录nc类别数量number of classesnames类别名称列表位置必须和标签编号一致。这里的路径是相对于运行train.py时的工作目录解释的。我们先进入yolov5_2_0再启动训练所以../MaskDataSet是正确路径。如果在其他目录启动脚本相对路径可能失效。初学阶段最稳妥的方式是先切换到YOLO工程根目录cd /d D:\Jupyter_Projects\PythonProject\AI_Projects\Mask_YOLO\yolov5_2_0测试集修复文件名对应关系后可以增加test:../MaskDataSet/test/images在对应关系未修复前不要强行加入测试路径。八、检查类别编号和样本分布训练前应检查所有标签是否恰好包含5列类别编号是否为0或1坐标是否都在01图片是否都有同名标签是否存在标签但缺少图片各类别标注框数量是否严重失衡。本项目统计结果数据划分maskno-mask总标注框train573123696valid14220162test91596全部数据共954个框。训练集中mask:no-mask约为4.7:1验证集中约为7.1:1类别失衡比较明显。需要区分“图片数量”和“标注框数量”。训练集只有105张图片但一张图片中可以有多个人脸所以共有696个训练框。调试提示可在PyCharm中运行以下代码片段在断点处查看类别分布importosfromcollectionsimportCounter label_dir../MaskDataSet/train/labelsclass_countsCounter()forfinos.listdir(label_dir):iff.endswith(.txt):withopen(os.path.join(label_dir,f),r)asfp:forlineinfp:clsint(line.strip().split()[0])class_counts[cls]1print(class_counts)# Counter({0: 573, 1: 123})通过调试器观察class_counts的具体数值确认类别失衡程度。九、模型配置与预训练权重项目使用YOLOv5smodels/yolov5s.yaml预训练权重为weights/yolov5s.ptyolov5s.yaml定义网络结构yolov5s.pt保存预训练参数。二者不是同一个概念。对于小数据集推荐从预训练权重开始而不是完全随机初始化。预训练模型已经学会大量通用视觉特征可以减少训练难度。超参数文件为hyp.yaml它包含学习率、动量、权重衰减、颜色增强、缩放和平移等训练设置。初学者应该先使用一套能够工作的配置建立基准结果再逐项实验不要一次修改许多参数。十、训练命令逐项解释本项目使用的训练命令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参数说明如下--data指定数据集YAML。脚本会从这里获得训练目录、验证目录、类别数和类别名--cfg指定模型结构。本项目选择体积较小、速度较快的YOLOv5s--weights指定初始化权重。填写weights/yolov5s.pt表示迁移学习--hyp指定超参数文件--epochs表示完整遍历训练集的次数。200个epoch意味着模型会多次看到同一批图片但每次可能经过随机增强--batch-size表示一次送入模型的图片数量。batch越大通常占用更多显存或内存。CPU环境使用4是较保守的选择--img-size 640 640表示训练和测试输入尺寸。旧版程序支持分别指定训练、测试尺寸--device cpu强制使用CPU。如果安装了可用的CUDA版PyTorch可使用--device 0指定第一块GPU。Windows CMD使用^表示命令换行。如果希望减少复制问题也可以写成一行。十一、正式开始训练完整执行顺序conda activate pytorch cd /d D:\Jupyter_Projects\PythonProject\AI_Projects\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第一次训练前可以运行帮助命令确认当前版本到底支持哪些参数python train.py --help这是避免“照抄了其他版本教程但参数不存在”的有效方法。CPU训练较慢终端一段时间没有刷新不一定代表卡死。可以观察CPU使用率、内存和runs目录中的文件更新时间。调试提示训练开始后可在runs/exp0目录中查看生成的train_batch0.jpg了解数据增强后的训练图片效果。如果发现明显异常的增强如人脸翻转、颜色严重失真可调整hyp.yaml中的增强参数。十二、训练过程中观察什么训练日志通常显示当前epoch、显存占用、损失、目标数量和验证指标例如199/199 0G box_loss obj_loss cls_loss total targets img_size P R mAP.5 mAP.5:.95重点观察box_loss是否总体下降obj_loss是否总体下降cls_loss是否总体下降验证集Precision、Recall和mAP是否提升后期是否出现训练损失继续下降但验证指标变差的现象。日志中的0G表示没有使用GPU显存即当前在CPU上训练。某一轮损失轻微上升很正常。随机batch、Mosaic、缩放和颜色增强都会让每轮难度不同。应该看整体趋势不要根据一两轮波动判断训练失败。十三、训练输出文件说明这个旧版工程将每次训练保存在本项目效果较完整的一次训练是runs/exp17。其中常见文件包括exp17/ ├─ hyp.yaml # 本次使用的超参数 ├─ opt.yaml # 本次训练命令参数便于复现实验 ├─ results.txt # 每个epoch的损失和指标 ├─ results.png # 训练曲线图 ├─ labels.png # 标签位置和尺寸分布 ├─ train_batch0.jpg # 数据增强后的训练图片 ├─ test_batch0_gt.jpg # 真实标签 ├─ test_batch0_pred.jpg # 模型预测 └─ weights/ ├─ best.pt # 验证集表现最优的模型 └─ last.pt # 最后一个epoch的模型opt.yaml保存本次训练命令参数便于复现实验。hyp.yaml保存本次使用的超参数。results.txt保存每个epoch的损失和指标。results.png将训练曲线绘制成图片。labels.png可用于观察标签位置和尺寸分布。train_batch*.jpg展示数据增强后的训练图片可以发现明显错标或异常增强。test_batch0_gt.jpg是真实标签test_batch0_pred.jpg是模型预测可用于直观比较。调试提示训练完成后打开results.png观察损失曲线。如果val_mAP在后期持续下降说明可能发生过拟合。如果训练损失持续下降但验证损失上升也是过拟合的信号。十四、best.pt与last.pt的区别last.pt是最后一个epoch保存的模型。best.pt是训练过程中根据验证表现选出的较优模型。最后一轮不一定是效果最好的一轮因为后期可能发生过拟合或指标波动。实际推理通常优先使用best.pt如果训练意外中断并希望继续训练通常使用last.pt恢复进度。旧版工程的恢复参数可以通过以下命令确认python train.py --help十五、验证模型并查看分类别指标只看训练日志中的all行不够。应使用--verbose输出每个类别的指标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评估mAP时使用较低的--conf-thres 0.001是为了让程序收集足够多的候选结果绘制完整的Precision-Recall关系。它不同于实际展示结果时常用的0.25。项目旧代码原本使用已经被新版NumPy删除的np.int需要替换为内置int才能正常评估。这属于旧代码兼容问题并不是数据集错误。十六、本项目的实际训练结果exp17完成了200个epoch。最佳权重按类别验证结果如下类别ImagesTargetsPrecisionRecallmAP0.5mAP0.5:0.95all291620.6710.9050.8720.557mask291420.8190.8800.9130.588no-mask29200.5220.9300.8300.526不能只看到mAP0.5 0.872就认为模型在所有场景都很好。no-mask的Recall为0.930说明验证集中大部分未戴口罩目标能够被找到但Precision只有0.522说明该类别预测不够稳定。并且验证集中只有20个no-mask框样本规模不足以全面代表真实摄像头环境。十七、使用图片测试模型从验证集图片进行推理python detect.py ^ --weights runs\exp17\weights\best.pt ^ --source ..\MaskDataSet\valid\images ^ --output inference\mask_result ^ --img-size 640 ^ --conf-thres 0.25 ^ --iou-thres 0.45 ^ --device cpu常用参数--weights权重路径--source一张图片、图片目录、视频或摄像头编号--output输出目录--conf-thres最低置信度--iou-thresNMS的IoU阈值--deviceCPU或GPU。应分别测试三类图片验证集图片用于确认模型和代码正常网上或手机拍摄的新图片用于测试泛化实际摄像头截图用于测试部署场景。如果第一类效果好、第三类效果差通常说明存在场景差异而不是权重文件损坏。十八、使用摄像头实时识别项目已经提供整理后的camera_mask.py运行方法conda activate pytorch cd /d D:\Jupyter_Projects\PythonProject\AI_Projects\Mask_YOLO\yolov5_2_0 python camera_mask.py程序默认打开摄像头0capcv2.VideoCapture(0)如果摄像头无法打开可尝试改为1或2。出现检测窗口后按q退出。程序自动选择设备DEVICEtorch.device(cuda:0iftorch.cuda.is_available()elsecpu)当前环境会显示Camera started on cpu. Press q to quit.十九、实时推理代码逐步解释加载模型modelattempt_load(str(WEIGHTS_PATH),map_locationDEVICE)model.eval()eval()会将模型切换到推理模式。它与torch.no_grad()作用不同eval()调整部分网络层行为如Dropout和BatchNormno_grad()关闭梯度记录以节省内存和计算。读取摄像头帧ok,framecap.read()使用Letterbox保持宽高比imgletterbox(frame,new_shapeIMG_SIZE)[0]这里必须使用旧版YOLOv5 2.0的接口。该版本不接受新版教程中的stridestride参数。转换颜色通道和数组维度imgimg[:,:,::-1].transpose(2,0,1)imgnp.ascontiguousarray(img)OpenCV读取的是BGR模型训练通常使用RGBOpenCV数组是[H, W, C]PyTorch模型输入为[C, H, W]。转换为张量并归一化img_tensortorch.from_numpy(img).to(DEVICE).float()/255.0img_tensorimg_tensor.unsqueeze(0)执行推理withtorch.no_grad():predictionmodel(img_tensor)[0]执行NMSpredictionnon_max_suppression(prediction,conf_thresNMS_CONF,iou_thresIOU_THRESH)最后使用scale_coords()把处理后图片中的框坐标映射回摄像头原图再通过cv2.rectangle()和cv2.putText()绘制结果。调试提示在推理代码中加入断点观察prediction的数据结构。prediction是一个列表每个元素形状为[N, 6]其中N为检测目标数6列分别为[x1, y1, x2, y2, conf, cls]。通过调试器可以查看具体检测结果。二十、关键推理参数如何调整当前实时程序采用MASK_CONF0.35NO_MASK_CONF0.15NMS_CONFmin(MASK_CONF,NO_MASK_CONF)IOU_THRESH0.45IMG_SIZE640MASK_CONF戴口罩框的最低显示阈值NO_MASK_CONF未戴口罩框的最低显示阈值。安全监控通常更重视不要漏掉未戴口罩目标所以可以设置得略低但过低会增加误报NMS_CONF必须不高于两个类别阈值中的最小值。否则no-mask候选框可能在分类别阈值判断之前就被NMS过滤IOU_THRESH控制重复框抑制0.45可以作为起点IMG_SIZE增大可能改善小目标但CPU推理更慢。建议一次只修改一个参数并使用同一组测试图片比较实验MASK_CONFNO_MASK_CONF目标A0.350.15当前基线B0.350.10检查是否减少no-mask漏检C0.400.15检查是否减少mask误报阈值不能解决模型把no-mask分类成mask的问题只能控制已有预测是否显示。二十一、为什么全部识别成戴口罩本项目出现“未戴口罩也标成戴口罩”主要可能来自以下几个方面。类别失衡训练集中573个mask框只有123个no-mask框。模型看到多数类的次数更多。公开数据集与摄像头画面差异数据集包含许多新闻和网络图片实际摄像头则可能是固定室内背景、近距离脸部、特定镜头和特定光线。验证指标只能代表验证集分布。原始实时代码强制拉伸图片原代码使用cv2.resize(frame,(640,640))它会改变人脸比例。现在已经改为letterbox()与YOLOv5的标准推理预处理保持一致。验证样本太少验证集中只有20个no-mask框。即使Recall较高也不足以覆盖所有未戴口罩情况。困难情况没有进入训练集例如手遮挡嘴部、低头、侧脸、逆光和画面模糊等情况模型可能把皮肤阴影误认为口罩区域。二十二、如何改善no-mask类别最有效的方法不是无限调整阈值而是补充与实际应用一致的数据。建议使用最终运行程序的同一摄像头采集不同人物未戴口罩画面正脸、左侧脸、右侧脸近距离、中距离、远距离白天、夜间和室内灯光坐着、站着、低头和移动手遮脸、围巾、衣领、眼镜等易混淆情况。目标是将no-mask标注框从123增加到至少400600使其接近mask数量。不要简单复制同一张图片因为复制不会增加场景多样性。补充数据后重新划分训练集约70%80% 验证集约10%20% 测试集约10%20%同一段视频的连续帧应尽量放在同一个划分中避免相邻帧同时进入训练集和验证集造成数据泄漏。标注时要明确规则。例如口罩没有遮住鼻子时究竟算mask还是no-mask必须全数据集统一。如果希望检测“错误佩戴”最好增加第三类0mask 1no-mask 2incorrect-mask增加类别后必须同步修改nc、names和全部标签编号。二十三、旧版YOLOv5与新环境兼容问题旧代码运行在较新的NumPy、PyTorch和torchvision上可能出现兼容警告或错误。NumPy的np.int错误错误示例AttributeError: module numpy has no attribute int原因是新版NumPy删除了已经弃用的np.int。旧代码中可以安全替换为intletterbox参数错误错误示例TypeError: letterbox() got an unexpected keyword argument stride原因是复制了新版接口但YOLOv5 2.0的letterbox()没有该参数。旧版正确写法为letterbox(frame,new_shape640)torchvision图片扩展警告Failed to load image Python extension当前项目主要使用OpenCV读取图片模型训练和推理已经实际运行成功因此该警告不是本次程序退出原因。但它说明torchvision与底层图片库并非完全匹配未来整理环境时应安装相互兼容的PyTorch与torchvision组合。torch.load FutureWarning这是PyTorch对未来默认行为和不可信权重安全风险的提示。自训练且可信的本地权重可以继续用于当前工程但不要随意加载未知来源的.pt文件。二十四、常见报错与处理方法问题现象可能原因解决方案ModuleNotFoundError: No module named torch当前环境没有PyTorch先执行conda activate pytorch找不到data.yaml或图片目录当前目录不对确认从yolov5_2_0目录启动训练No labels found图片和标签路径对应错误检查目录是否叫images与labels文件名是否相同类别名称显示反了标签编号与names不对应检查TXT中类别编号与names列表的对应关系摄像头无法打开设备被占用或ID不对关闭其他摄像头应用尝试VideoCapture(1)CPU推理卡顿输入尺寸过大降低IMG_SIZE或跳帧处理numpy的np.int错误新版NumPy不兼容将np.int替换为内置int训练很好但摄像头很差场景差异保存摄像头失败画面重新标注训练二十五、一套规范的实验流程为了避免“改了很多参数却不知道哪个有效”建议采用固定实验流程。第一步保存当前基线数据集版本v1 模型YOLOv5s 图片尺寸640 batch4 epochs200 预训练权重yolov5s.pt第二步记录每次实验的opt.yaml、hyp.yaml和结果目录不要覆盖旧结果。第三步使用固定验证集比较按类别Precision、Recall和mAP。第四步准备一组固定的真实摄像头测试图片人工记录未戴口罩漏检数量未戴口罩错分为戴口罩的数量戴口罩错分为未戴口罩的数量完全没有检测框的数量单张平均处理时间。第五步每轮只改变一个主要变量例如只增加数据、只修改阈值或只调整输入尺寸。第六步把失败样本加入下一版数据集形成训练 → 部署测试 → 收集失败样本 → 重新标注 → 再训练这比盲目堆叠训练轮数更接近真实工程流程。二十六、总结使用现成YOLOv5数据集时最重要的是确保图片、TXT标签和data.yaml三者严格对应。标签必须使用从0开始的类别编号和归一化xywh坐标。本项目使用Python 3.10的Condapytorch环境、YOLOv5s网络、预训练权重和640输入尺寸完成了200轮训练。模型在验证集上取得总体mAP0.5 0.872但no-mask类别样本过少真实摄像头效果仍受类别失衡和场景差异影响。完整项目的关键经验是固定正确的Python环境确认数据集路径和标签对应关系从预训练权重开始查看分类别指标而不是只看总体mAP保持训练与推理预处理一致使用实际摄像头失败样本持续扩充数据不要把阈值调整误认为可以替代重新训练。