简介这份俯拍航拍森林火灾检测数据集面向从事目标检测算法研究与智能视觉系统开发的科研人员和工程师用于训练和测试火灾与烟雾的识别定位模型可服务于森林火灾早期发现与应急响应等场景。资源包内共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 folderimages/folder filenamefire_0001.jpg/filename size width1920/width !-- 图像宽度俯拍图常见 1920 或 1280 -- height1080/height !-- 图像高度 -- depth3/depth !-- 通道数RGB 为 3 -- /size object namefire/name !-- 类别名只有 fire 和 smoke -- bndbox xmin420/xmin !-- 左上角 x -- ymin310/ymin !-- 左上角 y -- xmax680/xmax !-- 右下角 x -- ymax540/ymax !-- 右下角 y -- /bndbox /object object namesmoke/name bndbox xmin700/xmin ymin200/ymin xmax1100/xmax ymax600/ymax /bndbox /object /annotation逻辑说明VOC 的坐标是绝对像素值原点在左上角x 向右、y 向下。一张图里可以有多个 objectfire 和 smoke 可以同时出现。参数上要留意的是 size 里的宽高后面转 YOLO 格式时归一化要用到如果 xml 里的宽高和实际 jpg 不一致转换出来的框会整体偏移。我一般会先抽 20 张图核对一遍宽高确认没有缩放过的痕迹。2.2 YOLO 格式的 txt 行结构与归一化坐标YOLO 格式的 txt 文件名和 jpg 一一对应每行代表一个目标格式是类别索引 中心x 中心y 宽 高全部是 0 到 1 之间的归一化值。类别索引按你训练时的 names 顺序来这份数据集只有 fire 和 smoke常见做法是 fire 对应 0smoke 对应 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_okTrue) 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_okTrue) os.makedirs(lbl_out, exist_okTrue) 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 \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ workers4 \ projectruns/fire \ namebaseline逻辑说明model用 yolov8s 这种小模型先跑通确认数据和流程没问题再换大模型。imgsz640是常见起点如果你的俯拍图分辨率很高、小目标多可以试 1024但显存要够。batch16根据显存调爆显存就降到 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.1mosaic 保留但 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 框把火也包进去了那标注规则可能偏向“烟包含火”训练时就要注意类别边界。从那以后我每次拿到新数据集都强制先跑一遍框数统计和宽高分布再决定增强参数和输入分辨率。这份俯拍航拍森林火灾检测数据集格式齐全、类别干净拿来练手或做基线都合适但标注质量最终要靠你自己的统计和可视化来验证。希望帮到你。本文还有配套的精品资源点击获取