ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

烟火图像识别与分类:YOLOv8实战与避坑指南

烟火图像识别与分类:YOLOv8实战与避坑指南 简介这份资源面向计算机视觉入门者与安全监控、火灾预警方向的开发者围绕烟火图像的识别与分类任务提供从图像预处理、特征提取到模型训练与评估的完整实践素材。压缩包共3627个文件约11.18MB主体为3617张bmp格式的烟火样本图像另含xml标注文件、jpg示例图、py训练脚本、ipynb预处理与实验笔记及项目配置覆盖数据准备、模型搭建与调参全流程。已有217人学习下载。读者可借助这批带类别前缀的样本数据动手完成去噪、灰度化、二值化等预处理尝试SIFT、SURF或CNN特征提取并用SVM、随机森林及AlexNet、VGG、ResNet等模型进行训练与验证结合准确率、精确率、召回率和F1分数评估优化快速搭建可复用的烟火识别分类实验框架。1. 烟火图像的识别与分类从一张监控截图说起凌晨两点林场瞭望塔的摄像头回传了一张图画面右下角有一团模糊的橙红色区域。值班员放大看了半天拿不准是炊烟、车灯反光还是真的起火。这种场景下人眼判断的延迟和误判率都很高而烟火图像的识别与分类要解决的正是这件事——让模型在几秒内告诉你这张图里有没有烟、有没有火如果有是哪种形态的烟火。它适合做安防监控、森林防火、工业园区巡检的工程师也适合手里有烟火数据集、想把检测精度再往上推一截的算法同学。往下读你会看到从数据标注到模型选型、从训练参数到部署踩坑的完整路径。2. 烟火图像的数据准备标注策略与增强手段2.1 烟火数据集的三个来源与筛选标准做烟火识别第一步不是选模型而是搞清楚你的数据从哪来。常见做法有三类公开数据集、监控视频抽帧、以及自己用手机或无人机补拍。公开数据集里烟火类样本往往存在两个问题——烟雾和火焰的标注框重叠严重以及负样本类似烟火的云、雾、灯光比例过低。我一般会先做一轮筛选火焰标注框面积小于图像面积 0.5% 的直接剔除因为这种尺度在 640 分辨率下训练时几乎学不到有效特征烟雾样本则保留那些边缘清晰、与背景有明确色差的。监控视频抽帧要注意时间冗余。同一段火情视频连续抽帧相邻帧差异极小直接丢进训练集会导致验证集指标虚高。我的习惯是按秒抽帧每秒最多取 2 帧再用感知哈希做一次去重汉明距离小于 5 的只保留一帧。自己补拍时重点补两类清晨薄雾中的炊烟、夜间车灯在湿滑路面上的反光。这两类负样本是烟火模型翻车的高发区。筛选完之后建议按 7:2:1 划分训练、验证、测试集并且保证同一段视频的帧只出现在一个集合里。这个细节很多教程不讲但它是你后面调参时能不能信任验证集指标的前提。2.2 用 Albumentations 做烟火场景的数据增强烟火图像有一个特点火焰的颜色和纹理在 HSV 空间里比 RGB 空间更稳定而烟雾的形态对模糊和对比度变化很敏感。所以增强策略不能一套参数打天下。下面是我在烟火项目里常用的一套增强配置基于 Albumentations 实现。import albumentations as A import cv2 # 训练集增强火焰和烟雾分开处理 train_transform A.Compose([ # 几何变换火焰和烟雾都适用 A.RandomResizedCrop(height640, width640, scale(0.6, 1.0)), A.HorizontalFlip(p0.5), A.Rotate(limit15, border_modecv2.BORDER_CONSTANT, p0.3), # 颜色变换针对火焰的 HSV 扰动 A.HueSaturationValue(hue_shift_limit10, sat_shift_limit30, val_shift_limit20, p0.4), # 针对烟雾的模糊和对比度 A.OneOf([ A.GaussianBlur(blur_limit(3, 7)), A.MotionBlur(blur_limit7), ], p0.3), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.3), # 模拟遮挡树枝、建筑遮挡烟火 A.CoarseDropout(max_holes8, max_height40, max_width40, fill_value0, p0.2), ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels])) # 验证集只做 resize 和归一化 val_transform A.Compose([ A.Resize(height640, width640), ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels]))这段代码的关键参数逻辑RandomResizedCrop的scale下限设到 0.6是为了让模型见到更多小尺度烟火HueSaturationValue的hue_shift_limit只给 10因为火焰的色调偏移过大时会变成紫色或绿色反而引入错误标签CoarseDropout模拟遮挡但max_holes不超过 8否则一张图里烟火可能被完全盖住训练时梯度会乱。验证集不做任何随机增强只做 resize保证每次评估的输入一致。注意如果你的数据集里烟雾样本远少于火焰样本不要只靠增强来平衡。增强出来的烟雾多样性有限最好在损失函数里加类别权重或者用 focal loss 把烟雾类的关注度提上来。2.3 标注格式转换与边界框检查烟火数据集常见的标注格式有 VOC XML、COCO JSON 和 YOLO txt。如果你用的是 YOLO 系列需要把 VOC 转成 YOLO 格式。转换本身不难但边界框的边界情况容易出问题标注框超出图像边界、宽高为 0、坐标归一化后出现负数。下面这个脚本在转换时会做一轮清洗。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, img_w, img_h, output_dir): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip().lower() # 只保留 smoke 和 fire 两类 if cls_name not in [smoke, fire]: continue cls_id 0 if cls_name smoke else 1 bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 裁剪到图像边界内 x1, y1 max(0, x1), max(0, y1) x2, y2 min(img_w, x2), min(img_h, y2) # 跳过无效框 if x2 - x1 2 or y2 - y1 2: continue # 归一化为中心点 宽高 cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 再次检查归一化后的值 if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): continue 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(output_dir, txt_name), w) as f: f.write(\n.join(lines))转换完一定要抽查。我一般会随机抽 20 张图用 OpenCV 把 YOLO 格式的框画回原图肉眼确认框的位置和类别标签对得上。这一步花 10 分钟能省掉后面训练时几个小时的排查。3. 烟火分类模型选型YOLOv8 与轻量化骨干的取舍3.1 为什么烟火检测优先考虑 YOLOv8 而不是两阶段检测器烟火识别本质上是一个小目标检测问题而且对实时性有要求。Faster R-CNN 这类两阶段检测器精度上限高但推理速度在边缘设备上很难做到 30 FPS 以上。YOLOv8 在 640 输入下n 版本在 RTX 3060 上能跑到 100 FPS 以上m 版本也有 50 FPS 左右足够覆盖大多数监控场景。更重要的是YOLOv8 的 anchor-free 设计对小目标更友好烟雾这种边界模糊的目标anchor 机制反而容易引入匹配噪声。选 YOLOv8 还有一个工程上的理由Ultralytics 的生态比较完整从训练到导出 ONNX、TensorRT 都有现成接口部署时不用自己写后处理。如果你需要把模型塞进 Jetson Nano 或者瑞芯微 RK3588 这类板子YOLOv8n 剪枝量化后能压到 3MB 以内这是两阶段检测器很难做到的。当然YOLOv8 不是唯一选择。YOLOv9 和 YOLOv10 在结构上做了改进但社区工具链的成熟度还不如 v8。我的建议是如果你刚起步先用 YOLOv8 把基线跑通等精度和速度的瓶颈明确了再考虑换骨干或换检测头。3.2 用 YOLOv8 训练烟火检测模型的最小命令假设你已经按上一章准备好了 YOLO 格式的数据集目录结构是images/train、images/val、labels/train、labels/val下面这个 data.yaml 和训练命令可以直接抄。# fire_smoke.yaml path: /data/fire_smoke_dataset train: images/train val: images/val names: 0: smoke 1: fire# 安装 ultralytics pip install ultralytics # 开始训练 yolo detect train \ modelyolov8m.pt \ datafire_smoke.yaml \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ device0 \ projectruns/fire_smoke \ nameyolov8m_baseline参数说明modelyolov8m.pt是从预训练权重开始微调烟火数据集通常几千到几万张从头训容易过拟合lr00.01是初始学习率如果 batch 调到 32 以上可以适当加到 0.02lrf0.01是最终学习率因子配合余弦退火策略让学习率从 0.01 降到 0.0001patience30表示验证集指标 30 轮不提升就早停烟火数据集的收敛通常在 80 到 120 轮之间设 30 能避免浪费时间。训练过程中重点看两个指标mAP50-95和recall。烟火检测里 recall 比 precision 更重要漏报一场火灾的代价远大于误报。如果 recall 偏低优先检查烟雾类样本的标注质量而不是急着调模型结构。3.3 轻量化骨干替换MobileNetV3 与 ShuffleNetV2 的实测对比当你的部署目标是边缘设备时YOLOv8m 可能太重。这时候可以把骨干换成 MobileNetV3 或 ShuffleNetV2。Ultralytics 没有直接提供替换骨干的接口需要改模型配置文件。下面是一个替换为 MobileNetV3 的配置片段思路。# yolov8-mobilenetv3.yaml 关键部分 backbone: - [-1, 1, Conv, [32, 3, 2]] # 0-P1/2 - [-1, 1, MobileNetV3Block, [16, 3, 2]] # 需要自定义实现 # ... 后续层按 MobileNetV3 结构堆叠实际改起来比较繁琐更稳妥的做法是用timm库加载 MobileNetV3 的特征提取层然后接 YOLOv8 的 neck 和 head。我在 RK3588 上做过一组对比YOLOv8n 原始模型量化后推理一帧 640 图像约 18ms换成 MobileNetV3 骨干后降到 12ms但 mAP50 掉了约 3 个百分点。ShuffleNetV2 的速度和 MobileNetV3 接近mAP 略低 0.5 左右。如果你的场景对精度要求不是极致MobileNetV3 是更平衡的选择。提示换骨干后一定要重新做一轮学习率 warmup。预训练权重和骨干结构不匹配时初始梯度会比较大warmup 能避免训练初期 loss 震荡。4. 烟火识别避坑五条血泪经验4.1 烟雾和云朵分不清模型把白云标成 smoke现象验证集上烟雾的误报率很高可视化后发现大量天空中的白云被标成 smoke。原因训练集里烟雾样本的背景大多是树林或建筑缺少天空背景下的负样本模型学到了“白色团状物烟雾”的捷径。解决在训练集里加入至少 500 张带云朵的天空图像作为负样本同时把烟雾类的置信度阈值从 0.25 提到 0.4牺牲一点 recall 换 precision。4.2 夜间火焰检测全挂白天正常现象白天测试 mAP50 有 0.85夜间直接掉到 0.4。原因夜间火焰在图像里是高亮区域但训练集中夜间样本占比不到 10%模型没有学到夜间火焰的纹理特征只学到了亮度特征。解决补拍夜间火焰和夜间灯光负样本增强时对夜间图像单独做 gamma 校正把暗部细节提上来。另外夜间场景下把输入分辨率从 640 提到 960小火焰的召回会明显改善。4.3 训练 loss 正常但 mAP 不涨查了半天是标注类别搞反了现象训练 loss 稳定下降但验证集 mAP 一直在 0.1 左右。原因标注时 smoke 和 fire 的类别 ID 在部分文件里写反了模型学到的特征和标签对不上。解决写一个脚本统计每个类别 ID 对应的标注框数量和平均面积如果 smoke 的平均面积比 fire 还小大概率是标反了。重新检查 data.yaml 里的 names 顺序和标注文件里的 ID 是否一致。4.4 模型在服务器上跑得好部署到边缘设备精度暴跌现象ONNX 模型在 PC 上推理正常转成 RKNN 或 TensorRT 后 mAP 掉了 10 个点以上。原因量化过程中烟火图像的高动态范围区域火焰核心被过度压缩导致特征丢失。解决量化时用不少于 500 张的校准集校准集里必须包含白天、夜间、烟雾、火焰各场景的样本。如果掉点仍然严重改用 FP16 而不是 INT8速度损失约 20%但精度基本无损。4.5 视频流推理时帧间结果闪烁同一团烟一会儿有一会儿没现象对视频逐帧推理检测框在相邻帧之间跳变置信度忽高忽低。原因单帧检测没有利用时序信息烟雾在半透明状态下本身就不稳定。解决在推理后处理里加一个简单的跟踪器比如 ByteTrack把连续 3 帧以上检测到的目标才输出。另外对置信度做滑动平均窗口大小取 5 帧能明显减少闪烁。5. 烟火分类的进阶技巧用时序信息把误报再压一半单帧检测的上限是肉眼可见的但视频流里有时序信息可以用。我试过一个简单但有效的做法对同一摄像头连续 10 帧的检测结果做投票如果某一区域在 10 帧里有 7 帧以上被检出为烟火才最终判定为火情。这个逻辑用 Python 写起来不到 50 行。from collections import deque class TemporalVoter: def __init__(self, window10, threshold7, iou_thresh0.5): self.window window self.threshold threshold self.iou_thresh iou_thresh self.history deque(maxlenwindow) def update(self, detections): # detections: list of [x1, y1, x2, y2, conf, cls] self.history.append(detections) if len(self.history) self.window: return [] # 以当前帧的检测框为基准统计历史帧中匹配框的数量 confirmed [] for det in detections: count 0 for past_dets in self.history: for pd in past_dets: if self._iou(det, pd) self.iou_thresh and det[5] pd[5]: count 1 break if count self.threshold: confirmed.append(det) return confirmed def _iou(self, a, b): x1 max(a[0], b[0]); y1 max(a[1], b[1]) x2 min(a[2], b[2]); y2 min(a[3], b[3]) inter max(0, x2 - x1) * max(0, y2 - y1) area_a (a[2] - a[0]) * (a[3] - a[1]) area_b (b[2] - b[0]) * (b[3] - b[1]) return inter / (area_a area_b - inter 1e-6)这个投票器的核心参数是window和threshold。窗口越大误报越低但响应延迟越高。10 帧在 25 FPS 下是 0.4 秒对大多数监控场景可以接受。iou_thresh设 0.5 是为了容忍烟雾形态的轻微变化如果目标移动快可以降到 0.3。除了投票还有一个技巧是给模型加一个分类头把“烟火”进一步细分为“明火”“阴燃烟”“水蒸气”三类。阴燃烟和水蒸气在单帧里几乎无法区分但时序上阴燃烟的扩散速度更慢、边缘更稳定。我在一个工业园区项目里加了这层分类后误报率从每天 15 次降到了 3 次以下。最后说一个我自己的习惯每次训练完新模型不要只看 mAP一定要把验证集里所有漏报和误报的图导出来一张一张看。烟火识别这个方向数据质量比模型结构重要得多。我见过太多人花两周调网络最后发现是标注框偏了 10 个像素。希望帮到你。本文还有配套的精品资源点击获取
返回列表