YOLOv5 口罩目标检测实战(一):项目整体介绍与数据准备

前言

在公共场所佩戴口罩已成为常态化防护措施,但人工监督耗时费力且存在盲区。利用计算机视觉技术对摄像头画面进行实时检测,能够有效辅助安防和防疫管理。

本项目使用早期YOLOv5 2.0版本中的YOLOv5s模型 ,实现端到端的口罩佩戴检测。与图像分类任务不同,本项目需要同时解决两个核心问题:1. 找到每张人脸在画面中的位置(定位);2. 判断该人脸属于mask(戴口罩)还是no-mask(未戴口罩)。模型最终输出若干个目标框、每个框的类别和置信度。

本文作为系列开篇,重点介绍项目的目标、数据集构成、文件目录结构以及完整的数据流转流程。后续文章将深入网络结构、Anchor解码、训练调参与推理部署。


文章目录


一、项目目录结构与核心文件

项目代码基于早期YOLOv5 2.0工程,推荐使用PyCharm或VSCode打开。目录结构分为数据集存放区(MaskDataSet算法工程区(yolov5_2_0

text 复制代码
Mask_YOLO
│
├── MaskDataSet/                # 数据集根目录
│   ├── data.yaml               # 数据集配置文件(路径、类别数、类别名)
│   ├── train/                  # 训练集
│   │   ├── images/             # 训练图片(105张)
│   │   └── labels/             # 训练标签(YOLO格式txt)
│   ├── valid/                  # 验证集
│   │   ├── images/             # 验证图片(29张)
│   │   └── labels/             # 验证标签
│   └── test/                   # 测试集(独立复评)
│       ├── images/             # 测试图片(15张)
│       └── labels/             # 测试标签
│
└── yolov5_2_0/                 # 早期YOLOv5工程源码
    ├── train.py                # 训练总入口
    ├── test.py                 # 验证与测试入口
    ├── detect.py               # 图片/视频推理入口
    ├── camera_mask.py          # 【本项目核心】摄像头实时检测入口
    ├── models/                 # 网络结构定义
    │   ├── yolov5s.yaml        # YOLOv5s网络结构、Anchor尺寸
    │   └── yolo.py             # 模型构建与Detect检测头逻辑
    ├── utils/                  # 工具函数
    │   ├── datasets.py         # 数据加载、Letterbox、数据增强
    │   └── utils.py            # IoU计算、损失函数、目标匹配、NMS
    ├── hyp.yaml                # 超参数配置(学习率、损失权重、增强参数)
    └── runs/                   # 训练输出
        └── exp17/              # 当前项目训练结果
            ├── opt.yaml        # 训练命令参数(复现用)
            ├── hyp.yaml        # 本次训练实际超参数
            ├── results.txt     # 每轮训练损失与验证指标
            └── weights/
                ├── best.pt     # 验证集综合表现最佳权重(用于最终推理)
                └── last.pt     # 最后一轮权重(用于恢复训练)

核心文件职责说明

文件 职责 重要性
data.yaml 指定训练/验证图片路径、类别数量(nc: 2)、类别名称(mask, no-mask) 数据入口,必须正确配置
models/yolov5s.yaml 定义Backbone、Neck、Head结构及每层通道数、Anchor尺寸 修改网络结构需改动此处
hyp.yaml 控制学习率(lr0=0.01)、动量(0.937)、损失权重及Mosaic等数据增强参数 调优精度的关键文件
train.py 整合数据、模型、超参数,执行训练循环、验证和模型保存 训练主程序
camera_mask.py 调用OpenCV读取摄像头,执行预处理、推理、后处理(NMS)和画框显示 最终交付运行入口
best.pt 保存训练过程中验证集mAP等指标综合表现最好的模型权重 最终推理必须加载此文件

二、数据集详解与标签格式

1. 样本与标注框分布

原始数据集经过清洗和划分,当前分布如下:

数据集 图片数量 Mask框数量 No-mask框数量 说明
训练集(train) 105张 573 123 约4.7:1,明显偏向戴口罩类别
验证集(valid) 29张 142 20 少数类(no-mask)样本极少,指标易波动
测试集(test) 15张 91 5 仅用于独立复评,不参与训练调参
合计 149张 806 148 -

关键认知 :模型在训练时看过573道"戴口罩"的题,只看过123道"未戴口罩"的题。这种类别失衡是后续项目中容易出现"漏报未戴口罩"或"误报戴口罩"的根本原因之一。

2. YOLO标签格式详解

每张图片对应一个同名.txt文件(如0001.jpg对应0001.txt)。标签文件中的每一行代表一个真实目标框,格式如下:

text 复制代码
class x_center y_center width height

其中:

  • class :整数,0代表mask1代表no-mask
  • x_center, y_center, width, height :均为归一化到 0, 1 之间的浮点数。归一化公式为(以x_center为例):(框的左上角x + 框的右下角x) / (2 * 图片宽度)

标签示例0001.txt内容):

text 复制代码
0 0.5125 0.4300 0.2100 0.3200
1 0.7600 0.5000 0.1800 0.2900

这表示图片中有两个目标:第一个是戴口罩的人脸,位于画面中央偏左;第二个是未戴口罩的人脸,位于画面右侧。

3. data.yaml 配置文件

数据集路径和类别映射全部在MaskDataSet/data.yaml中定义:

yaml 复制代码
# data.yaml
train: MaskDataSet/train/images
val: MaskDataSet/valid/images
test: MaskDataSet/test/images

nc: 2  # number of classes
names: ['mask', 'no-mask']  # class names

调试提示 :YOLO读取图片依赖data.yaml中的路径。如果训练时报错FileNotFoundError,优先检查此处路径是否为相对路径 且与train.py所在目录的相对位置正确。


三、完整数据流转与训练推理总流程

下图展示了从原始数据到最终摄像头推理的完整链路,清晰标出了训练阶段与推理阶段的边界。
#mermaid-svg-c5XOllFOcoNtc9ZE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c5XOllFOcoNtc9ZE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c5XOllFOcoNtc9ZE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c5XOllFOcoNtc9ZE .error-icon{fill:#552222;}#mermaid-svg-c5XOllFOcoNtc9ZE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c5XOllFOcoNtc9ZE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c5XOllFOcoNtc9ZE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c5XOllFOcoNtc9ZE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c5XOllFOcoNtc9ZE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c5XOllFOcoNtc9ZE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c5XOllFOcoNtc9ZE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c5XOllFOcoNtc9ZE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c5XOllFOcoNtc9ZE .marker.cross{stroke:#333333;}#mermaid-svg-c5XOllFOcoNtc9ZE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c5XOllFOcoNtc9ZE p{margin:0;}#mermaid-svg-c5XOllFOcoNtc9ZE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-c5XOllFOcoNtc9ZE .cluster-label text{fill:#333;}#mermaid-svg-c5XOllFOcoNtc9ZE .cluster-label span{color:#333;}#mermaid-svg-c5XOllFOcoNtc9ZE .cluster-label span p{background-color:transparent;}#mermaid-svg-c5XOllFOcoNtc9ZE .label text,#mermaid-svg-c5XOllFOcoNtc9ZE span{fill:#333;color:#333;}#mermaid-svg-c5XOllFOcoNtc9ZE .node rect,#mermaid-svg-c5XOllFOcoNtc9ZE .node circle,#mermaid-svg-c5XOllFOcoNtc9ZE .node ellipse,#mermaid-svg-c5XOllFOcoNtc9ZE .node polygon,#mermaid-svg-c5XOllFOcoNtc9ZE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c5XOllFOcoNtc9ZE .rough-node .label text,#mermaid-svg-c5XOllFOcoNtc9ZE .node .label text,#mermaid-svg-c5XOllFOcoNtc9ZE .image-shape .label,#mermaid-svg-c5XOllFOcoNtc9ZE .icon-shape .label{text-anchor:middle;}#mermaid-svg-c5XOllFOcoNtc9ZE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-c5XOllFOcoNtc9ZE .rough-node .label,#mermaid-svg-c5XOllFOcoNtc9ZE .node .label,#mermaid-svg-c5XOllFOcoNtc9ZE .image-shape .label,#mermaid-svg-c5XOllFOcoNtc9ZE .icon-shape .label{text-align:center;}#mermaid-svg-c5XOllFOcoNtc9ZE .node.clickable{cursor:pointer;}#mermaid-svg-c5XOllFOcoNtc9ZE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-c5XOllFOcoNtc9ZE .arrowheadPath{fill:#333333;}#mermaid-svg-c5XOllFOcoNtc9ZE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c5XOllFOcoNtc9ZE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c5XOllFOcoNtc9ZE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c5XOllFOcoNtc9ZE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c5XOllFOcoNtc9ZE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c5XOllFOcoNtc9ZE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-c5XOllFOcoNtc9ZE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-c5XOllFOcoNtc9ZE .cluster text{fill:#333;}#mermaid-svg-c5XOllFOcoNtc9ZE .cluster span{color:#333;}#mermaid-svg-c5XOllFOcoNtc9ZE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-c5XOllFOcoNtc9ZE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c5XOllFOcoNtc9ZE rect.text{fill:none;stroke-width:0;}#mermaid-svg-c5XOllFOcoNtc9ZE .icon-shape,#mermaid-svg-c5XOllFOcoNtc9ZE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c5XOllFOcoNtc9ZE .icon-shape p,#mermaid-svg-c5XOllFOcoNtc9ZE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-c5XOllFOcoNtc9ZE .icon-shape .label rect,#mermaid-svg-c5XOllFOcoNtc9ZE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c5XOllFOcoNtc9ZE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c5XOllFOcoNtc9ZE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c5XOllFOcoNtc9ZE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

图片 + 同名YOLO标签
检查类别、坐标、空标签和数据分布
划分train、valid、test
读取data.yaml、yolov5s.yaml、hyp.yaml
加载YOLOv5s和预训练权重
DataLoader读取图片并同步增强标签
Backbone提取多层特征
FPN + PAN融合不同尺度特征
P3、P4、P5三个尺度输出候选
真实框与Anchor、Grid匹配
计算box、obj、cls三类损失
反向传播 + SGD更新参数
EMA平滑参数
在验证集计算P、R和mAP
综合表现是否更好
保存best.pt
继续训练并保存last.pt
摄像头或图片推理
Letterbox + RGB + CHW + 归一化
模型前向得到25200个候选
置信度筛选 + NMS
坐标映射回原图
画框、类别、置信度和WARNING
收集漏检、误检和错分类坏例

图2 项目训练与推理总流程图。上方为训练闭环(数据→增强→前向→损失→反向→保存),下方为推理闭环(摄像头→预处理→前向→NMS→画框→坏例收集)。


四、快速上手运行步骤

  1. 安装环境依赖(建议使用Python 3.8):

    bash 复制代码
    pip install torch torchvision opencv-python pandas matplotlib pyyaml tqdm
  2. 检查数据集

    • 确保MaskDataSet文件夹与yolov5_2_0位于同级目录。
    • 随机打开train/labels/下的一个txt文件,检查坐标是否在0~1之间。
  3. 开始训练 (在yolov5_2_0目录下):

    bash 复制代码
    python train.py --data ../MaskDataSet/data.yaml --weights yolov5s.pt --epochs 200 --batch-size 4 --img-size 640
  4. 摄像头实时检测

    bash 复制代码
    python camera_mask.py

五、常见问题与解决方案(数据阶段)

问题 原因 解决方法
训练报错 No labels found data.yaml中路径错误,或labels文件夹为空 检查train/val路径是否指向images父目录,确保标签与图片同名
标签解析报错 IndexError: list index out of range 标签文件中某行缺少空格或坐标数量不为5 使用文本编辑器检查txt文件,确保格式为class x y w h
类别数量不对应 data.yamlnc: 2,但标签里出现了2-1 检查所有标签文件,确保class只包含0和1
验证集mAP忽高忽低 验证集太小(仅29张,no-mask仅20框) 属于正常现象,应结合分类别指标和具体坏例分析,不只看总体mAP
图像无法读取(corrupted image 图片文件损坏或不是标准格式(如.jpg实际为.png 使用cv2.imread()逐张测试,修复或删除损坏图片

六、调试建议

建议在训练前运行以下简单脚本验证数据加载是否正确:

python 复制代码
import cv2
path = "MaskDataSet/train/images/0001.jpg"
label_path = "MaskDataSet/train/labels/0001.txt"
img = cv2.imread(path)
print(f"图片Shape: {img.shape}")  # 期望 (H, W, 3)
with open(label_path, 'r') as f:
    print(f"标签内容: {f.read()}")

观察变量:

  • img是否为None
  • 标签坐标值是否在0~1范围内。

七、总结与下篇预告

本文作为系列的开篇,梳理了YOLOv5口罩目标检测项目的基本盘:我们明确了目标检测与图像分类的区别 ,掌握了149张图片、954个标注框 的数据分布特点,理解了目录结构中每个关键文件 的职责,并且通过流程图对数据如何一步步变成检测框有了全局印象。

值得记住的是:本项目的训练集存在约4.7:1的类别失衡,且验证集规模较小,这意味着我们在后续解读mAP指标时必须保持审慎。

下一篇预告 :我们将深入YOLOv5s的模型内部,从像素、卷积、特征图开始,剖析Backbone(Focus/CSP/SPP)、Neck(FPN+PAN)和Detect检测头是如何将一张640×640的图片编码为25200个候选框的,敬请期待。


版权说明

本文为原创技术文章,转载请注明出处。

文中代码及配置均经过实际测试,可根据项目需求修改扩展。

如发现疏漏或错误,欢迎在评论区交流讨论。

复制代码
相关推荐
梵构广告1 小时前
平台怎么做品牌营销策划?—品牌营销
大数据·人工智能·平面·品牌策划
土豆12501 小时前
DeepSeek V4-Flash 正式版深度解读:一行 changelog 里的暗涌与野心
人工智能·llm
Aloudata1 小时前
Prompt 驱动分析 vs Skill 驱动分析:企业 AI 分析如何从会问走向可复用
大数据·人工智能·数据分析·prompt·skill·语义层
笨鸟先飞,勤能补拙1 小时前
下一个十年:网络安全正在被重写
网络·人工智能·安全·web安全·网络安全·github
weixin_429615991 小时前
快速生成流程图
人工智能·chatgpt·aigc·流程图
骇客野人1 小时前
PGSQL运维筛选近30天有数据改动数据库表和表名
人工智能
孪生质数-1 小时前
AI 应用实践篇——让大模型真正开始工作
linux·运维·服务器·人工智能·深度学习·语言模型·llama
维克兜率天2 小时前
【维克】回归诊断:如何知道你的模型是不是“坏了“?
人工智能·数据挖掘·回归