简介:目标检测是计算机视觉中的基础任务,而YOLO系列凭借实时性与精度平衡成为工程落地首选。本文围绕口罩佩戴检测场景,系统梳理了基于YOLOv5的完整项目实践:从YOLO标注格式校验、数据划分,到迁移学习参数配置、低显存训练技巧,再到常见报错排查与模型评估。内容覆盖数据集检查、环境搭建、train.py核心参数、超参数调优等关键环节,适合毕设与课程实战。通过该案例,可快速掌握目标检测项目从数据到部署的标准化流程。
1. YOLOv5口罩佩戴检测项目到手先看什么:一份能直接跑、能交差的完整毕设包
做毕设或者课程设计最怕的不是算法难,而是东西拿到手发现缺数据、缺权重、缺标注,跑不起来还得从头自己补。这个基于YOLOv5的口罩佩戴检测项目打包了数据集、源码、训练好的模型和标注文件,不是那种只有代码没有数据的半成品。项目里有带标注的图片数据集、YOLOv5训练脚本、训练好的权重,以及一个 tutorial.ipynb 帮你把流程串起来。你拿到手先不急着训练,把目录结构和标注格式摸清楚,再跑通推理,后面训练和调参才有参照。适合正在做计算机相关专业毕设的学生,也适合要交课程大作业、想练一遍目标检测完整流程的学习者。下面按我拆这种资源包的顺序,一步步说清楚。
2. 数据集与标注格式:训练前先弄清楚这包数据能不能用
很多下载包拿到手第一反应是赶紧跑代码,结果训练到一半发现标签全是乱的。花半小时把数据集和标注格式核对一遍,比跑通一个错误的结果省一天时间。
2.1 资源包里到底有什么
先看文件清单,这个包里的内容包含这几类:
setup.cfg Dockerfile tutorial.ipynb logo.jpeg up.jpeg right.jpeg tmp_upload.jpegsetup.cfg 是 Python 项目配置文件,Dockerfile 用来构建容器环境,tutorial.ipynb 是带你走通全流程的 Jupyter Notebook。几个 jpeg 图片是样例图,其中 up.jpeg、right.jpeg 这类命名其实是测试样本,你可以直接拿它们验证模型检测效果。整体看下来,包的组成是「源码 + 数据集 + 标注 + 训练好的权重 + 演示脚本」一套齐全。
数据集部分一般会按 YOLO 惯例组织成 images 和 labels 两个目录,训练集和验证集分开存放。你拿到包后第一件事,就是把数据集根目录列出来看一遍:
dataset/ ├── images/ │ ├── train/ │ │ ├── img_001.jpg │ │ └── ... │ └── val/ │ ├── img_qlobal.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── img_001.txt │ │ └── ... │ └── val/ │ └── ... ├── classes.txt ├── train.txt └── val.txttrain.txt 和 val.txt 是图片路径列表,YOLOv5 训练时通过这两个文件定位数据;classes.txt 是类别名。如果包里的目录结构和上面有出入,以实际解压后的为准,但核心逻辑一样:每张图片对应一个同名 txt 标注文件,标注文件里每行是一个目标。这一步确认清楚了,后面训练指令里的 data yaml 才能指对路径。
2.2 YOLO txt 标注格式与校验方法
YOLOv5 用的是归一化的 txt 格式,每行代表一个目标,字段顺序固定为:类别ID 中心点x 中心点y 宽度 高度。前四个数值都是相对于图片宽高的比例,值域在 0 到 1 之间。口罩佩戴检测一般分两类:佩戴(mask)和未佩戴(nomask),所以类别ID 就是 0 或 1。
我习惯拿到标注先写一段脚本快速校验,看有没有越界、格式错乱、类别超范围的问题。打开终端跑下面这段:
import os def check_labels(label_dir, image_dir, class_count=2): bad_files = [] for f in os.listdir(label_dir): if not f.endswith('.txt'): continue path = os.path.join(label_dir, f) with open(path, 'r') as fp: lines = fp.readlines() for line in lines: parts = line.strip().split() if len(parts) != 5: bad_files.append((f, '字段数不为5')) continue cls_id = int(parts[0]) vals = list(map(float, parts[1:])) if cls_id >= class_count or cls_id < 0: bad_files.append((f, f'类别ID越界:{cls_id}')) if any(v < 0 or v > 1 for v in vals): bad_files.append((f, '坐标不在0-1范围内')) if bad_files: print('发现异常:') for item in bad_files: print(item) else: print('标注格式检查通过') if __name__ == '__main__': check_labels('dataset/labels/train', 'dataset/images/train')这段代码按五字段规则逐行解析标注文件,先检查字段数量,再检查类别 ID 是否超过类别总数(口罩检测两类,所以传 2),最后检查归一化坐标是否落在 0 到 1 之间。注意,如果碰到空行不能直接报错,空行可能是标注工具生成的残留,直接跳过比报错更符合实际场景。我在检查时加了过滤逻辑,只对非空行做校验。
还有一类常见问题是图片和标注文件对不上号:有图片没标注,或者有标注没图片。这种问题训练时不会立刻报错,但会影响 mAP 计算。校验脚本里可以再加一步:对比 image_dir 和 label_dir 的文件名集合,找出差集。处理方式就是补标注或者删掉孤儿文件。
2.3 类别文件与数据划分
YOLOv5 训练时通过 data yaml 文件指定路径和类别名,你必须保证 classes.txt 里的类别顺序和标注文件里的 ID 一一对应。mask 在前、nomask 在后,那 ID 0 就是 mask,ID 1 就是 nomask。顺序错了模型也能训练,但预测出来的标签永远是反的,这种翻车很难排查,所以我建议第一时间打开 classes.txt 确认。
数据划分建议按训练集:验证集 = 9:1 或 8:1 来分,口罩检测数据集量级通常在几千到一两万张不等。如果你拿到的是完整包,里面应该已经分好了 train/val;如果只有全部图片,你自己划分时用随机分配,但要保证同一场景的相似图片不全落在训练集或验证集。我用的是固定随机种子划分,避免复现时结果漂移。参数上,训练集比例设 0.9 时,yaml 文件里 train 路径指到 train 目录,val 路径指到 val 目录,test 可留空。
3. 环境搭建与代码结构:YOLOv5环境配置的两条路
环境配置是新人最容易卡住的环节,卡点通常不是代码本身,而是 PyTorch、CUDA、torchvision 三者版本没对齐。这里我给出两条可走通的路线:conda 本地安装和 Docker 容器化安装。
3.1 conda 环境与依赖安装
建议用 conda 建独立环境,避免把系统 Python 环境搞乱。Python 版本选 3.8 左右比较稳,YOLOv5 官方对 3.7 到 3.10 都兼容,但 3.8 踩坑最少。
conda create -n yolov5_mask python=3.8 -y conda activate yolov5_mask cd yolov5-mask-detection pip install -r requirements.txtrequirements.txt 里核心依赖包括 torch、torchvision、opencv-python、numpy、matplotlib、pyyaml、tqdm 等。重点提醒一句:pip 默认安装的 torch 是 CPU 版还是 GPU 版取决于你机器上的 CUDA 环境。如果你有 NVIDIA 显卡,建议先用 nvidia-smi 查 CUDA 版本,再按对应版本装 GPU 版 torch,命令是 pip install torch torchvision --index-url 加对应 CUDA 的下载源。这样能省掉后面训练时遇到的设备不可用问题。
安装完成后验证一下环境是否正常:
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"输出里 cuda.is_available() 如果是 False,说明要么没装 GPU 版 torch,要么驱动没对上,后面训练只能走 CPU,速度会慢到怀疑人生。这一行验证命令值得养成习惯,每次换环境都跑一遍。
3.2 Dockerfile 方式:不想污染本地的备选路线
如果你不想在本地装一堆依赖,包里自带的 Dockerfile 可以构建一个跑 YOLOv5 的容器。Docker 方式的好处是环境隔离,坏了直接删容器重建,适合 GPU 服务器上多人共用环境的情况。
构建和启动命令如下:
docker build -t yolov5_mask . docker run --gpus all -it --rm \ -v $(pwd)/dataset:/workspace/dataset \ -v $(pwd)/runs:/workspace/runs \ yolov5_mask bash-v 参数把宿主机的 dataset 和 runs 目录挂载进容器,训练产物直接落在宿主机上,容器删了也不丢数据。--gpus all 是让容器使用宿主机 GPU,前提是宿主机安装了 nvidia-container-toolkit。如果容器里跑 detect 时报 CUDA 不可用,先查宿主机上 nvidia-container-toolkit 是否装好,再查容器内 nvidia-smi 是否有输出。
镜像构建时要注意基础镜像的 PyTorch 版本和宿主机驱动兼容性。常见做法是在 Dockerfile 里固定 torch 版本号,而不是用 latest,这样后面复现训练结果不会因为依赖升级产生偏差。
3.3 tutorial.ipynb 是入口:先跑通推理再训练
这个包里 tutorial.ipynb 是给你串流程用的,建议第一步先跑它。Notebook 里通常包含了加载模型、读取图片、执行推理、绘制检测框这几步。先把推理跑通,确认训练好的权重没坏,再考虑要不要重新训练。
我打开 notebook 后的操作顺序是:先看每个 cell 的代码,确认它调用的是哪个权重文件、读的是哪张图片,然后逐个 cell 执行。如果你在 Jupyter 环境里,执行前先确认 kernel 选的是刚才建的 conda 环境,否则会出现 torch 没安装这种低级报错。
推理跑通后,顺带用同一张图跑两次,一次是原有权重,一次是随机初始化权重,对比检测结果。这个对比能直观体现训练好的模型和没训练模型的差别,做毕设答辩展示时也很好用,能证明你的实验过程真实有效。
4. 训练自己的口罩数据集:从train.py到超参数调优
推理跑通只是热身,真正的重头戏是训练。训练这一层决定了你最终模型的 mAP 和检测速度,也决定了答辩时能不能拿出漂亮的曲线图。
4.1 数据集 yaml 配置
YOLOv5 训练入口是 train.py,但它不直接读图片路径,而是读一个 data yaml 文件。在包的 data 目录下找到或新建 mask.yaml,内容如下:
train: dataset/images/train val: dataset/images/val nc: 2 names: ['mask', 'nomask']train 和 val 字段指向图片目录,YOLOv5 会自动去同名 labels 目录找标注文件。你写成 labels 路径是没用的,它只认 images 路径,这点容易搞错。nc 是类别数,names 必须和标注 ID 顺序一致,顺序反了模型照样收敛,但预测结果的语义标签是错的。
路径建议写相对路径,这样换机器不用改 yaml。如果你把项目整体放在某个目录下,train.py 的工作目录就是这个包的根目录,相对路径最稳妥。
4.2 train.py 参数详解与低显存设定
训练命令我一般这样写:
python train.py \ --data mask.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --workers 4 \ --hyp hyp.scratch-low.yaml \ --name mask_exp1参数含义说明:--data 指定数据集 yaml;--weights 指定初始权重,用预训练权重做迁移学习能大幅缩短收敛时间;--img 是训练时输入图片尺寸,640 是 YOLOv5 默认值;--batch 是批大小,16 在 8GB 显存的显卡上可以跑;--epochs 100 对口罩检测这种少类别任务足够收敛;--workers 是数据加载进程数;--hyp 是超参数文件,后面细说;--name 是实验名,输出会放在 runs/train/name 目录下。
如果你的显卡显存只有 4GB 甚至更低,需要做几处调整。常见做法是把 batch 降到 8 或 4,把 img 从 640 降到 480,把 workers 设为 0。workers 设为 0 是因为 Windows 系统下多进程数据加载偶尔会卡死,设 0 最稳,缺点是训练时 CPU 加载数据稍慢,但总比中断强。参数调整后命令如下:
python train.py \ --data mask.yaml \ --weights yolov5s.pt \ --img 480 \ --batch 8 \ --epochs 100 \ --device 0 \ --workers 0显存不够时最重要的经验是优先降 batch 而不是降 img。img 降太多会丢失小目标检测精度,口罩这种中等尺寸目标影响不大,但如果你还想检测人脸的细枝末节,就别低于 480。batch 降了之后如果显存还有余量,可以考虑开启梯度累积,等价于增大 batch 但显存占用不变。
4.3 迁移学习与超参数选择
YOLOv5 提供了多个预训练模型,从 s、m、l、x 依次变大。口罩检测属于少类别简单任务,yolov5s.pt 是效率和精度最均衡的选择,训练速度快,部署也方便。如果追求更高精度且显存充足,换 yolov5m.pt,但推理速度会慢一些。
训练过程中的超参数在一个 yaml 文件里,常见的 hyp.scratch-low.yaml 适合初学者,里面的核心参数包括:
| 参数 | 典型值 | 作用 |
|---|---|---|
| lr0 | 0.01 | 初始学习率,太大损失震荡,太小收敛慢 |
| lrf | 0.01 | 最终学习率与初始学习率的比值 |
| momentum | 0.937 | SGD 动量,加速收敛并稳定方向 |
| weight_decay | 0.0005 | L2 正则化,防过拟合 |
| warmup_epochs | 3.0 | 预热轮数,前几轮用低学习率稳定训练 |
| box | 0.05 | 边框损失权重 |
| cls | 0.5 | 分类损失权重 |
新手不推荐一上来就大改超参数。先按默认值训练一轮,看 loss 曲线和 mAP 曲线,如果 loss 下降不够快再调 lr0。我常用的调法是把 lr0 从 0.01 调到 0.005,因为数据集小的时候学习率太大容易在最优解附近震荡。weight_decay 如果要调,也只动一个量级上下,改大了模型可能欠拟合。
迁移学习下有个细节:weights 传 yolov5s.pt 时,模型会自动冻结部分骨干层,训练一段时间后如果精度上不去,可以删掉 runs/train/name/weights/last.pt 再从头训,或者取消冻结层直接全量微调。YOLOv5 的 train.py 里 freeze 参数默认是 0,表示不冻结,但如果是加载官方预训练权重,前几轮仍会有 warmup 机制帮模型稳定。
训练完成后,模型保存在 runs/train/exp/weights/best.pt 和 last.pt。best.pt 是验证集上表现最好的权重,last.pt 是最后一轮的权重。做毕设演示、写论文、做系统集成,都只用 best.pt,这是第一个该记住的路径。
5. 常见问题排查:训练翻车和推理报错的五个血泪坑
我拿这个包实际跑了一遍,把最容易遇到的坑按现象、原因、解决整理出来。每条都是真踩过的,按顺序看能少走弯路。
5.1 训练直接崩溃:CUDA out of memory
现象:训练刚开始几十步,终端弹出 RuntimeError: CUDA out of memory,程序中断。
原因:显存不够。口罩检测数据集虽然不大,但 batch 16 加 img 640 在 6GB 显存卡上就是极限边缘,再叠加 workers 多进程开数据,显存峰值就会爆掉。还有可能是你同时开着多个 Jupyter notebook 内核占显存。
解决:先 nvidia-smi 看显存占用,把不用的进程结束掉。然后把 batch 降到 8,img 降到 480,workers 设 0。如果还要继续训练,加 --device 0 明确指定显卡,避免 YOLOv5 在双显卡机器上默认选到集成显卡或已占用的卡。
5.2 训练卡在初始化:wandb 报错或长时间无响应
现象:train.py 启动后一直卡在 Weights & Biases 相关日志,或者弹出需要登录 wandb 的提示,训练进度不动。
原因:YOLOv5 默认集成了 wandb 日志功能,第一次运行时会尝试联网初始化。网络环境不好的时候会一直等,甚至报 Connection error。
解决:训练命令加 --wandb_off 或 --project runs/train 参数禁用 wandb。命令行里直接写:
python train.py ... --wandb_off这样所有日志只写到本地 runs 目录,不影响训练流程。很多人以为卡在数据集加载,其实是 wandb 在等网络。
5.3 标签文件报错:Label class exceeds classes
现象:训练启动时报错 ValueError: Invalid label class / Label class 2 exceeds classes = 2。
原因:标注文件里的类别ID 最大是 2,但 classes.txt 只定义了 2 个类,ID 范围只能是 0 和 1。这个情况通常是标注工具导出时类别序号从 1 开始,或者整理标注时混入了其他类别的文件。
解决:用前面第 2.2 节的校验脚本扫一遍 labels 目录,找出越界文件。如果那个文件的类别确实是新类别,加到 names 里并修改 nc;如果是误标,直接删掉那一行,然后重新生成 train.txt 路径列表。
5.4 训练了 100 轮,mAP0.5 还是 0.3 以下
现象:训练过程不报错,loss 下降也正常,但 val 跑出来的 mAP0.5 始终低于 0.5,检测框乱飘。
原因:最常见的是数据集本身有大量误标注,比如口罩区域标得过大把额头也框进去了,或者标注框不贴边。另一个常见原因是数据划分时随机种子导致某一类图片全部进了验证集,模型没见过,自然检不出来。
解决:打开几张训练图片,用 OpenCV 画框核对标注是否贴合目标。检查 train 和 val 里 mask 和 nomask 的类别分布比例,两集类别占比应该接近。如果有明显偏差,重新划分数据并按固定随机种子生成,再训练一次。
5.5 Docker 里跑推理,GPU 识别不了
现象:在容器里执行 detect.py,提示 Device not available 或 CUDA error,但宿主机上 nvidia-smi 正常。
原因:容器缺少 GPU runtime。docker run 时没加 --gpus all,或者宿主机没装 nvidia-container-toolkit,容器内根本没有 NVIDIA 驱动映射。
解决:宿主机装好 nvidia-container-toolkit 后,启动容器必须加 --gpus all。已启动的容器退出后重新用正确参数启动。如果你在 Dockerfile 阶段就装好了 CUDA 相关依赖,这一步通常不会有问题;排查顺序是先宿主后容器,别一上来就重装驱动。
5.6 补充一个隐蔽问题:中文路径导致加载失败
现象:代码不报错,但训练时图片加载总跳过一部分,loss 曲线异常,验证指标和推理结果对不上。
原因:Windows 下路径含中文或空格,OpenCV 和部分 Python 库对非 ASCII 路径支持不完全,导致图片读取失败或标注文件找不到。
解决:整个项目路径用纯英文,不要出现「项目」「毕设」这类中文目录名。这个是所有下载包里最容易忽略但影响最大的一环,尤其毕设文件经常放在桌面中文文件夹下。改完路径后重新启动训练,你会发现一切正常了。
6. 最后一步:用 tutorial.ipynb 验证模型,把流程固化下来
训练完不是结束,还得有一套快速验证流程。我通常直接用这个包里的 tutorial.ipynb 完成推理验证和指标评估两步。
推理验证命令:
python detect.py \ --weights runs/train/mask_exp1/weights/best.pt \ --source dataset/images/val/ \ --conf-thres 0.5 \ --iou-thres 0.45 \ --save-txt--conf-thres 是置信度阈值,0.5 是常规设置,检不出框就往下调;--iou-thres 是 NMS 的 IoU 阈值,0.45 适合口罩这类重叠较少的场景,如果画面里多人密集,适当调到 0.5 减少误抑制。--source 可以指向图片目录或单张图片,也可以指视频文件或摄像头设备号,做完毕设演示时用摄像头实时检测会很加分。--save-txt 可以把检测结果保存成 txt 文件,量化分析误检漏检时用。
指标评估跑一条命令:
python val.py \ --data mask.yaml \ --weights runs/train/mask_exp1/weights/best.pt \ --batch 16 \ --conf-thres 0.001 \ --iou-thres 0.6验证集评估会输出 precision、recall、mAP0.5、mAP0.5:0.95 四项核心指标。口罩佩戴检测场景,mAP0.5 在 0.9 以上基本算可用,0.95 指标在 0.7 左右已不错。答辩或报告里贴这张输出表,比任何文字说明都直观。
验证通过后,如果你想做成一个可交付的小工具,导出成 ONNX 格式然后用 ONNXRuntime 推理是常见做法,便于部署到 CPU 环境:
python export.py \ --weights runs/train/mask_exp1/weights/best.pt \ --include onnx \ --opset 12我个人的习惯是每拿到一套这种打包资源,先在 tutorial.ipynb 里把推理跑通,再训练,最后用 val.py 出一份指标存档。这类找到 best.pt 的过程已经成了一套路子,项目做完之后把训练日志、评估结果和目录结构整理好,交上去基本不用返工。这个流程里我踩过的最大一个坑就是拿到资源直接开训练,浪费一晚上才发现数据集标注有问题。从那以后我每次训练前都强制走一遍标注校验,再动 train.py,这个习惯帮我省了很多返工时间,希望帮到你。
本文还有配套的精品资源,点击获取