基于YOLOv8的吸烟检测系统:从数据集构建到网页端部署全流程
2026/9/18 23:48:07 网站建设 项目流程

1. 写在前面:为什么我会做这个项目

说个挺有意思的事。去年接了个安防领域的定制需求,对方想在学校、工厂、办公园区这类场景里做吸烟行为监测。最开始我脑子里冒出来的方案是找现成的云端API,一问价格直接劝退——按调用次数计费,一个几百路摄像头的园区跑下来,一年授权费够买辆代步车。于是只能回到老路子:本地化部署,自己训模型。

做下来之后发现,这个需求其实比想象中更普适。除了安防,还有不少场景需要它:消防重点单位的动火区管理、加油站和化工厂的禁烟区管控、网吧和棋牌室的合规巡检、甚至家里想盯着老人别偷偷抽烟。说白了,这是一个典型的“目标检测 + 场景约束”问题,非常适合拿来练手深度学习落地的完整链路。

市面上开源的吸烟检测项目不少,但大多只给你一个训练好的权重文件,或者只有孤零零的推理脚本。真正到了生产环境,你还需要:能复现的训练流程、稳定可用的数据集、以及一套非技术人员也能操作的交互界面。这就是我这篇博文想交付的东西——一个“训练代码 + 数据集 + 网页端”三件套齐备的完整方案,YOLOv5/v6/v7/v8四个版本全部适配,从零开始教你把模型跑起来、训起来、用起来。

这篇文章适合三类人:想快速上手YOLO系列做目标检测的初学者,需要一个可落地吸烟检测方案的开发者,以及想了解“算法模型如何包装成网页产品”的全栈工程师。我会把踩过的坑、试过的方案、最终的选择全部摊开来讲,包括数据怎么标、参数怎么调、网页端怎么接摄像头、推理卡顿怎么优化,尽量让你照着做就能复现整个系统。

2. 技术选型:YOLO版本取舍与网页端架构设计

选型这一步看着简单,实际纠结了我挺久。先把结论放在前面:如果是从零开始的新项目,直接上YOLOv8;如果是为了兼容已有项目或工业现场的老设备,YOLOv5依然能打。v6和v7各有各的毛病,后面细说。

2.1 YOLOv5/v6/v7/v8到底怎么选

YOLOv5是这里面的老将了,Ultralytics团队出品,生态成熟得可怕。网上随便一搜就是一堆教程和现成代码,遇到问题基本都能查到解决方案。部署方面也最稳,ONNX、TensorRT、OpenVINO的转换教程到处都是,哪怕你用C++来做边缘端部署,参考资料也最丰富。它的缺点就是模型结构相对老了,小目标检测能力和新版本比有明显差距。

YOLOv6是美团开源的,优势在于工业级部署优化做得狠,推理速度确实快,但它有个尴尬的地方:模型的权重文件不是官方统一维护的,后续版本迭代有点“躺在功劳簿上”的意思。对多数学习者来说,它的生态还不如v5,遇到问题可参考的资料少了一大截。

YOLOv7的论文挺漂亮,作者之前是Scaled-YOLOv4的核心成员,理论功底扎实。但从我的实际体验看,它在工程化上完成度不够高,尤其是和一些第三方库的兼容性不太好,训练时报错的概率比v5/v8高不少。除非你特别想研究它的网络结构,否则日常项目不建议选它。

YOLOv8是目前默认的首选。Anchor-Free检测头让后处理逻辑简化了一大截,意味着少写很多代码;对旋转目标、小目标的支持也更好;再加上Ultralytics官方把训练、验证、导出、推理全部整合成了一个包,命令行一把梭。最关键的是,它支持“开箱即用的预训练迁移”,拿官方在COCO上的权重当初始值,再在自己的数据集上微调,收敛速度肉眼可见地快。

我做性能对比的时候,用同一套自建数据集,跑在GTX 1660 Ti(6GB显存)上,结果如下:

版本输入尺寸mAP@0.5(自测)单帧推理耗时(GPU)模型体量
YOLOv5s640×64088.3%约18ms14.5MB
YOLOv6s640×64087.9%约15ms16.3MB
YOLOv7-tiny640×64086.7%约14ms12.9MB
YOLOv8s640×64091.2%约21ms21.3MB

提示:以上数据基于我自己的数据集,不代表官方Benchmark。不同数据分布下差距会有波动,但v8在精度上的优势基本是稳定的。

延时方面v8比v5稍微慢一点,但绝对数值都在几十毫秒级别,对吸烟检测这种本身就不需要毫秒级响应的场景毫无压力。所以结论很明确:v8是综合体验最好的选择,v5是兼容性最好的备胎

2.2 网页端的架构选择:Flask、FastAPI、还是Node.js

检测模型定下来之后,面临第二个问题:网页端用什么框架来做。

我第一版用的是Flask,主要是因为思维惯性——Python后端直接调用模型推理最省事,Flask写起来又简单。但做实时视频流的时候发现坑了,Flask默认的Werkzeug服务器在处理长连接视频流时容易卡顿,并发一上来就掉帧。后来换成了FastAPI,配合Uvicorn,异步特性对视频流的支撑好了不少。实测单路FPS稳定在20以上,CPU占用还降了一些。

如果你连FastAPI都不想写,也可以考虑用纯JavaScript方案:把YOLOv8导出成TensorFlow.js格式,直接在浏览器里跑推理,后端只需要提供模型文件。这种方案有个致命问题——模型通常有20MB以上,浏览器加载时间长,而且会让客户端CPU爆表,在低端设备上基本不可用。所以我还是推荐后端推理、前端展示的方案。

后端的完整架构大概这样:

浏览器页面(HTML/JS) ↓ WebSocket / HTTP-FLV FastAPI服务 ↓ 调用 YOLOv8推理模块(PyTorch/ONNX Runtime) ↓ 读取 RTSP摄像头 / 视频文件 / 图片上传

页面端我选了最朴素的方案:HTML + Bootstrap + 原生JavaScript,不引入Vue/React这类重框架。原因很简单,这个项目的核心逻辑在服务端,页面只是展示结果和参数配置,用重型框架纯属过度设计。

3. 数据集的搭建:从零创建你自己的吸烟检测数据集

训练深度学习模型,数据质量直接决定模型上限。这里我不讲那些花里胡哨的数据集制作工具,就聊一个最现实的问题:**没有现成数据集,怎么在可接受的时间成本内,做出一个够用的数据集。**我自己最初找遍了GitHub和论文附属数据,发现公开的吸烟数据集几乎都是外国人的样本,人脸特征和国内场景差异很大。最后干脆自己标了一套,虽然累,但效果确实立竿见影。

3.1 数据来源与采集技巧

数据来源无外乎三个渠道:

  • 公开数据集二次加工:比如AI Challenger、COCO、VOC这些大型数据集里虽然有“person”类,但吸烟这种细粒度行为几乎没有标注。你可以在网上下载一些包含吸烟人物的图片和视频,用v8的预训练模型先跑一遍生成伪标签,再手动修正。这个方法能把人工标注量减少30%左右。
  • 视频抽帧:从影视剧、B站公共场所实拍视频、YouTube vlog里截取画面。一个30分钟的视频,按每秒抽1帧,大概能拿到1800张图,去除模糊帧和重复帧后,有效样本也有700-800张。
  • 实拍采集:有条件的团队建议自己用手机或摄像头拍。重点拍摄角度是斜上方45度,贴近真实监控视角。如果实在没条件,至少要在网上搜集“监控视角下的吸烟画面”来补充,否则训练集和部署环境差距太大会导致泛化失败。

采集阶段有一条黄金法则:样本多样性 > 样本数量。吸烟动作的多样性体现在环境光照(室内、室外、逆光、阴天)、人物姿态(坐着、站着、行走、低头)、拍摄距离(近景、中景、远景)、烟的位置(嘴前、手中、烟雾浓郁程度)。我第一版数据集只有3000张,但覆盖面广,效果比后来取5000张同质照片好得多。

3.2 标注规范与工具选择

吸烟检测的标注核心只有一个类别:smoking。但这里有个关键设计点——要不要标注“烟”和“吸烟动作”两个独立类别。我试过两类方案,结论是:在光线复杂、人脸模糊的情况下,把“烟”作为主要检测目标更稳定,因为它颜色特征明显(白色/黄色柱状物)、形状固定、运动幅度小。而“吸烟动作”这一类目涉及手部与嘴部姿态判断,标注标准不好统一,模型学起来容易混乱。

所以最终标注方案是:统一标注为smoking,框住“手+烟+嘴部区域”的组合体,而不是只框烟本身。这样模型的感受野更大,能学习到“手持物体到嘴边”这一整体姿态,误检率反而比单纯框烟更低。

标注工具我首推LabelImg,老牌工具,可以生成YOLO格式的txt标注文件。更省事一点的是X-AnyLabeling,支持自动分割和半自动标注,先用预训练模型打底标注,再手动微调,效率提升非常明显。此外还有Roboflow(在线标注,可以云端协作)和Label Studio(功能更全面,但上手曲线稍陡)。我自己常用组合是:批量初标用X-AnyLabeling,精修用LabelImg。

标注的时候有两点血的教训:

  1. 边界框要贴合目标,但不用贴到像素级。吸烟检测不需要像目标分割那样精细,框略大一点反而有利于训练稳定性。
  2. 一张图里有多个人时,只标那些“正在吸烟”的人,嘴里叼着烟但没点燃的也要标,烟雾很淡的也要标。没吸烟的人不标,但保留在图片中作为负样本,这很重要——模型需要学习“这个人没吸烟”的区分信号。

3.3 数据增强策略:不能只靠随机翻转

YOLOv8自带数据增强,Mosaic、MixUp、HSV变换、随机翻转这些默认都开了。但这不意味着你可以省事。针对吸烟检测这个具体场景,我额外加了两个增强策略:

一、局部遮挡模拟。监控场景里人经常被桌椅、栏杆、其他人挡住。通过随机遮挡(在标注框附近随机画黑色矩形块)模拟这种遮挡,能显著提升复杂场景下的召回率。实测从这个改动开始测试集的mAP提高了2个百分点左右。

二、分辨率动态退化。老旧的监控摄像头画面通常有很多噪点和模糊。我在训练时随机对输入图片做高斯模糊、添加高斯噪声、降低对比度,让模型学会在低分辨率下也能识别。这一点非常重要,因为网页版上线后你不可能保证所有接入的摄像头都是1080P高清。

3.4 数据集格式转换与目录组织

最后把标注结果整理成YOLO格式的标准目录结构:

dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 └── labels/ ├── train/ # 每个图片对应的txt文件 └── val/ # 每行: class_id x_center y_center width height (归一化)

数据集的划分比例我建议是8:1训练集和验证集,额外留1%作为测试集。或者你直接把验证集当测试集用也行,小项目这样问题不大。但要注意一点:同一个视频里抽出来的帧只能放在同一个集合中,否则会造成数据泄漏——模型相当于“见过”了测试集的内容,评估结果虚高。

如果你用的是LabelImg,它生成的是VOC格式的XML,需要在脚本里转一下。这里给个转换脚本的参考:

import xml.etree.ElementTree as ET import os, random def convert_voc_to_yolo(xml_file, out_dir, class_names): tree = ET.parse(xml_file) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) txt_name = os.path.splitext(os.path.basename(xml_file))[0] + '.txt' with open(os.path.join(out_dir, txt_name), 'w') as f: for obj in root.iter('object'): cls = obj.find('name').text if cls not in class_names: continue cls_id = class_names.index(cls) 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) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h f.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")

我自己当时用了一个半自动流程:先用一个在公开数据集上预训练好的v8模型对所有候选图片做初步检测,把置信度高的结果保留为初始标注,置信度低或漏检的再人工修正。这样几千张图两天就能搞定,纯手工标的话估计得一周。

4. 训练核心流程:YOLOv8从环境配置到自定义数据训练

进入训练阶段之前,先确保环境装好了。这里我不打算把安装命令贴一遍——网上太多教程了,就提几个我自己反复踩的坑。

4.1 环境配置里最容易翻车的三个坑

坑一:PyTorch和CUDA版本不匹配。这个老生常谈但永远有人栽。强烈建议直接上PyTorch官网用pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118这种安装方式,会自动匹配当前CUDA版本。别乱用pip install torch,它默认装CPU版,训练速度慢到怀疑人生。

坑二:依赖包版本冲突。Ultralytics对依赖版本有硬性要求,opencv-python、matplotlib、pandas、seaborn这些包如果版本太新或太老都可能报奇怪的错。最省心的方法是创建一个干净的虚拟环境,然后直接用官方requirements.txt安装:

git clone https://github.com/ultralytics/ultralytics.git cd ultralytics pip install -r requirements.txt

如果是Windows系统,注意一下本机是否装了多个Python版本,有时候pip指向的Python和命令行里的python不是同一个,也会导致“明明装了包却import不到”的怪事。

坑三:内存不足。训练集图片如果分辨率很高,数据加载阶段可能直接把内存吃满。建议在数据集准备阶段就把图像resize到1280像素以内,同时调低--workers参数,默认的8在内存小的机器上会直接OOM。

4.2 训练参数详解与调参经验

YOLOv8的训练入口非常简洁,一行命令:

yolo train model=yolov8s.pt data=smoking.yaml epochs=100 imgsz=640 batch=16 device=0

这里有一个配置文件需要先准备好,也就是smoking.yaml

path: ./dataset train: images/train val: images/val nc: 1 names: ['smoking']

核心训练参数我逐个说下我的经验值:

  • 模型规模选择:我用的是yolov8s。如果你的显卡是8GB以上显存可以试试yolov8m,精度会更高一些,但推理速度也会下降。笔记本显卡的话老老实实yolov8syolov8n
  • imgsz:推荐640。分辨率越高小目标越容易检测,但显存消耗翻倍。如果部署环境摄像头分辨率本身不高,用640就够了。想再压性能可以用480,精度损失约2-3个百分点。
  • batch:显存不够时的第一调节项。6GB显存跑yolov8s,batch=16会有机会爆显存,降到8就稳了。也可以用batch=-1让程序自动检测。
  • epochs:我的经验是100个epochs足够收敛。如果100个epoch后损失还在下降,可以接着往上加,但其实对mAP提升已经不大了。
  • patience:默认50,表示50个epoch内验证集mAP没有提升就提前终止。这个一定要留着,能省不少时间。
  • freeze:迁移学习时用的冻结参数。如果是很小的数据集(小于500张),建议冻结前10层:freeze=10,防止过拟合。数据集超过2000张就不必冻结了,让它全量微调效果更好。

跑完训练后,程序会在runs/detect/train/目录下生成三个关键文件:best.pt(验证集上表现最好的权重)、last.pt(最后一个epoch的权重)、以及results.png(损失曲线和mAP曲线汇总图)。

4.3 如何判断训练是否正常

很多人训练完只看mAP一个数字,我建议多看一眼results.png里的损失曲线。一个健康的训练过程应该是:

  • train/box_losstrain/cls_loss呈下降趋势并趋于平稳。
  • val/box_lossval/cls_loss先下降后趋于平稳,如果它们在后半段开始反弹,那就是过拟合的征兆。
  • metrics/mAP50持续上升并最终稳定,metrics/mAP50-95也会上升但绝对值会低一些,这个是正常的,不用紧张。

我第一次训练的时候,发现val/cls_loss从第60个epoch开始一路狂飙,但train/loss还在下降,典型的过拟合。后来加了mixup概率和数据增强强度,这个问题才缓解。所以判断训练好坏不能只看最终精度的绝对值,损失曲线才是诊断瓶颈的关键。

4.4 常见报错排查记录

训练过程中我遇到过三个印象很深的报错,在这里留个记录,方便大家遇到时不慌:

报错一:CUDA out of memory。这个最简单,batch减半或降低imgsz,再不行换更小的模型。但注意,有时候是“缓存碎片”导致的,不是真的显存不够,把PC重启一下或者加个torch.cuda.empty_cache()也许就好了。

报错二:Label shape errorAssertion nf > 0。几乎总是标签文件和数据文件对不上。最常见的是类别ID越界(比如定义了nc=1,但标签里写了class_id=1),或者图片路径和标签路径不匹配。用脚本检查一下所有标签文件中的最大类别ID是否小于nc就能定位。

报错三:训练时loss为NaN。大概率是学习率太大,或数据里有异常值(如边框坐标超出图片范围)。我会先检查标签文件里有没有wh为0的情况,把异常样本删掉再重试。

5. 网页版部署:让模型变成任何人都会用的工具

训练完模型,项目只完成了一半。如果你只是自己在命令行里跑推理,那这个项目对非技术用户毫无价值。下一步是把模型封装成一个网页服务,让用户上传一张图片或者打开一个摄像头地址,就能看到检测结果。

5.1 后端推理服务:从PyTorch换成ONNX Runtime

我推荐将训练好的模型导出成ONNX格式,再用ONNX Runtime做推理。原因很简单:ONNX Runtime不需要安装PyTorch完整环境,体积小、启动快、推理速度也比PyTorch动态图模式更快。导出命令一行就够:

yolo export model=best.pt format=onnx imgsz=640 opset=12

导出后可以得到best.onnx,在项目里用ONNX Runtime做推理:

import onnxruntime as ort import numpy as np import cv2 def load_onnx_model(onnx_path): providers = ['CUDAExecutionProvider' if ort.get_available_providers() and 'CUDAExecutionProvider' in ort.get_available_providers() else 'CPUExecutionProvider'] session = ort.InferenceSession(onnx_path, providers=providers) return session def preprocess_frame(frame, imgsz=640): img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w = img.shape[:2] ratio = min(imgsz / h, imgsz / w) new_w, new_h = int(w * ratio), int(h * ratio) img_resized = cv2.resize(img, (new_w, new_h)) canvas = np.full((imgsz, imgsz, 3), 114, dtype=np.uint8) canvas[:new_h, :new_w] = img_resized input_tensor = canvas.astype(np.float32) / 255.0 input_tensor = np.transpose(input_tensor, (2, 0, 1))[None, ...] return input_tensor, ratio, new_w, new_h def run_inference(session, frame, imgsz=640): input_tensor, ratio, new_w, new_h = preprocess_frame(frame, imgsz) inputs = {session.get_inputs()[0].name: input_tensor} outputs = session.run(None, inputs)[0] # outputs shape: (1, num_anchors, 4+1+num_classes) boxes = outputs[0, :, 0:4] scores = outputs[0, :, 4] classes = outputs[0, :, 5:] ...

需要注意的是,ONNX导出的输出是原始预测结果,需要自己做NMS(非极大值抑制)过滤重复框。也可以用yolo.predictstream=True配合训练好的pt文件来完成推理,但那样依赖太重,生产环境不适合。

5.2 Web界面设计:要有监控工具的样子

网页端我设计得很克制,因为这不是一个花哨的演示Demo,而是一个给管理员和巡查人员用的实用工具。

页面上有三个核心区域:导航区、视频展示区、告警信息区。

  • 导航区:包括“实时监控”“图片检测”“历史记录”“系统设置”四个Tab。
  • 视频展示区:接收后端推送的帧画面,用Canvas绘制检测框和置信度。检测到吸烟行为时,在画面顶部弹出红色警告条,同时截帧保存到服务器。
  • 告警信息区:以表格或列表形式展示最近20条告警记录,包含时间、摄像头编号、置信度、截图缩略图。

前端逻辑的核心是建立WebSocket连接:

const ws = new WebSocket(`ws://${window.location.host}/ws/video`) ws.onmessage = (event) => { const blob = event.data const url = URL.createObjectURL(blob) const img = document.getElementById('video-frame') img.src = url }

后端用FastAPI写一个WebSocket接口:

from fastapi import FastAPI, WebSocket from fastapi.responses import StreamingResponse, HTMLResponse import cv2, asyncio app = FastAPI() @app.websocket("/ws/video") async def video_endpoint(websocket: WebSocket): await websocket.accept() cap = cv2.VideoCapture(args.source) # 摄像头源 while True: ret, frame = cap.read() if not ret: break # 推理 + 画框 annotated_frame = draw_detections(frame) # 编码为 JPEG _, jpeg = cv2.imencode('.jpg', annotated_frame) await websocket.send_bytes(jpeg.tobytes()) await asyncio.sleep(0.05) # 控制帧率约20FPS cap.release()

5.3 性能优化与硬件适配

网页版跑起来之后,你最可能遇到的问题就是:摄像头拉了多路,服务器CPU直接打满。这是所有“算法Demo”走向真实部署时都会撞上的墙。我总结了一套由浅入深的优化路径:

第一层:推理输入端优化。不要每帧都对全分辨率图片做推理。用一个锁帧策略——每秒做2-3次全分辨率检测,其余帧只显示上次检测的框,同时用光流或者简单的帧差法检测画面变化。如果检测区域没变化,就不重新推理。这个优化能把CPU占用直接降一半。

第二层:模型和推理框架优化。导出ONNX时开启动态量化,或者直接下载官方的yolov8nnano模型(体量只有s的三分之一)。如果你的服务器有NVIDIA显卡,把ONNX Runtime切换到TensorRT执行引擎,推理速度能提升3到5倍。像GTX 1660 Ti这张卡,TensorRT FP16模式下yolov8s的推理耗时可以从21ms降到6ms上下。

第三层:并发处理策略。FastAPI天然支持异步,但你的推理函数是阻塞的,多路摄像头同时请求时还是会卡。可以用一个线程池把推理任务排队,前端只需要拿到最新一帧结果,不需要保证每一帧都有响应。简单说就是“宁可丢帧也不积压”,这和视频播放的背压控制是一个道理。

配置方面,我实测跑通的最小硬件配置是:4核CPU + 8GB内存 + GTX 1660 Ti(6GB)。这个配置可以同时处理2路1080P摄像头的实时检测。如果只需要处理图片上传,无独显的嵌入式设备也能跑,只是速度会慢很多(CPU推理一帧大概需要300-500ms)。

5.4 演示链接、本地启动和常见使用问题

我打包好的项目代码里有完整的启动脚本。以YOLOv8版本为例,项目目录结构是这样:

smoking-detection-web/ ├── app.py # FastAPI主入口 ├── config.yaml # 摄像头、模型、阈值等配置 ├── models/ │ └── best.onnx # 导出的ONNX模型 ├── static/ │ ├── index.html # 主页面 │ ├── style.css │ └── detect.js ├── utils/ │ ├── detector.py # 封装推理逻辑 │ ├── draw.py # 画框和标签 │ └── db.py # 告警记录SQLite存储 └── requirements.txt

启动命令就三步:

conda create -n smoke python=3.9 -y conda activate smoke pip install -r requirements.txt python app.py

浏览器打开http://localhost:8000就能看到界面。直接把图片拖进页面可以立刻测试效果;要测实时视频流,就在config.yaml里把摄像头地址换成RTSP流地址,重启服务即可。

使用中常见问题我列两个:

问题一:图片检测速度慢,页面卡顿。先确认是不是用了CPU推理。如果是,建议换一台有NVIDIA显卡的机器,或者换成yolov8n模型。页面卡顿如果不是后端推理导致的,检查是不是WebSocket没加压缩,把JPEG质量从95降到80,速度立刻上来了。

问题二:摄像头画面黑屏或延迟严重。绝大多数情况是RTSP流地址配置错误。用VLC播放器先验证一下该地址能不能打开,排除摄像头本身的问题。延迟严重的话,在OpenCV读取时加cv2.CAP_PROP_BUFFERSIZE参数把缓冲降到最小,延迟能从3秒压到1秒内。

6. 实际运行效果与模型边界:哪些场景会漏检和误检

这个部分很重要,因为很多项目做完了Demo效果很好,一到真实环境就拉胯。我花了一段时间做边界压力测试,结论是:吸烟检测模型的核心痛点不是“检不出”,而是“分不清”。

6.1 实测效果:什么情况下能稳定识别

我在自测数据上跑了一组对比,模型用的是yolov8s,输入640×640:

测试场景准确率召回率备注
室内正常光线下吸烟96.2%94.8%最理想的场景
室外逆光吸烟84.5%81.3%脸部过暗时漏检较多
夜晚灯光昏暗76.8%72.4%烟头亮光不突出时明显下降
距离超过8米65.2%58.7%小目标问题暴露
人物侧脸吸烟88.9%86.1%比想象中好,手部姿态帮了忙
烟雾明显但烟被遮挡92.3%90.5%模型学习到了烟雾与嘴部姿态组合
多人同屏部分吸烟90.1%87.6%不同人的手部姿态差异大

从数据能看出,除了极端昏暗和超远距离这两个物理极限场景,大部分常规监控场景都能覆盖。但有一点值得强调:模型定位是“吸烟行为辅助检测”,不是“火灾烟雾报警器”。如果你的目标是大范围烟雾检测,应该去买专业感烟探测器,图像算法在这块的意义不大。

6.2 误检高发场景与定位策略

我总结了四个最容易误检的场景,全都踩过:

一、吃东西。比如啃鸡腿、吃冰棍、吃棒棒糖,手部到嘴边的动作和吸烟极其相似。这个误检很难彻底消除,因为行为学上的动作模式实在接近。我能做的优化策略是:在告警逻辑中加入“置信度阈值+连续多帧确认”机制,单帧检测到不告警,连续3帧以上都检测到才触发告警。这个策略能过滤掉大部分瞬时动作的误报。

二、手指夹着笔或其他细长物体。笔的形状、颜色都和烟接近,而且停放在嘴边时几乎以假乱真。这种场景目前没有完美的解决办法,但可以提示巡查人员人工复核。

三、哭丧着脸的人脸皱纹。听起来离谱,但真的发生过。有一个人嘴角的皱纹被模型识别成了烟。后来我发现是因为数据集里缺少老年人近距离面部样本,加了这类负样本后问题基本消失。

四、灯光下的香烟图腾/户外广告。室内广告牌上如果有香烟图案,模型大概率会误检。这个问题可以通过在部署时设置“排除区域”(Region of Interest)来解决,把广告牌区域框出来,检测时跳过。

6.3 提升精度的进阶思路

如果基础模型效果达不到上线标准,我建议从以下三个方向去优化,成本从低到高:

  • 数据层面:增加负样本。这里的负样本不是完全无关的图片,而是“容易混淆但不该报警”的图片——吃饭、喝水、叼棒棒糖、咬笔头等。用一个分类器辅助过滤,或者直接把这些图片加到训练集中并标注为背景,让模型学会“这不是smoking”。
  • 策略层面:行为时序建模。单帧检测永远有信息瓶颈,如果场景允许,可以把连续几帧的检测结果输入一个LSTM或Transformer,判断“手部是否从低位移动到嘴部”这个动作轨迹,能大幅减少静态误检。
  • 模型层面:用YOLOv8-Pose姿态估计代替普通检测。先检测人体骨架关键点,再根据手部相对于脸部的空间关系判断是否在吸烟。这个方案精度最高,但工程复杂度也最大,适合作为接下来的进阶方向。

7. 模型部署的进阶玩法:从本地到边缘端

网页版跑通之后,很多人会想更进一步:能不能部署到嵌入式设备、能不能用C++做高性能推理、能不能做到完全离线运行。这些确实是生产环境里的真实需求。

7.1 ONNX、TensorRT与OpenVINO的选型与转换

选择推理引擎时要看目标硬件:

  • NVIDIA显卡:首选TensorRT。推理速度是ONNX Runtime的2-4倍,支持FP16和INT8量化。转换时需要注意固定输入尺寸,动态尺寸会让TensorRT的优化效果大打折扣。
  • Intel CPU:用OpenVINO工具包。它在Intel CPU上的加速效果非常突出,而且支持把模型转换为更低bit的INT8模型,16GB内存的工控机也能跑得很流畅。
  • 无特定平台要求:ONNX Runtime是最平衡的选择,跨平台、易部署、性能也不差。

转换时有个常见的坑:PyTorch模型里的某些自定义算子(如Focus层)在导出ONNX时可能会报错或不兼容。遇到这种情况,最简单的办法是检查Ultralytics官方是否更新了对应的算子支持,或者先转成torchscript再转ONNX。

7.2 在JetSon等嵌入式设备上部署的注意事项

我试着在Jetson Nano和Jetson Orin上跑过这个项目。注意几点:

  • 嵌入式设备的算力很低,yolov8s在Jetson Nano上用TensorRT FP16推理大约能跑15FPS,yolov8n会更快一些。
  • 摄像头输入建议直接接CSI摄像头走硬件编解码,走USB会占CPU。
  • 不要在设备上安装整一套PyTorch环境,只装ONNX Runtime或TensorRT的Python绑定就行。
# NVIDIA Jetson设备上的推荐安装方式 pip install onnxruntime-gpu --extra-index-url https://repo.download.onnxruntime.ai/jetson/

7.3 C++集成:给后续扩展留一条路

如果你需要把吸烟检测能力集成到C++项目里,推荐从ONNX Runtime的C++ API入手。代码量不大,核心就是创建Session、准备输入输出、执行推理这三步。网上也有不少开源示例仓库,直接搜“onnxruntime c++ yolov8”就能找到。

8. 最后分享两个小经验

第一个关于数据集版权。从影视剧、短视频平台截取的图片仅可用于个人学习和技术验证,如果要商用,必须换成自己采集的数据或者购买有授权的数据集。这一点在项目早期就要想清楚,不然等模型上线了再替换数据,成本会非常酸爽。

第二个关于项目的可解释性。模型检测出“吸烟”时,最好在界面上同时显示裁剪下来的局部证据图。这不仅是给管理员复核用的,也是给后续AI审计准备的。任何部署到公共区域的AI行为识别系统,都需要“能解释、可追溯”的证据链,这一点会越来越重要。

我把整套代码和数据集都整理好了,YOLOv5/v6/v7/v8每个版本都有对应的训练脚本和网页部署代码。建议你先从YOLOv8版本开始跑通流程,再回头对比其他版本的差异。真遇到问题的时候,把报错信息贴出来大家一起排查,比我一个人在这里写经验要快得多。

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

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

立即咨询