☰
俯拍航拍森林火灾检测数据集:6116张VOC+YOLO双格式实战指南
2026/10/10 5:28:45 网站建设 项目流程

简介:这份俯拍航拍森林火灾检测数据集面向从事目标检测算法研发的科研人员与工程师,用于训练和测试火灾与烟雾的智能识别模型,可服务于森林火灾早期预警与应急响应等场景。资源包共1个docx文件,约5.06MB,文档内整理了数据集说明、标注规则、图片预览与标注示例,便于快速了解数据构成与使用方式。数据集包含6116张jpg图像,同时提供Pascal VOC格式的xml标注与YOLO格式的txt标注,标注类别为fire和smoke两类,其中火标注框15380个、烟雾标注框12613个,总框数达27993,均通过labelImg以矩形框方式完成标注。目前已有62人学习关注。读者可据此直接开展模型训练与评估,借助预览示例核对标注细节,并需注意数据集不对训练所得模型或权重的精度作保证,最终性能仍取决于预处理、模型结构与调参等环节。

1. 俯拍航拍森林火灾检测数据集:6116 张 VOC+YOLO 双格式到底能训出什么

林火检测这个方向,真正卡住大多数人的从来不是模型结构,而是数据。你拿 COCO 里那点火焰图去训 YOLO,mAP 能看,一上真实航拍画面就集体翻车——因为俯拍视角下的火场和地面平视完全是两个分布。这份 6116 张、2 类别的俯拍航拍森林火灾检测数据集,价值就在于它把视角、光照、烟雾干扰这些真实变量固定下来了,VOC 和 YOLO 双格式并存,省掉了你自己写转换脚本的功夫。它适合三类人:想快速跑通目标检测全流程的新手、要做林火预警落地的工程团队、以及拿它当遥感目标检测 baseline 的研究者。2 类别通常对应火焰和烟雾(或火焰与非火焰干扰),具体类别名以你拿到的classes.txt为准,别照搬我的假设。下面从数据怎么读、格式怎么转、参数怎么调、坑在哪,一路讲透。

2. 先搞懂 VOC 与 YOLO 双格式:同一批图,两套坐标逻辑

2.1 VOC 的 XML 结构与你必须核对的四个字段

VOC 格式每张图配一个同名.xml,核心信息全在<object>节点里。拿到数据集第一件事不是急着训,而是抽 5 到 10 个 XML 打开看,确认字段没被上游改坏。一个标准的林火标注长这样:

<annotation> <folder>images</folder> <filename>fire_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>fire</name> <!-- 类别名,必须和 classes.txt 完全一致 --> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>820</xmin> <!-- 左上角 x,绝对像素 --> <ymin>410</ymin> <!-- 左上角 y --> <xmax>1105</xmax> <!-- 右下角 x --> <ymax>690</ymax> <!-- 右下角 y --> </bndbox> </object> </annotation>

四个字段必须核对:size里的宽高要和真实图片一致,不一致说明图片被缩放但 XML 没同步,坐标会整体偏移;name要和你的类别表严格对应,大小写、空格都算;xmin/ymin必须小于xmax/ymax,反了就是标注工具导出 bug;difficult为 1 的框在评估时通常要忽略,训练时看你的框架怎么处理。俯拍林火里烟雾边界极其模糊,标注员很容易把大片稀薄烟雾框得过大,这类框会拉低定位精度,建议训练前用可视化脚本过一遍。

2.2 YOLO 的 txt 格式:归一化坐标的五个数字

YOLO 格式每张图配一个.txt,每行一个目标,格式是类别索引 cx cy w h,全部归一化到 0 到 1 之间。注意这里的cx cy是框中心点,不是左上角,这是新手最容易搞混的地方。一行示例:

0 0.5013 0.5093 0.1484 0.2593

含义是:类别索引 0,中心点横坐标占图宽 50.13%,中心点纵坐标占图高 50.93%,框宽占图宽 14.84%,框高占图高 25.93%。类别索引从 0 开始,对应classes.txt里的行号。如果你的数据集里 YOLO 标签的类别索引和 VOC 的name对不上,训练出来的模型会把火焰认成烟雾,这种错误在 loss 曲线上看不出来,只有推理可视化才暴露。

2.3 两套格式的换算关系与自检脚本

换算公式很直接:cx = (xmin + xmax) / 2 / width,cy = (ymin + ymax) / 2 / height,w = (xmax - xmin) / width,h = (ymax - ymin) / height。反过来乘回去就行。但公式对不代表数据对,我一般会写个自检脚本,把 VOC 转成 YOLO 后再转回来,比对坐标误差是否在 1 像素内:

import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, classes): 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.strip() if name not in classes: print(f"[警告] 未知类别 {name} in {xml_path}") continue cls_id = classes.index(name) box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) # 边界裁剪,防止标注越界导致归一化后超出 [0,1] xmin, xmax = max(0, xmin), min(w, xmax) ymin, ymax = max(0, ymin), min(h, ymax) cx = (xmin + xmax) / 2 / w cy = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") return lines classes = ["fire", "smoke"] # 以你的 classes.txt 为准 xml_dir = "annotations" for f in os.listdir(xml_dir): if f.endswith(".xml"): res = voc_to_yolo(os.path.join(xml_dir, f), classes) print(f, len(res), "个目标")

这段脚本的关键在边界裁剪那两行。俯拍数据里经常有目标贴着图像边缘,标注员手一抖xmax就超过图宽,归一化后cx会大于 1,YOLO 训练时这类框会被静默丢弃或产生异常梯度。裁剪后再归一化,能救回一批边缘目标。classes列表的顺序决定类别索引,务必和数据集自带的classes.txt逐行对齐,不要自己重排。

3. 用 YOLO 在本地跑通林火检测的最小闭环

3.1 目录组织与 data.yaml 的五个必填项

YOLO 系列(v5/v8/v11 都适用)对目录结构有约定,最省事的组织方式是这样:

fire_dataset/ ├── images/ │ ├── train/ # 训练图 │ └── val/ # 验证图 ├── labels/ │ ├── train/ # 对应 txt │ └── val/ └── data.yaml

data.yaml是训练入口,五个字段一个都不能少:

path: /home/user/fire_dataset # 数据集根目录,绝对路径最稳 train: images/train # 相对 path 的训练图目录 val: images/val # 相对 path 的验证图目录 nc: 2 # 类别数,必须和 classes 长度一致 names: ['fire', 'smoke'] # 类别名,顺序即索引

nc写错是最隐蔽的坑:写成 1 但标签里有索引 1,训练直接报 index out of range;写成 3 但只有 2 类,最后一类永远学不到,mAP 里那一行是 0。path用绝对路径,相对路径在不同工作目录下启动训练会找不到数据,这个坑我踩过不止一次。

3.2 划分训练验证集:别用随机划分糊弄俯拍数据

6116 张如果随机 8:2 划分,会出现同一片火场的相邻帧同时进训练集和验证集,验证 mAP 虚高,上线就露馅。俯拍航拍数据往往来自连续拍摄,帧间高度相似,正确做法是按场景或按拍摄批次划分。常见做法是先按文件名前缀或采集日期分组,再整组划分:

import os, random, shutil random.seed(42) img_dir = "all_images" groups = {} for f in os.listdir(img_dir): # 假设文件名形如 fire_20230512_0001.jpg,取日期作为分组键 key = "_".join(f.split("_")[:2]) groups.setdefault(key, []).append(f) keys = list(groups.keys()) random.shuffle(keys) split = int(len(keys) * 0.8) train_keys, val_keys = keys[:split], keys[split:] for phase, ks in [("train", train_keys), ("val", val_keys)]: os.makedirs(f"images/{phase}", exist_ok=True) os.makedirs(f"labels/{phase}", exist_ok=True) for k in ks: for f in groups[k]: shutil.copy(os.path.join(img_dir, f), f"images/{phase}/{f}") txt = f.rsplit(".", 1)[0] + ".txt" shutil.copy(os.path.join("all_labels", txt), f"labels/{phase}/{txt}")

分组键的取法要看你的实际文件名规律,核心原则是让同一场景的图只出现在一个集合里。random.seed(42)固定随机种子,保证划分可复现,团队协作时别人能拿到和你一样的验证集,指标才可比。

3.3 训练命令与三个必调参数

环境装好后,一条命令就能起训:

yolo detect train \ data=fire_dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/fire \ name=exp1

三个参数决定成败。imgsz是输入分辨率,俯拍林火里烟雾目标往往占画面比例很小,640 可能把远处小火点缩没,建议先跑 640 看 mAP,不理想就上 960 或 1280,代价是显存和速度。batch受显存限制,16 是 8G 显存的稳妥值,爆显存就降到 8 并配合accumulate补梯度。lr0初始学习率,0.01 是默认,但小数据集上容易震荡,可以降到 0.001 观察 loss 是否更平滑。patience=20表示 20 轮没提升就早停,防止过拟合,林火数据里烟雾标注噪声大,早停能省不少时间。

3.4 训练过程看什么:loss 曲线与 mAP 的读法

起训后重点盯三样:box_loss、cls_loss和验证集mAP50。box_loss持续下降说明定位在收敛;cls_loss不降反升通常是类别不平衡或标注错误,2 类别里如果火焰样本远少于烟雾,cls_loss 会卡住,可以用cls权重或过采样缓解。mAP50是 IoU 阈值 0.5 下的平均精度,林火检测里火焰目标清晰,mAP50 通常能到 0.8 以上,烟雾因为边界模糊,能到 0.6 就算不错。如果训练集 mAP 高、验证集低,八成是划分泄漏或过拟合,回去检查 3.2 的分组。

4. 俯拍林火数据的避坑与排查清单

4.1 现象:训练 loss 正常但推理全是背景

原因通常是标签路径没对上。YOLO 找标签的规则是images/train/xxx.jpg对应labels/train/xxx.txt,只替换最后一级目录名和扩展名。如果你把图放在images/train而标签放在labels根目录,YOLO 找不到标签,会把所有图当负样本训,loss 照样降,但模型学的是「什么都不框」。解决:严格按 3.1 的目录结构摆放,训练启动时看日志里train: Scanning ... images, ... backgrounds这行,backgrounds 数量等于图片总数就是标签全没找到。

4.2 现象:mAP 高得离谱,上线一塌糊涂

这是划分泄漏的典型症状。俯拍航拍数据连续帧多,随机划分让相似帧跨集,验证集等于在考训练集原题。解决:按 3.2 的场景分组划分,划分后抽几张验证集图,确认和训练集没有肉眼可辨的同一场景。另一个隐蔽来源是数据增强,如果你的增强里用了 mosaic,它会把 4 张图拼一起,跨集拼接也会造成泄漏,验证阶段记得关掉 mosaic。

4.3 现象:小目标火焰检测不到

俯拍视角下远处火点可能只有十几个像素,640 输入下缩到几个像素,卷积特征直接消失。解决有三条路:提高imgsz到 960 或 1280;在数据里对小目标过采样;换用带 P2 小目标检测层的模型结构。先试提分辨率,成本最低。如果显存不够,用imgsz=960配batch=8,速度慢但能救回小目标。

4.4 现象:烟雾框抖动大,同一片烟时有时无

烟雾没有硬边界,标注一致性差,模型学到的框位置方差大。解决:训练前用可视化脚本把标注框画到图上,人工筛掉明显过大或过小的烟雾框;训练时适当降低烟雾类的损失权重,或者把烟雾和火焰分开评估,别让烟雾拖累火焰指标。推理阶段对烟雾用更低的置信度阈值,配合 NMS 的 IoU 阈值调高到 0.6,减少同一片烟被重复框。

4.5 现象:训练到一半显存爆了

多半是imgsz或batch设大了,也可能是 dataloader 的workers开太多导致内存碎片。解决:先把batch减半,还爆就降imgsz;workers在 Linux 上设 8 够用,Windows 上设 0 或 2,多了反而卡。另外检查有没有开cache=True,把整个数据集缓存进内存,6116 张 1920 分辨率的图能吃掉几十 G 内存,小内存机器别开。

5. 把 6116 张用到极致:进阶技巧与验证习惯

数据量固定时,提升空间在增强策略和评估方式上。增强别只开默认的翻转和缩放,俯拍林火可以加随机旋转(90 度倍数,因为俯拍视角旋转后语义不变)、HSV 的 V 通道扰动(模拟不同时段光照)、以及轻度高斯噪声(模拟传感器噪声)。但别加垂直翻转,火焰有明确的重力方向,翻转后烟雾朝上飘,语义就错了,这种增强是负收益。

验证阶段我有个固定习惯:除了看 mAP,一定导出混淆矩阵和 PR 曲线。混淆矩阵能告诉你火焰和烟雾有没有互相误判,PR 曲线能看出在哪个置信度阈值下召回和精度平衡最好。林火预警场景宁可误报也别漏报,所以我会把置信度阈值压到 0.25 左右,牺牲一点精度换召回,这个取舍要在部署前和业务方对齐,别自己拍脑袋。

再进一步是跨数据集验证。拿这份数据训完,找另一批不同季节或不同机型的航拍图做测试,如果 mAP 掉超过 20 个点,说明模型过拟合到本数据集的成像风格了,需要加更强的颜色和尺度增强。这个习惯能帮你在真正上线前发现泛化问题,比在验证集上刷高分管用得多。

最后说个我自己的教训:早期做林火检测,我图省事直接用随机划分,验证 mAP 0.85,兴冲冲部署到无人机上,实际漏检率超过一半。回头查才发现训练集和验证集里有大量同一火场的连续帧。从那以后,任何时序或航拍数据,我第一件事就是按场景分组划分,宁可训练集小一点,也要保证验证集干净。数据这关偷的懒,上线都会加倍还回来。希望帮到你。

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

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

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

立即咨询