☰
烟火检测数据集+YOLO训练全流程:从标注格式到误报规避实战
2026/10/29 7:25:51 网站建设 项目流程

简介:面向计算机视觉与深度学习开发者,一个已标注的烟火检测数据集可直接用于训练和评估YOLO系列目标检测模型,帮助解决公共场所火灾隐患的自动识别与定位问题。压缩包共2000个文件,其中1000张JPEG原始图像与1000个XML标注文件一一对应,标注信息采用边界框形式记录烟火位置,方便配合YOLOv3、YOLOv5等主流框架进行训练与微调,89.87MB的体积也便于快速下载和迭代实验。数据集中包含不同光照、背景和烟火形态的图像样本,适合从目标检测入门到项目落地阶段的研发人员使用,可通过JPEGImages与Annotations的目录结构直接接入现有训练流程。目前已有3245人学习下载,作为真实场景的标注数据资源,能够为模型提供充分的正负样本,结合数据增强手段进一步提升检测鲁棒性,是开发烟火识别系统的实用基础数据。

1. 烟火检测数据集:1000张已标注图能帮你跨过训练的第一道坎

做烟火检测训练的人,大概率经历过这种尴尬:模型结构选好了,训练代码调通了,结果手里只有几张从监控视频里截下来的图,标注还得自己一行一行画框。这份标题里的烟火检测数据集,指向的就是一份已经替你跨过这道坎的资源——约1000张带标注的烟火检测图像。它不是什么魔法模型,而是一份可以马上喂给YOLO、Faster R-CNN这类目标检测器的已标注数据资产。适合谁来用?刚接触目标检测、想把烟火识别落地到消防预警或安防监控里的开发者。它能解决的问题很具体:让你在几小时内从零跑到第一个能出结果的模型,而不是把时间花在采集、清洗和标注上。

2. 先看明白包里有什么:烟火数据集结构与标注格式的确认

2.1 解压后第一件事:摸清目录结构和图片规格

拿到zip别急着跑训练脚本。先解压,看看目录长什么样,确认图片和标注文件是不是一一对应。

unzip 烟火检测数据集1+1000IMG+已标注.zip -d smoke_data cd smoke_data find . -type f | head -50 find . -type f | wc -l

第一行把压缩包解压到smoke_data目录,第二行进入目录,第三行看前50个带路径的文件名,确认有没有二级目录、有没有README之类的说明文件,第四行数总文件数。如果包内确实有1000张图且每张图配一个标注文件,文件数应该在2000以上;如果文件数少一个量级,说明标注可能合并成了单个json,或者有一部分图没标。

接下来看图片格式和分辨率,这一步很多人跳过,但直接影响后面imgsz参数怎么设:

file images/* | head -10 python3 -c "from PIL import Image; import glob; imgs=glob.glob('images/*'); ws=[Image.open(i).width for i in imgs[:200]]; hs=[Image.open(i).height for i in imgs[:200]]; print('W', min(ws), max(ws), 'H', min(hs), max(hs))"

file命令确认图片到底是jpg还是png,避免把bmp和png混在同一批里;第二段脚本用PIL读前200张的宽高,快速判断分辨率跨度。烟火检测的数据常见来源有监控截图、无人机视角和手机拍摄,分辨率经常不齐,有的是1920x1080,有的是640x480。分辨率差异大时,训练要做统一resize,小分辨率图放大后火焰纹理会被插值磨平,这件事在训练前心里要有数。

2.2 标注格式决定你的工作量:YOLO、VOC 还是 COCO

打开一个标注文件,确认格式再决定要不要写转换脚本。

find . -type f \( -name "*.txt" -o -name "*.xml" -o -name "*.json" \) | head -20

如果是每行class cx cy w h的txt且数值大多在0到1之间,是YOLO格式,最省事;如果xml根节点是<annotation>,是VOC格式;如果json是带images、annotations字段的字典,是COCO格式。烟火检测数据集里,VOC和YOLO都比较常见,因为这方向很多资源是从安防项目沉淀下来的,标注习惯偏这两个格式。我一般会把格式统一到YOLO——后续训练YOLO系列不用写转换,部署导出ONNX时也直接吃这个格式。

还要看类别名。很多烟火数据集只标一个统一的烟火类,因为烟雾边界本来就难定、火焰又是动态变化的;也有的会拆成fire和smoke两个类,方便做火点报警和烟雾预警的区分。打开任意一个标注文件看一眼类别名,这决定了你的分类器是几分类。如果只有一个类,后面训练配置里nc直接设1,不用纠结。

2.3 标注与图片的命名对齐:最容易忽视的一步

YOLO格式的标注文件一般和图片同名,只是扩展名不同,比如frame_001.jpg对应frame_001.txt。检查一下是否有“有图无标”或“有标无图”的情况:

ls images | sed 's/\.[^.]*$//' | sort > /tmp/img_names ls labels | sed 's/\.[^.]*$//' | sort > /tmp/lbl_names comm -23 /tmp/img_names /tmp/lbl_names | head -20

comm -23列出只在图片列表里出现的文件名,也就是缺标注的图。如果缺得不多,比如两三张,直接删掉对应图片再训练;如果缺了几十张,就要考虑是不是标注文件命名规则不同,比如图片是000001.jpg而标注是frame_000001.txt,需要先做一次重命名对齐。命名问题不解决,训练时YOLO会静默跳过这些图,你甚至不会发现数据量少了。

3. 把数据集喂给 YOLO:VOC 转 YOLO 与最小训练命令

3.1 写一个可复用的 VOC 转 YOLO 转换脚本

如果你拿到的标注是VOC格式,第一步是转成YOLO要用的txt。下面这个脚本可以直接存成voc2yolo.py复用。

import xml.etree.ElementTree as ET import os, glob def voc_to_yolo(xml_path, out_dir, class_map): tree = ET.parse(xml_path) root = tree.getroot() w = int(root.find('size/width').text) 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 cls_id = class_map[name] bndbox = obj.find('bndbox') x1 = float(bndbox.find('xmin').text) y1 = float(bndbox.find('ymin').text) x2 = float(bndbox.find('xmax').text) y2 = float(bndbox.find('ymax').text) cx = (x1 + x2) / 2.0 / w cy = (y1 + y2) / 2.0 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") base = os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base + '.txt'), 'w') as f: f.write('\n'.join(lines)) class_map = {'fire': 0, 'smoke': 1} # 按实际标注的类名改 os.makedirs('labels', exist_ok=True) for xml in glob.glob('Annotations/*.xml'): voc_to_yolo(xml, 'labels', class_map)

逻辑说明:VOC的xmin/ymin/xmax/ymax是绝对像素坐标,必须除以图片宽高得到归一化的中心点坐标和宽高,YOLO训练时才不会把框画出边界。代码里的class_map需要按你数据里真实的类名改,如果只有一个烟火类,就写成{'fire_smoke': 0}并把cls_id对应改掉。还要注意一个细节:如果x2 - x1算出来是0,说明标注框退化成了一条线,这类对象建议直接跳过。

3.2 用 YOLOv8 跑通一个最小可用的训练命令

确认标注格式和目录结构后,写数据集描述文件smoke.yaml:

path: /absolute/path/to/smoke_data train: images/train val: images/val nc: 1 names: ['fire_smoke']

如果拆分了fire和smoke两类,nc: 2,names对应改为['fire', 'smoke']。这里path我建议写绝对路径。YOLOv8对相对路径的解析依赖你运行命令时所在的目录,如果不一致就会报数据集不存在,这个问题很多人排查半天最后发现只是路径写飘了。

然后执行训练:

pip install ultralytics yolo detect train data=smoke.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16

说明:训练入口是yolo detect train,model=yolov8s.pt会加载COCO预训练权重,比从头训练收敛快很多。imgsz=640是烟火检测的下限——火焰和烟雾在监控画面里经常只占几十个像素,压到416的话小目标基本就是丢;batch=16在显存不够时降到8,但不要用1,BN层在batch太小时统计不准,模型难以收敛。

3.3 切分数据集:先洗牌再划分,别按文件名顺序切

很多从视频抽帧得到的数据集,文件名带时间戳,按顺序切分会把同一个场景的视频段同时分到训练集和验证集,出现严重的数据泄漏。用脚本做随机切分:

import random, os train_ratio = 0.85 images = os.listdir('images') random.seed(42) random.shuffle(images) n = int(len(images) * train_ratio) with open('train.txt', 'w') as f: for img in images[:n]: f.write(os.path.abspath(os.path.join('images', img)) + '\n') with open('val.txt', 'w') as f: for img in images[n:]: f.write(os.path.abspath(os.path.join('images', img)) + '\n')

random.seed(42)固定随机种子,保证每次切分结果一致,复现实验结果时不会因为切分不同导致指标对不上。YOLOv8的data.yaml里train和val可以直接写这个txt文件的路径,也可以直接指向目录。注意如果类别不平衡,比如烟多火少,切分时最好按类别比例做分层采样,而不是纯随机。纯随机切分在小数据集上容易把某个类全切进验证集,训练时模型干脆学不到这个类。

4. 训练前先给数据做体检:类别分布与标注质量的检查

4.1 类别分布统计:看看有没有“空心类”

已标注数据集不等于干净数据集,先跑一个统计脚本看看每个类别到底有多少框。

import glob from collections import Counter c = Counter() for txt in glob.glob('labels/*.txt'): for line in open(txt): c[int(line.split()[0])] += 1 print(c)

输出类似{0: 3672, 1: 452}这样的结果。如果fire类有三千多个框而smoke只有四百个,这个模型会对烟几乎免疫——烟的目标更小、边界更模糊,天然难学,样本量还少,训练出来的权重很容易偏向火。应对办法有两个:一是训练时给少数类更高的loss权重,YOLOv8里可以用cls参数配合类权重来做;二是对稀有类的图片做重复采样,让每个epoch里烟和火的出现次数接近。对于1000张规模的数据集,第二种办法实现更直接,但要注意别把同一张图同时放进train和val。

4.2 小目标占比:烟火场景的几何真相

火焰和烟雾在画面里的尺寸分布,决定了输入分辨率和增强策略。用脚本统计框面积占比:

import glob sizes = [] for txt in glob.glob('labels/*.txt'): for line in open(txt): _, cx, cy, bw, bh = line.split() sizes.append(float(bw) * float(bh)) sizes.sort() print(f"中位数={sizes[len(sizes)//2]:.5f} 最小5%={sizes[len(sizes)//20]:.5f}")

框面积占比是归一化坐标下bw * bh,即框占整张图的比例。如果中位数在0.005以下,意味着有超过一半的目标不到画面面积的0.5%,属于典型的远程烟火监控场景。这时候有两条路:把imgsz提到960或1280,让目标在输入图上占据更多像素;或者用SAHI这类切片推理框架,把大图切成小块分别检测再合并结果。另外,YOLO默认打开的Mosaic增强会把四张图拼在一起,小目标在拼接时经常被裁掉一半,训练时可以考虑关闭Mosaic或只在最后20个epoch关闭。这条经验在烟火检测里尤其重要,因为火焰的碎片化特征和车辆、行人完全不同。

4.3 画框自查:标注质量比超参数更值得花时间

统计只能看出数量,看不出质量。写一个几十行的可视化脚本,抽样画框检查:

import cv2, glob img_path = glob.glob('images/*.jpg')[0] txt_path = img_path.replace('images', 'labels').replace('.jpg', '.txt') img = cv2.imread(img_path) h, w = img.shape[:2] for line in open(txt_path): cls, cx, cy, bw, bh = map(float, line.split()) x1 = int((cx - bw/2) * w); y1 = int((cy - bh/2) * h) x2 = int((cx + bw/2) * w); y2 = int((cy + bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite('check_0.jpg', img)

随机抽几十张图,把标注框叠加到原图上,重点看两类问题:一是烟雾框是否把整团弥散的烟都包含进去了,还是只框了烟的中心一团;二是画面里有明显火焰但完全没框。第一个问题影响mAP,第二个问题直接污染了训练信号。已标注数据集不等于完美数据集,这种检查能帮你判断标注者的标准和你业务诉求之间的差异,也能决定你后面的验收阈值怎么定。

5. 烟火检测数据集的五个坑:从拆包到调优的踩坑记录

5.1 现象:验证集 AP 高得离谱,一到真实监控视频就翻车

原因:视频逐帧抽帧产生的相邻帧内容几乎一样,随机切分时训练集和验证集里各出现同一个场景的不同帧,模型相当于在验证集上“见过”了答案。1000张规模的数据集如果来自视频抽帧,这个问题非常常见。解决:切分前按帧内容做去重,用感知哈希算一下相邻帧的相似度,低于阈值只保留一张;或者干脆按视频段落切分,而不是按单帧切分。这个检查应该在切分之前做,做完再重新跑第3.3节的脚本。

5.2 现象:训练 Loss 很低,但验证集的 PR 曲线拉不起来

原因:烟火被当成同一个类时,火焰高亮、动态、边界清楚,烟雾半透明、边界模糊,模型内部的特征表达互相干扰。如果标注里火和烟是分开的两个类,第一个模型建议只训fire或者只训smoke,先把单类别跑通;如果数据集只有一个统一的烟火类,训练时把conf阈值调低,让模型先学会召回再考虑精确,不要一上来追求高分框。1000张图对统一烟火类来说,数据量可以对得起一个baseline,但别期待它一步到位。

5.3 现象:白天测试集效果不错,夜里误报率暴涨

原因:数据集中白天场景占大多数,模型学到的颜色先验偏向白天光照下的火焰——高亮、偏橙红、背景较亮。到了夜间,路灯、车灯、反光标识的颜色分布和火焰高度重叠。解决:训练增强里把亮度、对比度的扰动范围拉大,hsv_h、hsv_s、hsv_v这三个增强参数里主要调hsv_v到 ±0.4,让网络不再死记白天的亮度特征;如果业务以夜间为主,最好能补充一些夜间烟火图,哪怕只有三五十张,对夜间的泛化提升比加两百张白天图更明显。

5.4 现象:标注框明显比目标大一圈,mAP@0.5 上不去

原因:标注者习惯把一团火焰连周围的发光区域和热浪一起框进去,导致预测框和标注框的IoU上不去。这是烟火数据的普遍现象——烟雾本身没有清晰边界,强行画框必然有主观误差。解决:验收指标用 mAP@0.5 而不是 mAP@0.75,0.75阈值在边界模糊的烟火场景下意义不大;推理部署时NMS的置信度阈值从默认的0.25提到0.35以上,把低分误报框过滤掉。频繁调NMS阈值的效果往往比调模型结构更直接。

5.5 现象:模型把晚霞、红色灯牌当成火

原因:火焰在RGB空间的特征和晚霞高度重叠,模型学到的主要是颜色纹理,而不是“火焰是动态的、烟雾是扩散的”这种时序语义。解决:收集一批hard negative——晚霞、路灯、红色车灯、烟囱蒸汽的图片,不加标注直接丢进val集做负样本评测,或者按10%的比例混入训练集。YOLO对没有标注文件的图片会自动当负样本处理,这张图如果被预测出烟火框,就会计入误报并反传梯度。我自己的教训是负样本至少要覆盖部署场景里会出现的干扰物,否则验证分数就是自欺欺人。

6. 训练完别只看 mAP:用误报样本反向修正数据集

训练结束后的第一步不是导出部署,而是把val集里的false positive全部调出来看一眼。跑一遍验证并保存置信度与预测框:

yolo detect val data=smoke.yaml model=runs/detect/train/weights/best.pt conf=0.35 save_txt=True save_conf=True

然后把runs/detect/val里的预测结果和真值标注打开对比。烟火检测的误报通常集中在三类:晚霞和夕阳、夜间灯光、树影和蒸汽。把这三类里典型的误报图单独建一个目录,命名为hard_negative,每次训练后都用它做一轮评测,重点看F1分数而不是只看mAP。一个模型如果为了不漏报而狂报,值班人员很快就会对报警疲劳,实际落地价值反而低于漏报率稍高但误报可控的模型。

我每次训练都会保留一份best.pt和对应的混淆矩阵截图,隔两周回来看一眼,马上能想起来上次是卡在召回还是卡在精确。烟火检测的难点不在模型选型,而在数据质量、类别平衡和误报控制这三件事。这份1000张已标注的数据集,价值在于它帮你建立了一条可以重复评测的基线——后面的优化都是在这个基线上迭代的。希望这篇文章能帮你少踩几个坑,也祝你的模型既看得见真正的火,也扛得住晚霞和车灯的考验。

本文还有配套的精品资源,点击获取

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

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

立即咨询