简介:面向计算机视觉开发者的YOLOv8烟雾检测完整工程包,包含已训练好的smoke权重模型、4500余张标注图像及配套XML/TXT标签文件,类别统一为smoke。代码基于PyTorch实现,并附带Qt可视化界面,便于本地加载模型进行图片、视频或实时摄像头烟雾识别,适用于化工园区、森林防火等场景的预警系统搭建与算法验证。
资源包共2000个文件,包含1283个txt标注文件、510个md说明文档、134个Python脚本、57个yaml配置以及少量C++、Shell、HTML等辅助文件,压缩包大小约241.58MB。目录结构清晰,从模型推理、训练配置到界面交互均有覆盖,可帮助使用者快速完成环境复现。目前已有333人学习下载,适合具备一定YOLOv8基础的开发者直接迁移使用,或作为烟雾检测数据扩充与算法对比的参考基准。
1. 项目概述与方案选型
做烟雾检测这件事,说难不难,说简单真不简单。早些年大家普遍用的是传统图像处理的路子,比如帧差法、背景建模、颜色阈值分割,对静态场景、固定摄像头还凑合,一旦遇到室外光照变化、雾气干扰、远距离小目标烟雾,效果就崩得一塌糊涂。深度学习普及之后,检测精度上来了,但实时性又成了老大难——毕竟火灾报警这种事,晚一秒钟都可能造成完全不同的结果。
我这次的项目选型是 YOLOv8 + 自建烟雾数据集 + 训练好的 smoke 模型,整套流程走下来,从数据准备、模型训练到实际部署,都有比较完整的实践经验。选 YOLOv8 而不是更早的 YOLOv5 或 YOLOv7,核心原因有三个:第一,YOLOv8 的 C2f 结构在保持轻量化的同时提升了梯度流动效率,在烟雾这种边缘模糊、形态不规则的检测目标上,特征提取能力比 C2f 的前身 C3 模块更强;第二,YOLOv8 内置了 Anchor-Free 检测头,省去了锚框聚类和调参的麻烦,对新手极其友好;第三,Ultralytics 官方把训练、验证、导出、部署的链路都封装好了,一条命令能跑通全流程,工程效率高不少。
这个项目适合谁参考?如果你是做毕业设计、实验室项目或者工业消防预警系统的前期验证,这套方案能帮你少走很多弯路。烟雾检测和普通物体检测有一个本质区别:烟雾没有固定的形状、颜色和纹理,它是一团不断扩散的气体颗粒集合体。这意味着你不能像检测行人、车辆那样依赖强结构特征,必须充分挖掘模型的上下文语义能力和多尺度特征融合能力。这篇文章我把自己踩过的坑、调过的参数、验证过的思路全部摊开来讲,希望能给你的项目提供实打实的参考。
2. 烟雾数据集构建与预处理
2.1 数据来源与清洗策略
训练烟雾检测模型,数据是地基。很多人上来就去网上扒公开数据集,比如某开源平台上的火灾烟雾数据集,下载下来直接用,结果训练出来的模型在测试集上效果尚可,一上真实场景就疯狂误报。问题出在哪?绝大多数公开烟雾数据集的场景单一,背景基本是天空、森林或者室内走廊,而且烟雾浓度、光照条件、摄像头角度高度同质化。模型学到的是“这个背景下的半透明区域是烟雾”,而不是“烟雾本身是什么”。
我的做法是混合多源数据,除了公开数据集之外,自己爬取并标注了一批不同场景的烟雾图片,包括城市街道、工厂车间、室外停车场、夜间灯光下的烟雾等。清洗时遵循几条硬性标准:分辨率低于 640×640 的模糊小图直接丢弃;烟雾占画面比例过小的图片保留但单独标注为小目标样本;有明显水印、严重压缩伪影的图片剔除。最终数据集规模控制在 8000 张左右,其中公共数据 5000 张,自采标注 3000 张。这个量级对于单类别检测来说完全够用,再往上堆数据边际收益就非常有限了。
2.2 标注细节与格式转换
标注工具我用的是 LabelImg,虽然界面老旧,但胜在稳定,支持 YOLO 格式直接导出。这里有一个关键细节:烟雾的标注框该怎么打?烟雾是扩散的,边界是渐变的,不像人、车那样有清晰轮廓。我实验下来,标注框应该紧贴烟雾的“核心浓密区”,不要把淡淡的薄雾也框进去。原因很简单——薄雾边缘的像素值变化极小,模型在这个区域学不到判别性特征,反而会把背景特征当作烟雾特征学进去,导致误检率飙升。
标注完成之后,需要把 VOC 格式转成 YOLO 格式。YOLO 格式的标注信息是归一化后的中心点坐标和宽高,分别存放在同名 .txt 文件中。转换脚本不复杂,核心代码大致如下:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_file, class_names, output_dir): tree = ET.parse(xml_file) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): class_name = obj.find('name').text if class_name not in class_names: continue class_id = class_names.index(class_name) xmin = int(obj.find('bndbox/xmin').text) ymin = int(obj.find('bndbox/ymin').text) xmax = int(obj.find('bndbox/xmax').text) ymax = int(obj.find('bndbox/ymax').text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(os.path.join(output_dir, os.path.splitext(os.path.basename(xml_file))[0] + '.txt'), 'w') as f: f.write('\n'.join(lines))数据集划分上,我采用 8:1:1 的比例,训练集 6400 张,验证集 800 张,测试集 800 张。划分时要注意:同一个场景拍摄的连续帧必须放到同一个集合里,否则会出现数据泄露,验证集指标虚高。
3. YOLOv8 训练细节与参数调优
3.1 环境配置与硬件门槛
先解决一个大家问得最多的问题:训练 YOLOv8 到底需不需要 GPU?答案是分情况。如果你的数据集只有几百张图片,模型选 YOLOv8s,那用纯 CPU 训练也能出结果,只是慢得让人怀疑人生。但如果像我这样 6000 多张训练图,CPU 训练一个 epoch 可能要二十多分钟,跑 300 个 epoch 就是一百多个小时,完全不现实。
我实际训练用的显卡是 GTX 1660Ti,6GB 显存,这张卡属于入门级,但跑 YOLOv8s 完全没问题(批次大小设 16,图像分辨率 640×640,显存占用约 4.2GB)。如果你也想用 1660Ti 或者其他 6GB 显存的卡训练,建议把 batch size 从默认的 16 降到 8,同时开启梯度累积,效果等同但显存压力小很多。环境配置按照 Ultralytics 官方文档一步步装即可,Python 3.9 + PyTorch 2.1 + CUDA 11.8 是我实测最稳的组合。
3.2 训练超参设置与损失曲线分析
训练命令我用的是:
yolo train data=smoke.yaml model=yolov8s.pt epochs=300 imgsz=640 batch=16 patience=20这里重点说一下几个参数的选择逻辑。patience=20是早停机制,表示连续 20 个 epoch 验证集指标没有提升就自动终止训练,能有效防止过拟合同时节省时间。imgsz=640是输入分辨率,如果你检测的是远距离小面积烟雾,建议尝试 768 甚至 896,小目标的特征在更大分辨率下更容易被模型捕捉,但训练速度和推理速度都会相应下降,需要根据实际场景权衡。
训练过程中要养成看损失曲线的习惯。YOLOv8 的训练日志里有 box_loss、cls_loss、dfl_loss 三类损失。烟雾检测场景下,重点观察 box_loss 和 cls_loss。正常情况下,box_loss 应该在训练初期快速下降,然后趋于平缓。如果 box_loss 出现剧烈震荡,大概率是学习率过大或者数据集里有标注错误;如果 cls_loss 降不下去,说明模型对“什么是烟雾”和“什么不是烟雾”的区分度不够,需要补充更多难负样本。
画损失曲线的方式有很多,最简单的是直接读取训练保存的 results.csv 文件,用 pandas 和 matplotlib 画图。这段代码我每次训练完都会跑一遍,方便复盘:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/detect/train/results.csv') plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.plot(df['box_loss'], label='box_loss') plt.plot(df['val_box_loss'], label='val_box_loss') plt.legend() plt.subplot(1, 2, 2) plt.plot(df['cls_loss'], label='cls_loss') plt.plot(df['val_cls_loss'], label='val_cls_loss') plt.legend() plt.savefig('loss_curve.png')3.3 训练结果与模型评估
我的 smoke 模型最终训练了约 260 个 epoch(触发了早停),best.pt 在验证集上的 mAP50 达到 0.942,mAP50-95 为 0.718,推理速度在 1660Ti 上单张图片约 12ms(640×640 输入)。这个精度对于烟雾检测来说已经属于优秀水平,毕竟烟雾目标的边缘不确定性极大,能到 0.94 的 mAP50 说明模型确实学到了烟雾的本质特征。
训练完成后会得到两个模型文件:best.pt 和 last.pt。best.pt 是验证集上表现最好的权重,last.pt 是最后一个 epoch 的权重。实际部署一定用 best.pt,这个不需要犹豫。
4. 模型部署与推理优化
4.1 本地摄像头实时推理
训练好的 smoke.pt 模型可以直接用于实时检测,Ultralytics 封装得很友好,加载模型跑摄像头视频流只需几行代码:
from ultralytics import YOLO model = YOLO('best.pt') results = model.predict(source='0', show=True, conf=0.35, imgsz=640, line_width=2)这段代码里source='0'表示调用第一个摄像头,如果你用的是 RTSP 网络摄像头,把source改成rtsp://用户名:密码@IP:端口/stream1即可。conf=0.35是置信度阈值,烟雾检测建议设置在 0.3~0.4 之间。太低容易误报,太高漏报严重。烟雾本身是半透明的,置信度天然不如“人”“车”这类实体目标高,我用 0.35 这个阈值在多个测试视频上表现最均衡。
4.2 模型导出与边缘设备部署
如果你的项目最终要部署到 RK3588、Jetson Orin Nano 这类边缘设备上,需要把 PyTorch 模型转换成 ONNX 或 TensorRT 格式,核心命令是:
yolo export model=best.pt format=onnx imgsz=640 opset=12导出成 ONNX 之后,在 RK3588 上可以利用 RKNN-Toolkit2 工具链转成 RKNN 格式,在 Jetson 设备上则用 TensorRT 的 trtexec 工具转成 engine 文件。这里有一个重要提醒:ONNX 导出时不要勾选 dynamic(动态输入尺寸),边缘设备的推理引擎对动态 Shape 支持较差,固定 640×640 的输入尺寸最稳妥。
部署方面还有一个容易被忽略的性能瓶颈——视频解码。很多人在推理优化上花了大功夫,结果瓶颈在 OpenCV 的 VideoCapture 解码速度上。处理 RTSP 流时,建议用 FFmpeg 硬解码或者英伟达的 PyNvVideoCodec 库,别让解码拖了推理的后腿。我实测 1080p 30fps 的 RTSP 流用软件解码要占用约 15% 的 CPU,换成硬解码后几乎可以忽略不计,这 15% 的 CPU 资源留给预处理和后处理,整个 pipeline 的延迟能降低不少。
5. 常见问题与排查技巧
5.1 漏检与误报的平衡
烟雾检测项目做多了,你会发现最棘手的问题不是模型精度不够,而是漏检和误报之间的平衡。传统机器学习时代,大家喜欢用一个固定的阈值一刀切,但深度时代完全可以把“疑似烟雾”和“确定烟雾”区分出来,分别设置不同的报警策略。我实际项目中采用了二级确认机制:第一级用低阈值(conf=0.25)做初步筛选,第二级用时序滤波对连续 5 帧中至少 3 帧检测到烟雾的区域才触发报警。这个机制大幅降低了误报率,而且只增加了极少的计算开销。
如果你的场景里存在蒸汽、灰尘、雾气等容易混淆的目标,建议在训练数据里刻意加入这些干扰物作为负样本。负样本不需要标注,放在训练集目录下但没有对应的 txt 文件即可。YOLO 训练时会自动将它们视为背景,这样模型被迫学习区分“真正的烟雾”和“看上去像烟雾的东西”。
5.2 单目标重复识别问题处理
热搜词里有一个很典型的问题:运动物体经过摄像头被识别了多次。这其实是目标跟踪中的 ID Switch 问题,如果只做单帧检测不做跟踪,每一帧都会独立输出检测结果,物体在画面中停留多久就会被识别多少次。解决方案是接入 ByteTrack 或 BoT-SORT 这类轻量级跟踪器,给每个目标分配唯一 ID,同一目标只触发一次识别事件。Ultralytics 已内置跟踪功能,只需把model.predict改成model.track,并加上persist=True参数:
from ultralytics import YOLO model = YOLO('best.pt') results = model.track(source='0', persist=True, tracker='botsort.yaml')5.3 数据增强的坑与经验
数据增强是提升模型泛化能力的利器,但用在烟雾检测上要格外谨慎。YOLOv8 默认开启马赛克增强,它把四张图拼接成一张。对于烟雾检测,马赛克增强有一个副作用:拼接边界处可能会出现两个不同场景的半透明烟雾叠在一起,模型容易学到错误的上下文特征。我的做法是训练前期(前 50 个 epoch)保持默认增强,训练后期手动关闭马赛克增强,让模型在正常分布上精调。具体做法是在训练命令中加mosaic=0.5参数调低马赛克的启用概率,或者干脆到训练后期把增强强度降下来。
颜色抖动增强同样需要克制。烟雾检测严重依赖颜色和亮度信息,如果把色相饱和度随机扰动拉得太高,模型会把某些极端颜色下的背景误判为烟雾。我建议 HSV 扰动参数设置为hsv_h=0.01, hsv_s=0.3, hsv_v=0.2,比默认值更低,实测对复杂光照场景的稳定性有明显改善。
5.4 显存不足与训练崩溃
最后说一个很多新手会遇到的问题:训练到一半显存爆炸,进程被杀。除了降低 batch size 之外,还有一个屡试不爽的技巧——开启梯度累积。假设 batch size 设为 8 会显存溢出,可以设置 batch size 为 4,同时配合accumulate=2,等效于用 4×2=8 的批次更新梯度,模型收敛效果几乎不受影响。另外,把训练参数中的workers调高(比如 8)能让数据加载速度追上 GPU 的计算速度,避免 GPU 空转。
我在这个项目里最大的体会有两点:一是数据集的质量上限决定了模型精度的上限,与其花大量时间研究网络结构改进,不如先把数据整理干净;二是烟雾检测没有一劳永逸的模型,换个场景、换个摄像头角度,模型的精度可能就大打折扣,需要针对实际场景做持续的数据补充和模型迭代。我在部署到具体项目时,一般会预留一个“现场数据回流”的机制,把漏检和误检的图片定期收集起来,重新微调模型,跑一次全流程的增量训练。这个习惯帮我解决了不少实际问题,也推荐你在做类似项目时尝试一下。
本文还有配套的精品资源,点击获取