☰
火灾烟雾检测数据集详解:VOC/YOLO双格式标注与YOLO训练实战
2026/10/1 11:01:48 网站建设 项目流程

简介:一套面向计算机视觉目标检测任务的火灾烟雾图像标注数据集,主要服务于算法工程师、科研人员及竞赛团队,用于训练和评估火灾烟雾识别模型。资源包含两千二百五十七张经专业人员精细标注的高质量图像,每张均绘制准确的边界框并标注类别,同时采用VOC与YOLO两种主流标注格式,可无缝对接常见目标检测框架,省去自行转换标注格式的麻烦。压缩包整体约二百六十六兆字节,便于下载与部署。目前已有两千二百八十一人学习使用,在智能安防、智慧家居、应急响应及无人机巡检等场景中具有切实的落地价值。借助该数据集,开发者可快速完成数据增强、超参数调优与不同检测器效果对比,降低自采数据和人工标注成本,为构建高精度、高鲁棒性的火灾烟雾检测方案提供扎实数据支撑。

1. 火灾烟雾图像标注数据集:2257 张双格式标注图,拿来就能训 YOLO 系检测器

做安防监控和消防预警的人应该都遇到过同一个尴尬:模型结构可以抄,训练代码可以借,唯独标注好的火灾烟雾图像千金难求。自己标 2257 张图,一个人运气好也要两周,标完还得核对坐标有没有歪。我拆完这份数据集之后可以明确告诉你:它最值钱的地方不是那 2257 张图,而是每张图同时给了 PASCAL VOC 和 YOLO 两套标注格式,意味着你下载之后不用再写一次格式转换脚本,U 盘拷下来改个data.yaml就能喂给 YOLOv5/YOLOv8 开训。这篇笔记我会把数据集内部结构、两种格式的坐标换算逻辑、训练前必须做的检查项和我在实际训练里踩过的坑逐一摊开,照着走基本不会翻车。

2. 数据集的真实结构:VOC 和 YOLO 两套标注各自怎么组织

拿到任何标注数据集,第一步不是看图片,而是先把目录树摸清楚。这个数据集既然提供了两种格式,目录组织上基本沿用两个生态的习惯:VOC 派系用 XML 存标注信息,YOLO 派系用 txt 存归一化坐标,两者读取方式完全不同,混着用必出问题。

2.1 目录结构与文件清单:先把家底盘清楚

我按照最常见的发布形式帮你把目录预期列出来,你解压之后对着看:

fire_smoke_dataset/ ├── VOC/ │ ├── JPEGImages/ # 原始 JPG 图像,2257 张 │ ├── Annotations/ # 与图像同名的 .xml 标注文件 │ └── ImageSets/ │ └── Main/ # train.txt / val.txt / trainval.txt ├── YOLO/ │ ├── images/ # 图像,可能直接复制或软链 │ └── labels/ # 同名 .txt 标注文件 ├── classes.txt # 类别清单(通常两行:fire, smoke) └── README.md # 类别顺序和来源说明

如果你拿到手的压缩包和这个结构有出入,比如Annotations和labels被合并到一个目录下,不用慌,关键只认三样东西:图像文件名、标注文件是否同名、类别顺序表。classes.txt里第一行对应标注 txt 里的 class id 0,第二行对应 class id 1,这个顺序在 YOLO 格式里是唯一权威,比 README 里写的都管用。

这个数据集的标注对象就两类:fire(火灾/火焰)和 smoke(烟雾),总共 2257 张图里包含单目标和多目标混合场景。所谓“高质量标注”指的是每张图的 bounding box 都尽量贴住目标边缘,没有那种只框一半火焰的粗放操作。

2.2 VOC 格式的 XML 标注:绝对像素坐标,人眼可读

VOC 格式的核心是 XML 文件,每张图对应一个.xml,文件名和图片名完全一致。打开任意一个文件,结构长这样:

<annotation> <folder>JPEGImages</folder> <filename>fire_001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>fire</name> <bndbox> <xmin>240</xmin> <ymin>156</ymin> <xmax>893</xmax> <ymax>674</ymax> </bndbox> </object> <object> <name>smoke</name> <bndbox> <xmin>410</xmin> <ymin>98</ymin> <xmax>1176</xmax> <ymax>520</ymax> </bndbox> </object> </annotation>

这里每个<object>块就是一个目标实例,<name>是类别名,<bndbox>里存的是绝对像素坐标。解读规则很简单:xmin/ymin是包围框左上角,xmax/ymax是右下角,单位是像素,不用做任何归一化。图像尺寸在<size>里,后面验证标注是否出界时全靠它。

VOC 格式最大的优点是可读性强,拿记事本打开就能人工检查;缺点是每个目标标签字节开销大。所以它适合做数据验收和二次修改,但不适合直接喂给 YOLO 系模型——你训练时还得写个解析器把 XML 读出来再转成张量,纯属多此一举。

2.3 YOLO 格式的 txt 标注:归一化坐标,一行一个目标

YOLO 格式的做法是完全反过来:每张图对应一个.txt,文件名和图片名一致,目标多的文件行数就多。每行五个数字,空格分隔,含义固定:

class_id x_center y_center width height

注意这个坐标不是像素值,而是相对图像宽度和高度的比例,取值区间在 0 到 1 之间。比如一张 1280×720 的图里,一个框的左上角是 (240, 156)、右下角是 (893, 674),换算步骤如下:

# VOC 像素坐标 -> YOLO 归一化坐标 import os img_w = 1280 img_h = 720 xmin, ymin, xmax, ymax = 240, 156, 893, 674 x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h print(f"0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") # 输出示例:0 0.442578 0.576389 0.510156 0.719444

这段代码里我先算边框的几何中心,再把中心坐标和宽高都除以图像边长,得到的就是 YOLO 需要的归一化值。这里有三个最容易写错的地方:一是width和height必须和图像尺寸的宽对宽、高对高,不能交叉;二是类别 id 从 0 开始,不是从 1 开始;三是保留小数位数至少 6 位,精度太低会导致小目标框偏移几个像素,训练时 mAP 会掉得莫名其妙。

VOC 格式转 YOLO 格式的脚本网上有很多版本,参数细节略有差异,核心逻辑就是我上面这段。如果你手里只有 VOC 格式的数据想转 YOLO,拿这段当成函数体套进循环里遍历整个Annotations目录即可。这个数据集帮你把两套都准备好了,省掉的就是这一步。

3. 训练前的数据准备:类别统计、格式校验和标注可视化

很多人在这一步栽跟头:拿到数据直接跑训练,跑到第三个 epoch loss 不降,回头才发现训练集里混了几张没有标注 txt 的图,或者是坐标全为 0 的“假标注”。数据检查不是走形式,它决定了你是花 3 天训完一个能用的模型,还是花 3 周在玄学报错里挣扎。

3.1 类别比例统计:先摸清火灾和烟雾的样本分布

目标检测模型对类别不平衡极其敏感。2257 张图听起来不少,但如果其中 90% 的标注框都属于烟雾,那模型大概率会把所有模糊目标都判成 smoke。我一般拿到数据集做的第一件事,就是写个小脚本统计每个类别的边框数量:

# 统计 YOLO 格式标注中每个类别的目标数量 import os label_dir = "YOLO/labels" class_counts = {"fire": 0, "smoke": 0} for filename in os.listdir(label_dir): if not filename.endswith(".txt"): continue with open(os.path.join(label_dir, filename), "r") as f: for line in f: class_id = int(line.split()[0]) # 每行第一个数字是类别 id if class_id == 0: class_counts["fire"] += 1 elif class_id == 1: class_counts["smoke"] += 1 print(class_counts)

这段代码只是最基础的统计,但它能直接暴露两个问题:如果某个类别的框数量不到另一个类别的十分之一,基本可以判定类别不平衡,后续要在损失函数或采样策略上做补偿;如果某个类别框数量为 0,说明类别 id 对应关系搞反了,classes.txt的顺序和标注文件对不上。这种情况我见过不止一次,根因往往是标注工具导出时选了不同的类别顺序。

统计输出的具体数字不用太纠结,重点看数量级。两类的目标数量在同一个数量级内(比如几百比几百、一千比一千),直接训练没有问题。如果烟雾框是火的 10 倍,那就得用第 6 章说的加权方案,不能硬训。

3.2 用 OpenCV 把标注画回图像:仅凭肉眼锁定错误

统计只能发现数字异常,无法发现“框偏了”“框漏了”“类别标错了”这类语义错误。我的习惯是随机抽取 100 张图,把标注框直接画回原图,肉眼扫一遍。

# 可视化 YOLO 格式标注,用于人工检查 import cv2 img_path = "YOLO/images/fire_001.jpg" label_path = "YOLO/labels/fire_001.txt" img = cv2.imread(img_path) h, w = img.shape[:2] colors = {"0": (0, 0, 255), "1": (128, 128, 128)} # 0=fire 红色, 1=smoke 灰色 with open(label_path, "r") as f: for line in f: parts = line.split() cls_id, xc, yc, bw, bh = parts[0], float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 还原为像素坐标 x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), colors.get(cls_id, (255, 255, 255)), 2) cv2.putText(img, cls_id, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors.get(cls_id, (255, 255, 255)), 2) cv2.imwrite("check_fire_001.jpg", img)

这段代码把归一化坐标还原成像素坐标再画框。xc - bw / 2是框的左边界,xc + bw / 2是右边界,乘上w和h还原实际像素位置。前面提过归一化坐标范围是 0 到 1,但偶尔会出现 0.98 这种接近边界的值,还原成像素后框的一部分落在图像外,这倒不一定是标注错误,可能是烟雾本身从图像边缘冒出来,目标主体仍然完整,训练时可以保留。

人工检查这一步能发现的最典型问题是“标注只有半截”:火焰或烟雾目标延伸到图像边缘,但标注框在边缘处被截断,导致模型学到的目标形态不完整。遇到这种情况,我的建议是保留它但不要让这种样本超过总量 5%,否则模型会把“半截烟雾”当成有效特征。

3.3 训练集划分:不要直接拿全部数据开训

这个数据集本身大概率已经带了划分好的train.txt和val.txt,也就是 VOC 目录下ImageSets/Main里的文件。但如果你要用 YOLO 格式训练,需要把划分同步到YOLO目录下。常见做法是做一个 8:2 的数据划分,并且保证同一个场景的多角度图像尽量只在训练集或只在验证集里出现。

真实的火灾烟雾图像往往来自连续视频帧,同一个火源会在相邻帧里反复出现。如果这些高度相似的帧同时进了训练集和验证集,验证指标会虚高,换到真实场景立刻打回原形。从项目正文看,这里收的是 2257 张静态图像,不一定有连续帧问题,但我在划分时依然会先按文件名排序,再每隔几帧抽一张进验证集,从根上避免数据泄漏。

4. 避坑排查:这个数据集训练时的五个经典翻车现场

下面五条是我在火灾烟雾检测项目里亲眼见过或者亲手踩过的坑,每一条都按“现象 → 原因 → 解决”给你写清楚。这章不能跳,你后面遇到诡异报错时,回来对照大概率能找到病根。

4.1 训练跑完 mAP 为 0,但 loss 正常下降

  • 现象:训练日志里各类 loss 都在降,val 阶段也能算出 loss,但最终 mAP50 和 mAP50-95 全是 0,预测出来的框和标注完全没有重叠。
  • 原因:标注类别 id 和数据配置文件里的类别清单对不上。比如classes.txt里 fire 在第 0 行、smoke 在第 1 行,但某个标注 txt 文件里把 smoke 写成了 0,模型把烟雾学成了火;或者是验证集的类别 id 和训练集不一致。
  • 解决:写一遍第 3.1 节的统计脚本,分别跑训练集和验证集,确认两个集合里每个类别 id 的分布都在预期范围内。再随机抽取验证集一批预测结果做可视化,确认框不是贴死在图像角落。

4.2 训练时报错 “All labels are empty”

  • 现象:启动 YOLOv8 训练时,刚加载完数据就报All labels are empty in dataset,或者类似提示。
  • 原因:标签文件中的坐标全是 0,或者标签文件是空文件。这通常发生在 VOC 转 YOLO 时,某些图片的标注框坐标读取失败,写成了0 0 0 0 0。
  • 解决:写一个过滤脚本,遍历所有 txt,凡是整行五个数字全是 0 的,就把对应图片连标签一起移出数据集。我在处理多来源合并数据时习惯加一步“坐标有效性校验”,任何一行 width 或 height 等于 0 的,直接删除。注意删除后要么重新划分数据集,要么让训练脚本自行过滤空标签。

4.3 训练集图像尺寸参差不齐导致小目标丢失

  • 现象:同一批训练里,有的图是 1920×1080,有的是 640×480,模型收敛后对远距离小火焰完全无感,但对大目标识别很好。
  • 原因:YOLO 训练时会对输入做 letterbox 缩放,宽高比差异大的图像会被填充大量灰边,小目标在缩放后面积不足几个像素,特征直接消失。
  • 解决:训练参数里固定imgsz=640或imgsz=1280,不要启用多尺度训练,至少第一轮先固定输入尺寸跑通基线。如果数据集中 1080p 图像占比高,用imgsz=1280效果更稳,代价是显存占用翻倍。

4.4 验证集里混进标注框缺失的图片

  • 现象:验证阶段 loss 正常,但 Precision 和 Recall 数值怪异,比如 Recall 突然掉到 0.2,怎么调参都拉不回来。
  • 原因:验证集里若干张图片明明有目标,但对应的 txt 里没有任何标注行。模型在这几张图上预测出目标后,被当作误检惩罚,Recall 被严重拉低。
  • 解决:把标注文件里有内容、且和图片文件一一对应的样本单独筛选出来,组成一个全新的验证集。做法是先遍历所有图片名,检查同名 txt 是否存在且非空,再做交集过滤。

4.5 把 VOC 的 XML 和 YOLO 的 txt 混在同一份数据配置里

  • 现象:训练脚本能跑,但中途频繁警告“found no labels in xxx.jpg”,有的报错指向 XML。
  • 原因:有人在整理数据时,把 VOC 目录里的Annotations也复制到了YOLO/labels子目录下,导致同一个图片对应了多个标注文件,训练器无法确定用哪个。
  • 解决:YOLO 格式训练时,labels目录下只允许放.txt,把 XML 文件全部移走。更稳妥的做法是单独建一个干净的dataset_clean/yolo目录,重新拷贝一份图片和标签进去,不用原始目录直接训练。

5. YOLOv8 训练自己的火灾烟雾模型:数据配置与参数调试

数据验证通过后,训练本身反而是最简单的一步。我拿目前社区用得最广的 ultralytics YOLOv8 做例子,这套配置和参数同样适用于 YOLOv5 和 YOLOv11。模型结构不用动,关键是data.yaml和三个参数:imgsz、epochs、batch。

5.1 数据配置文件的写法:YAML 里每个字段都有用

新建一个fire_smoke.yaml,内容如下:

path: /absolute/path/to/fire_smoke_dataset # 数据集根目录 train: YOLO/images # 训练集图片目录 val: YOLO/images # 验证集图片目录,先用同一个,后面可换 names: 0: fire 1: smoke

三个最关键的字段分别是train、val和names。这里我故意没有写nc字段,因为 ultralytics 会从names的长度推断类别数,写了反而容易和names不一致导致报错。train和val指向图像目录,框架会自动在同级目录下找labels子目录,所以你的标签必须放在YOLO/labels下,不能自定义改名。

第一次跑通验证时,val可以直接指到train同目录,跑个 5 个 epoch 确认流程没问题后再换成正式的验证集。这个策略能帮你快速排除是代码问题还是数据问题。

5.2 训练命令与参数选择:不是 epoch 越大越好

yolo detect train \ data=fire_smoke.yaml \ model=yolov8s.pt \ imgsz=640 \ epochs=150 \ batch=16 \ patience=20 \ project=runs \ name=fire_smoke_exp

我来逐项说明我为什么这么设。model=yolov8s.pt是 s 规模预训练权重,1200 万参数级别,火灾烟雾类别本身不算难学,s 完全够用,直接上 l 或 x 只会白白增加训练时间,对精度提升有限。imgsz=640是速度和精度的折中点,前面踩坑记录说过,如果多数图像是 1080p 及以上,可以加到1280,但要确保显存够。epochs=150配合patience=20是防止过拟合的组合拳:模型连续 20 个 epoch 验证集指标没提升就自动停止,你不用死盯着训练过程。batch=16是个安全值,24GB 显存跑 s 模型没问题,显存小调到 8。

从头训练(不用预训练权重)不是首选。火灾烟雾的图像特征和 COCO 数据集里的常见物体差别不大,预训练权重里的低级特征(边缘、纹理、颜色)完全可以直接迁移。直接加载yolov8s.pt能比随机初始化少训练 30% 以上的时间,最终收敛精度也更高。

5.3 训练过程看什么指标:不只看 loss

训练日志里最值得盯的是验证集的mAP50和mAP50-95,不是训练 loss。训练 loss 下降只说明模型在拟合训练集,mAP50才反映模型在没见过的图像上的真实表现。

火灾和烟雾这两个类别对 mAP 指标的敏感度不一样:火焰边界分明、颜色强烈,模型学得快,mAP50 通常前 30 个 epoch 就冲上 0.8;烟雾是半透明、边缘模糊的目标,mAP50-95 会被它拖累。如果你发现最终 mAP50 能到 0.85 但 mAP50-95 只有 0.5,别焦虑,这是烟雾类别的正常水平。

训练结束后,用下面命令输出最终指标:

yolo detect val \ model=runs/fire_smoke_exp/weights/best.pt \ data=fire_smoke.yaml \ imgsz=640

重点看两个值:验证集mAP50和每个类别的AP。如果smoke的 AP 明显低于fire,直接跳转下一章的不平衡处理方案。指标跑平后,best.pt和last.pt都在runs/fire_smoke_exp/weights/下,推理时用best.pt即可,last.pt是最后一个 epoch 的快照,断点续训时用得上。

6. 进阶技巧:类别不平衡时的加权策略与推理参数微调

如果你的统计脚本跑出来 smoke 的框数量是 fire 的 5 倍以上,直接训练出来的模型会偏向烟雾,把远处的云、水汽、甚至浅色建筑误检成 smoke。解决思路有两个层次:一是从损失函数入手,让模型更重视少数类别;二是在推理阶段调整阈值,用后处理弥补模型偏好。

6.1 用类别权重平衡损失贡献

目标检测的损失由分类损失和定位损失组成,类别不平衡主要冲击分类损失。ultralytics 提供的做法是在data.yaml里通过cls参数调分类损失的权重系数。假如 fire 有 800 个框、smoke 有 4000 个框,权重比值就按数量反比近似取 5:1:

path: /absolute/path/to/fire_smoke_dataset train: YOLO/images val: YOLO/images # 建议换成独立验证集 names: 0: fire 1: smoke

训练时追加:

yolo detect train \ data=fire_smoke.yaml \ model=yolov8s.pt \ imgsz=640 \ epochs=150 \ batch=16 \ cls=5.0 \ patience=20

cls=5.0表示把分类损失的全局系数放大到默认值(默认 0.5)的 10 倍。这个值不是越大越好,模型会因此更努力地识别少数类别的 fire,但也可能把一些背景误判为火。经验做法是从cls=2.0起调,跑完一轮看验证集里fire类别的 Recall 是否明显上升,没有变化就加到 5.0。这个参数名在 YOLOv5 里换成了cls_pw,作用类似,但要注意你用的具体框架版本。

6.2 推理阶段用置信度阈值和 NMS 微调

模型训练好后,推理时的默认置信度阈值是 0.25,NMS 的 IoU 阈值是 0.45。这两个值对火焰和烟雾应该区别对待。如果你部署在消防预警系统里,目标是“宁可误报不能漏报”,把置信度阈值降到 0.1,召回率会明显提升,代价是每帧出现几个误检框。如果你的场景是无人机巡检,希望报警足够精准,阈值调到 0.5 更合适。

# 推理参数调整示例 from ultralytics import YOLO model = YOLO("runs/fire_smoke_exp/weights/best.pt") results = model.predict( source="camera_frame.jpg", conf=0.15, # 降低置信度阈值提高召回 iou=0.35, # 降低 NMS IoU 阈值,减少重叠框 imgsz=640, save=True )

我在部署时习惯跑两组阈值对比验证集指标:一组conf=0.25, iou=0.45作为标准配置,一组conf=0.1, iou=0.3作为高召回配置。高召回配置下如果 mAP50 仍然能保持 0.6 以上,这个模型就敢直接接进告警链路。

6.3 对烟雾类别的针对性增强

烟雾和火焰视觉特征差异很大:火焰有明确颜色和边缘,烟雾则是半透明、形状不断变化。训练阶段给烟雾这类低纹理目标单独做亮度扰动和模糊增强效果很有限,真正有效的是把烟雾图像做随机裁剪而不是整体缩放——裁剪后烟雾在图像中占比变大,模型更容易学到纹理特征。实际操作时我一般单独写一个离线增强脚本,把训练集中的烟雾目标区域随机放大 1.2 到 2.0 倍再放回原图,跑 200 轮增强后用增强前后两组数据对比测试集 mAP。如果 mAP 没有明显提升,就说明基础模型已经够用,不要再叠加无谓的增强。

从那以后我每次启动一个新数据集训练,都会强制要求自己把类别统计、空标注过滤、边界框还原这三步可视化流程走完才允许开训练。数据检查本身不产生任何模型精度提升,但它能拦住 80% 的诡异报错,省下的全是复现时最珍贵的时间。希望这篇笔记对你后续的火灾烟雾检测项目有实际帮助。

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

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

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

立即咨询