资讯动态

航拍森林火灾检测数据集:VOC/YOLO双格式对齐与训练避坑

发布时间:2026/10/25 14:35:54 来源:尧图企业网站定制
简介面向森林火灾智能监测与航拍目标检测研究者的数据集说明文档围绕俯拍、航拍场景下的火焰与烟雾识别任务帮助读者快速了解一份VOC与YOLO双格式标注数据的构成与使用边界。文档系统梳理了6116张图像及对应标注VOC提供jpg图像与xml文件YOLO提供txt标签类别为fire与smoke标注框分别达15380和12613个总计27993个并交代labelImg矩形框标注流程、类别定义与标注示例预览便于在训练前核对标注规则和数据质量。压缩包共1个docx文件约5.06MB以文字说明、统计信息和预览节选为主适合作为数据检索、方案选型与实验记录的依据。当前已有62人学习。读者可从中获取数据规模、格式差异、类别分布及精度免责声明等关键信息为森林火灾早期发现模型的预处理、训练配置与评估验证提供参考。1. 俯拍航拍森林火灾检测数据集6116张双格式标注先看什么拿到一份俯拍航拍森林火灾检测数据集很多人的第一反应是把6116张图直接塞进训练脚本真正容易翻车的却是VOC与YOLO两套标注是否逐张对齐。这个数据集面向俯拍航拍视角的森林火灾检测2类别为 fire 和 smoke提供jpg、xml、txt三类文件标注框合计27993个适合做2类别目标检测训练、格式转换校验和烟雾小目标增强实验。它更适合已经能跑通检测流程、需要补足火烟场景样本的从业者不适合把它当成精度承诺标注只解决“标签在哪”模型效果仍由划分、增强和调参决定。2. 拆开VOC与YOLO双格式27993个框的坐标对齐与统计口径双格式不是简单“多给一份标注”。VOC的xml是绝对像素坐标YOLO的txt是归一化中心点坐标两者差一个换算层如果文件名、类别顺序或边界裁剪没对齐训练日志里的loss会看起来正常验证可视化却满屏错框。先把6116组三件套的对应关系锁死再谈统计和转换后面训练才有意义。2.1 先核对目录与文件命名6116组三件套缺一不可这份数据集没有附带划分路径的txt文件只有图片和对应的VOC xml、YOLO txt所以目录结构要自己整理。我一般会把图片、xml、txt分开放用文件stem做唯一键先检查jpg集合、xml集合、txt集合是否一一相等。不要用“文件数量都是6116”代替逐张核对数量相等但文件名错位的情况并不少见尤其是从压缩包解压后混入系统生成的隐藏文件时。from pathlib import Path import xml.etree.ElementTree as ET from collections import Counter, defaultdict IMG_DIR Path(images) # jpg 所在目录 XML_DIR Path(annotations/voc) # VOC xml 目录 TXT_DIR Path(labels/yolo) # YOLO txt 目录 CLASSES [fire, smoke] jpg {p.stem for p in IMG_DIR.glob(*.jpg)} xml {p.stem for p in XML_DIR.glob(*.xml)} txt {p.stem for p in TXT_DIR.glob(*.txt)} print(jpg, len(jpg), xml, len(xml), txt, len(txt)) print(缺xml, sorted(jpg - xml)[:5]) print(缺txt, sorted(jpg - txt)[:5]) print(多余xml, sorted(xml - jpg)[:5]) print(多余txt, sorted(txt - jpg)[:5]) box_counter Counter() per_image defaultdict(int) for p in XML_DIR.glob(*.xml): root ET.parse(p).getroot() for obj in root.findall(object): name obj.find(name).text.strip() box_counter[name] 1 per_image[p.stem] 1 print(类别框数, dict(box_counter)) print(总框数, sum(box_counter.values())) print(每图平均框数, round(sum(per_image.values()) / max(len(per_image), 1), 3))这段脚本先做三集合差集逻辑很直接jpg - xml是有图没VOC标注jpg - txt是有图没YOLO标注反向差集则是标注多余。后面解析xml只统计object/name和每图框数不做坐标转换用来快速判断类别分布和图片拥挤程度。参数上只需要改三个目录和CLASSES如果类别名里出现空格或大小写不一致name.strip()之后还要统一小写再比对否则统计会把同一类拆成两个键。对比项VOC xmlYOLO txt坐标形式xmin、ymin、xmax、ymax绝对像素class、cx、cy、w、h归一化到0到1类别表达name文本如 fire类别索引如 0 或 1主要用途人工核对、转换源、可视化回画直接喂给YOLO训练脚本常见错位文件名stem不一致类别索引与data.yaml不一致提示先跑一遍集合核对再跑坐标统计。文件名错位时谈框数没有意义统计结果会把你带偏。2.2 VOC xml 转 YOLO txt 的坐标换算与边界裁剪VOC转YOLO的核心公式不复杂cx ((xmin xmax) / 2) / widthcy ((ymin ymax) / 2) / heightbw (xmax - xmin) / widthbh (ymax - ymin) / height。容易出错的是边界裁剪和无效框过滤。labelImg画框时偶尔会留下越界坐标或者xmax小于xmin的拖拽残影直接归一化会得到负数或大于1的值训练脚本不一定报错但框会飞到图像外面。转换时统一裁到[0, width]和[0, height]再判断宽高是否大于0能挡掉大部分脏框。from pathlib import Path import xml.etree.ElementTree as ET SRC_XML Path(annotations/voc) DST_TXT Path(labels/yolo_converted) DST_TXT.mkdir(parentsTrue, exist_okTrue) CLASSES [fire, smoke] CLASS_TO_ID {name: i for i, name in enumerate(CLASSES)} def clamp(v, lo, hi): return max(lo, min(hi, v)) for xml_path in SRC_XML.glob(*.xml): root ET.parse(xml_path).getroot() size root.find(size) w float(size.find(width).text) h float(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_TO_ID: continue bnd obj.find(bndbox) xmin clamp(float(bnd.find(xmin).text), 0, w) ymin clamp(float(bnd.find(ymin).text), 0, h) xmax clamp(float(bnd.find(xmax).text), 0, w) ymax clamp(float(bnd.find(ymax).text), 0, 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{CLASS_TO_ID[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) (DST_TXT / f{xml_path.stem}.txt).write_text(\n.join(lines), encodingutf-8)这段脚本把xml逐张转成同名txtCLASS_TO_ID决定fire是0还是smoke是0必须和后面训练配置完全一致。clamp负责把越界坐标压回图像范围xmax xmin或ymax ymin的框直接丢弃避免写出宽高为负的标签。保留6位小数是为了减少归一化误差对6116张图、27993个框这种量级足够用。转换完不要立刻开训抽10张把txt画回原图看框是否贴合火点或烟雾边缘这一步比盯着loss曲线有用。2.3 用统计结果判断数据分布fire 15380 与 smoke 12613 怎么读按给定标注统计fire有15380个框smoke有12613个框总框数27993。按6116张图摊开平均每张图约4.58个框说明不少图片里同时存在多个火点或烟雾区域训练时不能假设“一图一目标”。fire框数占比约54.9%smoke约45.1%两类并不悬殊但烟雾的视觉形态更麻烦颜色接近背景、边缘扩散、对比度低框往往偏大且重叠多。框数均衡不代表难度均衡smoke的召回通常更值得盯。类别框数占全部框按6116张图平均fire15380约54.9%约2.51smoke12613约45.1%约2.06合计27993100%约4.58统计时还要看每张图的框数分布和空标注数量。如果某些图片的txt是0字节那是纯背景负样本不要当脏数据删掉如果一张图里有几十个框先确认是不是把大片烟雾拆成了多个小框或者把同一火点重复标了多次。对俯拍航拍场景框的宽高比也能说明问题火点通常偏小、偏圆烟雾容易拉长成不规则大片。后面做增强时scale和mosaic可以多一点但mixup要克制低对比度烟雾被混合后更容易糊成背景。3. 把数据集接进YOLO训练2类别配置、640输入与小目标增强格式对齐之后下一步是让训练配置和标注索引完全一致。这里最常见的误用是“拿到txt就直接训”不管类别顺序、不划分验证集、不检查空标注。YOLO训练脚本对格式很宽容错标签也能跑出下降的loss所以配置层要自己卡死nc2、names顺序、train/val路径、图片和标签的stem对应四项都对上再开训。3.1 data.yaml 与类别映射fire/smoke 顺序不能靠猜训练配置里最重要的是类别索引。转换脚本里CLASSES [fire, smoke]那么fire就是0smoke就是1如果原始YOLO txt的类别索引不是这个顺序要么重转要么改names不能只改nc。我一般会在data.yaml里显式写出0和1避免不同框架对names列表的解析差异。目录上让images/train和labels/train保持同名stem验证集同理这样训练脚本按路径推断标签时不会串。# configs/fire_smoke.yaml path: /data/fire_smoke_6116 train: images/train val: images/val test: images/test nc: 2 names: 0: fire 1: smokepath是数据集根目录train、val、test写相对路径根目录下要有对应的images和labels结构。nc必须是2names的键值要和txt里的类别索引对齐。如果暂时没有test集可以先把test留空或复用val但不要把它当成调参集反复看。没有官方划分文件时常见做法是按8:1:1或7:2:1切分但切分要按场景分组不能简单随机抽图原因在避坑章展开。3.2 训练命令与关键超参imgsz、batch、lr0 怎么落训练命令以常见YOLO命令行接口为例模型权重路径换成自己手里的预训练权重。输入尺寸先定640如果烟雾在图中占比很小可以试960但显存和速度会明显变化。batch从8或16起步显存不够就降batch不要硬撑导致最后几个batch的统计量抖动。学习率lr00.01是常见起点小数据集或微调时可以降到0.005lrf0.01控制末端学习率衰减patience30用于早停避免验证指标长时间不升还继续烧卡。# 以常见 YOLO 训练命令行接口为例权重路径换成自己的 yolo detect train \ modelweights/pretrained.pt \ dataconfigs/fire_smoke.yaml \ imgsz640 \ epochs120 \ batch16 \ workers4 \ lr00.01 \ lrf0.01 \ patience30 \ projectruns/fire_smoke \ namebaseline_640imgsz640是速度和精度的折中俯拍图里烟雾如果经常只占几十个像素640下经过下采样会更小所以第一轮不要只看mAP先把推理可视化打开。epochs120不是硬指标早停触发就停workers4按机器CPU核数调太大反而拖慢数据加载。project和name决定输出目录建议把baseline、增强版、换尺寸版分开命名后面比较结果时不会翻错文件夹。参数建议起点作用调整信号imgsz640烟雾偏小试960输入分辨率小框漏检多就加速度不够就降batch8或16批统计稳定性显存不足先降batchlr00.01微调0.005初始学习率loss震荡大就降patience30早停耐心轮数验证指标长期不升可减mosaic0.8左右多图拼接增强小目标多可保留纯背景多要下调注意不要用验证集反复调conf和iou再报告mAP。验证集参与调参后指标就不再是干净评估后面换场景会掉得很难看。3.3 小目标烟雾的增强策略mosaic、mixup 与尺度抖动怎么配烟雾的难点是低对比度和边缘模糊增强不能只堆强度。mosaic能增加小目标和多目标共存适合俯拍图里多个火点、多个烟羽的情况scale和translate让目标出现在不同位置减少模型依赖固定构图hsv_s和hsv_v可以模拟烟羽颜色变化和光照差异。mixup要谨慎烟雾本来就和背景接近混合过度会让标签区域更模糊训练集看起来更“难”验证集反而学不到稳定特征。俯拍场景可以保留水平翻转垂直翻转按数据来源决定如果航线方向固定垂直翻转比例不要太高。# configs/aug_smoke.yaml hsv_h: 0.015 hsv_s: 0.6 hsv_v: 0.4 degrees: 5.0 translate: 0.1 scale: 0.5 shear: 2.0 perspective: 0.0 flipud: 0.2 fliplr: 0.5 mosaic: 0.8 mixup: 0.1 copy_paste: 0.0这份增强配置里scale: 0.5让目标有一定尺度抖动mosaic: 0.8保留较高多图拼接概率mixup: 0.1只做轻微混合避免烟雾被过度糊化。flipud: 0.2和fliplr: 0.5针对俯拍视角如果数据来自固定航线且方向信息有意义可以把flipud降到0。copy_paste对烟雾这种不规则目标不一定合适先关掉等baseline稳定后再试。增强参数不是越多越好训练集mAP上升但验证集不动的增强基本可以撤掉。4. 训练后别只看mAP混淆矩阵、PR曲线与可视化抽查训练日志里的mAP是汇总值2类别火烟检测更该看每类召回和混淆方向。fire和smoke在远景里可能同时出现烟雾又容易扩散到火点周围混淆矩阵能告诉你模型是把烟判成火还是把背景判成烟。评估阶段先把验证集固定再谈阈值阈值扫得太随意最后得到的指标没法复现。4.1 划分验证集与评估命令先固定随机种子再谈指标验证集要和训练集同分布但不能同场景划分时固定随机种子记录哪些文件进了val。评估命令里conf0.001是为了算完整PR曲线不要用0.25这种部署阈值去评估mAPiou0.6是NMS的IoU阈值和训练时的框匹配阈值不是一回事。如果验证集里烟雾样本很少mAP会被fire主导最好再单独看smoke的recall。yolo detect val \ modelruns/fire_smoke/baseline_640/weights/best.pt \ dataconfigs/fire_smoke.yaml \ imgsz640 \ batch16 \ conf0.001 \ iou0.6 \ projectruns/fire_smoke_val \ nameeval_640model指向训练产出的best权重data必须和训练时同一份yamlimgsz也要一致否则小目标评估会偏差。conf0.001保留低置信度预测让PR曲线完整iou0.6用于NMS合并重叠框烟雾大片重叠时这个值会影响召回。评估输出目录里重点看混淆矩阵、PR曲线和每类指标表不要只抄一个mAP50就结束。4.2 看混淆矩阵定位 fire/smoke 混淆背景框和烟雾漏检怎么读混淆矩阵的横纵轴分别是真实类别和预测类别背景也会作为一类出现。fire被预测成smoke说明颜色和纹理特征没分开常见于小火点被大片烟雾包住smoke被预测成背景说明低对比度烟羽没被激活常见于远景薄烟。背景列偏高则是误检背景行偏高是漏检两者处理方式不同误检多先回捞负样本和调conf漏检多先查标注覆盖和增强强度。观察点可能原因优先动作fire与smoke互相混淆类别索引错位或视觉特征重叠先核对names顺序再看可视化smoke大量进背景薄烟对比度低、框太小提高输入尺寸补烟雾样本调低conf背景大量进fire/smoke负样本不足或conf过低回捞纯背景图保留空txtfire召回高、smoke召回低类别难度不均衡单独看smoke的PR曲线和框尺寸分布每类指标要分开看precision高、recall低说明模型保守阈值可以降一点precision低、recall高说明误检多先查背景样本和标注质量。mAP50到mAP50-95掉得厉害通常是框定位不准烟雾边缘模糊时更明显不一定是分类问题。4.3 推理可视化与阈值选择conf 0.25 只是起点部署阈值和评估阈值要分开。评估用低conf看全貌部署时从0.25起步再按业务接受度扫0.1、0.25、0.4。烟雾密集时NMS的iou可以降到0.4到0.5避免一片烟只留一个框火点分散时iou可以高一点。可视化不要只抽成功案例专门挑漏检、误检、重叠框各10张才能看出阈值和标注的边界。yolo detect predict \ modelruns/fire_smoke/baseline_640/weights/best.pt \ sourceimages/val \ imgsz640 \ conf0.25 \ iou0.5 \ save_txtTrue \ save_confTrue \ projectruns/fire_smoke_pred \ nameval_conf025_iou050conf0.25是部署起点iou0.5控制重叠框合并save_txtTrue把预测结果落盘方便和真值对比。save_confTrue保留置信度后面做阈值扫描时不用重新推理。如果烟雾大片只出一个框先把iou降到0.4看是否改善如果背景误检多先把conf升到0.4看误检是否来自低置信度。阈值没有全局最优只有当前场景的折中。5. 避坑与排查双格式坐标、空标注与烟雾低对比度的5个坑双格式数据集的问题往往不在训练脚本而在“看起来能跑”的细节。下面5个坑按出现频率排前两个是格式层后三个是训练和评估层。每个都按现象、原因、解决写照着排查能省掉反复重训。5.1 坑一VOC与YOLO坐标顺序搞反框飞到左上角现象验证可视化里框集中在左上角或者框宽高很小、几乎为0训练loss能降但mAP极低。原因把xmin, ymin, xmax, ymax直接写进txt或者把归一化的cx, cy, w, h当成绝对坐标。YOLO txt的顺序是class cx cy w h不是class xmin ymin xmax ymax两者混用后坐标范围完全不对。解决用2.2的转换脚本重转随机抽10张把txt画回原图。再跑一遍范围检查cx和cy必须在0到1之间w和h必须大于0且不超过1。发现越界先查转换公式不要先去改模型。from pathlib import Path bad [] for p in Path(labels/yolo).glob(*.txt): for line in p.read_text(encodingutf-8).splitlines(): if not line.strip(): continue cls, cx, cy, w, h line.split() vals list(map(float, [cx, cy, w, h])) if not (0 vals[0] 1 and 0 vals[1] 1 and 0 vals[2] 1 and 0 vals[3] 1): bad.append((p.name, line)) break print(异常文件数, len(bad)) print(bad[:5])这段检查只做范围校验不判断框是否贴合目标所以还要配合可视化。bad里出现文件名就先看对应图片确认是转换问题还是原始xml越界。范围检查通过但框仍然偏移重点查width和height取的是不是原图尺寸有些xml的size节点和实际jpg分辨率不一致转换前要以实际图片尺寸为准。5.2 坑二空txt与纯背景图被误删smoke 召回莫名下降现象清洗脚本把0字节txt删掉后训练集只剩有目标的图验证集烟雾召回下降背景误检反而变多。原因YOLO格式里空txt表示纯背景是负样本。删掉空txt等于告诉模型“所有图里都有火或烟”模型没见过无目标背景遇到云层、阴影、反光就容易误检。解决保留空txt和对应图片。先统计空标注比例如果空标注很少保留即可如果空标注很多按比例抽样不要让背景图淹没正样本。清洗脚本里把“txt为空”和“txt缺失”分开处理缺失是错位为空是负样本两者不能一起删。5.3 坑三烟雾框重叠与NMS阈值过高一片烟只留一个框现象可视化里大片烟雾只出一个框相邻火点被吞掉或者同一片烟被重复框出很多小框。原因烟雾标注常有大框重叠NMS的iou阈值过高会合并过度conf阈值过高会漏掉低对比度烟雾。评估阶段用0.001看全貌部署阶段直接套0.5以上的iou容易把重叠目标压成一个。解决评估用conf0.001、iou0.6看PR曲线部署时扫conf从0.1到0.4、iou从0.4到0.6按漏检和误检的接受度选点。烟雾密集场景优先降iou火点分散场景再考虑升conf。不要用验证集mAP最高的阈值直接上线业务误检成本不同阈值要单独定。5.4 坑四按图片随机划分导致同场景泄漏验证集虚高现象验证集mAP很高换一段航拍视频或另一个架次就崩烟雾召回掉得特别明显。原因同一段航线连续帧的纹理、光照、烟羽形态几乎一样随机分到train和val后验证集对模型来说近似“见过的图”。这种泄漏会让指标虚高实际跨场景泛化差。解决按架次、场景或时间窗口分组划分同一组只进train或只进val。文件名有场景前缀时按前缀分组没有前缀时按拍摄时间分桶或者按文件夹分。分组后固定随机种子把train/val文件列表落盘后续复现实验直接用同一份列表。from pathlib import Path import random from collections import defaultdict random.seed(42) groups defaultdict(list) for p in Path(images).glob(*.jpg): # 假设文件名形如 sceneA_0001.jpg按前缀分组 group p.stem.split(_)[0] groups[group].append(p.stem) keys list(groups) random.shuffle(keys) n_val max(1, int(len(keys) * 0.2)) val_groups set(keys[:n_val]) train, val [], [] for g in keys: (val if g in val_groups else train).extend(groups[g]) Path(splits).mkdir(exist_okTrue) Path(splits/train.txt).write_text(\n.join(train), encodingutf-8) Path(splits/val.txt).write_text(\n.join(val), encodingutf-8) print(len(train), len(val), val groups, sorted(val_groups))这段脚本按文件名前缀分组n_val控制验证组比例random.seed(42)保证划分可复现。文件名没有场景前缀时不能硬套改成按文件夹或时间字段分组。划分完检查val组列表确认没有同一场景同时出现在train和val否则mAP再高也不能信。5.5 坑五换模型只改nc不改names类别索引错位现象推理时fire显示成smoke或者两个类别概率互换混淆矩阵里两类对调。原因data.yaml里nc2改了但names顺序没改或者转换脚本的CLASSES和训练配置的names不一致。模型只认索引不认文本索引0被解释成什么完全由配置决定。解决固定一份类别映射转换、训练、评估、推理都用同一份。data.yaml里显式写0: fire、1: smoke训练前抽一张图打印txt类别并叠框核对。换模型或换框架时先检查names再检查nc最后才看权重加载。6. 进阶技巧用分层抽样和负样本回捞把烟雾召回拉上来烟雾召回上不去很多时候不是模型容量不够而是验证集里烟雾组合太少、负样本又被清掉了。我一般先按每张图的fire框数、smoke框数分桶再按桶比例抽train/val保证验证集里同时有“只有fire”“只有smoke”“两者都有”“纯背景”四类样本。这样调conf和iou时看到的是烟雾真实边界不会被fire样本掩盖。分桶判定方式抽样注意bothfire框数0且smoke框数0验证集必须保留观察共存场景fire_only只有fire框防止fire主导指标smoke_only只有smoke框烟雾召回的关键桶bg空txt保留一部分做负样本训练后把验证集里误检最高的背景图回捞进训练集空txt继续保留不要因为“没有目标”就删。部署阈值也别照搬评估阈值conf从0.25起步iou从0.5起步烟雾密集时把iou降到0.4看重叠框是否回来。从那以后我每次拿到双格式数据集都先跑统计、抽10张叠框、再按场景分组划分最后才本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑