☰
基于YOLOv8的口罩检测系统:数据集、模型训练到UI部署全流程
2026/10/21 3:25:39 网站建设 项目流程

这两年口罩检测项目的实际需求一直没断过,从园区门禁到课堂考勤,从工地安全到公共区域巡查,都需要一个能实时识别人员口罩佩戴情况的系统。网上类似的“基于YOLOv8的口罩检测系统”项目标题很多,但真正把数据集构建、模型训练、UI界面集成整条链路讲清楚的却不多。大部分教程要么只讲训练,要么只给一堆跑不通的代码,等你拿到手里才发现环境、数据格式、界面调用全是对不上的。这篇文章我把整个项目的核心环节拆开讲,包括数据怎么准备、训练参数怎么调、UI界面怎么把模型真正用起来,以及我实际跑这个项目时踩过的坑,希望能帮你少走弯路。

这套系统适合这几类人参考:想入门YOLO目标检测的初学者,需要一个课设或毕设项目的在校学生,以及正在做安防、考勤、巡检类应用开发的开发者。无论你手头是NVIDIA显卡还是只能靠CPU硬扛,下面都会给出对应的处理方案。

1. 项目整体设计与技术选型思路

1.1 口罩检测需求解析

先把这个项目的真实需求拆清楚。口罩检测本质上是目标检测任务,也就是在一张图片或一帧视频画面里,找到“人”以及“人的口罩佩戴状态”对应的位置,并用边界框框出来。具体到类别设计,常见做法是分两类:一类是with_mask(佩戴了口罩),一类是without_mask(没有佩戴或佩戴不规范)。也有项目细分出mask_weared_incorrect(口罩佩戴不正确,比如露出鼻子),类别更多对数据要求也更高,初学者建议先从两类版本入手,把链路跑通后再考虑精细化。

这个系统的输出信息其实有两层:第一层是“人”的位置,第二层是“这个人的口罩状态”。实际部署时,很多场景只关心“有没有人没戴口罩”,所以不少方案会在检测后加一个判断逻辑:只把without_mask的检测框标记为红色并发出提醒,with_mask的框显示绿色。这个逻辑可以在界面层做,不需要改模型。

从应用场景看,口罩检测对实时性有明确要求。门禁摄像头通常是25-30帧每秒的视频流,如果每帧处理时间超过150毫秒,画面就会出现明显卡顿。YOLOv8n或YOLOv8s这类轻量级模型在普通显卡上单帧推理时间能控制在10-30毫秒,在CPU上通过缩小输入尺寸也能跑到50-100毫秒,基本满足实时性需求。这是选择YOLOv8作为检测核心的关键原因之一。

1.2 为什么选YOLOv8而不是其他模型

目标检测领域可选方案很多,Faster R-CNN、SSD、YOLOv5、YOLOv8以及最新的YOLOv11,各有特点。对口罩检测这个项目来说,选YOLOv8是综合权衡的结果。

先看Faster R-CNN。它的检测精度在众多两阶段模型里确实高,但速度是硬伤,在CPU上跑一帧需要数秒,在GPU上也只有十几FPS,不适合做实时视频检测。SSD的速度比Faster R-CNN快,但对小目标检测效果一般,而口罩在画面中属于中小目标,SSD在远距离场景下容易漏检。YOLOv5虽然成熟稳定,但YOLOv8在结构上做了升级,引入了Anchor-Free检测头,简化了锚框设计和后处理逻辑,训练和部署都更省心。YOLOv8的模型大小和推理速度也有明显优化,比如YOLOv8n只有3.2M参数,模型文件约6MB,很适合快速部署。

如果你关注最新模型,YOLOv11已经有更高精度,但YOLOv8的生态更成熟,资料多、问题解答多、部署工具链齐全,对初学者更友好。做工程项目,稳定性和可维护性比追新更重要。所以我最终确定用YOLOv8作为检测核心,搭配PySide6(或PyQt5)做桌面UI界面,整个项目用Python组织,既方便快速迭代,又能直接调用OpenCV处理视频流和摄像头输入。

1.3 完整系统架构拆解

这套系统从功能模块上划分,包含以下组成部分:

  • 数据层:戴口罩/不戴口罩图片数据集,标注文件为YOLO格式的txt文件,按类别编号记录中心点坐标与宽高。
  • 模型层:YOLOv8模型权重,加载后提供predict接口,输入图像帧,输出检测框、类别、置信度。
  • 业务层:处理检测结果,过滤低置信度目标,执行非极大值抑制(NMS)去重,将结果映射到原图坐标。
  • 界面层:提供用户操作入口,支持上传图片、读取视频文件、打开摄像头实时检测,实时绘制检测框并显示统计信息。
  • 辅助工具:包括数据集划分脚本、格式转换脚本、模型导出脚本等。

模块之间通过Python对象和回调函数耦合,结构清晰,方便后续替换模型或扩展功能。整个项目跑通后,你可以把它改造成口罩佩戴统计系统、安全帽检测系统等,技术栈完全通用。

2. 数据集准备与标注处理

2.1 数据集来源与筛选策略

训练一个可用的口罩检测模型,数据质量比数据数量更重要。常见做法是选用公开的口罩检测数据集,加上自采数据进行补充。

数据筛选时要注意几个原则:一是场景多样性,室内、室外、不同光照条件都要覆盖;二是人物尺度多样,既有近距离单人特写,也要有远距离多人场景;三是口罩类型多样,医用外科口罩、N95口罩、布口罩都应当出现,否则模型容易对某类口罩过拟合;四是负样本要足够,也就是有人物但没戴口罩的图片数量要充足。

我实际用的方案是:基础数据集约5000张图片,其中with_mask约2800张,without_mask约2200张。后来又补充了一些自制数据,用手机在不同角度和光线下拍摄,再手动标注,补充了约300张。最终训练集约4500张,验证集约600张,测试集约200张。这个规模对二分类目标检测来说完全够用,模型能学出不错的特征表达。

提示:数据集切分一定要用脚本随机划分,保证训练集和验证集不重叠。否则验证结果会虚高,模型换到真实场景后表现大幅缩水。

2.2 标注格式与目录规范

YOLO格式的标注文件是每个图片对应的一个txt文件,文件名和图片名保持一致,扩展名为txt。文件每行格式为类别ID 中心点x坐标 中心点y坐标 边界框宽度 边界框高度,其中坐标都归一化到0-1之间。

比如一张416x416的图片里,某人脸边界框左上角坐标为(83, 70),右下角坐标为(166, 145),那么中心点坐标是((83+166)/2/416 = 0.2993, (70+145)/2/416 = 0.2584),宽度是((166-83)/416 = 0.1995),高度是((145-70)/416 = 0.1803),标注txt内容就是:

0 0.2993 0.2584 0.1995 0.1803

类别ID 0对应with_mask,1对应without_mask。目录结构建议这样组织:

dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml

data.yaml内容如下:

train: ./dataset/images/train val: ./dataset/images/val nc: 2 names: ['with_mask', 'without_mask']

如果你用的是LabelImg或Labelme标注的VOC格式XML文件,需要转换为YOLO格式。转换脚本的关键是解析XML里的<object>节点,读取<bndbox>中的xmin, ymin, xmax, ymax,再结合图片宽高做归一化运算。这里注意一个坑:XML里的像素坐标是整数,但归一化结果必须保留浮点数,至少保留六位小数。

2.3 数据增强与类别平衡处理

口罩检测场景里,人脸遮挡、暗光、模糊是三大挑战。直接用原始数据训练,模型在亮光下的晴天场景表现尚可,一到昏暗的走廊或逆光环境下就容易漏检。所以我训练时启用了Mosaic数据增强、随机仿射变换、HSV色彩空间增强、随机翻转等手段。

Mosaic增强是YOLOv8默认开启的特性,把四张训练图片拼接成一张再送进网络,这能显著提升模型对遮挡和小目标的适应能力。HSV增强通过调整色相、饱和度、亮度模拟不同光照条件。随机翻转则让模型对方向变化更鲁棒。

类别平衡方面,如果with_mask和without_mask的数量差距过大,模型会偏向检测数量多的类别。我的数据集两类比例大约1.27:1,差距不大,不需要做额外重采样。如果你的数据集两类比例超过3:1,建议对少样本类别做过采样复制,或者用数据增强补充少样本类别的图片数量。

3. 模型训练全流程实操

3.1 环境配置清单

训练YOLOv8推荐显卡显存至少4GB,8GB以上更从容。如果你只有CPU,也不是不能跑,就是慢,训练时间可能是GPU的十倍起步。环境配置版本建议如下:

  • Python 3.8或3.10
  • PyTorch 2.0及以上,CPU版本与GPU版本二选一
  • CUDA 11.8或12.1,对应显卡驱动版本
  • ultralytics库,建议8.0.0以上版本
  • opencv-python、pillow、matplotlib等依赖

安装命令:

pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

第一行会顺带安装opencv、numpy、pandas等依赖。第二行装GPU版PyTorch,具体CUDA版本号要根据你的显卡驱动和PyTorch官网要求选择。装完后写一行代码验证环境:

import torch print(torch.__version__) print(torch.cuda.is_available())

如果输出True说明GPU可用,输出False说明当前是CPU模式,训练前先排查驱动版本或PyTorch安装版本。

3.2 训练参数配置与调整逻辑

我用ultralytics提供的训练接口,核心参数配置如下:

from ultralytics import YOLO model = YOLO('yolov8n.pt') results = model.train( data='dataset/data.yaml', epoch=100, imgsz=640, batch=16, lr0=0.01, patience=10, optimizer='AdamW', project='runs/train', name='mask_detect' )

逐个说下参数考虑:

  • model='yolov8n.pt'表示加载YOLOv8n预训练权重,n是nano版本,最轻量。如果你显卡性能强且追求更高精度,可以换成yolov8s.pt。
  • epoch=100是因为口罩检测不算特别复杂的任务,100个epoch足够模型收敛。经验上,训练到60-80轮时验证集精度通常已经趋于稳定。
  • imgsz=640是训练输入尺寸,YOLOv8默认支持640x640。尺寸越大精度越高但显存占用和推理时间也增加。显存不够可以降到416或320。
  • batch=16取决于显存大小,8GB显存跑YOLOv8n建议batch设为16,如果报CUDA out of memory,降到8或4。
  • patience=10是早停参数,如果连续10轮验证集损失没有下降,训练自动停止,避免过拟合。

训练完成后runs/train/mask_detect目录下会生成best.pt和last.pt,前者为验证集表现最好的权重,后者为最后一轮权重,部署时用best.pt。

3.3 训练过程分析与结果评估

训练过程中要持续关注两类指标变化:训练损失下降曲线和验证损失曲线。正常情况下,训练损失持续下降,验证损失在震荡中缓慢下降,最终两者都趋于平缓。如果训练损失很低但验证损失不降反升,就是过拟合了,这时可以减少epoch或者增加数据增强强度。

YOLOv8在验证阶段会自动计算并输出mAP50、mAP50-95、precision、recall等指标:

  • mAP50:IoU阈值0.5时各类别平均精度,口罩检测通常能达到0.85以上就算可用。
  • mAP50-95:IoU阈值从0.5到0.95取平均值,要求更严苛,一般要比mAP50低20-30个百分点。
  • precision:检出的目标里真正正确的比例,反映“检出的是否准确”。
  • recall:真正存在的目标里被正确检出的比例,反映“有没有漏检”。

口罩检测场景中,我更看重recall,因为漏检一个未戴口罩的人比误报一个戴了口罩的人后果更严重。如果recall偏低,可以适当降低置信度阈值,或者提高输入图片尺寸。

我有一个实际踩过的教训:第一版训练时只跑了50轮,precision到了0.93但recall只有0.78,在测试视频里连续漏掉好几个侧脸和低头的人。后来改了两点:一是加了随机旋转和上下翻转(注意:人脸倒置场景),二是把epoch从50提到100,recall升到0.89,整体效果就稳定下来了。

3.4 模型测试与推理验证

训练完不要急着接UI,先用一段测试脚本检查模型效果。核心代码:

from ultralytics import YOLO import cv2 model = YOLO('runs/train/mask_detect/weights/best.pt') img = cv2.imread('test.jpg') results = model.predict(img, conf=0.5) for result in results: boxes = result.boxes for box in boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) xyxy = box.xyxy[0].tolist() label = f'{model.names[cls_id]} {conf:.2f}' cv2.rectangle(img, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (0, 255, 0), 2) cv2.putText(img, label, (int(xyxy[0]), int(xyxy[1]) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)

测试时找几张真实场景图片,分别覆盖:单人戴口罩、多人混合、暗光环境、低头、侧脸、用手遮挡面部等。这些边缘场景最能检验模型真实水平。如果在这批图上表现稳定,再进入UI阶段。

注意:model.predict默认置信度阈值是0.25,界面中我建议把阈值设成0.5左右。阈值太低会出现大量误报框,界面上的体验会很差。

4. UI界面设计与系统集成

4.1 界面框架选型

Python桌面UI方案主要有Tkinter、PyQt5/PySide6、Gradio三种。Tkinter胜在自带,但控件简陋,做检测框叠加显示和实时视频刷新会比较吃力。PySide6功能全面,支持自定义绘图事件,适合做实时视频显示。Gradio更快,几行代码就能出一个网页版界面,但定制性有限,适合快速原型。

这个项目我选PySide6。原因很直接:它能在QLabel或QGraphicsView上实时刷新图像帧,配合QTimer做视频循环读取,信号槽机制让按钮点击和线程通信都很顺畅。相比PyQt5,PySide6是Qt官方维护的Python绑定,协议更宽松,对个人项目更友好。

界面布局分为三块:

  • 顶部按钮区:选择图片、打开视频、打开摄像头、退出。
  • 中央图像显示区:显示检测结果。
  • 右侧信息区:显示当前检测到的总数、戴/未戴数量、FPS、置信度阈值滑动条。

4.2 界面核心功能实现思路

核心逻辑分三条链路:图片检测、视频文件检测、摄像头实时检测。

图片检测最简单,用QFileDialog选择图片路径,读取后送入模型,绘制检测框,把图像从BGR转RGB再转成QPixmap显示。

视频文件检测需要解决两个问题:一是不能让视频循环阻塞界面,二是不能每帧都做完整推理导致卡顿。解决方案是用QThread开一个工作线程,线程中循环读帧、推理、发信号把结果传回主线程刷新界面。

摄像头实时检测逻辑和视频文件类似,只是视频源从文件路径换成摄像头设备索引。打开摄像头时,要调用cv2.VideoCapture(0)创建捕获对象,然后设置帧率与分辨率,常用的设置比如:

cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 960) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 540)

这里有个经验:摄像头默认分辨率往往过高,比如1920x1080,每帧送进模型会导致推理延迟明显增加。限制到960x540后,画面清晰度够用且推理速度大幅提升。

4.3 推理性能优化实践

界面集成过程中,模型推理性能直接决定体验。我实测的优化手段,按效果排序:

第一,设置合理的置信度阈值。默认0.25会产生较多低置信度框,实际界面中把conf调到0.45-0.55,既能过滤误报,又不至于漏检。这个值在界面上做成滑动条,方便现场调节。

第二,控制推理输入尺寸。视频帧太大时先resize到合适尺寸,YOLOv8的效率与输入尺寸强相关。实测1080p原图推理一次约45毫秒,先缩放到640x640再推理约15毫秒,速度提升三倍,而检测精度几乎无差别。

第三,用跨帧检测策略。如果目标只是监控统计而不是精确框选,可以每隔一帧做一次检测,非检测帧直接沿用上一帧结果。在25FPS视频中,这能把有效FPS从15提升到接近24。

第四,导出模型格式。如果后续想部署到边缘设备或无GPU环境,可以用以下命令把权重导出为ONNX格式:

model = YOLO('best.pt') model.export(format='onnx', imgsz=640)

导出后可以用ONNX Runtime做CPU推理,速度比PyTorch原生CPU推理快20%-40%。如果你用的是NVIDIA显卡且熟悉TensorRT,导出engine格式还能进一步加速,但工程复杂度增加,如果只是课程项目或小型应用,ONNX足够。

4.4 界面线程安全与卡顿排查

写PySide6界面最容易犯的错是直接在UI线程里做模型推理。因为在UI线程里跑循环,界面会不断提示“未响应”,甚至操作系统直接弹出强制关闭窗口。

正确做法是全部推理工作放到QThread的run方法中,只通过信号把结果传回UI线程。示例:

class VideoThread(QThread): change_pixmap_signal = Signal(QImage) def run(self): cap = cv2.VideoCapture(self.video_path) while True: ret, frame = cap.read() if not ret: break results = self.model(frame, conf=self.threshold) frame = self.draw_detections(frame, results) qt_img = self.convert_to_qimage(frame) self.change_pixmap_signal.emit(qt_img)

主界面里用thread.change_pixmap_signal.connect(update_image)接收信号,刷新QLabel。这样即便推理耗时较长,界面线程也不会卡死。

5. 常见问题与排查技巧实录

5.1 训练阶段常见故障速查表

问题现象可能原因排查与解决方法
训练时CUDA out of memorybatch过大或输入尺寸过大把batch降到4或8,imgsz降到416或320
验证集精度高但新图效果差数据泄露或过拟合检查训练/验证集是否重叠,减小epoch或增强数据扩充
损失不下降学习率过高或过低检查lr0,过高的lr会导致震荡,过低则收敛极慢
检测框偏向画面中心标注格式错误重新检查txt文件坐标,确认是否做了归一化
训练很慢CPU推理或GPU未启用用torch.cuda.is_available()确认环境,检查CUDA版本匹配
训练中途报找不到图片路径配置错误data.yaml中train和val路径必须使用绝对路径或相对于启动目录的合法路径

一个高频陷阱是路径分隔符。Windows下data.yaml里的路径建议用正斜杠/,避免反斜杠被转义解析错误。另外,图片和标注文件严格同名,扩展名不同,注意不要出现jpg图片对应txt标注但文件名多了空格的情况。

5.2 推理与界面集成典型故障

界面阶段常见的问题,按我遇到的频率排:

第一,中文乱码。PySide6默认字体对中文支持不理想,检测框上的中文标签可能显示为方块。解决办法是在绘制时设置支持中文的字体,比如:

font = QFont('Microsoft YaHei', 10)

或者在OpenCV绘制时使用cv2.putText的默认字体,但标签内容用英文(with_mask/without_mask),完全避开中文渲染问题。

第二,摄像头打开失败。可能是摄像头被其他程序占用,或者设备索引不是0。排查方法:

for i in range(5): cap = cv2.VideoCapture(i) if cap.isOpened(): print(f'摄像头索引 {i} 可用') break

第三,视频文件检测到最后一帧退出后,界面没有返回初始状态。要在run方法的循环结束后发出一个finished信号,UI线程收到后重新启用“打开文件”按钮,否则用户会认为程序卡死。

第四,检测框的坐标在界面上显示偏移。原因是模型输出的边界框坐标是相对于输入尺寸的,而界面显示时可能对图像做了缩放。转换公式要统一:模型输出坐标除以输入尺寸再乘以显示尺寸,同时注意YOLO输出的xyxy是原始像素坐标,直接映射即可,不需要再做归一化回退,否则框就会错位。

5.3 模型精度提升的具体措施

如果训练完发现精度不达标,按以下顺序排查和调整:

先检查数据问题。看标注框是否贴合人脸,框太大包含太多背景、太小截断人脸都会影响训练。用标注可视化工具检查几百张图,如果发现标注框普遍偏大或偏小,重新标注比换模型更有效。

再尝试更高等级的模型。YOLOv8n精度不足时换yolov8s.pt或yolov8m.pt,参数量增大,特征提取能力更强。代价是推理变慢,需要结合你的部署设备选择。

然后调整训练尺寸。从imgsz=640提升到imgsz=1280通常能提升小目标检测效果,特别是摄像头画面中距离较远的人脸。但显存占用急剧上升,推理时间也可能翻倍,需根据硬件权衡。

最后考虑增加困难样本。专门收集一批戴口罩但面部有遮挡、眼镜起雾、口罩颜色与肤色相近等难例图片,单独做一轮微调训练,精度往往会有明显提升。

写在最后的一点经验

这个项目我前前后后完整跑过两三遍,最大的体会是:口罩检测本身不是难点,真正耗时的是数据整理和环境适配。千万不要跳过数据标注检查,也不要因为训练集精度高就急着上UI,先把真实场景的测试图跑到位,后面会省很多事。另外在选择模型版本时,除非你对精度有极端要求,否则YOLOv8n到YOLOv8s是性价比最高的区间。如果你后续想扩展,这套代码架构可以直接换成安全帽检测、工服检测乃至农作物病虫害检测,只要替换数据集和类别配置,根本不需要重写界面。做目标检测类小项目,掌握这套“数据-训练-部署-界面”的组合拳,以后遇到同类需求都能快速落地。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询