资讯中心

基于YOLOv12的吸烟识别检测系统设计与PyQt5实现

📅 2026/9/28 11:13:30
基于YOLOv12的吸烟识别检测系统设计与PyQt5实现
前阵子接了一个安防类的小需求在厂区场景里做一套能识别吸烟行为的监测系统。需求方给的限定条件很直接——要能跑实时视频流要有登录界面后期还要交给不懂技术的人去点鼠标操作。我最后落地的技术路线是YOLOv12做核心检测PyQt5写UISQLite做用户管理数据集按YOLO格式整理训练整套代码加模型一起交付。这篇文章就围绕这套“基于YOLOv12的吸烟识别检测系统”来拆从模型选型、数据集构建、训练调参到UI界面和登录注册模块的实现把可以复用的步骤和踩过的坑都写清楚。不管是想复现整个系统还是只想把YOLOv12用在自己的检测项目里都能从这里抄到作业。1. 项目整体设计与YOLOv12选型思路1.1 为什么这个项目选择YOLOv12先回答一个绕不开的问题市面上YOLOv5、YOLOv8、YOLOv11都遍地开花了为什么选YOLOv12。YOLOv12是2025年初放出的版本核心改进在于引入了区域注意力机制Area Attention把传统注意力计算里的全局交互改成了分区域的局部交互。这个改动带来的直接价值就是在计算量可控的前提下模型对中、小目标的特征表达比之前的版本更强。吸烟识别偏偏就是典型的小目标问题——一支烟在1080P画面里可能只占几十个像素手部遮挡又严重非常考验模型对局部纹理和边缘细节的敏感度。我用同一份自建数据集做过对比YOLOv12s在mAP50上比YOLOv8s大概高2到4个点比YOLOv11s高1到2个点。推理速度也确实比v8慢一点但慢得不多——在RTX 3070上跑640输入v12s单帧大约18到20毫秒完全够实时视频流的25到30帧需求。如果你手里的显卡更弱用v12n也能跑精度仍然比v8n好。还有一点很实际YOLOv12底层还是基于ultralytics框架的训练命令、数据集格式、导出工具链跟YOLOv8几乎一模一样。也就是说你以前写过的数据预处理脚本、训练脚本、导出脚本基本不用大改迁移动成本很低。这点对做实际项目的人来说特别重要。1.2 技术栈与整体架构整个系统的组件划分大概是这样的Python负责全部逻辑模型侧用YOLOv12基于ultralytics封装界面用PyQt5用户信息存SQLite数据库数据标注用LabelImg格式转换用自写脚本最终推理通过ONNX或直接加载PyTorch权重完成。视频输入支持三种方式本地图片、视频文件、USB摄像头。架构上的核心原则是尽量解耦。检测引擎和界面完全分离界面只管展示结果和接收操作检测引擎只负责把帧变成检测框。这样做的直接好处是后期换模型、换硬件、加功能都不用动界面代码。我见过太多项目把推理逻辑写死在按钮回调里加一个功能就要重构一遍非常痛苦。2. 数据集构建从标注到格式转换2.1 数据从哪来、怎么标才算合格数据集是整个项目的地基。吸烟识别这种场景网上没有特别标准、开箱即用的中文大模型数据集我当时的组合方案是公开数据集打底自己补充采集。公开部分可以用开源的吸烟检测数据集和人体行为数据集里筛出来的吸烟样本自采部分就是在办公区、厂区楼道这些合规场所架摄像头拍一段再抽帧标注。最终正样本控制在2200张左右负样本没有烟但人手持类似物体的画面大约600张这个比例对单类检测来说比较健康。标注规则上有一条必须提前定死到底标“烟”还是标“手持烟的区域”。我的选择是标“包含烟及持烟手部的整体区域”而不是只标烟头。原因很简单烟头在远距离下可能只有几个像素标注框太小会让模型学不到有效特征把周围的手部区域一起框进来相当于给模型提供了上下文实际检测效果会好很多。这也是自己做标注和用现成数据集时最容易踩的坑。2.2 VOC转YOLO格式的核心脚本与计算逻辑LabelImg默认导出VOC格式的XML文件而YOLOv12需要的是txt格式。转换逻辑本身不复杂但归一化坐标的计算经常有人写错。VOC框给的是绝对像素坐标xmin、ymin、xmax、ymax。YOLO需要的是一行五个值类别ID、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。换算公式就是cx (xmin xmax) / 2 / image_widthcy (ymin ymax) / 2 / image_heightw (xmax - xmin) / image_widthh (ymax - ymin) / image_height对应脚本核心逻辑如下这份代码可以直接复用import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_names): 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.findall(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) 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.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if lines: txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines)) class_names [smoking] xml_dir annotations out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, xml_file), out_dir, class_names)写这个脚本时有三个容易出错的地方一是图片宽高必须从XML里的size标签读不能用自己猜的尺寸二是坐标没有做越界clip有些标注工具会标出轻微越界框最好加一句xmax min(xmax, img_w)三是txt和jpg文件名必须完全一致ultralytics是按前缀匹配图片和标签的。2.3 数据增强与小目标处理策略数据增强方面ultralytics的YOLOv12默认开启Mosaic、MixUp、HSV扰动、随机翻转等。Mosaic把四张图拼在一起训练对小目标特别有用——相当于变相扩大了视野里的目标数量让模型在更复杂的背景下学习特征。但Mosaic不是越多越好。默认情况下训练最后10到20个epoch建议关闭Mosaic否则模型容易学到的都是拼接图的伪分布验证集上表现不稳定。我一般先按默认跑完一轮再从倒数20个epoch开始关闭这个逻辑在ultralytics的配置里通过mosaic系数就能控制。另外对吸烟这种小目标任务我在训练时还额外做了两个操作一是把所有训练图统一做一次轻度随机裁剪模拟远近不同的距离二是保证每个epoch里负样本按固定比例混入避免模型学成“见到手就报警”。3. 模型训练实战与参数调优3.1 环境部署注意事项环境配置是新手最容易卡住的第一关。YOLOv12的依赖核心是PyTorch和ultralytics库CUDA版本跟PyTorch必须匹配。我推荐直接用conda建独立环境Python 3.9或3.10PyTorch 2.x官方推荐2.0以上CUDA 11.8或12.1都行然后pip安装ultralytics。如果你只想快速验证效果CPU也能跑训练但速度会慢到怀疑人生。一张8GB显存的显卡比如RTX 3060 Laptop就可以训练v12sbatch调到8到12都能跑。显存实在不够时优先降batch不要降输入尺寸——输入尺寸对检测小目标的影响远大于batch。一个隐藏很深的坑是中文路径。训练数据的父目录、图片文件名、标签文件名里如果有中文ultralytics的某些版本会在读取时直接报错或者静默跳过部分数据。我一律把所有数据放到英文路径下文件名用纯数字加英文前缀省得排查半天。3.2 训练配置与关键参数解读训练命令本身非常简单复杂的是参数怎么给。我最终的训练配置是imgsz640batch16epochs150配有早停patience30。数据配置文件smoking.yaml长这样path: ./datasets/smoking train: images/train val: images/val nc: 1 names: [smoking]关键参数其实就几个逐个说下我调参的理解。imgsz输入尺寸直接决定小目标能不能被看到。640是把常规目标和小目标平衡得比较好的值但如果你发现远距离漏检多可以在第一轮训练完成后用imgsz960对best.pt做继续训练相当于专门做一轮小目标微调。这个策略比一开始就用960训练收敛更快、效果也更好。batch不是越大越好。batch从8升到16训练速度提升有限但显存占用翻倍还可能引入BatchNorm统计不稳定的问题。我一般先按batch8跑通确认数据读取没有bug再决定要不要往上加。epochs和早停要配合使用。训练损失下降曲线如果在验证集上连续30个epoch没有改善就自动停止没必要硬跑满150轮。实际训练中我经常在80到100轮就触发早停出来效果已经够用。学习率用ultralytics默认的0.01就行配合warmup前3个epoch对新手来说不是调优重点。3.3 模型评估与导出训练结束后看三个指标就够了mAP50、mAP50-95、混淆矩阵。对单类检测mAP50比较直观地反映“检没检出来”mAP50-95则反映框位置的精确程度。吸烟检测属于安全监测类我宁愿框稍微大一点也不要漏检所以更关注mAP50同时兼顾mAP50-95不要比mAP50低太多否则说明框的位置很不稳定。ultralytics的验证命令能直接出PR曲线、F1曲线、混淆矩阵这些图都是训练完自动保存的不用自己写。模型导出这块交付时我习惯导出两种格式一个是PyTorch的best.pt方便二次训练和调试另一个是ONNX格式方便部署到没有PyTorch环境的机器上。导出命令就一行yolo export modelruns/train/exp/weights/best.pt formatonnx opset12opset不用追新12到14都兼容大部分推理框架。导出后建议用onnxruntime跑一遍验证确认输出尺寸是[1, 5, 8400]这种结构再做后续的推理逻辑开发。4. 推理管线与吸烟判定逻辑4.1 三种输入方式的统一推理流程系统要同时支持图片、视频文件、摄像头三种输入但内在的推理流程是同一套读帧 → 缩放到640 → 模型推理 → 后处理 → 绘制结果 → 显示。摄像头输入有个细节要重点处理cap.read()的帧率可能跟模型推理速度不一致如果直接边读边推理画面会越来越卡。我当时的做法是单独用一个线程读摄像头把帧放进一个长度为2的队列推理线程从队列取最新帧做检测。如果队列满了就直接丢弃最旧的一帧保证画面一直是最新的。这个“丢帧保实时”的策略对实时监测系统非常关键千万不要为了不漏帧去无限堆积队列那样延迟会越堆越高。视频文件输入反而简单因为不需要追求实时性逐帧推理然后按原帧率播放即可。4.2 降低误报的跟踪与状态判定模型输出的是每一帧的检测框和置信度但如果直接拿单帧结果做告警误报会非常频繁。笔、饮料瓶、手指弯曲的动作都可能被模型误判成烟。我的做法是加一层轻量级时间滤波检测到目标后不立刻触发告警而是记录同一目标连续出现的帧数和平均置信度。只有当一个目标在连续10到15帧里稳定出现、且平均置信度超过阈值才判定为“有效吸烟行为”。实现上不需要非常复杂的算法简单的IOU匹配加计数器就能做到。代码逻辑的核心是维护一个目标字典key是目标框的当前位置value是出现次数和累计置信度。每次新帧的检测框跟现有目标做IOU匹配IOU大于0.5就认为是同一个目标。这套方案实测能过滤掉大量偶然误检。比如有人拿笔在手里转了一下可能就是三五帧的检测结果计数器还没到阈值就消失了不会触发告警。真正持续吸烟的行为会稳定触发。5. PyQt5界面与登录注册系统实现5.1 登录注册模块设计界面部分用PyQt5实现整体分为登录页、注册页、主页面三块。登录和注册是两个独立的QWidget通过QStackedWidget切换登录成功后进入主页面。用户数据用SQLite存储表结构很简单username、password_hash、salt、create_time。密码绝对不能明文保存我用hashlib.sha256加盐处理。注册时生成随机salt把salt拼到密码后面一起做哈希数据库里只存salt和哈希结果。登录时用输入的密码加存储的salt再哈希一次对比结果即可。这个方案虽然不是最顶级的加密方式但对桌面应用来说已经足够而且实现成本很低。注册页面要做的交互校验包括用户名不能为空、密码至少6位、两次输入必须一致。这些用Qt的信号槽在按钮点击事件里校验就行。登录失败时弹QMessageBox提示连续错误5次就把登录按钮禁用30秒防止爆破。这里有个UI设计上的实用经验登录框和注册框在切换时用QPropertyAnimation做一个简单的透明度过渡视觉上舒服很多但千万别让动画阻塞主线程。动画和业务逻辑要分开不然会出现“点登录卡一下”的奇怪体验。5.2 检测主界面功能拆解主页面是系统的核心操作区布局上分四块。左侧是视频源选择区三个单选按钮分别对应图片、视频、摄像头下面是“打开”“停止”“导出报表”按钮。中间是检测结果画布用一个QLabel显示实时画面推荐把QLabel放进QScrollArea应对小分辨率屏幕。右侧是检测结果表格用QTableWidget展示每次检测的序号、时间、目标类型、置信度、位置坐标。底部是一个状态栏显示“当前帧率”“已检测帧数”“当前告警状态”。置信度阈值这个参数我在界面上做成了一个QSlider默认0.55允许用户在0.3到0.9之间滑动。这个看似很小的设计在交付后非常加分——使用者可以根据现场环境快速调节灵敏度不需要改代码重新打包。导出报表功能用csv模块实现一键导出当天的检测记录。表格里的每一行写入CSV文件用utf-8-sig编码直接能用Excel打开不乱码。这个功能很多需求方都会提提前做上能省掉后续加需求的麻烦。5.3 多线程架构彻底解决界面卡顿界面卡顿几乎是所有PyQt视觉项目的通病根源只有一个推理或视频读取操作阻塞了UI线程。Qt的UI线程要持续处理窗口重绘和鼠标事件如果在这个线程里跑模型推理每帧处理50毫秒界面就会明显掉帧、拖拽卡顿。解决方案是标准的主线程显示工作线程处理结构。我按功能拆了两个线程CaptureThread负责读视频/摄像头帧DetectionThread负责模型推理。两个线程通过queue.Queue传递数据推理结果通过Qt信号槽发回主线程更新UI。这里有个细节要特别注意跨线程更新UI必须用信号槽不能在工作线程里直接调用label.setPixmap()。我在开发早期没注意这点程序频繁崩溃加了信号槽之后彻底稳定了。帧率优化上有个小技巧QLabel显示图像前先把帧缩放到QLabel的尺寸而不是把原始1080P图直接塞进去。QLabel显示上限就那么大塞1080P图纯属浪费内存和绘制时间。缩放用cv2.resize保持宽高比空白区域留黑边即可。另外摄像头释放必须放在线程的finally块里否则退程序时摄像头会被占用导致下次启动报错“Device or resource busy”。我遇到过好几次都是因为用户直接关窗口没走正常停止流程。6. 常见问题与避坑实录6.1 界面卡顿、画面延迟高这是被问最多的问题实际表现是画面像幻灯片或者延迟达到两秒以上。排查路径先看是不是推理在UI线程跑再看是不是摄像头帧堆积太多最后看图像缩放是不是频繁创建设备资源。解决办法就是前面说的三件套QThread加queue.Queue(maxsize2)加信号槽刷新。还有一个容易被忽略的点cv2.VideoCapture的buffer默认会积攒好几帧可以在读取前用cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)把缓冲减到最小实测延迟能降不少。6.2 漏检多和误检多怎么平衡漏检多的时候先看目标尺寸。近距离漏检一般不是模型问题可能是置信度阈值设太高远距离漏检则基本是输入尺寸不够要把推理的imgsz从640提到960或者用原始分辨率的分块检测。误检多最有效的两个手段是第一在训练集里增加负样本硬挖掘把那些容易混淆的画面人手握笔、饮料罐、打火机单独挑出来标成背景或直接设为负样本第二采用第四章说的连续帧判定利用时间信息过滤偶发误检。记住一个原则降低置信度阈值本质上是提高召回率代价是误检变多调整阈值解决不了根本问题数据侧和后处理侧的改进才是治本。6.3 训练与部署的其他坑训练时显存不足是最常见的报错onnx导出失败、读取数据集时找不到标签文件、摄像头分辨率过高导致推理缓慢这些都是高频问题。这里整理一个速查表方便对照排查问题现象直接原因处理办法训练显存不足batch过大或imgsz过大batch降到8缓存关闭必要时用half精度验证集mAP为0标签文件和图片名不匹配检查txt文件名和jpg文件名是否完全一致中文路径报错ultralytics读中文路径异常统一改用英文路径和纯数字文件名ONNX导出后结果异常动态输入尺寸或opset过新固定输入尺寸opset设为12或14摄像头打不开上一次程序未释放设备检查进程占用释放后再启动检测框乱跳目标跟踪时IOU阈值过低提高IOU匹配阈值到0.6以上6.4 关于模型小目标优化的一点心得最后再分享一个从实际调试里总结出来的经验对于吸烟识别这类小目标检测任务不要指望靠单模型一劳永逸。我最后交付的版本用了两段式策略——主模型负责常规尺度检测对画面中上半部分区域吸烟时手部通常出现的区域单独做一个更高输入分辨率的推理分支两个结果合并。这个做法不需要重新训练复杂的模型只是在推理阶段多了一次推理但对远距离小目标的召回提升非常明显。我个人在实际操作中的体会是这类项目的技术难点往往不在模型本身而在数据质量、界面流畅程度和误报控制这几件“脏活累活”上。模型训练可能两三天就出结果后面花在调UI、调线程、调阈值、跑现场数据的时间要多得多。如果你是把这个系统作为自己的毕业设计或工程实践来做的提前做好这个心理预期把多线程和数据处理这些基础打扎实整个项目的完成度会高很多。这套代码的结构其实是通用的换个数据集就能变成口罩检测、安全帽检测、工服识别之类的系统。需要扩展时重点改动三个地方yaml数据集配置、类别列表、UI上的显示文案和告警逻辑模型部分和线程框架基本不用动。这也是当初把逻辑层和界面层彻底解耦带来的最大好处。

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案