简介:这是面向计算机视觉与深度学习入门及进阶人群的烟火识别训练数据集,聚焦公园、街道、室内等小范围场景中的烟火检测任务。包内共1204个文件,压缩包约69.68MB,包含600张jpg原始图像、600个配套XML标注文件及4个txt说明文件。XML标注提供烟火目标的边界框、类别与关键特征,可直接用于YOLO、SSD、Faster R-CNN等常见检测模型的训练与验证;txt文件可能包含数据集划分或类别清单,便于整理训练流程。目前已有951人学习使用,适合需要快速获得标注数据以开展烟火检测实验、完成课程设计或进行迁移学习实践的研究者。借助该数据集,可帮读者省去手动采集与标注的繁琐环节,专注于模型搭建、调参与效果评估。
1. 小场景的烟火识别数据集:通用数据集为什么在小监控里翻车
同一个检测模型,在森林火灾、厂房大空间这类公开数据集上 mAP 能跑到 0.9,一装到厨房、配电箱、楼道小监控画面上,烟是半透明的,火只有十几像素,漏检率立刻飙升。这个标题要做的就是把小场景烟火识别数据集这件事拆开:从采集方案、标注规范、格式转换到训练验证,最后落到避坑和持续扩充,形成一条能重新跑通的链路。它适合做安防摄像头联动、机房告警、工地消防监测的算法工程师和学生,前提是你手头已经有真实场景的图像或视频抽帧,并且愿意在数据质量上花时间。这里不解决大场景遥感火情,不讲怎么从零搭模型,只讲怎么把数据集做扎实。
2. 数据长什么样:来源、类别与小场景采集方案
烟火识别数据集听起来简单,真正动手才发现难点全在样本形态。小场景的“烟火”和通用火灾检测里的“烟火”不是一回事。这里先立住这个概念,后面的采集、标注和训练才不容易跑偏。
2.1 小场景与通用场景数据集的主要差异
通用场景数据集绝大多数拍的是大场面:森林火灾的烟墙、工厂仓库的大火、远处直升机洒水。这些画面里火焰和烟雾占画面比例高,识别目标大,纹理信息丰富。小场景不一样的地方有三点。
第一是目标尺度小。一个安装在厨房吊顶的摄像头,视野大概两三米见方,灶台上的火苗框起来可能只有 30×40 像素,在 1920×1080 原图里占比不到百分之一。第二是烟雾半透明。烟雾没有清晰的边缘,透出背后的橱柜和墙砖,标注和模型都在跟“似有似无”的纹理搏斗。第三是干扰源密集。厨房水蒸气、炒菜油烟、窗外阳光、红色调料瓶、手机屏幕亮光,都会让模型把普通画面当成烟火。
还有一个常被忽略的差异:拍摄视角。通用数据集多为平视或俯视的大广角,小场景监控往往是斜下角或正下角,目标有遮挡、形变、明暗突变。所以做这个数据集的时候,不能直接下载一个公开火灾数据集就开训,必须先从视角、尺度和干扰源三个维度确认它是否匹配你的场景。
2.2 数据来源:公开数据集、实拍与合成增强
常见的做法是三条腿走路。
公开数据集方面,网上能下到的火灾识别数据集绝大多数偏向大场景,少数包含室内近景的样本,但数量少且标签风格不一致。有人会直接套用 coco2017 数据集结构去凑烟火类别,但 COCO 本身没有 smoke/fire 类,只能算作预训练特征来源,不能当烟火数据用。更实际的做法是把公开数据集当作补充样本库,只挑其中火焰占比小、视角贴近监控画面的图片,抽出来手动复核标签后再并入自己的训练集。
实拍是数据集质量的主心骨。不要真的在室内点火拍,合规和安全隐患都太大。我一般会去采集三类素材:厨房日常炒菜时的油烟和灶火、工地/工厂电焊火花、蜡烛和燃烧盘等可控小火焰。拍摄时分别用手机和低分辨率监控头各拍一段,让模型见过两种成像质量。如果项目里已经有客户提供的现场监控视频,抽帧前记得确认数据权限。
合成增强可以作为补充。用带透明通道的烟雾贴图叠加到真实背景上,能快速扩出大量烟雾样本,代价是真实感有限。下面是个最小叠加脚本:
import cv2 import numpy as np bg = cv2.imread("kitchen.jpg") # 真实背景,小场景优先 smoke = cv2.imread("smoke.png", cv2.IMREAD_UNCHANGED) # RGBA透明烟雾贴图 h, w = bg.shape[:2] smoke = cv2.resize(smoke, (w, h), interpolation=cv2.INTER_LINEAR) alpha = smoke[:, :, 3:4].astype(np.float32) / 255.0 # 取出alpha通道并归一化 alpha = cv2.GaussianBlur(alpha, (0, 0), sigmaX=1.5) # 让边缘更柔和 bg_f = bg.astype(np.float32) smoke_rgb = smoke[:, :, :3].astype(np.float32) blend = (bg_f * (1 - alpha) + smoke_rgb * alpha).astype(np.uint8) cv2.imwrite("kitchen_smoke_syn.jpg", blend)这段代码的逻辑是:把烟雾贴图的 alpha 通道作为前景权重,背景按 1-alpha 保留,两张图加权求和。注意对 alpha 做高斯模糊,因为真实烟雾边缘是逐渐消散的,硬边贴图一眼假。参数里 sigmaX 建议 1.0~2.5,太小边缘生硬,太大烟雾会散成一片看不清。合成样本只建议占总样本的 20% 以内,并且要人工筛选掉严重失真的图,否则模型会把“模糊”学成烟雾特征。
2.3 采集方案与场景清单
采集前先列一份场景清单,避免拍来拍去都是同一类画面。表格是实际项目中会用到的底稿:
| 场景类型 | 建议角度 | 主要干扰源 | 建议样本量 |
|---|---|---|---|
| 厨房灶台 | 斜上45°、顶部吊装 | 水蒸气、油烟、红椒 | 500+ |
| 配电箱/机柜 | 正面、侧上方 | 指示灯、红色线缆 | 300+ |
| 楼道走廊 | 两端对角 | 灯箱、安全出口红光 | 300+ |
| 垃圾桶/废纸篓 | 俯视 | 塑料袋反光 | 200+ |
| 工地小角落 | 平视、斜视 | 焊光、红色安全帽 | 400+ |
每类场景要覆盖白天、夜晚、开灯、关灯、逆光五个光照条件。别只挑“看得清”的画面,故意多拍一些模糊、过曝、偏暗的样本,这些才是部署时真正会遇到的画面。视频抽帧时不要每秒都抽,否则连续帧几乎一样,浪费存储还造成数据泄漏,建议每秒抽 1~2 帧,并且保留抽帧时间戳,后面划分数据集时按时间戳分组。
3. 标注与格式转换:把图像变成 YOLO 能吃的数据集
采集完的原始图像还是半成品。烟火识别的难点不只是“目标在哪里”,而是“烟到哪里为止、火算不算红外光晕”。这一步标注规范定不好,之后的训练、评估全部受影响。
3.1 标注目标:烟、火分开标,还是合成一个
我的建议是分开标成两类:class 0 为 smoke,class 1 为 fire。虽然最终部署时可以合并成一个告警,但训练阶段分开有两个好处:一是火焰和烟雾在形状、纹理、运动特征上差异明显,让 loss 分别学习两类特征比强行合成一个类别更稳定;二是推理时如果经常出现“烟很多但火不识别”,看混淆矩阵就能立刻定位是哪一类出了问题。
标注框的范围要写进规范。对火焰,框住亮芯加橙色外焰,不要框进烟雾;对烟雾,框住肉眼可见的半透明核心区,淡到几乎看不清的透明边缘一律不框。可能有人觉得“边缘也是烟”,但标注边缘会导致标签框大幅抖动,同一帧不同人标出的框 IoU 只有 0.4,模型训练等于在跟噪声作对。
小目标也要设一个下限。单边像素小于 4 的框直接丢弃,因为 resize 到 640 之后这些目标只剩 1~2 像素,模型不可能学出有效特征。保留它们只是让 loss 被随机噪声污染。
3.2 标注工具与标注规范
标注工具我一般用 X-AnyLabeling,支持输出 YOLO 格式,也支持半自动预标注。先把类别写进 classes.txt:
smoke fire然后设置自动保存间隔为 5 分钟,标注快捷键保持默认。多人协作时一定要先定“核心烟团”的定义,拿 10 张图让每个标注员独立标一遍,计算两两之间的 IoU,低于 0.6 就说明规范没对齐,宁可停下来重新讨论。
3.3 把 VOC 转成 YOLO 格式:转换脚本与无效框过滤
很多公开数据下载下来是 VOC 的 XML 格式,训练自己的数据集时通常要转成 YOLO 的 txt。这里给出一个带过滤逻辑的转换脚本,处理小场景烟火数据时比通用脚本多两个关键判断。
import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_path, class_map, min_side=4): tree = ET.parse(xml_path) 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"): name = obj.find("name").text if name not in class_map: continue # 跳过未定义类别或负样本标记 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) w = xmax - xmin h = ymax - ymin if w < min_side or h < min_side: continue # 过滤掉小于4像素的框,小目标不能小到丢失全部特征 xmin = max(0, min(xmin, img_w - 1)) xmax = max(0, min(xmax, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) ymax = max(0, min(ymax, img_h - 1)) if xmax <= xmin or ymax <= ymin: continue # 过滤掉越界后被压成0宽度的框 x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h cls_id = class_map[name] lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") if len(lines) == 0: return with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines))参数说明:min_side 是保留框的最小边长,设 4 是对应 imgsz=640 的常见选择;如果你后面训练用 1280,可以适当放松到 2。归一化坐标全部保留 6 位小数,太短会引入量化误差。转换完成后要随机抽 20 张图,把 txt 坐标画回原图人工看一眼,最常见的问题是 XML 标注把宽高写反。
3.4 按场景划分 train/val/test 并统计类别分布
烟火视频抽帧得到的图片时间相关性强,直接随机划分会把同一段视频的相邻帧分到训练集和验证集,验证指标虚高。正确做法是先按视频片段或采集时间戳分组,再把整组数据划入同一集合。
import os import random import shutil from collections import Counter # 假设 images 目录下文件名格式为 scene001_frame0001.jpg # 每组按场景ID划分,而不是按单张图 files = [f for f in os.listdir("images") if f.endswith(".jpg")] scene_groups = {} for f in files: scene_id = "_".join(f.split("_")[:1]) # 取文件名前缀,如 scene001 scene_groups.setdefault(scene_id, []).append(f) scene_ids = list(scene_groups.keys()) random.seed(42) random.shuffle(scene_ids) train_scenes = scene_ids[:int(len(scene_ids) * 0.8)] val_scenes = scene_ids[int(len(scene_ids) * 0.8):int(len(scene_ids) * 0.9)] test_scenes = scene_ids[int(len(scene_ids) * 0.9):] # 统计类别数量,作为训练权重参考 label_counter = Counter() for scene in train_scenes: for img in scene_groups[scene]: label_file = os.path.splitext(img)[0] + ".txt" label_path = os.path.join("labels", label_file) if not os.path.exists(label_path): continue for line in open(label_path, "r", encoding="utf-8"): label_counter[int(line.strip().split()[0])] += 1 print(label_counter)这段代码先按场景分组,再按组划分,保证同一个监控点位拍出来的画面不会在训练集和验证集里同时出现。random.seed(42) 固定随机种子,别人复现你的结果时拿到一致的划分。类别统计会告诉我们 smoke 和 fire 的比例,如果 fire 只有 smoke 的十分之一,后面训练就要做过采样或样本加权。
4. 训练自己的烟火识别模型:YOLOv8 与关键参数
数据集就位以后,接下来是用它去训练一个能用的检测模型。这里不推冷门框架,选 YOLOv8 和 YOLOv5 之一,它们的问题不是“哪个更强”,而是“哪个更适合你的部署环境”。
4.1 选型:YOLOv8n 还是 YOLOv5s
我的经验是:需要跑在 Jetson、RK3588 这类边缘盒子上,优先考虑 YOLOv8n,导出 ONNX 和瑞芯微转换工具的支持度更好;如果团队已有 YOLOv5 的部署代码和量化流程,就继续用 YOLOv5s,不折腾。两者在这个任务上的精度差距远小于数据质量带来的差距,把时间花在补数据上更划算。
注意一点:预训练权重用的是 COCO 预训练模型,但 COCO 缺少烟火类别,所以加载预训练权重只取 backbone 特征,Head 层要按 nc=2 重新初始化。YOLO 训练命令会自动处理这一步,你不要手动改权重文件,否则容易踩维度不对的坑。
4.2 数据集 YAML 与训练命令
处理数据集用于 YOLOv8 训练时,最常犯的错是 YAML 里路径写绝对路径,换一台机器就废。推荐用相对路径加path字段指定数据根目录。新建smoke_fire.yaml:
path: /data/smoke_fire train: images/train val: images/val test: images/test nc: 2 names: 0: smoke 1: fire说明:train/val 下面直接是图片文件,标注 txt 放在labels/train和labels/val,YOLO 靠找同名文件实现对应,无需在 YAML 里写 label 路径。启动训练前先打印配置确认一下:
yolo cfg=smoke_fire.yaml确认无误后开始训练:
yolo train model=yolov8n.pt data=smoke_fire.yaml imgsz=640 batch=16 epochs=300 project=runs/smoke_fire命令里没有加device=cpu,默认优先 GPU。第一次训练建议先用小 batch 跑 20 轮,确认 loss 不发散、不出现 NaN,再跑完整训练。
4.3 小场景训练参数怎么设
小场景烟火识别不适合直接套用公共教程里的默认参数。下面这张表是实测后比较稳的起点:
| 参数 | 建议值 | 原因 |
|---|---|---|
| imgsz | 640 | 兼顾小目标与显存;火焰不足 10 像素可试 1280 |
| batch | 16(8G显存)/ 32(16G以上) | 显存不足优先降 batch,不降 imgsz |
| epochs | 300 | 烟雾纹理弱,需要更久才能收敛 |
| mosaic | 1.0 前 250 轮,最后 10 轮关闭 | 防止合成烟和真实烟长混 |
| hsv_h | 0.015 | 色相偏移太大会把火焰变成绿色 |
| hsv_s | 0.5 | 保留烟雾的灰白色调 |
| close_mosaic | 10 | 关闭 mosaic 后模型适应单一尺度 |
hsv 增强是烟火识别的双刃剑。火焰颜色是有强先验的,色相偏移 0.1 就会生成大量橙红色物体当火焰,干扰反而更大。所以这里把 hsv_h 压到很小,保留亮度变化,不改变颜色的基本分类依据。
4.4 评估指标:不要被 mAP 骗了
训练完先看results.csv里的 mAP@0.5 和 mAP@0.5:0.95。小场景烟火更关键的是 F1 和混淆矩阵。烟雾因为半透明,视觉特征弱,漏检往往比误检多,所以重点看 Recall 而不是 Precision。如果 F1 不理想,先去val_batch_pred.png里翻漏检图,确认是烟太淡还是目标太小,再决定是补数据还是调阈值。
还有个小技巧:把验证集的置信度阈值从默认的 0.25 降到 0.1,导出更多预测框,专挑那些“低置信度但实际有火”的样本。这些样本就是数据集缺口的直接证据,应该回填到训练集,而不是简单地靠调阈值硬扛。
5. 烟火识别数据集避坑:5 条踩坑记录
数据集的坑比模型结构多得多,而且每一坑都会直接反映到最终效果上。下面这五条是我做同类项目时反复踩过的问题,每条都按“现象 → 原因 → 解决”展开。
5.1 验证集虚高:连续帧泄漏
现象:训练时 mAP@0.5 到 0.98,部署到新场景基本不能用。
原因:视频抽帧后随机划分,同一段 10 秒视频的 20 帧分到了训练集和验证集,模型等于做过“开卷考试”。小场景监控画面背景固定,只需要记住背景就足够过验证,真实场景一换角度就失效。
解决:按视频场景分组划分,而不是按帧划分。前面 3.4 的脚本已经打了样,务必把同一个时间段、同一机位的帧归到同一个组。
5.2 烟雾框边缘抖动
现象:同一张图两次标注的框相差 30% 以上,训练 loss 一直降不下来。
原因:标注规范没有定义“烟到哪里为止”。半透明烟雾边缘扩散,有人框到浓烟核,有人框到整个烟团,标签噪声直接放大。
解决:把标注规范写死:只框不透明度超过 50% 的核心区域;做一次双人标注一致性测试,IoU 低于 0.6 就重新培训再开工。这个成本远低于后期清洗标签。
5.3 红色干扰物体误报
现象:红色灯笼、厨房红椒、晚霞、红色安全帽都被识别成 fire。
原因:小场景里火焰颜色成了最强特征,模型学到的是“橙色红色块”,而不是火焰的形状和闪烁纹理。
解决:专门建一个负样本集,包含上述干扰场景但不标注,训练时放进 images 目录且 labels 目录留空,让模型学会“有红色但没有火”的背景。这批负样本同样需要按场景划分,不能全都放训练集。
5.4 小火焰在 resize 中丢失
现象:火焰在 1920×1080 里是 20×20 像素,送到 640×640 后只剩 7×7,特征完全被压缩成色块。
原因:统一 resize 对大图小目标不友好,缩放过程中纹理和边缘细节被抹掉。
解决:优先用 imgsz=1280 训练;显存不够就切成瓦片,把大图按 640×640 重叠裁剪,火焰完整落在某一块再训练。推理时也用瓦片拼接,不要图省事整图缩放。
5.5 烟多火少类别失衡
现象:模型对烟识别很好,对火的召回率特别低,但总 Precision 看起来还不错。
原因:标注数据中 smoke 样本远多于 fire,尤其厨房场景几乎不断烟,火焰只在爆炒或起火时出现。loss 被多数类别主导。
解决:训练时让 fire 类在采样器里过采样,或在 loss 权重中给 fire 更高权重。注意不要把权重调太大,否则模型会把一切橙色物体都当火。我一般先统计 3.4 里的类别数量,按比例从 1.0 到 3.0 之间试。
6. 让数据集越用越强:难例回捞与版本迭代
数据集做完不是终点,上线后保持迭代,效果才会越用越稳。我最后的习惯是用训练好的模型反哺数据:拿一批没有标注的新场景视频跑推理,置信度低于 0.35 的框特别值得看,尤其那些“模型认为像烟但我们知道没烟”的干净场景,以及漏掉的淡烟雾,都是难例。把这些难例帧抽出来,人工标注,合入下一版训练集。
验证方法可以简单固定下来:每次迭代后跑同一组留存的测试集,比较 mAP 和漏检数,不要用用户反馈当评估指标,太滞后。迭代记录记在数据集目录的 CHANGELOG 里,写清楚新增了多少场景、标签规范改了没有、有没有换过划分方式,不然三个月后你都不知道上一版为什么效果更好。
做烟火识别数据集最大的教训就是:模型改结构只能涨两个点,数据质量能涨十个点。与其反复调参,不如先回看标注规范有没有把半透明烟雾处理干净。希望我的这些经验能帮你少走点弯路,把数据集一步做到位,让模型真正在小场景里扛住实战。
希望帮到你。
本文还有配套的精品资源,点击获取