简介这是一份带COCO标注的火灾检测数据集面向需要训练火焰与烟雾识别模型的开发者、算法工程师及消防安全研究人员可用于目标检测算法的训练、验证与对比。压缩包内共含2000个文件主体为1995张JPG图片另配3个JSON标注文件和2个TXT类别配置文件整体大小约550.82MB标注信息与图片一一对应可直接用于常见检测框架也支持转换为其他标注格式。该数据集完整版本标称包含9332张图片本包精选其中1995张代表性样本覆盖近景火焰、远距离烟雾、夜间火点等复杂场景兼顾不同光照与背景干扰有助于提升模型在真实火情中的泛化能力。目前已有903人学习下载既适合入门者理解COCO标注结构也适合有经验的研究者基于现成数据快速迭代模型节省大量人工标注时间加速火灾检测方案落地。1. 火灾检测最难的从来不是“检测”而是分清哪些是火、哪些是烟火灾检测里模型翻车最多的地方不是漏检而是把灯光、晚霞、烧荒的烟误判成火焰或者把火焰和烟雾混在一起报。这个标题给的是一个现成标注数据集9332张图片全部按COCO格式标注能分出火焰、烟雾、以及火焰和烟雾同框的“烟火”场景。一句话说它是给目标检测模型做训练和评估用的燃料专门解决“分不清火和烟”这个老大难。适合谁做森林防火、园区监控、工业安防视觉方案的人或者想快速验证“我的检测模型跑火灾场景到底行不行”的人。下面按拿到数据的顺序先说COCO标注怎么看再说怎么验证、怎么转格式、怎么避开五个常见坑。2. 9332张COCO标注长什么样从image_id到segmentation的完整链路2.1 先读懂三个类别火焰、烟雾、烟火的边界在哪标题说“可区分烟火火焰和烟雾”落到数据集里一般就是三个类别ID。类别定义直接决定标注质量也决定后面训练时损失函数怎么算、评估指标该怎么看。常见映射长这样category_idname中文含义典型场景0fire火焰明火、燃烧的物体、火苗1smoke烟雾浓烟、灰烟、远处升起的烟柱2fire_smoke烟火火焰烟雾同框着火的建筑、灌木丛、同时冒烟和着火的车这里有个容易误会的地方中文里的“烟火”常指烟花但火灾数据集里的烟火不是烟花而是“火焰和烟雾同时出现”的复合场景。为什么要把同框场景单独做成一类实际标注时一张图上火光和浓烟紧挨着让标注员分别画两个不重叠的框非常难模型训练时也容易因为二者共存而混淆。把它作为复合类既降低标注难度又给了模型一个明确的训练信号看到火中有烟、烟中有火别只报其中一个。在COCO的JSON里categories数组通常这样组织[{id: 0, name: fire, supercategory: fire}, {id: 1, name: smoke, supercategory: fire}, {id: 2, name: fire_smoke, supercategory: fire}]注意supercategory可以全部归为fire也可以单独归为fire_scene这个字段只影响层级关系不影响训练。真正影响训练的是id和name的一一对应。拿到数据后第一件事就是用脚本打印categories确认是不是三类、id有没有跳号。如果id是从1开始而不是0后面转YOLO格式时就必须做显式映射不能想当然。2.2 images 和 annotations 字段bbox、area、segmentation 哪个最容易读错COCO JSON顶层有三个数组images、annotations、categories。images里每张图片有id、file_name、width、height这四个字段是后面所有转换脚本的地基缺一个都跑不起来。annotations里每个目标有id、image_id、category_id、bbox、area、segmentation、iscrowd。先说bbox。COCO的bbox是[x, y, width, height]像素坐标左上角为原点。这里最容易读错的是坐标系很多人在OpenCV里习惯了(x, y)是框左上角但在COCO里直接读出来的就是这个值不需要换算。真正要小心的是转YOLO时COCO的x, y对应YOLO中心点格式的x w/2很多人直接拿x当中心点用框全歪。segmentation字段分两种。单个目标一般用多边形polygon是一个扁平列表形如[x1, y1, x2, y2, ...]长度一定是偶数两个坐标一组。当一个目标被严重遮挡、边界特别碎时标注会用RLE格式并配合iscrowd1。训练检测模型时多数脚本会忽略segmentation只读bbox但如果你要跑分割模型或者想用mask去校验bbox质量polygon就是必须的了。area字段表示目标像素面积不是归一化值。它有个不起眼但重要的用途按COCO官方定义面积小于32×32像素是小目标32×32到96×96是中目标大于96×96是大目标。火灾场景里远处火苗往往只有20×20像素所以在后面统计时不能只按官方阈值要结合自己的画面尺寸重新切。读数据时不管用pycocotools还是纯json解析都要先建立image_id到图片索引的映射。pycocotools的coco.loadImgs()和coco.annToMask()这两个接口最常用前者拿图片信息后者把polygon或RLE转成二值mask。如果你发现某个image_id在images数组里但没有任何annotation别急着删先确认是不是这张图真的没有目标。COCO官方允许图片没有标注但YOLO训练时不接受这种情况后面会专门讲这个坑。3. 训练前先给数据集做个体检可视化脚本与统计脚本3.1 用 pycocotools 把标注画到图上肉眼确认质量拿到任何带标注的数据集我第一步不是看数据卡而是把所有标注画到图上逐张看。因为标注错乱、类别贴错、框画飞这类问题统计指标是骗不了人的但肉眼扫一遍能快速发现系统性错误。下面的脚本用pycocotools读取COCO JSON把bbox和类别名画到原图上import cv2 from pycocotools.coco import COCO coco COCO(annotations/instances_train.json) cat_map {cat[id]: cat[name] for cat in coco.dataset[categories]} for img_id in list(coco.imgs.keys())[:50]: info coco.imgs[img_id] img cv2.imread(fimages/{info[file_name]}) if img is None: print(missing image:, info[file_name]) continue ann_ids coco.getAnnIds(imgIdsimg_id) anns coco.loadAnns(ann_ids) for ann in anns: name cat_map[ann[category_id]] x, y, w, h [int(round(v)) for v in ann[bbox]] color (0, 0, 255) if name fire else (0, 255, 0) if name smoke else (0, 255, 255) cv2.rectangle(img, (x, y), (x w, y h), color, 2) cv2.putText(img, name, (x, max(0, y - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(fcheck_{info[file_name]}, img)这段逻辑很简单getAnnIds拿到某张图的所有标注idloadAnns加载具体内容bbox的x、y直接用于画框不需要做任何归一化。三个类用三种颜色区分火红色、烟绿色、烟火黄绿色。如果你发现大量“烟火”类的框只盖住了一团烟而没盖住火说明标注员对这个类的理解有偏差后续训练就容易出现类别间混淆。3.2 统计类别分布和bbox尺寸划定小目标基线可视化抽查只能发现大问题样本分布还得靠统计。下面这段统计三类目标的数量以及每类的面积分布用来定位小目标占比import json from collections import Counter with open(annotations/instances_train.json, r, encodingutf-8) as f: data json.load(f) cat_name {c[id]: c[name] for c in data[categories]} img_count len(data[images]) ann_count len(data[annotations]) per_cat Counter() area_small Counter() for ann in data[annotations]: cat cat_name[ann[category_id]] per_cat[cat] 1 if ann[area] 32 * 32: area_small[cat] 1 print(图片总数:, img_count) print(标注总数:, ann_count) for cat, cnt in per_cat.items(): print(cat, cnt, 小目标占比:, round(area_small[cat] / cnt, 3))参数说明32×32是COCO官方小目标阈值单位是像素面积。如果你的监控画面是1080P一个20×20像素的火苗在100米外可能就是一个点这时建议把阈值改成40×40或50×50再看一遍。小目标占比超过40%后续训练就要考虑提高输入分辨率、开启多尺度增强不然AP50和AP75会差得离谱。统计结果出来后还要看一眼“每张图平均标注数”。火灾场景经常出现一张图十几个小烟雾目标也有大量图只有一两个目标。如果平均每张图标注数小于1.5说明大量图片只有单目标模型学到的上下文信息会很少容易对所有不相关区域产生误报。这种情况下训练时的batch size和数据增强策略都要跟着调整。4. 把COCO转成YOLO格式转换脚本与归一化细节4.1 转换脚本JSON 到 txt 标签文件YOLO系列训练用的标签格式是每张图一个txt每行一个目标格式为类别ID cx cy w h其中cx、cy是归一化中心点坐标w、h是归一化宽高。COCO的bbox是左上角宽高两者必须做转换。下面这段脚本把COCO JSON转成YOLO格式import json import os with open(annotations/instances_train.json, r, encodingutf-8) as f: data json.load(f) img_info {img[id]: img for img in data[images]} os.makedirs(labels, exist_okTrue) for ann in data[annotations]: img img_info[ann[image_id]] w, h img[width], img[height] cat_id ann[category_id] x, y, bw, bh ann[bbox] cx (x bw / 2) / w cy (y bh / 2) / h bw_n bw / w bh_n bh / h label_path os.path.join(labels, img[file_name].rsplit(., 1)[0] .txt) line f{cat_id} {cx:.6f} {cy:.6f} {bw_n:.6f} {bh_n:.6f}\n with open(label_path, a, encodingutf-8) as out: out.write(line)逻辑说明先把images数组转成img_id - 图片信息的字典方便后面按image_id查找宽高。转换核心是cx (x bw / 2) / w即COCO的左上角x加上半个框宽得到中心点x再除以图片宽。同理高度方向用cy (y bh / 2) / h。最后宽高直接除以图片宽和高就行。这里的参数有三个地方容易写错第一中心点坐标计算时除以的是图片宽和高不是bbox宽高第二bw_n和bh_n也是相对图片的不是相对类别第三输出的txt文件名必须和图片文件名一致只换扩展名目录层级也要和images对应YOLO训练时会按相对路径去匹配labels和images。如果某张图片没有目标必须生成一个空txt文件不能跳过。否则YOLO会在训练时报警告“Image without labels”虽然不会中断训练但这类图不会参与损失计算等于白白浪费。4.2 按类别分层切分训练集和验证集随机切分在火灾这种样本不均衡的场景里特别容易出事尤其是烟雾和火焰数量差距大的时候随机切分很可能把某个类在验证集里切得只剩几十张。我建议做分层切分按“包含某类别的图片”来划分而不是按标注实例划分import json import random from collections import defaultdict random.seed(42) with open(annotations/instances_all.json, r, encodingutf-8) as f: data json.load(f) img_ids_by_cat defaultdict(set) for ann in data[annotations]: img_ids_by_cat[ann[category_id]].add(ann[image_id]) train_ids, val_ids set(), set() for cat, ids in img_ids_by_cat.items(): ids list(ids) random.shuffle(ids) split int(len(ids) * 0.85) train_ids.update(ids[:split]) val_ids.update(ids[split:]) train_imgs [img for img in data[images] if img[id] in train_ids] val_imgs [img for img in data[images] if img[id] in val_ids]这段的细节在于一个图片可能同时含fire和smoke两类它会被同时计入两类的候选集合但最终只会落进train或val不会既在训练又在验证。分层切分保证每类在验证集里的图片数量不小于该类总量的15%评估结果更稳定。seed固定为42方便复现。如果你有验证集是外部固定的就把自己的划分逻辑改为只统计验证集的类别分布并和训练集对比。5. 用这 9332 张图训练检测模型5 个绕不开的坑5.1 训练 loss 不降先查图片与标注对不上的空样本现象数据加载一顿操作不报错但loss一直抖mAP几乎为0。很多人这时候去调学习率、换backbone其实问题在数据。原因常见的有两类。一是images数组里的某个image_id在annotations里找不到任何标注或者反过来标注引用了不存在的image_id二是部分图片在磁盘上缺失读取时返回空矩阵模型等于在喂空白数据。解决训练前写脚本做三件事。第一扫描磁盘上的实际图片文件和images数组里的file_name做交集第二检查annotations里的image_id是否都在images数组里第三检查每张图是否有对应txt如果转YOLO格式。发现缺图直接剔除不要补什么占位图报警让标注方补标才靠谱。5.2 火焰和烟雾互相误报问题出在“烟火”这个复合类现象模型在验证集上把火焰标记烟雾或者把烟雾标记成火焰混淆矩阵里这两类的误报率特别高。原因标注员对“烟火”这个复合类的边界理解不一。有的只要同时出现火和烟就标fire_smoke有的把“烟囱冒烟且远处有火光”也算导致该类内部标准漂移。模型学到的是“烟多火少的图烟火”而不是“火和烟都在烟火”。解决先用第3章的可视化脚本抽查100张fire_smoke标注。如果发现大量只框住烟没框住火的就把fire_smoke拆成fire和smoke两个标签规则是同时包含明显火区和烟区才保留复合类否则按实际内容归类。拆标可以用bbox重叠率辅助如果火和烟的框重叠率超过50%这两个目标大概率是同一个着火点合并成一个复合类框重叠率低则拆开。5.3 小目标场景下 AP50 和 AP75 差一大截现象AP50看起来还行AP75直接掉一半远程火苗的召回率几乎是零。原因COCO小目标定义是面积小于32×32像素。火灾场景里早期火焰、远处浓烟正好落在这个区间模型下采样后的特征图对这些小目标几乎无响应。P3层特征图对32×32的目标已经很吃力再小就全靠运气。解决评估时把mAP按small/medium/large分开打印看小目标那一列到底多少。小目标占比超过40%输入尺寸从640提到960是性价比最高的改动代价是训练时间涨一倍。如果设备扛不住就开mosaic增强和多尺度训练让模型见过不同分辨率的火焰。5.4 转完 YOLO 坐标错乱十有八九是归一化除错了现象训练时loss发散打开训练日志发现框全部挤出图像边界或者一个框塌缩成一条线。原因COCO的bbox是[x, y, w, h]YOLO是[cx, cy, w, h]归一化。错误典型有两种一是直接把COCO的x当中心点二是除以图片宽时把高也除成宽。这两类错误在代码里都不报错肉眼单看一行数字也很难发现问题。解决转完格式必须反向可视化验证一次。用一个脚本读取labels txt画框覆盖到原图上和原COCO可视化的结果做叠图对比。不要相信“转换程序跑完没报错”就等于“转换结果正确”坐标系的错是逻辑错程序越顺利越要警惕。5.5 验证集类别失衡随机切分藏着的坑现象训练集三类样本比例看着正常验证集里smoke只有几十张每次训练评估结果波动好几个点换个seed结果又跳。原因随机切分对小概率类太残酷。9332张图如果smoke只占20%随机85%/15%切分下验证集里可能只有一两百张烟再按“图是否真的有效”过滤一遍就剩几十张了。解决用第4.2节的分层切分按类别各自取15%进验证集。如果发现某两类经常在同一张图同时出现火焰和浓烟同框就按图片的“类别组合”来分层保证验证集里同时含火、烟、烟火的图片占比和训练集一致。更稳妥的做法是先不切验证集用K折交叉验证跑一轮看哪些类别在不同折里波动最大再决定该给验证集多留多少样本。6. 摸清误报来源之后下一步迭代的两个方向6.1 从难例里挖真负样本给模型加“不检测”的能力火灾检测上线后误报比漏报更让人头疼。同一个模型在数据集上mAP挺高放到真实监控里天天把车灯、晚霞、烧秸秆的烟当火报。光靠调阈值解决不了本质问题。我的做法是单独建一个负样本库把误报框截出来专门给模型做难例挖掘。每轮训练结束把模型在真实场景里置信度最高的误报框采样15%加入下一轮的损失计算让模型被迫学会“这个东西不是火也不是烟”。原始标注里的“烟火”类也先别删哪怕它标注得不太好留着作为难例素材比删掉更划算。6.2 用同一套 COCO 标注同时做分割与检测的互相校验既然数据集带COCO格式的segmentation标注就不要只拿来画框。建议在同一份数据上同时训练一个带mask分支的模型把mask生成的bbox和检测分支的bbox做IoU比对。两者的IoU如果整体低于0.5说明bbox和mask的标注边界对不上数据源有问题如果某个类别整体IoU偏低那就是这个类的标注标准需要重新校准。这个互检方式比只看mAP更能暴露问题也为后续接帧间跟踪做准备——有mask的检测结果做跟踪时少了误报框也能稳定不少。我现在的习惯是拿到任何带COCO标注的数据集先做三件事打印categories映射表、按类别分层切分、做可视化和面积分布统计。这三件事做完了再谈训练调参因为调参救不了一个标注错乱的数据集但一次分层切分能让后续评估稳好几个点。前年我在某图像处理Demo上就是随机切分吃了大亏验证集里火焰类只剩三十张每次评估结果全靠玄学后来才把这几步钉死在流程里。希望上面的脚本和坑位判断能帮到你至少让这9332张图不白烧一次机器。本文还有配套的精品资源点击获取