简介:这份俯拍航拍森林火灾检测数据集面向从事目标检测算法研究与智能视觉系统开发的科研人员和工程师,用于训练和测试火灾与烟雾的识别定位模型,可服务于森林火灾早期发现与应急响应等场景。资源包内共1个docx文件,约5.06MB,以文档形式汇总了数据集的格式说明、类别统计与标注示例,便于快速了解数据构成。数据集包含6116张图像及对应标注,同时提供Pascal VOC格式的xml文件与YOLO格式的txt文件,标注类别为fire和smoke两类,其中火标注框15380个、烟雾标注框12613个,总框数达27993,均通过labelImg以矩形框方式完成标注。文档中还附有标注图片预览与标注例子,帮助读者直观理解标注规则与细节。目前已有62人学习关注。需要留意的是,数据集仅提供准确合理的标注,不对训练所得模型或权重文件的精度作任何保证,使用者需自行评估验证。
1. 俯拍航拍森林火灾检测数据集:6116 张双格式标注到底能跑出什么
森林火灾的早期发现,靠人眼盯着监控画面基本不现实,尤其是俯拍航拍视角下,烟和火的形态跟地面视角差别很大。这份俯拍航拍森林火灾检测数据集,就是冲着这个场景来的:6116 张 jpg 图像,每张都配了 Pascal VOC 的 xml 和 YOLO 的 txt 两套标注,类别只有 fire 和 smoke 两个,总框数 27993。标注工具用的是 labelImg,画的是矩形框,没有分割路径的 txt,也没有多余的分心文件。
它适合谁?如果你正在做遥感目标检测、无人机巡检、林火预警这类方向,想找一个类别干净、格式齐全、拿来就能转训练集的数据源,这份资源省掉了自己从视频抽帧、标注、格式转换的整条链路。不适合谁?想要实例分割掩码的、想要多光谱或红外通道的、想要时序视频片段的,这份数据集给不了。它就是一个标准的、双格式的、二类目标检测数据集,边界很清楚。
2. 双格式标注拆解:VOC 的 xml 和 YOLO 的 txt 各自怎么读
2.1 VOC 格式的目录结构与 xml 字段含义
拿到压缩包解压后,常见做法是看到两个平行目录,一个放 jpg,一个放 xml,或者 jpg 和 xml 混在同一个文件夹里。VOC 格式的核心是每张图对应一个同名 xml,里面记录了图像尺寸、目标类别和矩形框的左上角、右下角坐标。下面是一个典型的 xml 结构,我按森林火灾场景补了注释:
<annotation> <folder>images</folder> <filename>fire_0001.jpg</filename> <size> <width>1920</width> <!-- 图像宽度,俯拍图常见 1920 或 1280 --> <height>1080</height> <!-- 图像高度 --> <depth>3</depth> <!-- 通道数,RGB 为 3 --> </size> <object> <name>fire</name> <!-- 类别名,只有 fire 和 smoke --> <bndbox> <xmin>420</xmin> <!-- 左上角 x --> <ymin>310</ymin> <!-- 左上角 y --> <xmax>680</xmax> <!-- 右下角 x --> <ymax>540</ymax> <!-- 右下角 y --> </bndbox> </object> <object> <name>smoke</name> <bndbox> <xmin>700</xmin> <ymin>200</ymin> <xmax>1100</xmax> <ymax>600</ymax> </bndbox> </object> </annotation>逻辑说明:VOC 的坐标是绝对像素值,原点在左上角,x 向右、y 向下。一张图里可以有多个 object,fire 和 smoke 可以同时出现。参数上要留意的是 size 里的宽高,后面转 YOLO 格式时归一化要用到,如果 xml 里的宽高和实际 jpg 不一致,转换出来的框会整体偏移。我一般会先抽 20 张图核对一遍宽高,确认没有缩放过的痕迹。
2.2 YOLO 格式的 txt 行结构与归一化坐标
YOLO 格式的 txt 文件名和 jpg 一一对应,每行代表一个目标,格式是类别索引 中心x 中心y 宽 高,全部是 0 到 1 之间的归一化值。类别索引按你训练时的 names 顺序来,这份数据集只有 fire 和 smoke,常见做法是 fire 对应 0,smoke 对应 1,但一定要以你实际拿到的 txt 为准,不能想当然。
0 0.286458 0.393519 0.135417 0.212963 1 0.468750 0.370370 0.208333 0.370370逻辑说明:第一行是 fire,中心点在图像宽度 28.6%、高度 39.3% 的位置,框宽占全图 13.5%,框高占 21.3%。第二行是 smoke,中心点偏右,框更大。参数上最容易翻车的是类别索引顺序,如果你把 fire 和 smoke 的索引搞反,训练出来的模型会把火认成烟,mAP 看着还行但实际完全不能用。我一般会写个脚本统计每个索引对应的框数,跟简介里的 fire 15380、smoke 12613 对一下,对不上就说明索引映射有问题。
2.3 两种格式的互转脚本与校验逻辑
虽然数据集同时给了两套格式,但你训练时往往只需要一种。如果你用 YOLO 系列,直接拿 txt;如果你用 Detectron2 或 MMDetection,可能更习惯 VOC 或 COCO。下面这个脚本把 VOC 的 xml 转成 YOLO 的 txt,同时做一遍框越界检查:
import os import xml.etree.ElementTree as ET # 类别映射,必须和训练时的 names 一致 CLASS_MAP = {"fire": 0, "smoke": 1} def voc_to_yolo(xml_path, out_txt_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("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] bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) # 越界裁剪,防止标注框超出图像边界 xmin = max(0, min(xmin, w)) xmax = max(0, min(xmax, w)) ymin = max(0, min(ymin, h)) ymax = max(0, min(ymax, h)) if xmax <= xmin or ymax <= ymin: continue # 跳过无效框 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(out_txt_path, "w") as f: f.write("\n".join(lines)) # 批量处理 xml_dir = "annotations" out_dir = "labels" os.makedirs(out_dir, exist_ok=True) for fname in os.listdir(xml_dir): if fname.endswith(".xml"): voc_to_yolo( os.path.join(xml_dir, fname), os.path.join(out_dir, fname.replace(".xml", ".txt")) )逻辑说明:脚本先读 xml 的宽高,再把绝对坐标转成归一化中心点和宽高。越界裁剪那几行是血泪经验,俯拍图里偶尔会有框贴着边缘甚至超出几个像素,不裁的话 YOLO 训练时可能报错或者学出畸形的框。参数上,CLASS_MAP必须和你的data.yaml里 names 顺序完全一致,否则类别全乱。跑完之后建议统计一下每个类别的框数,跟 15380 和 12613 对一下,差太多就说明有 xml 解析失败或者类别名拼写不一致。
3. 从 6116 张到训练集:划分、配置与增强策略
3.1 训练集验证集划分与 data.yaml 配置
6116 张图不算大,但也不小,按 8:1:1 划分训练、验证、测试是常见做法。注意划分时要保证 fire 和 smoke 的分布大致均衡,不能出现验证集里全是 smoke 没有 fire 的情况。下面这个脚本按文件名列表随机划分,并生成 YOLO 训练需要的目录结构:
import os import random import shutil random.seed(42) # 固定随机种子,保证可复现 img_dir = "images" label_dir = "labels" out_root = "dataset" # 收集所有图片文件名(不含扩展名) names = [os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")] random.shuffle(names) n = len(names) n_train = int(n * 0.8) n_val = int(n * 0.1) splits = { "train": names[:n_train], "val": names[n_train:n_train + n_val], "test": names[n_train + n_val:] } for split, items in splits.items(): img_out = os.path.join(out_root, "images", split) lbl_out = os.path.join(out_root, "labels", split) os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for name in items: shutil.copy(os.path.join(img_dir, name + ".jpg"), os.path.join(img_out, name + ".jpg")) shutil.copy(os.path.join(label_dir, name + ".txt"), os.path.join(lbl_out, name + ".txt")) print(split, len(items))逻辑说明:固定random.seed(42)是为了让每次划分结果一致,方便复现实验。参数上,8:1:1 不是铁律,如果你数据量小可以 9:1 只留训练和验证。跑完之后检查一下三个目录的图片数和标签数是否一一对应,缺标签的图会在训练时被跳过,白白浪费数据。
对应的data.yaml这样写:
path: ./dataset train: images/train val: images/val test: images/test nc: 2 names: ["fire", "smoke"]逻辑说明:nc是类别数,这里是 2。names的顺序必须和 txt 里的类别索引一致,fire 在前 smoke 在后。如果你拿到的 txt 索引是反的,要么改 txt,要么改 names,二选一,不能两边不一致。
3.2 针对俯拍小目标的增强参数怎么设
俯拍航拍图里,早期的火点和烟团往往只占几十个像素,属于典型小目标。默认的 YOLO 增强参数里,mosaic 和 mixup 对小目标有帮助,但 scale 和 translate 的幅度不能太大,否则小目标容易被裁掉或者缩到看不见。我一般会这样调:
# 增强相关参数,按需覆盖默认值 mosaic: 1.0 # 开启 mosaic,四图拼接增加小目标上下文 mixup: 0.1 # 轻微 mixup,太高会让小目标糊掉 scale: 0.3 # 缩放幅度控制在 0.3,避免小目标缩没 translate: 0.1 # 平移幅度 0.1,太大容易把边缘目标移出画面 fliplr: 0.5 # 水平翻转,俯拍图左右翻转通常不改变语义 flipud: 0.5 # 垂直翻转,航拍视角上下翻转也合理 hsv_h: 0.015 # 色调扰动小一点,火和烟的颜色是重要特征 hsv_s: 0.5 hsv_v: 0.3逻辑说明:mosaic 设 1.0 是 YOLO 系列的默认值,对小目标检测有正向作用。scale 和 translate 调小是为了保护小目标,翻车案例里常见的是 scale 开到 0.9,结果烟团被缩成几个像素,模型根本学不到。flipud 在普通地面数据集里一般不开,但俯拍航拍图上下翻转不改变物理语义,可以开。hsv_h 调小是因为火偏红、烟偏灰白,色调扰动太大会让颜色特征失真。
3.3 用预训练权重跑通第一轮训练
配置好之后,第一轮训练建议用预训练权重,不要从零开始。命令大致如下:
yolo detect train \ data=./dataset/data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ workers=4 \ project=runs/fire \ name=baseline逻辑说明:model用 yolov8s 这种小模型先跑通,确认数据和流程没问题再换大模型。imgsz=640是常见起点,如果你的俯拍图分辨率很高、小目标多,可以试 1024,但显存要够。batch=16根据显存调,爆显存就降到 8 或 4。workers是数据加载线程数,Windows 下设太高有时会卡死,4 比较稳。跑完看runs/fire/baseline下的结果,重点看 fire 和 smoke 各自的 mAP,如果 smoke 明显低,说明烟雾标注框可能偏大或者边界模糊,需要回头检查标注质量。
4. 避坑与排查:标注、格式、训练里最容易翻车的几件事
4.1 类别索引对不上导致火烟互换
现象:训练 loss 正常下降,mAP 看着也不低,但推理时把火框成 smoke,把烟框成 fire。原因:txt 里的类别索引和data.yaml的 names 顺序不一致,比如 txt 里 0 是 smoke、1 是 fire,而 names 写的是["fire", "smoke"]。解决:写个脚本统计每个索引的框数,跟简介里的 fire 15380、smoke 12613 对一下,哪个索引对应哪个类别一目了然,然后统一改 txt 或改 names。
4.2 xml 宽高与实际图片不一致导致框偏移
现象:转出来的 YOLO 框整体偏上或偏左,可视化时框和火烟对不齐。原因:xml 里的 size 宽高是标注时的尺寸,如果图片后来被缩放或裁剪过,宽高就对不上,归一化坐标全错。解决:抽 20 张图用 PIL 读实际宽高,跟 xml 里的 size 对比,不一致的要么重新标注,要么按实际宽高重新归一化。我一般会在转换脚本里加一句断言,宽高差超过 2 个像素就报警。
4.3 空 txt 文件被当成负样本
现象:训练时提示某些图片没有标签,或者 loss 异常。原因:有些图可能确实没有 fire 和 smoke,对应的 txt 是空的,YOLO 会把空 txt 当成负样本,但如果空 txt 是因为转换失败产生的,就会误导训练。解决:统计 txt 文件的行数,行数为 0 的图单独拿出来看一眼,确认是真的没有目标还是转换漏了。如果是真的负样本,可以保留,但比例不能太高,否则模型会偏向预测背景。
4.4 小目标被增强参数裁掉
现象:训练前期 mAP 涨得还行,后期停滞,可视化发现小烟团漏检严重。原因:scale 或 translate 幅度太大,小目标在增强时被裁出画面或缩到几个像素。解决:把 scale 降到 0.3 以下,translate 降到 0.1,mosaic 保留但 mixup 调低。另外可以在训练前统计一下框的宽高分布,如果大量框小于 32 像素,就属于小目标,增强要保守。
4.5 验证集里某一类完全缺失
现象:验证时 smoke 的 mAP 是 0,但训练集里明明有 smoke。原因:随机划分时验证集恰好没分到 smoke 样本,或者 smoke 样本在验证集里极少。解决:划分时按类别分层抽样,保证每个 split 里 fire 和 smoke 的比例接近整体比例。简单做法是先按类别分组,再分别随机划分,最后合并。
5. 进阶技巧:用框数分布反推标注质量与模型瓶颈
拿到一份数据集,别急着直接训练。我习惯先做一件事:统计每个类别的框数、每张图的框数、框的宽高分布。这份数据集 fire 15380 框、smoke 12613 框,平均每张图约 2.5 个 fire 框和 2 个 smoke 框,说明大部分图里火和烟是同时出现的,场景比较密集。如果某张图框数特别多,可能是大面积火灾,也可能是标注时把一片区域拆成了很多小框,后者会让模型学到碎片化的目标。
下面这个脚本统计框的宽高分布,帮你判断小目标比例:
import os label_dir = "labels/train" w_list, h_list = [], [] for fname in os.listdir(label_dir): if not fname.endswith(".txt"): continue with open(os.path.join(label_dir, fname)) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue _, _, _, bw, bh = map(float, parts) w_list.append(bw) h_list.append(bh) # 按归一化宽高统计小目标比例 small = sum(1 for w, h in zip(w_list, h_list) if w * 640 < 32 or h * 640 < 32) print(f"总框数: {len(w_list)}") print(f"小目标框数(640分辨率下小于32像素): {small}") print(f"小目标占比: {small / len(w_list):.2%}")逻辑说明:这里用 640 作为参考分辨率,把归一化宽高乘回去,小于 32 像素的算小目标。如果小目标占比超过 30%,训练时就要特别小心增强参数,并且考虑用更高的输入分辨率。参数上,label_dir换成你的训练集标签目录即可。跑完这个统计,你对这份数据的难度就有底了,也能解释为什么某些模型在这份数据上 mAP 上不去。
另一个技巧是看 fire 和 smoke 的框重叠情况。烟往往在火的上方或周围,如果两者框大量重叠,模型可能会依赖上下文而不是目标本身。我一般会抽几十张图可视化一下,确认框的位置合理。如果发现 smoke 框把火也包进去了,那标注规则可能偏向“烟包含火”,训练时就要注意类别边界。
从那以后我每次拿到新数据集,都强制先跑一遍框数统计和宽高分布,再决定增强参数和输入分辨率。这份俯拍航拍森林火灾检测数据集格式齐全、类别干净,拿来练手或做基线都合适,但标注质量最终要靠你自己的统计和可视化来验证。希望帮到你。
本文还有配套的精品资源,点击获取