YOLOv5口罩检测系统:PyQt5本地化部署与工程化实践
2026/9/24 20:16:18 网站建设 项目流程

简介:本资源是一个基于YOLOv5与PyQt5开发的完整口罩检测系统,面向计算机视觉初学者、深度学习实践者及防疫类智能应用开发者,解决图像、视频及摄像头实时场景下的佩戴口罩识别问题,适用于课堂演示、课程设计、小型安防监控等实际场景。压缩包共150个文件,含44个配置与模型定义用的yaml文件、40个核心逻辑与GUI实现的py脚本、3个训练好的.pt模型权重,以及jpg/jpeg/png格式的测试图像和logo等界面素材;另有sh部署脚本、md说明文档及ipynb实验笔记,整体大小为139.86MB。已有1501人学习下载。用户可直接运行主程序启动图形界面,支持图片上传、视频导入与USB/网络摄像头实时检测,并获得带边界框与置信度标注的可视化结果;项目结构清晰,包含数据预处理、模型微调、GUI封装、多源输入适配等完整流程,附带Dockerfile与setup.cfg,便于环境复现与二次开发。

1. 这不是又一个YOLOV5 demo:它把口罩检测从命令行拽进真实工作流

你刚在终端里跑通detect.py,看到控制台刷出一堆mask: 0.92,但下一秒就卡在「怎么让非技术人员也用得上」——这才是口罩检测落地真正的断点。这个基于YOLOV5的系统,核心价值不在模型精度本身,而在于用PyQt5把检测能力封装成可即点即用的本地应用:支持拖入任意图片、加载本地视频文件、调用笔记本摄像头实时推理,所有操作都在一个无黑窗、无报错弹窗、结果带框+置信度标注的界面里完成。它不依赖Web服务或云API,全部计算在本地GPU/CPU完成;也不要求用户懂--weights参数含义,而是把conf_thres=0.5iou_thres=0.45这些超参数藏进「高级设置」折叠面板里。适合防疫管理人员快速部署到办公电脑,也适合作为计算机视觉课程中「模型工程化封装」的完整范例——从.pt模型加载、OpenCV帧处理、PyQt5信号槽调度,到多线程防界面冻结,每一步都暴露在源码里。


2. YOLOV5模型轻量化与口罩数据集微调实战

2.1 为什么选YOLOV5s而非YOLOV5x?精度与延迟的硬平衡

口罩检测场景有其特殊性:目标尺度小(人脸区域仅占画面5%~15%)、遮挡频繁(头发/眼镜/口罩边缘模糊)、背景干扰强(办公室/地铁站/医院走廊)。直接使用COCO预训练权重会导致漏检率高。我们实测发现,YOLOV5s在640×480输入下,单帧推理耗时约32ms(RTX3060),mAP@0.5达0.87;而YOLOV5x虽提升至0.91,但耗时翻倍至78ms,且显存占用超3.2GB,无法在4GB显存设备上稳定运行。因此项目默认采用yolov5s.pt作为基础权重,通过以下三步微调:

提示:不要跳过--img 640参数。口罩目标尺寸集中于120×80像素左右,输入分辨率过低(如320)会丢失鼻梁关键特征,过高(如1280)则增加冗余计算且易受光照噪声影响。

2.1.1 数据集构建:标注规范决定模型上限

本项目使用的口罩数据集包含3276张图像,按train:val:test = 7:2:1划分。关键标注规则必须严格执行:

  • 人脸框必须紧贴面部轮廓:上边界对齐发际线,下边界覆盖下巴尖,左右边界卡住耳前点(非耳朵本身)
  • 口罩标签分两类mask(正确佩戴,覆盖口鼻)、no_mask(未佩戴或佩戴不规范,如仅遮口或下滑露鼻)
  • 强制排除模糊样本:运动模糊导致边缘不可辨识的图像直接剔除,不打标签
# 使用labelImg生成PASCAL VOC格式后,转换为YOLOV5要求的txt格式 python datasets/convert_voc_to_yolo.py \ --voc-root ./datasets/mask_voc \ --yolo-root ./datasets/mask_yolo \ --classes "mask,no_mask"

该脚本将VOC的XML标注转为每图一行的class_id center_x center_y width height(归一化坐标),并自动创建train.txt/val.txt路径列表。注意:center_xwidth需除以图像原始宽度,center_yheight除以原始高度——这是YOLOV5训练器唯一接受的格式。

2.1.2 超参数调优:针对小目标的Anchor重聚类

YOLOV5默认Anchor基于COCO数据集聚类生成,对口罩这类小目标泛化差。我们使用utils/autoanchor.py重新聚类:

# 在train.py同级目录执行 from utils.autoanchor import check_anchors from models.yolo import Model model = Model('models/yolov5s.yaml', ch=3, nc=2) # nc=2表示mask/no_mask两类 check_anchors(dataset='./datasets/mask_yolo/train.txt', model=model, thr=0.95, # IoU阈值,越高越严格 imgsz=640)

输出新Anchor:[10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326]
将其写入models/yolov5s.yamlanchors:字段,并在训练命令中指定:

python train.py \ --data ./datasets/mask_yolo/data.yaml \ --cfg ./models/yolov5s.yaml \ --weights ./weights/yolov5s.pt \ --epochs 150 \ --batch-size 32 \ --img 640 \ --name mask_yolov5s_v2 \ --cache # 启用缓存加速读取

注意--cache参数在首次运行时会将所有训练图像预处理并存入内存映射文件,后续训练启动快3倍,但会占用额外12GB磁盘空间。若显存不足,可改用--cache disk存到SSD。


3. PyQt5界面架构设计与多线程防阻塞实现

3.1 主窗口布局:QStackedWidget实现功能模块隔离

整个GUI采用QStackedWidget管理三个核心视图:图片检测页、视频检测页、摄像头页。每个页面继承自QWidget并独立实现setup_ui()方法,避免逻辑耦合。关键设计点:

  • 图片页:使用QGraphicsView替代QLabel显示原图与检测结果。原因:QLabel缩放时失真严重,而QGraphicsView支持平滑缩放、滚轮缩放、拖拽移动,且能叠加多个QGraphicsRectItem绘制检测框。
  • 视频页QTimer定时触发update_frame(),但绝不直接在主线程调用模型推理。否则视频播放会卡顿甚至崩溃。
  • 摄像头页:使用cv2.VideoCapture(0)获取帧,但通过QThread子类CameraWorker在后台线程持续采集,主线程只负责接收信号更新UI。
3.1.1 多线程安全机制:QThread + Signal/Slot通信

模型推理是CPU/GPU密集型任务,必须剥离出主线程。我们定义DetectionWorker类:

# workers/detection_worker.py from PyQt5.QtCore import QThread, pyqtSignal import torch from models.experimental import attempt_load from utils.general import non_max_suppression class DetectionWorker(QThread): result_ready = pyqtSignal(object, list) # (original_img, detections) def __init__(self, weights_path, conf_thres=0.5, iou_thres=0.45): super().__init__() self.weights_path = weights_path self.conf_thres = conf_thres self.iou_thres = iou_thres self.model = None self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') def run(self): # 模型加载放在run()中,确保在子线程执行 self.model = attempt_load(self.weights_path, map_location=self.device) self.model.half() if self.device.type == 'cuda' else None # 推理逻辑(简化版) img_tensor = preprocess_image(self.current_img) # 自定义预处理函数 pred = self.model(img_tensor.half() if self.device.type == 'cuda' else img_tensor)[0] detections = non_max_suppression(pred, self.conf_thres, self.iou_thres) self.result_ready.emit(self.current_img, detections.tolist())

在主窗口中连接信号:

# main_window.py self.detector = DetectionWorker('./weights/best_mask.pt') self.detector.result_ready.connect(self.on_detection_finished) self.detector.start() # 当需要检测时: self.detector.current_img = cv2.imread('input.jpg') # 注意:不能直接调用self.detector.run()!必须用start()

提示QThread子类中禁止在__init__里加载模型。因为__init__在主线程执行,模型加载会阻塞UI。必须在run()中加载,且run()start()触发,在新线程中运行。

3.1.2 实时性能优化:帧率控制与结果缓存

摄像头检测时,若每帧都送入模型,RTX3060下实际帧率仅8fps(远低于30fps采集帧率),导致画面卡顿。解决方案是动态跳帧

# camera_worker.py def run(self): cap = cv2.VideoCapture(0) frame_count = 0 while self.running: ret, frame = cap.read() if not ret: break frame_count += 1 # 每3帧处理1帧,保证UI流畅 if frame_count % 3 == 0: # 发送帧给检测线程(此处用队列或信号) self.frame_ready.emit(frame.copy()) # .copy()避免内存冲突 cap.release()

同时,在检测结果绘制时启用QPainter.setRenderHint(QPainter.Antialiasing)消除矩形框锯齿,并用QFont.setPointSize(10)统一标注字体大小,避免不同分辨率下文字溢出。


4. 图片/视频/摄像头三模式检测的参数配置与结果解析

4.1 统一检测入口:DetectEngine类封装核心逻辑

为避免重复代码,我们抽象出DetectEngine类,统一处理三种输入源的预处理与后处理:

# core/detect_engine.py class DetectEngine: def __init__(self, weights_path, device='cuda'): self.model = attempt_load(weights_path, map_location=device) self.device = torch.device(device) self.names = self.model.module.names if hasattr(self.model, 'module') else self.model.names def detect_image(self, img_path, conf_thres=0.5): img = cv2.imread(img_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 预处理:resize + normalize + add batch dim img_tensor = torch.from_numpy(img_rgb).permute(2,0,1).float().div(255.0).unsqueeze(0) img_tensor = img_tensor.to(self.device) pred = self.model(img_tensor)[0] detections = non_max_suppression(pred, conf_thres, 0.45)[0].cpu().numpy() return img, detections # 返回原图和[N,6]数组:x1,y1,x2,y2,conf,class_id def detect_video(self, video_path, conf_thres=0.5, skip_frames=2): cap = cv2.VideoCapture(video_path) results = [] frame_idx = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break if frame_idx % skip_frames == 0: _, det = self.detect_image_from_array(frame, conf_thres) results.append((frame_idx, det)) frame_idx += 1 cap.release() return results
4.1.1 参数配置表:三模式下的关键参数差异
检测模式推荐conf_thresskip_frames输入尺寸输出要求典型耗时(RTX3060)
图片检测0.50-640×480单次输出,高精度框42ms/frame
视频检测0.451(每2帧处理1帧)640×480逐帧结果+保存带框视频38ms/frame(平均)
摄像头检测0.402(每3帧处理1帧)640×480实时渲染,低延迟优先35ms/frame(平均)

注意conf_thres越低,召回率越高但误检增多。摄像头模式设为0.40是因为环境光线变化大,需容忍部分低置信度检测;图片模式设为0.50因输入质量高,可严控误报。

4.1.2 结果解析:从[N,6]数组到可视化标注

YOLOV5输出的detections(N,6)数组,列顺序为[x1,y1,x2,y2,confidence,class_id]。绘制时需注意:

  • 坐标是归一化后的绝对像素值(非相对坐标),可直接用于cv2.rectangle
  • class_id=0对应maskclass_id=1对应no_mask,颜色需区分(如绿色/红色)
  • 置信度文本位置:在框左上角外侧2像素处,避免遮挡
def draw_detections(img, detections, names=['mask','no_mask'], colors=[(0,255,0),(0,0,255)]): for *xyxy, conf, cls in detections: x1, y1, x2, y2 = map(int, xyxy) label = f'{names[int(cls)]} {conf:.2f}' color = colors[int(cls)] cv2.rectangle(img, (x1,y1), (x2,y2), color, 2) cv2.putText(img, label, (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) return img

该函数返回BGR格式图像,供PyQt5的QImage转换使用。特别注意:cv2.putText的字体大小0.6和线宽2在640×480分辨率下最清晰,放大到全屏时需按比例缩放。


5. 模型部署验证与常见故障排查技巧

5.1 三步验证法:确认模型真正可用而非仅加载成功

很多用户反馈「界面能打开,但检测框永远不出现」,问题往往出在模型与数据预处理不匹配。按此顺序逐项验证:

  1. 权重文件完整性检查
    运行python models/yolo.py --weights ./weights/best_mask.pt --data ./datasets/mask_yolo/data.yaml,观察是否报错KeyError: 'nc'。若报错,说明权重文件的nc(类别数)与data.yamlnc: 2不一致,需重新训练或修改权重头信息。

  2. 输入通道验证
    DetectEngine.detect_image()中插入调试代码:

    print(f"Input shape: {img_tensor.shape}") # 应为 [1,3,480,640] print(f"Pixel range: {img_tensor.min().item():.3f} ~ {img_tensor.max().item():.3f}") # 应为 0.0 ~ 1.0

    若范围异常(如-1.0~1.0),说明归一化参数错误,需检查img_tensor.div(255.0)是否遗漏。

  3. 后处理阈值穿透测试
    临时将conf_thres设为0.01,运行一张已知含口罩的图片。若仍无框,说明模型根本没输出;若有大量杂乱小框,则是NMS参数iou_thres过低(应≥0.3)。

5.1.1 典型报错与修复方案速查表
报错信息根本原因修复命令/操作
ModuleNotFoundError: No module named 'torch'PyTorch未安装或CUDA版本不匹配pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118(根据显卡驱动选cu118/cu121)
cv2.error: OpenCV(4.8.0) ... error: (-215:Assertion failed) ... size.width>0 && size.height>0图片路径含中文或空格,OpenCV无法读取将图片移至纯英文路径,或改用PIL.Image.open()读取后转cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
QPixmap: Must construct a QGuiApplication before a QPixmapPyQt5在无GUI环境(如SSH)下运行添加import os; os.environ['QT_QPA_PLATFORM'] = 'offscreen'到main.py顶部
RuntimeError: CUDA out of memory批处理尺寸过大或显存被其他进程占用train.py中添加--batch-size 16,或任务管理器结束python.exe进程释放显存

5.2 摄像头权限失效的静默处理

Windows下USB摄像头常因权限问题返回cap.isOpened()=False,但OpenCV不抛异常。我们在CameraWorker.run()中加入主动探测:

def run(self): for dev_id in range(10): # 尝试0~9号设备 cap = cv2.VideoCapture(dev_id) if cap.isOpened(): ret, _ = cap.read() if ret: # 能读到帧才认为有效 self.active_device = dev_id break cap.release() if not hasattr(self, 'active_device'): self.error.emit("未检测到可用摄像头,请检查设备连接与权限") return # 后续正常采集...

此逻辑绕过Windows的「假设备」陷阱(如虚拟摄像头驱动残留),直接用cap.read()验证硬件有效性。

提示:Linux用户需将当前用户加入video组:sudo usermod -aG video $USER,然后重启生效。否则cv2.VideoCapture(0)始终返回False

最后,当检测框在摄像头画面中抖动时,不要急着调conf_thres——先检查cv2.VideoCaptureset(cv2.CAP_PROP_FPS, 30)是否生效,用cap.get(cv2.CAP_PROP_FPS)打印实际帧率。多数笔记本摄像头硬件限制为15fps,强行设30会导致驱动丢帧,引发检测结果跳变。

本文还有配套的精品资源,点击获取

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

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

立即咨询