
简介该数据集围绕火灾火焰图像分割任务构建包含背景与火焰两类像素级标注图像分辨率统一为640×640适用于细粒度火焰区域提取与分割模型训练。数据划分为训练集和验证集并经过随机翻转等增广处理可直接用于U-Net、DeepLab或yolov5等常见分割框架。压缩包共2000个文件其中1998个为png格式掩膜文件另含1个txt类别说明和1个py可视化脚本包体总大小约155.74MB。附带的show脚本可一键将掩膜叠加到原始图像上查看标注效果便于快速检查数据质量classes文件则方便模型导入时逐一对应类别。目前已有173人学习使用适合正在开展火灾检测、图像分割或yolov5分割实战的开发者参考。1. 火灾火焰图像分割为什么这个数据集比想象中更难搞做图像分割的同行应该都有感触通用分割数据集一抓一大把但真要找一份能直接用来训练火灾检测、火焰定位模型的带标签数据翻遍各大平台往往要折腾半天。火灾火焰图像分割数据集之所以金贵是因为火焰本身是典型的“高动态、强边缘模糊、背景干扰大”目标——烟雾、反光、颜色相近的橙色物体都会让标注员和模型同时犯难。这份数据集的核心价值在于它同时提供了原始图像、对应的分割标签mask以及 classes 文件拿来即可以跑 UNet、DeepLabV3、SegNet 这些常见分割网络省去了自己从零标注和整理类别映射的时间。适合两类人一是要做烟火识别、早期火灾预警落地的算法工程师二是拿火焰场景练手、想搞懂分割任务全流程的学生或转行者。先说结论这份数据集的坑不在“能不能用”而在“你拿到手之后怎么处理标签和训练配置”——下面展开讲。2. 数据集内部结构先搞清数据、标签、classes 文件的组织方式2.1 解压后的目录通常长什么样常见做法是拿到压缩包后解压内部一般分成 images或 JPEGImages、masks或 SegmentationClass、以及一个 classes.txt 或 classes.json。不要急着写训练脚本先把文件分布摸清楚。数据集的目录结构决定了你后续写 DataLoader 的方式尤其是标签文件的格式——是单通道 PNG、三通道 RGB 伪彩色图还是灰度图加调色板这三者的读取方式完全不同。# 解压后建议第一时间执行的探查命令 tar -xzf fire_seg_dataset.tar.gz cd fire_seg_dataset find . -maxdepth 2 -type d | sort ls -lh classes.txt python -c from PIL import Image; imImage.open(masks/00001.png); print(im.mode, im.size)这段命令先解压再用 find 看目录层级最后用 PIL 读取一张标签图确认颜色模式。PIL输出的 mode 如果是P说明是带调色板的索引图如果是RGB说明标签可能是伪彩色的三通道图如果是L则是单通道灰度图。这三种情况对应的训练预处理逻辑完全不一样MODE 没搞清楚就写 Dataset 类后面十有八九要返工。2.2 classes 文件的内容和用途classes.txt 通常是纯文本每一行一个类别名行号对应类别 ID。火焰分割数据集的类别一般不会太多常见的有background、fire有的还带smoke。如果你用的是 MM Segmentation、PaddleSeg 这类框架这个文件可以直接作为配置里的类别列表传入如果是自己写 PyTorch 训练循环就需要手动读取并构建class_name - id的映射。# 读取 classes 文件并建立映射 with open(classes.txt, r) as f: classes [line.strip() for line in f.readlines() if line.strip()] class_to_id {name: idx for idx, name in enumerate(classes)} id_to_class {idx: name for name, idx in class_to_id.items()} print(类别映射, class_to_id)这里的逻辑很简单但要注意一个细节如果 classes 文件里第一行是background那它对应的 ID 就是 0这正好和分割任务里ignore_index或背景类的默认约定一致。但有些数据集会把background放在最后或者中间插一个unlabeled这种细微差别会直接影响损失函数的计算和 mIoU 的评估结果。建议先打印出来人工核对一遍再进训练流程。2.3 标签文件的命名对齐问题图像文件和标签文件的命名通常是对应的比如00001.jpg对应00001.png。但也有数据集在分发时给 mask 文件加了后缀比如00001_fire.png或者放在不同子目录下但文件名不完全一致。稳妥的做法是在写 Dataset 之前先做一次文件名对齐检查把不匹配的文件挑出来。import os from pathlib import Path img_dir Path(images) mask_dir Path(masks) img_names {p.stem for p in img_dir.glob(*.jpg)} mask_names {p.stem for p in mask_dir.glob(*.png)} missing_mask img_names - mask_names missing_img mask_names - img_names print(f缺标签的图像: {len(missing_mask)} 张) print(f缺图像的标签: {len(missing_img)} 张) if missing_mask: print(sorted(missing_mask)[:10])这一步看似多余但真踩过坑的人都知道训练到一半报FileNotFoundError还算好的最怕的是标签和图像错位——模型在拿 A 图的像素去学 B 图的标签训练曲线看着正常验证集效果却一塌糊涂。命名对齐是分割数据集的第一个隐藏坑。3. 数据预处理把原始标签转成训练可用的格式3.1 标签编码从 RGB 伪彩色转到单通道索引图很多公开数据集在标注时为了方便人眼查看会把 mask 保存成 RGB 伪彩色图比如火焰区域涂成红色背景涂成黑色。这种图直接喂给模型是不行的损失函数在计算交叉熵时要求的是[H, W]的整数索引图每个像素的值是类别 ID而不是[H, W, 3]的颜色值。所以第一步就是把 RGB 标签映射回索引。import numpy as np from PIL import Image # 定义颜色到类别ID的映射表按实际数据集的 palette 调整 color_to_id { (0, 0, 0): 0, # 黑色 - 背景 (255, 0, 0): 1, # 红色 - 火焰 (128, 128, 128): 2, # 灰色 - 烟雾如果有 } def rgb_mask_to_index(rgb_mask_path, out_path): rgb np.array(Image.open(rgb_mask_path).convert(RGB)) h, w rgb.shape[:2] idx_map np.zeros((h, w), dtypenp.uint8) for color, idx in color_to_id.items(): match (rgb color).all(axis-1) idx_map[match] idx # 未匹配到的像素保持0也可以转成255表示ignore Image.fromarray(idx_map).save(out_path)这段代码的核心是构建一个颜色字典然后逐像素匹配。有一个容易翻车的地方如果标签图经过了 JPEG 压缩或者缩放过边缘像素的 RGB 值不会恰好等于(255, 0, 0)而是会变成(254, 0, 1)之类的值导致匹配缺失。所以转换前先检查标签是 PNG 还是 JPG是 PNG 的话基本没问题是 JPG 的话就建议直接放弃这份数据或者用最近邻匹配。3.2 类别不平衡处理火焰区域通常只占整张图的 1% 以下火灾火焰分割数据集的一个显著特点是类别极不平衡——大部分图像里火焰只占画面的一小块区域背景占了 95% 以上。如果直接用普通交叉熵损失函数模型会学出“全预测为背景”的偷懒解mIoU 看起来还行但火焰区域一个像素都没分割出来。这时候要么换损失函数要么做采样策略调整。# 统计每张 mask 的类别占比找出极端不平衡样本 mask_dir masks_index stats [] for name in os.listdir(mask_dir): mask np.array(Image.open(os.path.join(mask_dir, name))) total mask.size fire_ratio (mask 1).sum() / total stats.append((name, fire_ratio)) stats.sort(keylambda x: x[1]) print(火焰占比最低的5张) for n, r in stats[:5]: print(f{n}: {r:.4%})跑完这段你会看到火焰占比低于 0.5% 的图不在少数。对付这种情况我一般会做两件事第一在损失函数里给火焰类别加权重比如class_weight [0.5, 10.0]第二在数据加载时对包含火焰的样本做过采样保证每个 batch 里都有正样本。这两个操作能明显改善小目标火焰的分割效果比盲目堆训练轮数有效得多。3.3 数据增强的边界别把火焰颜色给增强没了分割任务常用的增强手段有随机翻转、随机缩放、颜色抖动、CutOut 等。但火焰分割有个特殊性火焰的颜色和亮度本身就是重要的判别特征。如果你用了ColorJitter把色调大幅偏移火焰可能变成蓝色或绿色背景里橙色的物体反而更像火焰模型学到的颜色语义就被打乱了。# 适合火焰分割的增强策略albumentations 写法 import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform A.Compose([ A.RandomResizedCrop(height512, width512, scale(0.5, 1.0), p0.8), A.HorizontalFlip(p0.5), A.VerticalFlip(p0.2), A.RandomBrightnessContrast(brightness_limit0.1, contrast_limit0.1, p0.5), A.ShiftScaleRotate(shift_limit0.05, scale_limit0.1, rotate_limit15, p0.5), ToTensorV2() ])注意这里没用HueSaturationValue只用了轻微的亮度和对比度调整旋转角度也限制在 15 度以内。火焰不像工业零件那样有固定的几何结构大幅旋转会生成不真实的火焰形态模型反而学不到普适特征。增强策略的度需要根据验证集效果反复试没有绝对统一的答案但这个边界意识得先有。4. 训练配置基于这份数据集跑通分割模型的参数设置4.1 模型选型轻量还是高精度火焰分割的落地场景通常有两种一是边缘设备上的实时预警二是服务器端的离线分析。针对这两类场景模型选型策略完全不同。如果是 Jetson Nano、RK3588 这类设备建议直接用 UNet 的轻量变体或 STDC如果是服务器训练、云端推理DeepLabV3 配 ResNet50 或 EfficientNet 做骨干是稳妥选择。# 以 PyTorch 为例加载一个轻量分割模型 import torch import torch.nn as nn class FireUNet(nn.Module): def __init__(self, num_classes2): super().__init__() # 简化版 UNet 编码器实际建议用预训练 backbone self.enc1 self._conv_block(3, 32) self.enc2 self._conv_block(32, 64) self.enc3 self._conv_block(64, 128) self.pool nn.MaxPool2d(2) self.dec3 self._conv_block(128, 64) self.dec2 self._conv_block(64, 32) self.dec1 nn.Conv2d(32, num_classes, kernel_size1) def _conv_block(self, in_ch, out_ch): return nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue) ) def forward(self, x): e1 self.enc1(x) e2 self.enc2(self.pool(e1)) e3 self.enc3(self.pool(e2)) d3 self.dec3(self.pool(e3)) d2 self.dec2(self.pool(d3)) # 这里省略了上采样和跳跃连接完整实现需补齐 return self.dec1(d2)实际工程里很少有人从零手写 UNet更多是直接用 segmentation_models_pytorch 库一行代码加载带预训练权重的模型。但了解内部结构有助于调参——比如当显存不足时先砍哪一层、怎么调整输入尺寸心里要有数。上面这段代码只是为了展示模型输入输出的大致形状训练时建议换成成熟的库实现。4.2 损失函数和评估指标的选择前面提到类别不平衡损失函数上常见的解法是Focal Loss或Dice Loss的组合。火焰分割的场景里Dice Loss 的收敛速度通常比交叉熵快而且对前景占比不敏感但单独用 Dice Loss 容易在训练初期产生震荡。我一般会把交叉熵和 Dice 按 0.5:0.5 的比例混合既保留像素级梯度又让模型关注区域重叠度。class CombinedLoss(nn.Module): def __init__(self, weight_ce0.5, weight_dice0.5): super().__init__() self.ce nn.CrossEntropyLoss(weighttorch.tensor([0.5, 10.0])) self.weight_ce weight_ce self.weight_dice weight_dice def forward(self, pred, target): ce_loss self.ce(pred, target) # Dice loss 计算简化版多类别需遍历 pred_softmax torch.softmax(pred, dim1) pred_fire pred_softmax[:, 1] # 假设类别1是火焰 target_fire (target 1).float() intersection (pred_fire * target_fire).sum() dice (2.0 * intersection) / (pred_fire.sum() target_fire.sum() 1e-7) dice_loss 1.0 - dice return self.weight_ce * ce_loss self.weight_dice * dice_loss评估指标上mIoU 是业界通用标准但对于火焰这种小目标单独看 mIoU 会掩盖问题——背景的 IoU 可能高达 99%火焰的 IoU 只有 0.2平均下来数字还挺体面。所以我建议同时打印每类的 IoU 和像素准确率重点盯火焰那一类的 IoU。4.3 训练超参数的参考值基于这类中小规模数据集常见的训练配置大致如下输入分辨率建议至少 512x512太小的话火焰边缘细节会被下采样丢掉batch size 根据显存调整8 到 16 之间初始学习率 1e-4 配合 Poly 学习率衰减训练轮数 80 到 120 轮足够配合早停机制防止过拟合。# 一个可参考的训练启动参数示例以 segmentation_models_pytorch 为例 python train.py \ --model unet \ --backbone efficientnet-b0 \ --encoder-weights imagenet \ --classes 2 \ --input-size 512 512 \ --batch-size 8 \ --lr 1e-4 \ --epochs 100 \ --loss ce-dice \ --device cuda:0这套参数对新手来说是个相对稳的起点。如果你的显存只有 8G可以把输入分辨率降到 384 或者 batch size 降到 4如果发现训练集损失降得很慢优先检查数据加载部分是不是标签读取出了错而不是急着调大学习率。5. 避坑指南火焰分割数据集使用的四个典型“翻车”现场5.1 标签里有第三类“未标注”区域现象训练时 loss 莫名奇妙不收敛或者验证时模型在图像边缘预测出一圈奇怪的类别。原因有些图像的标注者没有把远处的小火焰标出来背景里被忽略了的前景像素在标签里是 0但和真正的背景语义不同。这类未标注区域在标签转索引时通常会被归到背景类模型被迫去拟合这些“脏标签”。解决先统计每张图的标签类别分布如果发现大量255或ignore值就在损失函数里设置ignore_index255。如果是 0 值混杂了未标注区域只能靠人工抽检修正或者训练时给背景类加更低的学习权重避免模型过拟合到这些噪声上。5.2 数据集划分没做“视频去重”现象训练集 mIoU 很高验证集 mIoU 也很高但一到真实场景测试效果稀烂。原因火焰分割数据集往往来自视频抽帧同一个火灾场景的连续帧非常相似。如果划分数据集时按文件顺序随机切分同一段视频的帧会同时出现在训练集和验证集里造成严重的数据泄漏。解决按文件名前缀分组通常同一段视频的帧名带有相同的时间戳或序号段需要先按分组做 StratifiedSplit。from sklearn.model_selection import GroupShuffleSplit img_paths sorted(glob(images/*.jpg)) # 假设帧名类似 frame_001234.jpg取前4位作为场景分组ID groups [os.path.basename(p).split(_)[1][:4] for p in img_paths] gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(img_paths, groupsgroups))用GroupShuffleSplit替代普通train_test_split保证同一场景的所有帧要么全在训练集、要么全在验证集。这一步做不做直接决定了模型上线后的真实泛化表现。5.3 输入尺寸统一时的拉伸变形现象模型训练正常但推理时对某些宽高比极端的图像效果明显变差。原因数据加载时直接把所有图resize到 512x512横屏拍摄的火焰图被压扁火焰的细长形态特征被扭曲。解决改用Resize加PadIfNeeded的组合保持宽高比不变填充区域在标签里标为 ignore。val_transform A.Compose([ A.Resize(height512, width512, interpolationcv2.INTER_LINEAR), A.PadIfNeeded(min_height512, min_width512, border_modecv2.BORDER_CONSTANT, value0), ToTensorV2() ])做推理时同理记录原始图像的宽高比输出 mask 后再反算回原始尺寸。这个细节尤其在监控视频画面里影响巨大因为大多数摄像头输出是 1920x1080 或者 16:9 的横屏直接拉伸会牺牲火焰的形状信息。5.4 验证时用错颜色空间导致可视化“假阳性”严重现象模型预测的 mask 可视化出来后整片区域都是红色看着像误报一片但 mIoU 数值却不低。原因可视化代码直接把预测的索引图乘了一个倍数再转 RGB或者混淆了[H, W]和[H, W, 3]的维度导致显示时每个通道都用了同一个值看起来像一大片颜色相同的高亮区。解决可视化时使用独立的调色板进行映射确认每个类别的颜色值在色差上有明显的区分度比如背景用深蓝色(30, 30, 70)火焰用亮红色(255, 0, 0)烟雾用灰色(128, 128, 128)。import matplotlib.pyplot as plt palette {0: (30, 30, 70), 1: (255, 0, 0), 2: (128, 128, 128)} def visualize( image, mask): h, w mask.shape vis np.zeros((h, w, 3), dtypenp.uint8) for cls, color in palette.items(): vis[mask cls] color fig, axes plt.subplots(1, 2, figsize(10, 5)) axes[0].imshow(image) axes[1].imshow(vis) plt.show()可视化不仅仅是给人看的它同时也是最廉价的模型 debug 方式——预测 mask 的边缘是否贴合火焰轮廓、是否存在大块空洞、是否存在碎斑透过可视化比任何损失曲线都直观。6. 进阶技巧用难例挖掘提升火焰边缘分割质量分割模型训练到后期往往瓶颈不在整体识别而在火焰边缘的精细度——边缘像素对比度低、和背景交融模型在该区域的输出往往呈“锯齿状”或“糊成一团”。针对这个问题一个有效的进阶做法是难例挖掘Hard Example Mining。具体思路是在训练过程中动态统计每个 batch 里哪些像素的预测概率接近 0.5把这些像素在损失计算时额外加权逼迫模型加大对易混淆区域的注意力。# 损失计算中根据预测置信度动态调整权重 def hard_example_loss(pred, target, threshold0.6): prob torch.softmax(pred, dim1) max_prob, _ prob.max(dim1) # 对置信度低且属于前景的像素加大权重 uncertain (max_prob threshold).float() fire_target (target 1).float() weight_map 1.0 uncertain * fire_target * 5.0 ce_loss nn.functional.cross_entropy(pred, target, reductionnone) weighted_loss (ce_loss * weight_map).mean() return weighted_loss实现要点是不要把权重加到所有低置信度像素上否则背景边缘的大量像素会主导梯度反而干扰训练。只对“低置信度且确实是火焰目标”的像素加权相当于让模型在火烧边缘附近投入更多拟合能力。我自己的经验是训练后期把这项加入之后火焰 IoU 能再提升 3 到 5 个百分点在白天强光背景和夜间暗光场景下尤其明显。另外两个值得养成的工程习惯一是每次实验固定随机种子包括 Python 的random、NumPy 的numpy.random、PyTorch 的torch.manual_seed以及 CUDA 的torch.cuda.manual_seed_all否则同样的参数跑两遍结果都不一样你很难判断一个改动到底有没有效果。二是训练完成后保留一份“坏样本清单”——验证集里预测效果最差的几十张图把缩略图和预测 mask 拼在一起输出到一个文件夹。每次调参后都翻一遍这个清单你会发现自己的直觉会越来越敏锐看到一张图就能猜出模型会在哪里翻车。做分割数据集的落地项目我最深的感受是数据本身的价值一半在收集另一半在预处理和配套脚本的完备程度。这份火灾火焰图像分割数据集拿来直接训练是可以的但真正拉开效果差距的是你怎么处理类别不平衡、怎么划分数据集、怎么设计可视化验证闭环。希望这套从目录结构到难例挖掘的流程能帮你少走点弯路把时间花在模型改进而不是无休止的 debug 上。本文还有配套的精品资源点击获取