资讯动态

yolov7口罩检测实战:从数据标注到模型部署的避坑指南

发布时间:2026/10/18 6:37:24 来源:尧图企业网站定制
简介基于YOLOv7的口罩检测模型资源包适合需要快速搭建口罩佩戴识别功能的开发人员、安防项目团队及相关专业学生能判断正常佩戴口罩、未戴口罩和口罩佩戴不规范三类状态适用于机场、商场、校园等人员密集场所的实时防疫监控。压缩包包含150个文件整体约669MB其中权重文件可一键加载Python脚本负责训练和推理yaml文件用于模型参数配置另附标注样例、示例图像、日志记录与说明文档结构清晰便于直接验证和改进。已有3049人学习模型实测精度约93%并内置多个训练好的权重附有详细使用教程读者可结合教程快速集成到监控视频流或智能终端中减少从零训练的时间成本。资源包中另含训练日志与预测结果图像可帮助复盘训练过程整体目录组织规范适合作为课程设计、毕业设计或企业原型验证的参考素材。1. yolov7口罩检测看起来谁都能训落地却总在翻车学校门口、工地闸机、商场入口这类场景里的口罩检测看起来是目标检测的入门题——基于yolov7的口罩检测模型下载预训练权重、标几千张图、训一晚上mAP就能到0.95。但真把模型接到摄像头前面问题才刚开始五米外的人脸只有二十几个像素口罩挂在下巴上的违规动作被当成正常逆光下整张脸黑成一片模型要么漏检要么乱框。这篇笔记不打算重复网上那些「一行命令出模型」的教程。我把数据怎么标、模型怎么训、参数怎么调、上线前怎么验收这条链路完整讲一遍里面的坑大多是我自己翻车翻出来的。适合正在做门禁、考勤、工地安全与口罩联检这类落地项目的工程师和学生。2. 为什么选yolov7口罩检测对模型的三个硬要求2.1 口罩检测不是「分类」而是「小目标遮挡密集」三合一问题先说为什么常见的人脸检测口罩分类两阶段方案在落地时容易翻车。第一步人脸检测框如果不够准截出来的图要么少了额头、要么把背景切进来第二步的口罩分类器拿到的输入分辨率往往只有几十像素下半脸又被口罩盖住分类器只能靠肤色和纹理猜误判自然多。更麻烦的是远处的人脸框本身就小两阶段pipeline会把误差逐级放大。所以这类任务通常直接用单阶段检测模型一次输出「位置类别」端到端训练误差不叠加。yolov7口罩检测要同时扛住三个压力。小目标五米外的人脸在640分辨率输入下通常只有20到30像素模型的多尺度输出头里负责小目标的浅层特征图得能捕捉到眉眼的弱纹理。遮挡口罩遮住口鼻下半脸信息基本失效模型实际是在靠眉眼、额头和脸型判断这要求特征提取足够鲁棒。密集闸机口排队时人与人互相遮挡NMS参数稍紧就会把相邻目标吃掉。这三个压力决定了一个事实模型既要对小目标召回好又要推理够快还不能在遮挡场景里乱框。要补一句判断如果你的场景是「门口单人在两米内刷卡」那用什么模型都行麻烦只会出现在距离远、人流密、光线乱的真实环境里。下文所有讨论都以这个现实场景为前提。2.2 yolov7的几个关键设计在口罩场景里值在哪yolov7不是横空出世的算法它是在yolov5基础上改结构、加训练策略的产物。我在口罩数据集上多次对比后确认它对小目标和遮挡的收益集中在四件事上。第一是ELAN结构。它把不同层的特征通过更密集的梯度路径串起来浅层细节和深层语义能更好地融合。对口罩检测来说浅层特征负责眉眼边缘深层特征负责「这是一张人脸」的整体判断两者融合好了低分辨率人脸才不至于只有轮廓没有证据。第二是辅助训练头。yolov7在训练时额外接一个aux head用更粗粒度的特征参与loss计算推理时去掉。它相当于给小目标召回加了一个免费助推训练成本增加不多但五米外小人的漏检明显减少。第三是重参数化卷积。训练时的多分支结构在推理时被融合成单路卷积模型跑起来更快这对动辄几十路摄像头并发推理的现场很重要。第四是autoanchor机制它会根据你的标注框重新聚类anchor比手工猜anchor靠谱尤其适合人脸这种宽高比相对固定的目标。不过要冷静一点这些设计解决的是「同样的数据下多榨几个点」的问题不是「数据很烂也能训好」的问题。口罩检测的精度瓶颈通常不在模型结构而在标注口径和场景分布这一点后面两章会反复强调。2.3 选型对比yolov7比yolov5和yolov8好在哪、差在哪如果你正在这几个常被问到的模型之间做选择我按口罩检测场景画了一张对比表不是跑分表是落地感受模型精度与速度特点部署生态适合场景yolov7速度和精度平衡小目标略强TensorRT资料多后处理坑有现成答案已有yolov5经验、需要提点提速的团队yolov5稳社区排错贴最多老设备兼容最好快速出demo、团队不熟yolov7yolov8API友好内置增强多新框架部分旧芯片部署资料少原型验证多、部署环境新的项目我的默认选择是yolov7。原因很实际口罩检测是二分类小模型yolov7和yolov5在最终mAP上差距通常不超过一个点但yolov7的aux head让远距离小人漏检更少yolov8虽然训练代码更优雅但很多老相机、老工控机环境里别人踩过yolov7的TensorRT坑你遇到的概率就小。做落地项目选「坑有人踩过」的比选「指标好看」的更稳。什么时候别用yolov7如果你的设备是树莓派级别的内存和算力用yolov7-tiny如果项目要用最新的训练API快速试错且部署环境全在新平台上yolov8更省事如果公司已经有完整的yolov5前后端链路别为了零点几个点迁移。选型是成本问题不是面子问题。2.4 先跑通一次推理确认环境再决定要不要投入在开始标数据之前我强烈建议先用仓库自带的预训练权重把流程跑一遍。这一步不产出口罩检测效果目的是确认环境、显存、依赖没问题免得标了两千张图才发现训练起不来。git clone yolov7官方仓库地址 cd yolov7 pip install -r requirements.txt python detect.py --weights yolov7.pt --source test.jpg --img-size 640这个detect.py会用COCO上预训练的yolov7权重对一张普通图片做检测输出到runs/detect目录。如果你看到画面里person被正确框出来说明依赖装好了、模型能跑如果报错先看是不是权重没下载完整或者版本不匹配。等后面训练出best.pt把命令里的--weights换成best.pt就能直接检测口罩。注意这里的detect.py逻辑它对每张图先做letterbox缩放再做一次前向推理最后做NMS然后把框画回原图。口罩场景里如果输入图宽高比很极端letterbox产生的灰边会占掉一部分分辨率实际有效分辨率比640更低这就是为什么很多人在超宽监控画面上感觉模型变笨了。这个问题后面讲部署时还会碰到。3. 数据准备标注规范、格式转换与难例挖掘3.1 标注口径两类还是三类先想清楚再动手我在好几个项目里吃过标注口径不一致的亏所以先把这一节放在最前面。口罩检测最常见的类别设计有两种两类with_mask / without_mask或者三类加一个incorrect表示口罩没戴好。两类方案简单标注快模型也好收敛三类方案能直接识别「口罩挂在下巴」「口罩遮住眼睛」这类违规动作但incorrect和without_mask在视觉上经常只差几厘米标注员很容易把同一张图标出不同类别模型会在这两个类之间反复摇摆。我的经验是首版先做两类把口罩不在口鼻位置的样本统一归入without_mask。为什么因为你要检测的本质是「这个人的口罩戴没戴好」而不是给口罩状态做精细分类。先保证without_mask的召回上线后如果业务方确实需要区分「没戴」和「戴一半」再补第三类重训而不是一开始就把标注一致性搞崩。标注框的规范也要写进给标注团队的文档里框住人脸主体不要包含太多头发和肩部口罩遮住脸时按可见轮廓标注目标很密集、互相遮挡时也要逐个标注不能因为「看不清」跳过——漏标比标错更难排查模型会把漏标的对象学到背景里去。3.2 从VOC XML到YOLO txt转换脚本与四个边界坑多数标注工具导出的是VOC格式的XML而yolov7训练需要的是每张图一个同名txt每行「类别id cx cy w h」的归一化坐标。转换脚本不复杂但边界坑不少下面这个脚本是我常用版本的浓缩import xml.etree.ElementTree as ET CLS_NAMES {with_mask: 0, without_mask: 1} def convert_voc(xml_path, img_w, img_h, out_txt_path): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLS_NAMES: continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) cx ((xmin xmax) / 2) / img_w cy ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) if w 0.001 or h 0.001: continue lines.append(f{CLS_NAMES[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w, encodingutf-8) as f: f.write(\n.join(lines))这个脚本的逻辑很直白解析XML里的每个object把左上右下角坐标换成归一化的中心点和宽高再按yolov7要求的格式写进txt。传入的img_w和img_h是原图尺寸必须和XML里size字段一致否则归一化坐标会整体偏移。这里四个坑要特别说明。第一类别id一定要从0开始和后面配置文件里的names顺序对死否则模型训练时类别错位mAP再高都是假的。第二clip操作会把贴边框的中心点拉向图像内部如果目标一半在画面外clip后的框其实是错的——这类图应该人工复核或者直接不标别指望脚本替你纠正。第三宽高小于0.001的框说明标注质量极差或者目标极小保留它会污染box_loss直接丢弃并打印日志方便追溯。第四读写都指定utf-8编码Windows环境下XML里带中文路径时默认编码处理会直接报错。3.3 难例挖掘把「戴歪的口罩」喂进训练口罩检测最容易漏的图往往不是标准正脸而是逆光、侧脸、低头、口罩挂在下巴、手正好挡住嘴这些日常动作。训练数据里如果没有足够的这种样本模型学到的就是「正中光线下完整人脸」的强特征一换环境就失灵。难例来源最靠谱的是目标场景实地录视频再抽帧。我的做法是在每个点位录30分钟视频按每5秒抽一帧抽完人工过一遍把模糊帧和重复帧去掉剩下的大概率覆盖了逆光、人流密度变化、各种姿态。注意抽帧要按时间段分布不要集中在同一个小时的连续画面否则又是一批同源数据。数据增强方面yolov7的mosaic默认是开的四张图拼一张小目标数量变多变密对提升远距离人脸召回很有帮助。但如果你的loss在训练中后期一直震荡可以试试把mosaic关掉或降到0.5——它不是越多越好拼图产生的伪影在二分类任务上有时弊大于利。mixup同理。口罩检测的瓶颈大多是数据分布问题而不是数据量问题增强只是辅助手段。3.4 数据划分与类别平衡先统计再训练划分数据集时最容易犯的错是随机划分。同一段视频相邻帧之间高度相似随机划分会把同一批人的前后帧同时分进train和val验证集失真训练时模型相当于提前看到了验证对象。正确做法是按「视频片段」或「摄像头」划分一个片段的帧全部进train或全部进val这样验证集才是真正没见过的画面。训练前我还会跑一个类别统计脚本看看正负样本比例。代码很简单就是遍历label目录统计每个类别出现次数import os from collections import Counter counts Counter() for split in [train, val]: label_dir fdatasets/mask/labels/{split} for filename in os.listdir(label_dir): with open(os.path.join(label_dir, filename), encodingutf-8) as f: for line in f: cls line.split()[0] counts[(split, cls)] 1 print(counts)这个脚本会输出每个split下每个类别的标注框总数比如train的with_mask有8000个、without_mask有2000个。如果两类样本比例超过1:5我会先补少数类数据而不是急着在loss上加权重。数据侧解决类别失衡比调参可靠因为你增加的样本同时也在丰富场景多样性。比例在1:1.5到1:2之间时不用处理这个任务天然允许一定偏斜。4. 训练口罩检测模型目录、配置与参数4.1 目录结构先建好能复现的数据仓库yolov7训练时靠的是「图片路径同名txt标签」这套隐式对应关系目录结构不规范后面所有脚本都会跟着乱。我习惯这样组织mkdir -p datasets/mask/images/train mkdir -p datasets/mask/images/val mkdir -p datasets/mask/labels/train mkdir -p datasets/mask/labels/val图片放images下同名txt放labels下文件名除了扩展名外必须完全一致。比如images/train/00001.jpg对应labels/train/00001.txt。训练前我还会跑一条bash命令检查有没有图片没标签、或者标签没图片的孤儿文件for f in datasets/mask/labels/train/*.txt; do base${f%.txt} [ -f $base.jpg ] || echo missing image: $base.jpg done这个检查很重要。标签和图片对不上是最隐蔽的训练事故之一它不会让你训练报错只会让模型在某个类上悄悄变笨事后极难排查。把这条命令也跑一遍val目录两边都干净再进下一步。4.2 修改配置五个必改项第一步是写数据配置我一般放在data/mask.yaml里train: datasets/mask/images/train val: datasets/mask/images/val nc: 2 names: [with_mask, without_mask]train和val写相对路径注意是从yolov7仓库根目录出发的路径别写成绝对路径否则换机器训练还要改配置。nc是类别数names的顺序要和转换脚本里CLS_NAMES的id完全一致。第二步是改模型配置。训练用的模型配置文件在cfg/training/yolov7.yaml注意是training目录下的那个不是cfg/deploy下的——deploy版本没有aux head直接拿它训练会少一个关键的小目标辅助分支很多人在这里翻车。只需要把文件里的nc改成2其他结构参数不要动。注意训练用cfg/training/yolov7.yaml推理导出用cfg/deploy/yolov7.yaml二者不能混用。第三步是anchor。yolov7在训练时会根据你的标注框自动重新聚类anchor所以手动改anchor通常没必要。除非你的目标尺寸分布极其特殊否则先让autoanchor自己跑。第四步是超参数。hyp.scratch.yaml里我常调的是mosaic和mixup前面说过loss后期震荡时把mosaic降到0.5试试学习率lr0默认0.01如果batch-size比较小8以下我会降到0.005防止梯度更新太激进。第五步是输入分辨率。img-size默认是640如果场景人脸普遍很小可以试736或768但速度会明显下降。通常640起步训练完用难例集验证再决定要不要加。4.3 训练命令与参数调法配置改完就可以启动了。下面是我在口罩数据集上最常用的一条命令python train.py \ --weights yolov7.pt \ --data data/mask.yaml \ --cfg cfg/training/yolov7.yaml \ --epochs 120 \ --batch-size 16 \ --img 640 \ --workers 8 \ --device 0 \ --cache-images参数逐个说清楚。--weights用COCO预训练权重yolov7.pt迁移学习起点必须是它从随机初始化训练二分类模型既慢精度又差--data和--cfg就是上面改好的两个文件--epochs我一般先给120口罩数据集规模不大60轮左右通常就收敛了多出来的轮数靠best.pt机制兜底--batch-size 16在24G显存的卡上没问题8G卡降到4到8显存不够先降batch而不是降分辨率--img 640是速度和精度的折中--workers 8是数据加载线程数Windows下如果卡在加载阶段把它改成0--device指定GPU--cache-images把训练图一次性缓存进内存小数据集能明显加快每轮迭代缺点是吃内存数据集超过两万张就不要开。训练日志和权重默认输出到runs/train/exp目录每次运行自动递增exp1、exp2。我习惯启动后用nvidia-smi看一眼显存占用确认没有OOM风险再离开。如果OOM把batch-size砍半重跑不要先动分辨率。4.4 训练中看什么loss与mAP怎么配合训练开始后最值得盯的文件是runs/train/exp*/results.png它画了每个epoch的box_loss、obj_loss、cls_loss和mAP曲线。口罩是个二分类任务cls_loss通常很快收敛成一条平线不用管重点看box_loss和obj_loss是否稳定下降以及mAP0.5是否持续抬升。如果P精确率很高但R召回率一直上不去说明漏检集中在难例上大概率是数据问题而不是训练问题去补难例数据比继续加epoch有用。如果mAP0.5连续15到20个epoch不增长我就手动停掉用已经保存的best.pt。yolov7默认保存两份权重best.pt和last.ptbest.pt是验证集mAP最高的那一版last.pt是最后一轮最后用best.pt。训练中途断掉也不用从头再来python train.py --resume runs/train/exp/ --data data/mask.yaml注意resume时保持原来的数据配置和目录不变改了路径会加载不到状态。也别把--weights再填一遍resume逻辑会自己读last.pt。恢复后先确认日志里的epoch数接得上再接再训。5. 训练与落地避坑五条血泪经验5.1 口罩挂下巴被当成「第三种状态」漏掉现象测试视频里有人把口罩拉到下巴或脖子上模型不输出框或者输出的置信度只有0.1出头而业务恰恰要求这种「没戴好」也要报警。原因标注口径里把这类样本归入了without_mask但数量太少。with_mask和without_mask的视觉差异主要是「口罩位置」模型学到的是「下半脸有没有被遮住」这个强特征。挂下巴时下半脸暴露按逻辑应该判成without_mask但样本少模型根本没把「下巴位置的口罩」和「没戴口罩」关联起来最后表现出来就是干脆不输出。解决把「口罩不在口鼻位置」的样本全部归入without_mask并针对这个难例做定向补数据。我实测过把挂下巴样本从二十来张补到两百张后这个难例上的召回涨了约五个百分点。补数据比调阈值有效得多因为本质是特征分布没覆盖到。同时类别的判定规则要写进标注文档训练前抽查10%样本的标注一致性别让标注员按自己的感觉自由发挥。5.2 验证集与训练集同源mAP虚高现象训练时mAP0.5到0.95同一批摄像头抽帧的验证集也是0.94但模型部署到一个没参与过训练的新摄像头mAP掉到0.75左右。原因随机划分把同一段视频的前后帧同时分进train和val模型实则靠背景纹理和画面连贯性在认人而不是靠人脸本身。验证集和训练集越像mAP就越不可信。判断方法很简单如果train loss和val loss差距小到异常而且验证集里几乎每张图都跟训练集某几张图「长得像」大概率就是同源了。解决按视频片段或摄像头划分数据集一个片段的帧全部进train或全部进val。另外无论数据怎么划分都要单独留一组跨场景难例集永远不参与训练和验证只在每次训练后测试。这组数据才是你对外承诺精度时真正要看的依据。5.3 loss曲线正常但mAP震荡像是玄学现象box_loss和obj_loss稳定下降但mAP0.5在0.85到0.95之间来回跳同一份数据重训一次最后的结果还不一样。原因验证集太小统计噪声大几百张图里多漏一张脸指标就掉一个点mosaic和mixup每轮生成不同的拼接图等于每轮数据分布都在变学习率策略在收敛末期不够平滑导致权重在最优解附近来回摆动。解决先把验证集扩到500张以上这是成本最低的手段然后固定随机种子保证每次实验可比再把lr0从0.01降到0.005或者把hyp里的lr_scheduler改成cos让末尾段学习率平滑下降。另外yolov7默认开了EMA指数滑动平均best.pt是带EMA的权重不要拿last.pt直接上生产那版权重通常更颠。我遇到这类问题通常先改验证集规模因为最容易发现是「模型没训好」还是「指标本身在跳」。5.4 新场景漏检光线、角度、分辨率三个变量现象室内灯光下准确率很高一到户外逆光就漏检平视摄像头没问题从下往上拍的仰角画面漏检严重。原因训练数据里逆光样本和仰角样本太少模型对亮度通道和姿态分布非常敏感。口罩目标本身特征就弱光线或角度一变眉眼轮廓的纹理被高光或阴影压掉模型就认不出来。解决到目标场景实地录视频补样本覆盖逆光、侧光、仰角、低头四种最常翻车的条件。逆光样本选在早上和傍晚两个时段各录一段仰角样本用低位安装的摄像头拍。抽帧时分散在不同时间段避免集中在同一小时内的连续帧否则补进来的又是不平衡的同源样本。补完再跑一遍难例集看这几类漏检有没有针对性改善。5.5 显存不够就降分辨率结果小目标全丢现象8G显卡跑640分辨率OOM把--img降到416就能训模型也能收敛但一测远端小人全漏检。原因分辨率从640降到416输入面积缩小了近60%原本20到30像素的人脸变成12到18像素小目标特征几乎消失数据增强也救不回来。这不是模型变差了是输入信息本身不够。解决优先降batch-size其次开--cache-images减少IO压力再不行换yolov7-tiny从头训。降分辨率是最后手段而且要在降完之后用难例集重新验证一遍而不是只看mAP没降就放心。如果必须用416跑输入端的裁切策略也要跟着改比如超宽监控画面先做目标区域裁切而不是整幅图缩进去。6. 用「难例集」验收模型比盯着mAP靠谱的固定动作6.1 建一个不进训练集的难例集在项目目录旁边放一个hard文件夹选20到30张真正让模型难受的图逆光、密集排队、口罩挂下巴、五米外的小人、仰角视角。这些图只用来验收永远不进训练集。我每次换数据、改参数、调超参之后都要跑一遍。python detect.py --weights runs/train/exp/weights/best.pt \ --source hard/ --conf-thres 0.2 --img-size 640detect.py会遍历hard目录把带框结果保存到runs/detect目录。--conf-thres设0.2比线上常用的0.25更严苛逼着模型把低置信度目标也框出来这样你能看到它是「漏了」还是「框了但置信度低」两种情况处理方式完全不同。漏了去补数据置信度低则先调阈值或看是不是输入分辨率不够。6.2 养成先跑难例集再看指标的习惯我现在训完模型的第一件事不是看mAP而是拿最新best.pt跑一遍这20来张难例图和上一次训练的输出逐张对比。漏检数变多了先查数据或参数变动而不是急着部署。我自己靠这个习惯抓住过好几次「mAP没掉但难例漏检悄悄变多」的回归问题。阈值选择上也给一个实际经验口罩检测场景里漏检的代价通常比误检高比如没拦住一个没戴口罩的人远比把海报人脸误报一次严重。线上阈值我一般设在0.2到0.25如果误报太多常见于海报、玻璃反光上的脸就往上调并增加目标跟踪去重不做单帧硬判。mAP是统计数字难例图上漏了就是漏了骗不了人这个习惯帮我挡掉了好几次翻车。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑