ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深度学习口罩检测系统:从YOLOv8训练到ONNX部署的实战指南

深度学习口罩检测系统:从YOLOv8训练到ONNX部署的实战指南 简介面向高校毕业设计、课程设计与期末大作业的深度学习实战项目基于YOLO目标检测算法实现口罩佩戴识别适用于校园、工地、商场等场景的监控与考勤辅助也适合已有Python基础、希望快速上手计算机视觉的初学者与开发者。项目内置YOLOv3、Darknet53、YOLOv3-Tiny三套模型配置配套训练、预测、批量检测、格式转换等Python脚本可完整跑通从数据标注到模型推理的流程。压缩包共45个文件包括15个Python脚本、12个文本标签/数据文件、6个pyc依赖、3个cfg模型参数及测试图片等整体仅2.64MB结构清晰、类型分明便于按需学习。当前已有41人学习使用。资料还提供kmeans锚点聚类、VOC数据集处理、darknet权重转换等工具并附README说明文档可帮助理解锚点生成、数据增强、模型微调等关键细节是完成课程项目或入门目标检测的实用参考。1. 口罩检测系统究竟在解决谁的什么问题从校门口刷脸测温的闸机到建筑工地进场打卡的摄像头再到写字楼前台的访客通道“有没有戴口罩”已经是最常见的视觉判断场景之一。一个基于深度学习的口罩检测系统本质上就是拿一批标注好“戴/未戴/戴错”的图片训练出一个小模型然后部署到普通摄像头或者边缘盒子上做到实时判断、触发语音提示或门禁联动。你在网上看到的“基于深度学习的口罩检测系统.zip”并不是某个天才发明的黑科技它通常是一整套可跑的方案数据集整理脚本、模型训练代码、推理脚本和部署说明打包在一起适合毕业设计二次开发也适合拿来当工业智能安防的基线替换掉过去靠红外测温仪顺带观察的土办法。这篇笔记我就顺着“拿到压缩包之后怎么用”的思路把选型、训练、部署和踩坑一起讲透。2. 选型与数据为什么是YOLOv8以及数据怎么准备2.1 先定模型边界口罩检测为什么被“小目标”和“多尺度”卡住口罩检测不是简单的二分类问题。一张画面里可能有远景的几个人头近处又有人侧着身口罩在脸部的占位可能只有几十个像素传统手工特征根本扛不住。深度学习目标检测模型里Faster R-CNN这类两阶段方法精度好但推理速度在边缘设备上很难做到每秒25帧以上SSD轻快但小目标召回率差。口罩检测恰恰要同时处理远近不同的人脸所以目前从业者手里的方案绝大多数已经收敛到了YOLO系列尤其是YOLOv8或YOLO5。YOLOv8把分类头和回归头解耦anchor-free机制让它在小目标上比旧版YOLOv5更稳模型导出时可以直接出onnx、tensorrt后续部署省掉大量格式踩坑。我一般会先做一个快速对比实验用同一份5000张数据集分别跑YOLOv8n和Faster R-CNN前者在RTX3060上训练一版的用时大概是后者五分之一推理速度接近十倍差距mAP0.5在口罩这类相对规整的目标上反而能高出1到2个点。这就是选择YOLOv8的核心理由不是因为它最准是因为它在“精度、速度、部署成本”这三个条件上最平衡适合作为一套系统的默认底座。2.2 数据集与标注格式从VOC转YOLO是每个人都会遇到的坎如果你下载的压缩包自带了一份数据集跳过这一步但多数公开口罩数据集用的是VOC格式也就是xml标注文件而YOLOv8训练又默认要求txt格式的YOLO格式标注。即使压缩包没有这份数据也建议自己标注一小批补充数据因为公开集里“口罩戴错”这类样本往往特别少。我常用的转换逻辑是读取xml里的object坐标统一归一化到图片宽高然后写入txt文件每一行是“class_id x_center y_center width height”。下面这段转换脚本是整套系统里最常被反复改的工具代码直接用Python标准库就能跑不依赖额外包适合先放在数据处理目录里让数据集制作“后悔药”随时可用。import os import glob import xml.etree.ElementTree as ET # 需要修改的三个路径xml目录保存txt的目录类别名列表 xml_dir ./annotations # 输入的VOC xml目录 txt_dir ./labels # 输出YOLO txt目录 class_names [with_mask, without_mask, mask_weared_incorrect] # 类别顺序必须后续与训练yaml一致 os.makedirs(txt_dir, exist_okTrue) def convert_xml_to_yolo(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) / img_w ymin float(box.find(ymin).text) / img_h xmax float(box.find(xmax).text) / img_w ymax float(box.find(ymax).text) / img_h w xmax - xmin h ymax - ymin # 修正truncated边缘避免训练时出现负数或不合法宽度 x_center xmin w / 2.0 y_center ymin h / 2.0 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return lines for xml_file in glob.glob(os.path.join(xml_dir, *.xml)): txt_name os.path.basename(xml_file).replace(.xml, .txt) lines convert_xml_to_yolo(xml_file) with open(os.path.join(txt_dir, txt_name), w) as f: f.write(\n.join(lines))转换脚本有两个必须盯紧的参数。第一类别列表的顺序要在后续训练用的自定义data.yaml里保持一致否则推理时框和标签完全对不上这是最常见的“挂羊头卖狗肉”报错。第二我在脚本里加了坐标夹紧操作因为公开数据集里偶尔有标注出界的点如果不修正训练时损失函数会跳到nan。你也许会顺手用labelimg重新标注一批自己的现场图记得导出格式选择YOLO这样免去转换步骤还能避免VOC里name大小写不一致造成的麻烦。2.3 数据增强的尺度口罩目标太小马赛克增强反而容易误杀训练脚本默认会开YOLOv8自带的马赛克增强也就是把四张图拼成一张去训练。这招对常规目标很有效但对于“远景小脸 口罩”的场景容易出问题四张图拼完之后原图里本来只有32像素的口罩区域被缩小到16像素模型学到的是模糊纹理而不是口罩特征。我的习惯是在处理口罩数据集时先把马赛克增强概率从默认1.0降到0.2同时加大mosaic之后的一次随机裁剪比例。另一个值得开的增强项是HSV微调因为不同工地的光线环境差异大颜色饱和度和亮度变化能帮助模型适应夜间弱光。补充一份自己的样本来做困难样本挖掘尤其重要。压缩包里如果带了训练脚本别马上全量训练先挑出那些“远处三个人排成一队”的图片单独做一个验证切片。很多基于深度学习的口罩检测系统在公开测试集上mAP很高但现场一开摄像头就发现两个人前后重叠时漏检严重原因就是训练数据里近景大头占多数模型没有见过中远景多人交互的pattern。把这个维度的数据补齐比盲目换网络结构管用得多。3. 训练这套模型的本地与云服务器参数最小可跑命令与三个必调项3.1 从压缩包到第一条训练命令拿到压缩包以后本地环境首先要把依赖装好。常见做法是使用miniconda建一个干净环境然后安装pytorch、ultralytics、opencv-python、onnxruntime这几个核心包。很多翻车现场都出在pytorch版本和显卡驱动不匹配上所以我的流程是先跑一段nvidia-smi确认自己的CUDA版本再选择对应的pytorch安装命令。如果你手里只有一台CPU笔记本照样能训练但通常建议用云平台租GPU深度学习环境配置这件事GPU场景和CPU场景完全是两种节奏。训练脚本本身并不复杂压缩包里一般会有类似train.py或直接调用ultralytics YOLO接口的入口。下面是我基于ultralytics封装过的一套最小训练命令它把数据集路径、轮数、分辨率、batch size都暴露成命令行参数方便你在调试时不用反复改主脚本。# train.py from ultralytics import YOLO import argparse parser argparse.ArgumentParser() parser.add_argument(--data, typestr, defaultdatasets/data.yaml) parser.add_argument(--weights, typestr, defaultyolov8n.pt) parser.add_argument(--imgsz, typeint, default640) parser.add_argument(--batch, typeint, default16) parser.add_argument(--epochs, typeint, default100) parser.add_argument(--device, typestr, default0) args parser.parse_args() model YOLO(args.weights) # 加载预训练权重不是从零训练 model.train( dataargs.data, imgszargs.imgsz, batchargs.batch, epochsargs.epochs, deviceargs.device, workers4, patience15, project./runs/train, namemask_exp, )注意第三个参数、第五个参数imgsz和batch是口罩检测训练里影响结果最大的两个旋钮。imgsz默认640但如果你的摄像头画面里人脸经常很小推荐升到768或832代价是训练时间增加约20%推理速度下降反过来如果只识别近距离闸机640完全够用。batch一定不要为了塞满显存无脑开大口罩数据集里背景相似度高过大batch容易让模型收敛进一个平坦的局部最小值导致验证集mAP反而更低。我一般从16起步看到loss曲线震荡后在8、24之间试探。weights参数也值得说一句。强烈建议使用yolov8n.pt或yolov8s.pt作为预训练权重而不是随机初始化。COCO预训练模型已经学会了人脸轮廓、布料纹理这类底层特征口罩检测属于典型的下游迁移任务不用白不用。如果你是做毕设或竞赛追求报告里一个好看的数字那就用s甚至m模型如果目标是部署到Jetson或树莓派尽量锁在n规模不然后续TensorRT推理帧率会很难看。3.2 数据yaml与三类样本的类别平衡训练前你还需要一份和数据集匹配的data.yaml这个文件决定了模型知道该预测几个类别、训练集和验证集分别在哪里。口罩检测数据集最常见的标签是三类with_mask、without_mask、mask_weared_incorrect。我建议保留三类而不是简化为两类因为“戴错口罩”的工程价值很高例如鼻子露在外面这类情况只有三类模型才能提示用户纠正佩戴姿势。下面是实际在用的yaml模板和前面转换脚本里的类别顺序保持一致。# data.yaml path: ./datasets # 数据集根目录 train: images/train # 训练图片目录 val: images/val # 验证图片目录 test: images/test # 可选测试集 nc: 3 # 类别数量 names: 0: with_mask 1: without_mask 2: mask_weared_incorrect路径这块有个高频坑path字段一旦写错后续都说找不到图片。最好用相对当前工作目录的路径避免硬编码别人的绝对路径。另外检查一下验证集是否有错标样本尤其“戴错口罩”这个类别最容易标错标注者常常把口罩拉到下巴的图标成without_mask这会直接污染模型对两类边界的判断。我自己跑过一轮训练发现val mAP卡在0.82上不去后来逐张检查验证集图片发现约3%的without_mask标签里混着口罩下滑到颈部的图手工修正后mAP立马上到0.87。3.3 断点恢复与早停省时间的关键参数训练口罩检测这类中小规模数据集不需要每次都把100个epoch跑完。patience15意思是连续15个epoch验证集mAP没有提升训练就自动停止。这个参数是压缩包里的“后悔药”能让你避免周末挂着训练、周一发现早就在过拟合。断点恢复可以这样用如果训练到一半意外中断不需要重头开始运行同一条训练命令时会自动从runs/train/mask_exp/weights/last.pt接着训练。另外你要养成每轮看训练完的results.png而不是只看控制台loss的习惯图中左上角的train/box_loss如果持续下降但val/box_loss在第40个epoch开始反弹说明模型开始学习背景噪声这时应该停掉、调低学习率或加大数据增强。还有一个参数是cos_lrTrue我常用余弦学习率衰减替代默认线性衰减它在训练后期能多挤0.5到1个点mAP。如果你手头有多张显卡device参数可以写0,1但口罩检测数据集通常不大单卡足够多卡的数据同步反而会引入额外的调参时间。不要把毕设作品幻想成一定要分布式训练很多系统压缩包里声称的“分布式”实际落地还是单卡更稳。4. 部署落地把模型导出到ONNX并用摄像头每秒跑多少帧4.1 从PyTorch权重到ONNX文件的最小导出训练完成后工程上不会直接把.pt文件丢给生产环境因为PyTorch运行时太重推理库也不统一。常见做法是先导出成ONNX再按目标硬件做成CPU上的onnxruntime推理或者进一步转成TensorRT引擎。下面这段导出代码几乎可以直接抄进你的工程脚本。# export_onnx.py from ultralytics import YOLO model YOLO(runs/train/mask_exp/weights/best.pt) # imgsz必须与训练时一致否则reshape会失效 model.export(formatonnx, imgsz640, opset12, simplifyTrue)导出参数里有两个关键opset版本和simplify开关。opset12兼容性最广尤其是在Windows下用onnxruntime 1.16系列不会遇到算子缺失simplifyTrue会剔除一些多余的计算图节点让模型体积缩小约10%推理速度也能提升5%。如果后续要上TensorRT建议导出时也把dynamic保留为默认False即固定输入尺寸640x640因为动态尺寸在TensorRT中会显著增加显存占用和预热时间。固定尺寸损失的是多长宽比适应性但换来的是稳定帧率这在门禁系统里更有价值。导出完成后不要直接用测试集大图去验证先把一张现场拍的照片缩放到640x640对比PyTorch原模型的框坐标和onnx模型的框坐标。只要坐标差小于0.01基本可以认为导出没有破坏精度。这一步能帮你隔离开“模型没训练好”和“部署代码写错了”两类问题避免后面的排查进入玄学阶段。4.2 用onnxruntime跑实时摄像头推理部署端不需要再装深度学习训练框架只需要onnxruntime和opencv。下面是摄像头推理的核心循环我已经去掉与业务无关的显示逻辑只保留最核心的预处理、推理和后处理。import cv2 import numpy as np import onnxruntime as ort sess ort.InferenceSession(mask_model.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider]) input_name sess.get_inputs()[0].name input_shape sess.get_inputs()[0].shape # [1,3,640,640] def preprocess(frame): img cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) # 与导出尺寸一致 img img.astype(np.float32) / 255.0 # 归一化到0~1 img np.transpose(img, (2, 0, 1)) return np.expand_dims(img, axis0).astype(np.float32) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break inp preprocess(frame) outputs sess.run(None, {input_name: inp})[0] # [1,84,8400] # 后处理解析框、置信度、类别并映射回原图坐标 # 这里省略NMS代码生产环境建议直接使用onnxruntime内置的NMS工具或另写向量化NMS cv2.imshow(mask_deploy, frame) if cv2.waitKey(1) 0xFF 27: break cap.release() cv2.destroyAllWindows()这段代码里最容易翻车的是输入输出张量的shape。YOLOv8输出的维度是[1, 84, 8400]其中84由4个框坐标加上80个COCO类概率组成。但你自己训练的口罩检测模型只有3类输出应该是[1, 7, 8400]也就是4个坐标加3个类别概率。很多人直接从网上抄YOLOv8后处理脚本忘记把类别的索引大小从80改成自己的类别数结果推理结果一片乱飘。我一般会把输出张量shape打印出来看一眼这是排查推理问题最快的一步。参数方面providers列表顺序决定了推理优先用GPU还是CPU。如果你在Jetson这类设备上部署建议把TensorrtExecutionProvider放在最前面其次是CUDA最后是CPU。但使用CPU设备时不要图省事把CUDAExecutionProvider也留在列表里onnxruntime每次初始化都会尝试加载CUDA失败后再回退CPU启动时间会平白无故慢三到五秒。对闸机这类需要快速启动的场景最好在部署脚本里通过命令行参数指定provider而不是写死。4.3 帧率与功耗实时口罩检测的工程红线实时监控场景有一个硬指标摄像头采集帧率是25fps或30fps如果AI推理跟不上就会出现画面卡顿和漏检。CPU上跑YOLOv8n口罩模型640x640输入大概在10到15fps勉强能用但会掉帧GPU或Jetson Nano上使用四线程推理可以跑到25fps左右。我建议在系统里加一个统计推理耗时的模块记录最近100帧的平均耗时当耗时超过40毫秒就自动降低输入分辨率或者跳帧处理。跳帧不是丢帧而是每隔一帧做一次检测上一个框坐标保持20毫秒监控这种场景完全够用。另一个常被忽略的参数是摄像头分辨率。很多摄像头默认输出1080p但模型输入只有640x640这种做法会浪费编码和解码的时间。部署时直接把cv2.VideoCapture的宽高设置成1280x720或960x540不仅预处理开销降低摄像头自动曝光也更稳定。口罩检测这类目标不要求超清细节720p足够。5. 避坑记录口罩检测在实战里最常见的六个翻车点5.1 训练loss一直在0.2上下不动mAP也一直为零现象训练相当流畅loss快速下降后陷入平台验证集mAP表现为0或异常低。原因绝大多数情况是data.yaml类别映射与标注txt中的类别编号错位。例如转换脚本把with_mask放在第0位但yaml中第0位却写成了without_mask模型学到的特征和标签完全对调。解决先打印任意一张训练图片对应的txt标注内容再用可视化脚本把框画到图上看框的位置是否和口罩位置匹配。这是所有基于深度学习的图像识别项目通用的第一课不要绕过。另一个导致loss不降的原因是目标极小且标注框过小模型在默认anchor配置下很难匹配到正样本。解决调低模型输入imgsz到640以下不对应该加大imgsz到960让面部区域在特征图上占更多像素。同时可以降低anchor_t阈值YOLOv8里对应anchor_t4.0让更多候选框参与匹配。5.2 训练时爆显存显卡温度飙升现象batch设置为32模型加载后不到10分钟报CUDA out of memory。原因输入图片分辨率高、batch大、workers多数据加载进程和训练进程并发抢占显存。解决先把batch降到8imgsz降到640用workers2确认能跑通后再逐步增加。如果是云服务器检查一下nvidia-smi里是否有别人残留的进程占用显存。我遇到过几次“显存不够”实际是被上一个中断的训练进程占着资源杀掉进程后空间立刻释放。这条排错顺序比调参重要。5.3 夜间场景下人脸和口罩都偏暗漏检严重现象白天mAP达到0.9夜间摄像头画面上模型几乎找不到人。原因训练集里大部分是白天自然光图片模型依赖亮度颜色特征而不是形状纹理。解决从训练数据中抽一部分白天图片用opencv做亮度随机降低、加噪、模拟夜间红外效果生成离线增强数据。另一个有效做法是调整摄像头设置强制开启红外补光并把曝光时间固定在1/50秒让输入画面亮度保持稳定比增强模型更省事。5.4 模型在测试集上很好一接摄像头就乱框现象静态图片测试时检测框稳定但打开摄像头后手一挥、门一开画面里出现大量几十毫秒的闪烁误检。原因测试集图片是独立的而摄像头视频流有时间连续性模型对模糊帧极其敏感另外人是动态的运动模糊使脸部特征发生畸变。解决在后处理中加“连续帧检测抑制”逻辑只有同一个框连续出现2到3帧才输出告警单帧结果直接丢弃。这类工程技巧并不会降低模型理论精度但能让现场误报率大幅下降。5.5 转换模型后检测框全部偏移到左上角现象同一张图PyTorch推理正常onnx或OpenVINO推理框全部偏左上角且偏移量固定。原因这往往是预处理scale方式不一致导致的。PyTorch侧用letterbox带填充灰边onnx侧用直接resize两边把坐标映射到原图时没有去除填充部分。解决统一两端的预处理逻辑推荐直接使用ultralytics里封装好的letterbox函数并在后处理时减去pad的宽度和高度。不要自己手写resize除非你完全确定边缘填充逻辑一致。5.6 语音提示模块一直响闸机却不联动现象检测业务逻辑正常但语音播放或串口命令频繁触发导致现场体验失控。原因没有做“检测结果置信度阈值”和“状态机”区分。模型对每个框输出一个概率如果设置成0.25可能把远处模糊人脸判成未戴口罩触发警报。解决业务层把置信度阈值单独调高到0.4甚至0.5同时让系统状态在“已提醒/未提醒”之间切换避免重复触发。这不算模型问题但部署现场的差评有一半来自这种阈值设置不合理的“活体噪音”。6. 进阶验证用测试集切片与特征图确认检测能力省下“黑匣子”的调试时间当训练与部署都跑通以后不要急着写报告或上生产先做一轮“模型诊断”。我自己的习惯是准备一个约200张的现场切片集按三种场景归类正脸近距离、侧脸远距离、逆光带墨镜。对每个切片分别统计精确率和召回率然后画混淆矩阵。这一步能帮你明确系统到底是漏检多还是误检多两个方向对应完全不同的修法。如果漏检集中在远距离那去调整imgsz和数据增强如果误检集中在模糊帧改后处理逻辑如果类别混淆集中在with_mask和mask_weared_incorrect之间那基本是标注边界不清晰需要回到标注阶段统一标准。另一个值得做的验证是特征图可视化。用PyTorch临时跑一次前向并把backbone最后一层的feature map叠加到原图上看模型是不是真的关注了口罩区域。如果发现网络大部分时候盯着背景或额头说明训练数据分布出了问题。我见过一个案例模型在教室场景表现很好但拿到工地后疯狂漏检特征图显示模型把注意力放到了反光帽檐上原来公开数据集里大量样本都是戴安全帽的人模型学到了“帽子”和“口罩”的关联。这种问题只有特征图才能看出来只看mAP永远发现不了。训练结束后还可以对模型做一次鲁棒性测试把测试图片的分辨率降低到480p、调高锐度、加入高斯噪声观察mAP下降幅度。遵循“一块边缘盒子的真实算力”去评估而不是在RTX3090上给出一个漂亮数字。实际上真正常规部署环境是CPU或嵌入式GPU所以在导出ONNX后我会用onnxruntime跑一遍完整的benchmark脚本输出每一帧的预处理、推理、后处理耗时记录到日志文件里。这组数字才是你向领导或导师汇报时最有说服力的落地数据。最后说一个我吃过亏的经验不要迷信压缩包里自带的README和参数配置别人能跑通的环境不代表你能跑通。拿训练脚本时先把data.yaml路径改成自己的绝对路径再把预训练权重下载好最后重头跑一遍训练确保没有任何隐式依赖。好系统的标准不是一次跑出完美模型而是能让你在拿到数据后快速迭代还有体面地回滚到上一个版本。这套基于深度学习的口罩检测方案做到测试切片、特征图、耗时记录这三件事后才算真正从“能跑”变成了“能交付”。希望这些踩坑记录能帮你少熬几个夜也省下那些起初看似诡异、事后都指向数据或预处理的debug时间。本文还有配套的精品资源点击获取
返回列表