简介图像烟雾检测是安全监控、火灾预警与环境监测中的重要技术。面向图像处理初学者及相关开发者这份基于MATLAB的代码项目提供了一套可直接运行的烟雾检测实现覆盖颜色空间转换、Canny/Sobel边缘检测、Gabor/LBP纹理分析、阈值分割与区域生长以及SVM分类等常见算法能够帮助理解从数据预处理、特征提取到分割检测和结果评估的完整流程。压缩包为rar格式共包含1个m文件包体大小仅1KB轻量精简适合快速上手实验。目前已有3245人学习浏览可作为课程设计、算法验证或项目初期的参考基线。通过该脚本读者不仅能掌握烟雾图像的基本特征分析与区域分割思路还能在此基础上扩展机器学习方法提升检测准确率并迁移至视频流实时检测场景。 图像烟雾检测这个方向我在实际项目里断断续续做了快两年。最开始是给一个化工厂做安防联动客户要求“发现冒烟马上报警”后来发现这事远比想象中麻烦——烟雾是半透明目标边缘模糊形态变化快光照稍微一变就各种误报白天和晚上的表现完全是两回事。这篇文章把我踩过的坑、试过有效的方案、以及最终的工程落地思路完整分享一下希望能帮你少走弯路。1. 项目概述与需求分析1.1 烟雾检测到底在解决什么问题烟雾检测属于计算机视觉里比较特殊的细分方向核心任务是从图像或视频中判断“画面里有没有烟”。听起来和火焰检测像但实际难度高不少。火焰有明确的颜色分布和形状特征容易用规则框出来烟雾恰恰相反它的颜色从纯白到灰黑都有形状受风向和温度影响随时在变边缘是渐变的和背景的雾气、水汽、扬尘在视觉上非常相似。这个方向的实际需求通常来自几个场景森林防火远距离观察山间冒烟、工厂园区安全生产露天堆场、危化品区域、城市消防早期预警、以及室内楼道或仓库的烟雾报警。不同场景对算法的要求完全不同比如森林防火看重检测距离和视野覆盖工厂园区更看重实时性和低误报率。我做的项目主要是园区安防摄像头已经在现场装好了基本都是720p或1080p的枪机角度固定覆盖范围从十几米到上百米不等。客户的核心诉求是烟雾出现后尽快报警同时不能因为傍晚的雾气、食堂的蒸汽、车辆扬尘就乱喊。1.2 为什么传统传感器不够用必须用图像很多人会问烟感报警器不是已经很成熟了吗为什么还要用摄像头做检测。这里有个关键区别传统烟感器是接触式检测必须等烟雾分子扩散到传感器位置才能触发适合室内封闭空间但室外场景、大空间厂房、森林这类环境烟感器根本没法部署。而且烟感器只能给你一个“冒烟了”的布尔信号没有位置信息没有画面证据。图像烟雾检测的价值恰恰在这里它能通过现有摄像头实现大范围、非接触式监测报警的同时还能提供现场画面方便值班人员立刻确认。摄像头是现成的算法跑在服务器或边缘设备上部署成本比密集布设传感器低很多。1.3 项目技术路线概览烟雾检测的完整技术栈大致分三层数据层烟雾图像的采集、标注、数据增强检测算法层传统图像处理或深度学习模型工程化层模型推理加速、报警策略、联动逻辑下面从方案选型开始讲这是整个项目最关键的决策点。2. 方案选型解析从传统图像处理到深度学习2.1 传统方法的思路和局限刚接触烟雾检测时我首先尝试了传统图像处理方法毕竟它不依赖大量标注数据。传统方法的典型思路可以拆成几个方向但理论和实战结合起来看效果都不太理想。颜色分析法。基于烟雾在特定颜色空间的像素分布特征比如在YCbCr空间里烟雾像素的色度分量范围比较窄亮度分量偏高。我最初试过用OpenCV把图像转到YCbCr然后根据经验阈值把疑似烟雾的像素筛出来再做连通域分析。在实验室测试里效果还行但一放到真实环境就露馅了傍晚的灰色天空、白色墙面、水泥地面这些像素特征和烟雾非常接近误报率直接失控。帧差法。利用视频中相邻帧的差异来捕捉动态区域烟雾出现时会引起灰度变化。这个思路对静态背景有效但是对树叶晃动、云影移动、夜间灯光闪烁太敏感。而且烟雾扩散速度不快帧差响应反而慢等到帧差足够明显时火已经着大了。纹理特征法。烟雾的纹理和背景有明显差异可以用局部二值模式LBP或灰度共生矩阵GLCM提取特征再输入SVM分类器。这个方案在公开数据集上能跑出不错的准确率但泛化能力差换一个场景就掉点严重。传统方法用下来我的结论是受限于特征表达能力和场景泛化能力传统方法更适合作为辅助验证手段或者用在算力极低的单片机上做个粗检。想要可靠落地必须走深度学习。2.2 深度学习模型怎么选深度学习方案的选择可以从模型架构和检测范式两个维度来思考。从检测范式上看烟雾检测有分类、目标检测、分割三条路。分类适合判断“整张图有没有烟”适合摄像头固定且画面内容单一的监控场景目标检测用边界框标出烟雾位置适合大范围画面里烟雾只占其中一块区域的情况分割可以到像素级精度最高但标注成本大且推理速度慢。最终我选了目标检测路线具体用的是YOLOv8。理由有三条烟雾在画面里通常是个局部目标用目标框定位更符合监控需求既报警又能告诉值班人员往哪看YOLO系列部署生态成熟OpenVINO、TensorRT都能良好支持方便接边缘设备检测框的置信度可以用于后续报警策略的“多帧确认”降低单帧误报有人可能问为什么不用Transformer类的检测器烟雾检测的实际部署对象经常是Jetson、工控机这类算力有限的设备Transformer固然精度上限高但推理时延和显存占用对边缘部署不太友好。在工程化项目里YOLO依然是性价比最高的选择。2.3 数据准备是烟雾检测的命门模型选型再完美没有高质量数据都白搭。烟雾检测最大的难点之一就是数据获取。公开数据集方面比较可用的有Bilkent烟雾数据集包含雾天和烟雾场景用于分类和检测Mivia Smoke Detection数据集面向视频烟雾检测Kaggle上的smoke detection图像集质量参差不齐需要仔细清洗但真实项目中公开数据远远不够。场景差异太大会导致模型迁移效果不佳比如公开数据集里多为森林背景我的园区背景是水泥地、钢结构、化学储罐模型表现必然打折。必须做场景自采数据。我在园区里蹲了一周用了几种方式采集在安全合规的前提下燃烧少量纸张、木材、废弃布料等材料制造真实烟雾场景从网络上搜集工厂冒烟、火灾现场的视频截图利用视频抽帧工具把监控视频转成图片数据集每隔几帧抽一张保证样本多样性数据标注我使用了LabelImg全部标注为矩形框。这里有个细节烟雾的边缘是渐变的标注时框的大小要尽量贴合烟雾的不透明核心区域不要硬把浅淡的薄烟边缘全部囊括进来否则会引入过多噪声。最后一共整理了约6000张真实数据加上公开数据的迁移补充构成训练集。经过数据增强比如随机裁剪、色彩调度、亮度变化、翻转最终参与训练的有效样本接近2万张。事实证明数据量的提升和场景匹配度对模型精度的提升比调参更明显。3. 核心细节解析与实操要点3.1 数据标注的边界把握烟雾检测标注和普通物体标注有很大区别。标注一盒香烟是一回事标注一团烟是另一回事。普通物体边界清晰勾住边缘就行烟雾是透明或半透明的能看到背景穿透过来很难判断什么才算“烟的区域”。我踩过几次坑之后总结的经验是以烟雾浓度较高的核心区域为准不要执着于标注所有淡薄区域如果一块区域既有烟又有蒸汽原则上标注为烟但要在训练中通过负样本平衡标注框尽量保持正方形比例因为烟雾扩散趋向于各向同性方形框匹配更合理对模糊不清的样本宁可删除也不要勉强标注数据标注完成后还要做一个统计检查烟雾目标的尺寸分布。如果数据集中目标框都很大而实际部署场景中烟雾早期目标很小模型就会对小目标漏检。针对小目标可以把图像切块训练或者专门补充小目标样本。这个细节直接影响模型在早期火灾发现上的实用性。3.2 负样本工程把误报率打下来误报是烟雾检测项目落地最大的敌人。训练集里如果只有烟雾的正样本模型很可能学成“纹理较灰的动态区域就是烟”。真实环境的干扰源很多晨雾、水汽、暴雨、扬尘、汽车尾气、灯光光晕、飞鸟、树叶摆动。解决思路是专门构建负样本集合让模型见过足够多的“假烟”。我从监控视频中截取了不同时段、不同天气下的画面把看起来很像烟但不是烟的场景挑出来包括清晨的低空雾气背景焊接作业产生的白色弧光和轻微烟气洒水车经过后的水雾工人在角落里抽烟的烟雾该样本在特定场景下需谨慎处理在可以合规使用的场景中保留空气污染严重时的灰霾天气这些负样本训练后模型对“假烟”的误报率下降了大约60%。判断模型好坏的指标不能只看mAP更要看“每路摄像头每天误报次数”。这个指标才是客户真正在意的。3.3 检测模型训练与调参要点我用Ultralytics YOLOv8作为主体框架训练过程中有几个参数对结果影响很大。输入分辨率。YOLO默认的640x640输入尺寸对烟雾这种纹理细节丰富的目标设置偏低。我的实测是把训练和推理分辨率统一提升到960x960小目标检测率上升约8个百分点。代价是推理速度下降但工程上换来的检测效果值得。训练轮次和批次。使用单张旗舰级显卡训练初始epoch设为200batch size为16。设置早停机制后实际有效训练在第150轮左右收敛。训练时开启混合精度训练速度提升约40%。损失函数占比。YOLOv8的box loss、class loss、DFL loss分配默认即可不需要大改。但如果出现分类置信度和定位精度不匹配的问题可以尝试略微提高box loss的权重。训练完成后最重要的验证手段不是测试集指标而是回放真实监控视频。我习惯留几段没参与任何训练的真实监控录像作为“最终考试”在录像上做全时段的实时抽帧检测统计误报和漏报。这个流程比任何指标都更能反映模型是否可用。4. 实操过程与核心环节实现4.1 从训练到模型部署的完整流程训练好的模型要真正发挥作用还得走完部署这一环。下面是完整的实现流程。第一步环境准备。训练环境用Python 3.10 PyTorch 2.0 CUDA 11.8推理部署环境根据实际设备选择。Jetson Orin上使用JetPack自带的PyTorch和TensorRT适配版本x86工控机上用OpenVINO。第二步训练脚本核心配置。下面是我整理的一个训练代码片段可以直接参考from ultralytics import YOLO # 加载预训练模型 model YOLO(yolov8m.pt) # 训练配置 model.train( datasmoke_dataset.yaml, # 数据集配置 epochs200, imgsz960, # 输入分辨率 batch16, device0, patience30, # 早停耐心值 cacheTrue, # 缓存数据加速训练 optimizerAdamW, lr00.0005, # 初始学习率 augmentTrue, # 开启自动增强 valTrue, )第三步导出为TensorRT引擎。如果要在NVIDIA设备上部署模型导出成TensorRT的engine格式是性能最优解yolo export modelbest.pt formatengine device0 imgsz960第四步搭建推理服务。我用的是一个轻量级的异步消息队列摄像头拉流进程把抽帧图像放入队列推理进程消费队列做检测检测结果送人报警判断模块。这样把采集、推理、报警解耦摄像头数量增多时可以横向扩展推理进程。4.2 消融实验不同方案的真实效果对比为了说明各个改进点对最终效果的影响我整理了一个消融实验表格用同一份固定测试集500张含烟图像500张无烟图像做对比方案召回率误报数测试集备注传统颜色帧差法68%187明显不可用YOLOv8m 640输入81%63基线深度学习方案YOLOv8m 960输入89%41分辨率提升有效YOLOv8m 960输入 负样本强化87%9误报大幅下降召回略降YOLOv8m 960输入 负样本强化 多帧确认86%2最后部署版本上面的数据可以说明负样本的加入虽然会让召回率轻微下降但误报数从41降到了9这是质的飞跃。再加上多帧确认策略后实际部署环境的误报几乎被压制到可接受范围。4.3 多帧确认与报警策略单帧检测结果直接报警是不现实的哪怕误报率只有1%一天下来也够让人烦。我采用的报警策略很简单但极其有效连续N帧N通常取5到10中至少有70%的帧检测到烟雾且检测置信度大于等于阈值比如0.45且检测框的中心点位置变化幅度不超过一定范围保证不是瞬间飘过的干扰满足以上条件才触发报警。这个策略能过滤掉大部分瞬时性的干扰比如闪光、飞鸟、快速移动的车辆。代价是报警延迟增加了约1-3秒对于火灾预警来说完全可接受。判断逻辑用Python写起来很简单class SmokeAlertStrategy: def __init__(self, min_frames5, hit_ratio0.7): self.min_frames min_frames self.hit_ratio hit_ratio self.history [] def update(self, detect_frames): self.history.append(detect_frames) if len(self.history) self.min_frames: self.history.pop(0) if len(self.history) self.min_frames: return False hit_count sum(1 for frame in self.history if frame) return hit_count / len(self.history) self.hit_ratio实际运行中线别烟是一团一团持续涌出的通常不会断续出现这种多帧“投票”能有效区分偶尔被误判的画面。5. 常见问题与排查技巧实录5.1 白天误报多、晚上漏检严重这是最常见的反馈。白天误报多的核心原因是可见光干扰包括太阳角度变化带来的阴影、地面反射的高光、以及远处公路的车辆尾气。解决思路是建立场景光照模型在特定时段提高置信度阈值。晚上漏检是因为普通监控摄像头有红外夜视模式画面是黑白的烟雾的视觉特征变化很大。我的处理方法是晚上单独跑一套夜间专用的检测模型或者对输入图像做自适应直方图均衡化增强低光照下的纹理信息。5.2 检测框闪烁抖动的处理单帧检测的边界框往往会出现抖动相邻帧之间框的位置和大小跳来跳去。如果报警联动摄像头跟踪或者云台控制这种抖动会让操作员非常烦躁。我的处理方案是引入简单的卡尔曼滤波对检测框的中心坐标和宽高做平滑预测。这里给出核心代码思路import numpy as np class KalmanBoxSmoother: def __init__(self): # 状态: [cx, cy, w, h, vx, vy, vw, vh] self.state None self.transition np.eye(8) for i in range(4): self.transition[i, i4] 1 def update(self, box): if self.state is None: self.state np.array([box[0], box[1], box[2], box[3], 0, 0, 0, 0], dtypenp.float32) return box predicted self.transition self.state # 简化:取预测结果和观测结果的加权平均 alpha 0.6 for i in range(4): predicted[i] alpha * predicted[i] (1 - alpha) * box[i] self.state predicted return predicted[:4]5.3 摄像头安装位置对检测效果的影响图像烟雾检测和传统视觉检测一样装得高看得远但目标变小装得低画面清晰但覆盖范围受限。我的经验是室外烟雾检测摄像头尽量安装在距离地面6-10米的高度角度略微俯视10-20度这样既能获得开阔视野又不会让烟雾被前方建筑遮挡。顺光比逆光重要得多。逆光环境下烟雾的视觉对比度会显著下降模型漏检率大幅上升。如果摄像头朝向无法调整建议开启摄像头的宽动态功能并且算法侧对过曝区域做掩膜避免高光区域干扰检测。5.4 模型在不同季节表现不稳定的排查春天杨柳絮、夏天暴雨前的雾气、秋天农田焚烧在允许的地区、冬天的锅炉蒸汽每个季节都有新花样。有一个很大的教训是不要只在单一的天气条件下采集数据。有条件的话项目上线后前三个月保持数据采集和人工复核每月用新样本做一次增量训练模型才能慢慢适应现场的季节变化。5.5 小目标漏检的专项优化园区监控画面里烟雾出现初期往往只占画面面积的1%-2%。这种小目标YOLO检测效果天生不差但也不够好。我试过两种方式提升小目标效果把原始图像切成4块重叠的patch分别做检测后再合并结果。对1080p图可以显著提升小目标命中率在数据集中专门保留大量小目标样本并通过CopyPaste增强方式把小目标烟雾贴到不同背景上增加小目标样本的数量切图方案的缺点是推理次数成倍增加实际部署时可以考虑只在“粗检到的低置信度区域”才触发切图细检这样兼顾了速度和精度。5.6 平台工程的稳定性问题再强调一次工程问题。模型性能只是系统的一部分推理服务的稳定性更关键。我在现场遇到过一次推理进程内存泄漏导致系统运行三天后崩溃。排查后定位为TensorRT的context没有正确释放。建议线上服务加上监控每半小时记录一次推理进程的内存占用量设置内存超过阈值自动重启进程所有报警记录写入数据库方便事后复盘另外摄像头画面丢失没有处理会导致算法一直对黑图或灰图推理产生大量无效告警。需要在接入层加“画面正常性检测”用简单的亮度方差判断画面是否有效画面异常时直接跳过推理。6. 最后想说的几句实在话做烟雾检测这个项目最大的体会是算法模型只是整个系统的一部分甚至不是最复杂的部分。真正决定项目能否落地的是数据是否贴近现场、误报策略是否符合实际运营需求、以及部署后是否有持续迭代优化的机制。如果你准备在这个方向上做一个实际项目我建议先从场景调研开始花点时间去现场蹲点搞清楚一年四季、一天不同时间段里画面中会出现哪些“像烟但不是烟”的东西。这一步没做好后面模型再怎么调优都是白费。当前这个版本在园区环境里已经稳定运行了大半年误报基本压到了每路摄像头每天0-1次的水平烟雾发现时间在15秒以内。如果要继续提升我的下一步计划是对烟雾的浓度和扩散趋势做帧间运动估计这样才能提前判断烟雾是从哪里冒出来的把预警时机再往前拉一点。本文还有配套的精品资源点击获取