口罩佩戴识别实战:从数据清洗到边缘部署的YOLO检测全流程
发布时间:2026/10/21 3:43:14 锦皓数字建站

做口罩佩戴识别这几年在园区门禁、车间闸机、考场入口这些场景里大量落地乍一看是个很成熟的活儿随便拉个检测模型就能跑。但真正把系统部署到现场时你会发现模型能跑和模型稳定能用之间隔着一条很宽的沟。我们最近在做一个代号为 YOLO26 的口罩佩戴智能识别实验项目完整走了一遍数据整理、模型训练、推理部署的流程踩了不少可以写进文档的坑。这篇文章把整套实现方法复盘一遍为什么用目标检测而不是分类数据上哪些细节直接决定效果上限训练参数怎么调才能收敛得快还不容易过拟合以及上了摄像头实时流之后会遇到哪些意想不到的问题。适合两类人看刚入门目标检测的读者可以把它当一个从零到一、逻辑完整的案例课已经做过检测项目的工程师重点建议看数据增强和边缘部署那几节那是实际项目里最容易翻车的地方。先亮个底这个项目最终的模型很小推理速度也够但做完之后最大的体会是模型算法只占整个系统的一小部分决定成败的往往是数据治理和工程细节。1. 需求拆解与方案选型这为什么是一个目标检测问题1.1 单一分类模型搞不定真实场景“判断一张图里有没有人戴口罩”这句话说成分类问题听起来没毛病。但摄像头画面里往往是多人同时出现有的人戴了有的人没戴有的人把口罩拉到下巴上只遮住嘴没遮住鼻子人脸大小差异也很悬殊离镜头近的人脸可能占整个画面的三分之一走廊尽头的人脸可能只有十几个像素。这种“既要找得到人又要判断戴没戴”的需求天然是一个多目标检测任务。如果硬要用图像分类来做也不是完全不行管线会变成先跑一个人脸检测器把人脸裁出来再逐个送入分类网络。两个模型串联误差叠加放大人脸检测漏掉一个后面分类再好也救不回来而且两段推理加在一起很难在低算力设备上达到实时要求。检测模型的好处是一个前向过程同时输出目标位置和类别流程短、速度快、误差传递也少。这个任务在技术分类上属于通用目标检测里的双类检测类别数很少场景变化却很丰富难点不在算法复杂度而在数据分布和泛化能力。很多初学者容易把精力全放在调模型结构上等你调到吐不如回头把训练数据里那几百张暗光、模糊、遮挡样本补齐这个事放在后面细说。1.2 从速度、精度、生态三个维度对比检测方案选型时认真对比过几类方案。最早考虑传统视觉方法用肤色分割找脸部区域再用几何特征判断口鼻位置有没有被遮挡。这个方法在光线稳定、背景干净的实验环境里能玩出花但一到真实闸机口反光、逆光、肤色相近的背景就能把它彻底废掉完全不具备泛化能力。两阶段检测器在精度上通常占优它对小目标召回确实好但速度是硬伤。口罩识别系统要按帧跑视频流对延迟有要求两阶段模型在服务器上也许勉强够用到边缘设备上帧率就跌到没法看。单阶段检测器平衡了速度和精度社区资料最多遇到问题基本都能找到现成经验工程落地最省心。我还对比过另一个思路人脸检测加分类网络串联。优点是每个环节可以单独替换优化但工程复杂度高端到端调试时问题容易被来回踢皮球——是前置检测漏了还是分类判错了排查起来非常费劲。单阶段检测模型一出错基本都能定位到是样本问题还是阈值问题排障链路清晰得多。最终选型很明确实时性要求高的场景用单阶段检测模型做骨架再针对性做改进。下表是当时做的简单对比。方案推理速度小目标精度工程生态结论传统视觉方案快差一般环境变化大放弃两阶段检测器慢高较成熟无法满足实时边缘部署人脸检测 分类串联中中一般管线复杂调优成本高单阶段 YOLO 系检测器快中高非常成熟作为项目基线1.3 YOLO26 这个编号的由来与实验设计先解释一下“YOLO26”这个编号免得有人较真。它不是某个公开版本号而是项目内部立项时的实验代号对应第 26 版实验记录。整套方案的核心逻辑是以公开的 YOLO 系单阶段检测框架作为基线针对口罩检测这种类别少、场景杂、实时性要求高的任务在数据策略和训练策略上做专门优化同时小范围尝试注意力机制和轻量化结构替换。你可以把它理解成一套搬家方法论不管你的基线用的是哪一代 YOLO 实现思路都可以直接照搬。实验设计分三路做比对。第一路是原始基线直接拿预训练权重在数据集上微调第二路在基线上加针对性数据增强和类别均衡策略第三路换轻量化 backbone 并做蒸馏。三路模型在验证集上的表现分别记录方便定位每种改动的真实贡献。这种“先保底线再逐步加料”的结构最大好处是每次改动都能看清变量带来的影响不会出现模型效果变好了、但不知道是谁的功劳这种尴尬局面。2. 数据工程决定模型上限的隐蔽战场2.1 数据采集与清洗不要直接拿一堆开源数据就开工口罩检测的公开数据源不少但质量参差不齐。有的数据是纯人脸特写一整张图一个人脸到真实现场根本不够用有的标注粒度不统一有的框脸有的框口罩混在一起模型学出来的位置偏差很大。我的建议是公开数据可以拿但只能当底料用之前必须做两件事。第一把所有源的数据统一成同一种标注格式第二筛掉极端样本比如分辨率小于 20 像素的人脸、标注框越过原图边界、整张图模糊到人眼都看不出来的情况。这些样本只会给训练增加噪声不会带来有效信息。经验数据是要做出一套能支撑现场使用的模型每个类别建议至少有 5000 个有效实例整体训练图片在 1 万张以上。少于这个量级泛化能力会很飘。公开数据凑不齐的部分最有效的方式是自己到现场录。找一个摄像头视角固定的位置录半小时视频按帧抽图场景覆盖度立刻不一样。我当时的抽帧策略是每 5 秒抽 1 帧避免相邻帧太像导致重复样本。清洗这一步强调再多都不够。网上搜集过来的数据命名可能五花八门标签文件可能缺行漏项甚至同一个文件夹里图片和标注数量对不上。实际踩过的坑是某个数据源把“without_mask”和“with_mask”两个类别的标签顺序搞反了训练出来的模型对“没戴口罩”几乎零召回排查了很久才发现是标签贴反。所以数据集合并之后一定要花时间做一次标签字段校验写个小脚本检查每个标注框的长宽、坐标是否越界、类别编号是否在合法范围内。这种脏数据不清理后面所有工作都白做。2.2 标注规范与格式转换标注工具的选择相对简单找个能导出框坐标的开源标注工具就够了。重要的是规范类别顺序固定为 0 代表 with_mask、1 代表 without_mask一旦确定就不要改动。后面改类别顺序意味着所有标签文件重写而且已训练权重里分类头的索引已经固化强行改会造成灾难。YOLO 系列使用的标注格式是归一化的中心点坐标和宽高每行由类别 id 加四个小数组成。如果你拿到的是 COCO 或 VOC 格式的数据需要做一次转换。下面是我做格式清洗时用的一段最小工具代码按需保留 category_id 到自定义 id 的映射就行。import os def convert_coco_to_yolo(coco_ann, out_dir, id_map): os.makedirs(out_dir, exist_okTrue) for img_info in coco_ann[images]: img_id img_info[id] width, height img_info[width], img_info[height] lines [] for ann in coco_ann[annotations]: if ann[image_id] ! img_id: continue cat_id id_map.get(ann[category_id]) if cat_id is None: continue x, y, w, h ann[bbox] x_c (x w / 2) / width y_c (y h / 2) / height w_n, h_n w / width, h / height lines.append(f{cat_id} {x_c:.6f} {y_c:.6f} {w_n:.6f} {h_n:.6f}) txt_path os.path.join(out_dir, img_info[file_name].replace(.jpg, .txt)) with open(txt_path, w) as f: f.write(\n.join(lines))这个函数只是最小实现真正用的时候还要处理没有对应标注的图片要生成空 txt 的问题以及图片路径和 txt 文件名严格一致的约定。很多框架训练时会根据图片名自动寻找对应 txt文件名对不上整个样本会被悄悄跳过且不报错表现就是训练集数量突然少了一半这种问题非常隐蔽排查要花半天。2.3 增强策略逼着模型去学“戴没戴”而不是学“口罩颜色”口罩检测模型有一个很隐蔽的陷阱模型容易依赖口罩颜色来判断。训练集里如果蓝色口罩占绝大多数模型很可能把“蓝色”当成正样本的重要特征现场一旦出现黑色、白色、花色口罩漏检率立刻上升。我采用的方法是做大尺度色彩扰动把色调、饱和度、亮度随机调整甚至按一定比例把图像转成灰度逼模型去学口罩和面部之间的轮廓、遮挡结构关系而不是色彩。实践下来这个增强策略比换一个更大的 backbone 带来的提升还明显。Mosaic 增强是 YOLO 系列训练里的默认选项把四张图拼成一张对提升小目标检测很有帮助。人脸检测中小目标很常见所以 Mosaic 要开但要注意控制强度拼接过的样本如果过于无规律模型前期收敛会变慢。随机旋转、裁剪、透视变换也要加用来模拟现场摄像头俯拍角度。随机遮挡增强我一开始没当回事直到发现现场经常有人抬手挡脸或者压低帽子才意识到这种遮挡类增强比想象中重要。有一件事给新手强调数据增强不能盲目堆数量。有些增强会让语义发生根本变化比如把人脸旋转 180 度这在真实监控场景里基本不会出现模型学到这种奇怪分布后反而影响正常场景精度。增强策略要贴近现场实际拍摄情况宁可少加也不要加出诡异样本。2.4 数据集的划分与验证集选择数据划分很容易做得草率但影响非常大。不能直接随机洗牌切分因为现场视频抽帧出来的数据相邻帧高度相似如果同一段视频的帧同时出现在训练集和验证集里验证集指标会虚高现场一测就露馅。正确做法是先按视频片段或采集时间段分组再在组级别上切分训练集、验证集、测试集。验证集要刻意覆盖不同光线、不同摄像头机位、不同人群密度确保看到涨点是真的泛化而不是对某一批数据过拟合。我还会专门保留一个困难案例集专门放那些人眼都觉得难判断的样本半张脸被手挡住、口罩挂在下巴上、画面极度暗光。每次训练完先跑普通验证集再跑困难集两边都看才能知道模型上限卡在哪里。只盯着 mAP 数字的人往往模型上线前就已经埋下了雷。3. 训练全流程实操从配置文件到完整的训练策略3.1 环境准备与显存规划训练环境的坑集中在版本匹配上。PyTorch 版本和 CUDA 版本不匹配会导致 GPU 不可用OpenCV 版本问题会导致读图报错numpy 版本过新或过旧都可能触发莫名其妙的问题。我的习惯是用 conda 单独建一个环境把 Python 版本固定在 3.9 或 3.10 附近先按 CUDA 版本选配套的 PyTorch 安装命令再装依赖。不要在已经塞满包的环境里强行加训练框架否则冲突排查会消耗大量时间。显存这块要提前算账。以输入分辨率 640、batch size 16 为例消费级显卡 8GB 显存通常够用但如果你开了 Mosaic 增强、大通道模型或累积梯度12GB 以下还是会紧张。显存不够的第一处理手段是降低 batch size 并同步调低学习率而不是直接缩小输入分辨率。640 这个分辨率对中小脸目标已经比较友好随便降到 416 会明显影响小脸召回。训练服务器和测试客户端的分工也要明确不要试图在开发机上跑所有实验。3.2 数据配置与模型参数修改使用 YOLO 系开源实现时数据配置通常是一个 data.yaml 文件。需要注意的字段就那几个训练集路径、验证集路径、类别数量 nc、类别名称 names。路径这里建议直接用绝对路径很多新人把代码目录和数据目录分开放相对路径一跑就报错改成绝对路径最省心。下面是一个典型的数据配置文件结构path: /data/mask_detection train: images/train val: images/val nc: 2 names: 0: with_mask 1: without_mask模型配置里除了把类别数改成 2还有一件容易被忽略的事预训练权重是按 COCO 的 80 类结构来的加载时会把模型头部的最后几层丢弃只保留特征提取部分。换句话说真正从预训练权重继承到的是 backbone 对通用图像特征的理解力分类头是从随机初始化开始学的。刚开始训练时如果学习率设得太大新分类头的梯度更新会非常剧烈导致 loss 不降反升。另外很多 YOLO 实现会自动计算锚框但人脸目标的长宽比非常集中基本接近 1:1和 COCO 那种五花八门的物体形状差异很大。我们实验里对训练集标注重新做了锚框聚类收敛速度和最终精度都有改善。如果你用的版本是 anchor-free 设计这一步可以跳过但无论如何都要检查一下目标尺度的分布心里有数。3.3 迁移学习与超参数策略迁移学习是这个项目里性价比最高的技巧。从头训练检测模型通常需要百万级数据而口罩识别作为微调任务最佳方式就是加载预训练权重后做参数继承。常见做法是先冻结 backbone 训练若干轮等分类头收敛得差不多再解冻全部参数做联合微调。冻结阶段用较低学习率解冻后可以稍微提高一点但不要激进。超参数上实测下来比较稳的组合是初始学习率 0.01以 batch size 64 为基准配合 warmup 和余弦退火weight decay 保持常见默认值epoch 数量在 120 到 200 之间配 EarlyStopping。单卡训练时 batch size 往往到不了 64学习率要按线性缩放法则调整batch size 减半学习率大致也减半。很多人训练不收敛第一反应是改网络结构实际上大概率是学习率太大或者数据没对齐网络很少是背锅的那一个。3.4 训练过程监控、早停与中断恢复训练时只盯终端打印的 loss 数字是不够的要同时看 train loss、val loss、mAP50 和 mAP50-95 四条曲线。一个常见现象是 train loss 持续下降但 val loss 开始反弹这是过拟合信号此时早停机制就该发挥作用。如果从头训练且没开预训练权重前几个 epoch 的 mAP 会是 0不要慌等 loss 降下来自然就有了。训练中断恢复这个问题说大不大说小不小。口罩识别模型一轮训练几小时很常见遇到断电、显存爆掉、服务器重启如果没开 resume 功能前面训练全部白费。所以训练脚本从一开始就要加好 resume 开关中断后从最近权重继续跑而不是重头再来。为了对比实验公平性还要固定随机种子否则做了两版实验最后分不清效果差异来自代码改动还是随机性。4. 评估、导出与部署能不能用才是硬道理4.1 用业务指标替代纯学术指标学术上衡量检测模型看 mAP但把 mAP 当项目验收唯一标准很容易翻车。口罩识别业务的真实诉求是没戴口罩的人经过系统必须报警漏报是比误报严重得多的事故同时误报太多现场管理人员会对系统失去信任最后把报警关掉系统就成了摆设。所以项目里我更关注三个数字召回率、假阳性率、单帧推理耗时然后拿这些指标去跟现场需求对齐。实际操作上我会在验证集上画出 Precision-Recall 曲线在业务允许的误报范围内挑选置信度阈值。比如把置信度阈值从默认的 0.5 降到 0.3召回率可能从 0.88 涨到 0.96但每 1000 帧多出 8 次误报如果现场可接受就用 0.3。阈值选择一定要结合现场回传的视频流做最终确认静态图片测试和动态视频流测试差异很大移动中的人脸带运动模糊静态验证集根本体现不出来。4.2 导出与加速ONNX、TensorRT 与量化训练好的模型直接拿去做摄像头推理不是不可以但帧率往往不理想。标准流程是先导出 ONNX再根据部署环境选择推理后端。ONNX Runtime 是通用兼容的首选TensorRT 在有对应加速卡的设备上提升最明显OpenVINO 更适合 CPU 端场景。导出这一步有一个反复踩的坑模型里包含了训练时才有的操作比如数据增强和锚框生成细节导出前需要把这些逻辑关掉或固化下来。量化是边缘部署的关键环节。FP16 量化几乎无损优先做INT8 量化能显著减小体积并提高帧率但要花心思准备校准数据集。校准集要覆盖现场真实分布不同光照、不同距离、不同口罩类型数量建议 500 张以上质量比数量重要。当时 INT8 量化后速度确实上去了但夜间暗光场景漏检率明显上升最后退回 FP16 才满足业务需求。这个取舍要记录在项目文档里不要简单认为 INT8 一定更好。4.3 边缘端实时流接入要点接上摄像头之后工程复杂度直线上升。拉 RTSP 流时不能把解码和检测放同一个线程网络抖动一次整个检测循环就卡死。标准结构是单独一个线程负责拉流解码把视频帧放进有长度上限的队列检测线程从队列取帧。队列长度建议 10 到 20 帧满了就丢最老的帧保证系统永远在消费最新画面不会把时延越堆越大。低算力设备上典型优化手段有三个第一把输入分辨率从 640 降到 416速度提升明显小脸损失靠多尺度训练弥补第二加一个人体移动侦测画面里没人移动时不跑检测有人出现再触发平均功耗和算力占用明显下降第三限制检测频率比如每 3 帧检测一次中间帧沿用上一次结果对闸机场景完全够用。设备内存增长的问题也很常见大部分是解码后的图像对象没释放或者队列积压写代码时要注意及时清理。5. 训练和部署中的高频坑位排查实录5.1 漏检小目标与暗光场景漏检最集中的两个场景是远距离小目标和暗光。小目标漏检的根源是特征层对细节信息保留不足常规做法是多尺度训练加更高输入分辨率。当时用 768 分辨率训练版本对比 640 版本远距离人脸召回率提升了约 7 个百分点但推理耗时也涨了四成。这个选择要结合算力评估不要无脑上高分辨率。暗光漏检主要靠数据弥补标注一批真实夜间数据比任何算法层面的技巧都有效。还有一种漏检情况是人脸被口罩和帽子双重遮挡只剩眼睛周围一条窄区域。这种极端目标在通用人脸检测里都难做更别提带口罩检测了。处理思路是允许更低的检测置信度把候选框先送出来再由跟踪模块做时序确认。我在项目里加了简单的目标跟踪逻辑某个人脸连续 3 帧出现才触发报警连续 2 帧消失才清除误报大幅下降。5.2 误检把背景当前景误检来源通常有两个。一是负样本缺失模型没见过“没有人的空背景”结果把墙上的图案、滚动屏幕里的明星脸、海报上的卡通形象当成了人脸。解决方法是往训练集里混入大量无人背景图并在增强阶段加入类似 Copy-Paste 的负样本操作。二是置信度阈值太低模型把低置信度候选框也输出。这类误检在闸机反光地面上尤其明显排查时建议把模型的置信度输出可视化逐个看误检框对应的分数分布就能快速判断是负样本不足还是阈值问题。5.3 训练不收敛与过拟合特征训练不收敛的排查顺序是先看数据再看学习率最后才看网络结构。我记得有一次怎么调 loss 都不降最后查出来是训练集和验证集的图片顺序在某个数据源里写错了标签根本没对齐。如果 loss 曲线一开始就剧烈震荡优先怀疑学习率过大调到原来的十分之一再看如果 loss 下降但 mAP 不升怀疑标签噪声或过拟合要去检查数据增强是否引入了错误语义。过拟合的特征是 train loss 一直向下val loss 在一定轮数后掉头向上。常见原因包括数据量太少、增强不足、训练轮数过多。我的处理顺序是先做更稳的数据增强再开 EarlyStopping还不行就减少模型参数量或引入 drop out。在口罩检测这种小数据集任务上EarlyStopping 几乎是必修课不看 val loss 硬跑满 200 个 epoch大概率得到一个在训练集上表现很好、现场完全不能用的模型。5.4 部署精度掉点与重复框模型导出后精度下降第一反应要检查训练和推理阶段的预处理是否一致。训练时做了归一化、随机翻转推理脚本里忘了做同样的预处理精度会直接崩。第二反应是检查量化校准是否充分尤其 INT8 模型校准集数量和场景覆盖不足掉点几乎必然发生。排查完仍然掉点退回 FP16 往往能得到一个较好的平衡点。重复框问题在后端推理里很常见同一个目标被输出两三个框原因是导出后非极大值抑制没有被正确执行或者不同后端对 NMS 算子支持不一致。统一做法是把 NMS 逻辑固化到导出模型内部或者在应用层自己写一份后处理训练和推理尽量复用同一份代码避免“PyTorch 里正常、ONNX 里多框”这种诡异现象。5.5 高频问题速查表问题典型现象排查方向远距离小脸漏检mAP 不低现场回传视频漏报提高输入分辨率或多尺度训练暗光漏检白天正常夜间掉点采集夜间数据不要过度依赖色彩增强背景误检墙上海报被识别为人脸加入负样本背景调节置信度阈值训练不收敛loss 震荡不降检查标签对齐降低学习率验证集指标虚高现场效果远差于验证集检查数据切分是否有视频帧泄露部署掉点导出后精度明显下降检查预处理一致性、量化校准集质量重复框单目标被检出多次固化 NMS统一训练和推理后处理这个速查表不能解决所有问题但能帮你在排查时找到正确起点。后面很多看起来很玄的 bug按这个顺序走一遍八成都定位得到。6. 从实验模型到可用系统的最后一公里6.1 模型轻量化与蒸馏经验如果边缘设备算力真的很紧张蒸馏值得一试。实验里我拿一个较大的模型做教师把特征图输出作为软标签指导学生网络训练学生模型在保持推理速度的同时精度比直接训练好不少。这个步骤需要一些工程耐心但收益稳定。轻量化 backbone 的替换也很直接但瓶颈往往不在模型结构而在数据量数据不够时轻量化模型很容易欠拟合要先把数据底子做厚再换结构。剪枝和量化如果时间充裕可以都试试但要给足返工时间。剪枝后模型通常会掉点需要重新微调微调又要重新准备数据集整体周期在几天到一周不等。项目排期紧张时这两个优化不是首选项优先压缩输入尺寸和检测频率就够了。6.2 现场数据回流闭环迭代效果最明显的不是换模型结构而是把现场新采集的困难样本回流到训练集。系统上线后每天把误报和漏报的画面截图隔周做一次标签补充和重训模型现场表现会肉眼可见地变好。我见过太多团队交付完模型就结束现场效果飘忽不定最后被客户追责。其实只要建立回流标注和重训机制很多问题在早期就能被发现。数据闭环在算法项目里应该是一等公民而不是项目收尾后的附加项。6.3 摄像头场景里的隐私合规设计最后提一个容易被忽略的点口罩识别涉及人脸图像摄像头场景下一定要管控数据边界。我个人的工程习惯是识别系统只输出结构化结果原始视频帧检测后立即丢弃不在本地或云端长期留存人脸画面。如果需要存证可以在结构化结果里附带最小必要信息比如出现时间和检出类别。这个设计既对现场人员友好也让项目交付时少了很多不必要的麻烦。整套流程跑下来我最想对后来者说的是不要被“YOLO26”“智能识别”这些名字唬住口罩佩戴识别这个任务的算法门槛真的不高决定一个项目能走多远的是数据是否干净、增强是否贴合场景、部署链路是否稳定、有没有数据回流迭代机制。我们项目里收益最大的一次改动不是换了结构而是把训练集里标签标反的一百多张图清洗掉再补了两千多张现场真实视角的夜间样本。另一个很有价值的习惯是把所有实验配置、数据集版本和训练结果记录下来否则你永远没法精确回答“为什么这版比上一版好”这个工程师每天都在面对的问题。如果你正准备做类似检测项目建议先把四件事打好底数据清洗、增强贴合、指标对齐、回流迭代你踩的坑会比我少很多。最后分享一个小技巧现场部署前一定要拿一段真实监控视频离线跑一遍观察模型在连续帧上的表现而不是只测几张图片这是性价比最高的上线前验证方式。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。