尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

俯拍航拍森林火灾检测数据集:YOLOv8训练与避坑全攻略

俯拍航拍森林火灾检测数据集:YOLOv8训练与避坑全攻略 简介面向森林火灾检测任务整理的俯拍/航拍数据集提供6116张图片对应的Pascal VOC与YOLO双格式标注共fire、smoke两个类别总计27993个标注框其中fire框15380个、smoke框12613个覆盖多种俯拍视角下的火灾与烟雾场景。压缩包共2000个文件以1999个xml标注文件为主另附1个txt使用说明整体约300.93MB文件命名清晰、便于按图片索引检索。该数据集使用labelImg工具画矩形框完成标注类别划分明确适合已有一定目标检测基础、需要真实航拍俯拍样本扩充训练集的算法工程师、研究人员及相关专业学生使用。目前已有717人学习/下载可作为火灾监测算法验证、模型调参与同类数据对比的基准素材作者同时声明仅保证标注准确合理不对训练后的模型精度作任何承诺。1. 俯拍航拍森林火灾检测数据集为什么说它是冷启动的首选森林防火巡检项目里最头疼的不是选模型而是冷启动时手里没有像样的数据。俯拍航拍森林火灾检测数据集把这个问题压缩到最小6116张俯拍影像、火与烟两个类别、VOC与YOLO两种标注格式一并打包成7z压缩包解压即用。它的价值不在“多”而在“现成”——不用再花一周时间去爬图、清洗、标注。训练目标很直接让模型在无人机高空视角下稳定框出火点和烟柱。适合用它跑YOLOv8训练、做算法对比、验证正负样本比例也适合刚接触航拍目标检测的人理解VOC和YOLO两种标注格式的差异。2. 俯拍视角的检测难点为什么同一个模型在地面好用、上天就翻车2.1 俯拍与水平视角的本质差异目标尺度、烟雾形态与背景纹理先说一个常见误区很多人直接拿公开目标检测权重跑森林火灾视频结果FPS是上去了召回率却很难看尤其是小目标。问题不在模型而在视角。俯拍和水平视角下目标的物理特征完全不同。水平视角的火灾火焰高度、烟柱宽度可以直接依赖轮廓判断俯拍视角下大部分火焰被树冠遮挡模型实际看到的往往是树冠缝隙透出的红光、热辐射区域以及高温气流导致的图像纹理畸变。烟雾在俯拍下也不是水平视角那种整壮上升的柱体而是被风拉成带状或在高空扩散成半透明的羽流边界极难界定。尺度分布是另一个要命的因素。无人机飞行高度越高单个像元覆盖的地面范围越大一个2米宽的火点在一张1920×1080的影像上可能只占十几个像素。这意味着如果目标普遍小于32×32像素直接跑标准YOLOv8的P5检测层几乎学不到东西。大标注框会把loss带偏小目标被当成背景噪声。对比维度水平视角俯拍视角火焰可见性火焰柱体轮廓清晰常被树冠遮挡只见红光和热区烟雾形态圆柱形向上扩散带状漂移或半透明羽流背景复杂度天空、建筑、道路树冠、裸岩、水体、云影相互混杂典型目标占比大目标占比高大量32×32像素以下的小目标光照影响侧光为主正午顶光、低色温晨昏光交替背景纹理的问题也很突出。森林场景里裸露岩地、河流反光、云影、薄雾都能和火、烟产生颜色和纹理层面的混淆。薄雾或低层云与烟在RGB图像上几乎无法区分。如果数据集里没有覆盖这类难负样本训练出来的模型会在早晚时段产生大量误报。这个数据集的真正价值在于“俯拍视角下的现代标注”——火和烟两个类别覆盖了无人机巡检最关心的目标而不是拿地面视角的样本硬套。用俯拍数据集训练等于给模型重新校准了一遍“视角先验”。如果采集高度集中在150米以下模型对更高空视角的泛化就会变差建议训练前先做一次抽样可视化看看高度和时段分布再决定是否要多尺度训练。2.2 两个类别的标注语义fire和smoke的可操作定义数据集的2个类别是典型二分类fire和smoke。这个命名本身不复杂但实操中标注边界才是决定模型上限的东西。我拿这套数据训练前会先把类别定义理清楚。fire类别凡是图像中能明确看到火焰本体特别是树冠火焰、地表火的区域都归为fire。框的核心是“火焰像素的连通区域”不包括整片过曝的高光区。有些标注会把烟囱、地热口的白汽标进去那是脏数据。smoke类别凡是由燃烧产生的、可见的、在空中扩散的烟雾区域归为smoke。难点在分类于半透明烟与浓烟的处理浓烟边界清楚半透明烟往往和背景融在一起标注时常常“能少标就少标”。但训练时少标意味着模型会把半透明烟学成负样本。还要注意类间互斥策略。同一个区域火芯带少量烟是标fire还是smoke常见做法是火芯和烟柱重叠区域标成fire烟柱主体标成smoke框之间允许重叠但不允许同一目标同时贴两个标签。这样做让分类别互相独立避免混淆矩阵里fire误检为smoke的比例被拉高。这个数据集还有一个隐性好处2个类别的正样本数量往往是smoke略多于fire。因为火点一旦被树冠遮挡能可靠标注的火焰框远少于烟雾框。这个比例和真实巡检场景一致所以用它训练得到的模型在真实航拍数据上的类别先验比较稳不需要额外做类别重加权。如果要对目录做训练集/验证集划分我一般会按“框数均衡”而不是“图片数均衡”来切。比如6116张里验证集选500张但要保证这500张里fire的框数和smoke的框数各自占到全体标注框总数的15%左右。只用随机切分容易让验证集落在图片高度重叠的连续航迹片段上造成指标虚高。2.3 为什么VOC和YOLO双格式并存比单一格式更省心先看两种格式的本质差异。VOC格式即Pascal VOC的XML标注每张图片对应一个XML文件里面写文件名、尺寸、目标类别以及bndbox框的绝对坐标xmin, ymin, xmax, ymax。最大的优势是“人类可读”打开一个XML就知道这张图里有哪些目标、框在什么位置很多老工具链例如mmdetection早期的数据输入直接吃VOC。YOLO格式则是给每个目标写一行txtclass_id cx cy w h中心点坐标和宽高全部归一化到[0,1]浮点数。计算效率高训练器读取时不用做坐标换算但人看不直观也没法直接判断一个框是不是标错了。如果数据集只给一种格式你就会遇到反复倒腾的麻烦今天把VOC喂给YOLOv8明天把YOLO转回COCO做对比实验。每次转换都是一次踩坑机会坐标归一化除以宽还是高、类别id从0还是1开始、XML没有object节点时会不会崩溃这些坑我全都踩过。双格式数据集的存在意义在于VOC格式用来做“人工核查”YOLO格式用来做“直接训练”。要排查某个框的坐标是否正确我一般直接用VOC的XML在图上画框要改训练集划分我直接读写YOLO的txt文件不用绕XML解析。这一来一去省下来的时间比多占的磁盘空间值钱得多。具体到这个数据集拿到手之后第一件事不是训练而是核对文件树和标签索引。下一章就按这个顺序走。3. 数据落地第一步7z解压、文件树核对与VOC转YOLO脚本3.1 7z解压的正确打开方式命令行与图形工具的选择拿到压缩包后很多人的第一冲动是双击解压。但面对一个体积较大的.7z文件尤其是带密码的我个人从不双击。图形界面的解压工具出问题时只会弹一个“密码错误”或“文件已损坏”的提示没有诊断信息。推荐直接用7-Zip的命令行Windows或者p7zipLinux。Linux下需要先确认p7zip是否安装然后列出压缩包内容# Debian/Ubuntu 安装 p7zip sudo apt update sudo apt install -y p7zip-full # 列出压缩包内容不实际解压先确认结构和大小 7z l forest_fire_dataset.7z这条命令先干两件事确认压缩包能被正常读取、确认里面文件的结构和大小分配。如果这一步就报错说明压缩包本身有问题别急着解压。看输出里的路径能提前发现压缩包内是否直接是images/、labels/这些目录而不是多嵌套了一层文件夹。解压命令# 解压到指定目录-o后面不要有空格 7z x forest_fire_dataset.7z -o./fire_dataset -p你的密码参数说明x表示完整解压并保留目录结构-o指定输出目录注意Windows下-o和目标目录之间没有空格-p后面直接跟密码如果密码里有特殊字符用单引号包起来。没有密码就去掉-p参数。解压完成后强烈建议立刻验证压缩包完整性# 校验压缩包内文件的CRC是否与解压结果一致 7z t forest_fire_dataset.7zt是test模式读取压缩包内每个文件的校验值并和解压结果对比。这一步看起来多余但对后续训练稳定性影响巨大压缩包其实已经损坏但图形工具假装解压成功的情况我遇到过不止一次最后训练到一半发现图片解码报错浪费的时间比解压多十倍。3.2 文件树核对三个目录对不齐训练就会静默丢样本解压完以后第一件事是把目录树打出来。常见的数据集目录结构大概是这样的fire_dataset/ images/ train/ 000001.jpg 000002.jpg val/ 001123.jpg labels/ train/ 000001.txt 000002.txt val/ 001123.txt annotations/ train/ 000001.xml 000002.xml val/ 001123.xml train.txt val.txt dataset.yamlimages/、labels/、annotations/三个顶层目录分别对应“图片”、“YOLO格式标签”、“VOC格式标签”。图片文件名必须与txt、xml的主文件名一致任何一位数的偏差都会导致训练器在数据加载阶段静默丢掉该样本。这也是为什么我建议先做一次文件名比对# 遍历训练集jpg检查对应txt和xml是否存在 cd fire_dataset for f in images/train/*.jpg; do base${f##*/} base${base%.jpg} if [ ! -f labels/train/$base.txt ]; then echo missing label: $base fi if [ ! -f annotations/train/$base.xml ]; then echo missing xml: $base fi done这段脚本遍历训练集所有jpg检查对应的txt和xml是否存在。任何missing输出都建议直接看原图判断是数据集本身缺失还是解压时丢失了文件。如果缺失样本很少可以直接删除对应图片保持三个目录严格对齐如果缺失超过总数的1%我会重新解压因为可能压缩包本身就不完整。还有一个容易被忽略的点类别名是否与文件夹名一致。VOC的XML里类别名可能是fire、smoke也可能是Fire、Smoke。YOLO的txt里类别是数字id最终以dataset.yaml的names顺序为唯一标准。不一致时训练器不会崩指标会莫名其妙变低。3.3 VOC转YOLO脚本类别id、归一化坐标与边界框裁剪如果数据集像标题说的那样已经给了VOC和YOLO两种格式理论上不需要自己转换。但实际项目中永远要准备一份转换脚本。原因很简单VOC标注里经常藏着脏框我需要有能力把XML重新转成YOLO文本并检查。而且当你把数据送进模型之前极大概率要对标注做一次清洗这时候手写一个转换脚本能救命。这是一个只依赖标准库的转换脚本# voc_to_yolo.py # 把VOC格式XML标注转为YOLO格式txt文件 # 运行方式: python voc_to_yolo.py --xml_dir ./annotations/train --out_dir ./labels_clean/train import os import argparse import xml.etree.ElementTree as ET class_map {fire: 0, smoke: 1} # 类别名到id的映射必须在0..n-1之间 def convert_xml_to_txt(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) if img_width 0 or img_height 0: print(f[skip] {xml_path}: image size invalid) return lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_map: print(f[warn] {xml_path}: unknown class {name} skipped) continue cls_id class_map[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) # 防止标注越界导致归一化坐标1 xmin max(0.0, min(xmin, img_width - 1)) xmax max(0.0, min(xmax, img_width - 1)) ymin max(0.0, min(ymin, img_height - 1)) ymax max(0.0, min(ymax, img_height - 1)) # 剔除零面积框避免训练时出现NaN loss if xmax xmin or ymax ymin: print(f[warn] {xml_path}: zero-area box skipped) continue cx (xmin xmax) / 2 / img_width cy (ymin ymax) / 2 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) base_name os.path.splitext(os.path.basename(xml_path))[0] out_path os.path.join(out_dir, base_name .txt) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--xml_dir, requiredTrue) parser.add_argument(--out_dir, requiredTrue) args parser.parse_args() os.makedirs(args.out_dir, exist_okTrue) for f in os.listdir(args.xml_dir): if f.endswith(.xml): convert_xml_to_txt(os.path.join(args.xml_dir, f), args.out_dir) print(f[done] converted {len(os.listdir(args.out_dir))} txt files)这段脚本为什么值得留存第一它处理了越界坐标。很多标注工具的框允许略微超出图像边界如果不加裁切归一化后的坐标可能大于1YOLO训练器会报错或者把anchor计算带偏。第二它做了零面积框过滤这是避免训练时出现NaN loss的隐藏原因。第三类别id从0开始与YOLOv8的data.yaml里class list按顺序对应。使用时的硬性要求class_map的顺序必须和后续训练时data.yaml里names数组的顺序完全一致。如果data.yaml里写的是[fire, smoke]那class_map里fire就必须对应0。一旦顺序错位模型训练出来的类别语义就是错乱的混淆矩阵会显示两个类别总合对不上这是最容易踩的坑之一。转换完以后立刻拿一张训练图做可视化把YOLO的txt坐标解析回绝对像素画框和原图叠看一遍。肉眼确认5张左右就够了。这一步花不了5分钟但能直接发现“框漂移”“比例反了”“类别错位”三类高发问题。4. 用6116张两类别数据跑通YOLOv8训练最小命令与调参基线4.1 数据集配置文件yaml里最容易写错的三个字段YOLOv8训练的第一步不是敲训练命令而是写一份data.yaml。这份文件决定了训练器去哪里找图片和标签以及类别名是什么。对于这套数据集常见写法如下# dataset.yaml # 训练和验证图片目录的相对或绝对路径 path: /data/fire_dataset train: images/train val: images/val # 类别数量与名称顺序必须与txt标签的第一列id一致 nc: 2 names: 0: fire 1: smoke三个细节值得注意。path字段可以写相对路径也可以绝对路径但train和val相对路径一定要基于path去拼接。names下用缩进编号0和1这两个编号就是txt文件里第一列的class_id。最后是nc: 2如果写错训练不会直接崩但类别预测维度会错位loss曲线看着正常混淆矩阵永远是乱的。还有一个容易被忽视的检查val集的图片目录里不能出现训练集没覆盖到的场景分布。比如6116张里如果val只有晴天的图像训练集却有雾天样本模型在雨雾天气的泛化能力会被严重高估。我会先统计训练集和验证集里fire、smoke两个类别的框数量分布尽量让验证集类别比例接近训练集。4.2 最小训练命令与关键参数imgsz、batch、epochs、预训练权重拿到数据集的第一次训练不要追求精度先追求“跑通”。以YOLOv8为例最小命令是这样的# 使用yolov8n.pt预训练权重开始训练 # --data指定数据集配置首次跑通用50轮 yolo detect train \ modelyolov8n.pt \ data/data/fire_dataset/dataset.yaml \ imgsz640 \ epochs50 \ batch16 \ device0 \ workers8 \ projectruns/fire \ nameexp_fire_001训练器会读取yolov8n.pt的权重结构把最后一层检测头换成nc2的结构再开始训练。这里推荐用预训练权重而不是从零开始航拍森林火灾数据虽然专门但大模型预训练特征在纹理、边缘、颜色上的泛化能力足够用从零训练需要8到10倍的epoch才能追回来。几个关键参数的取舍逻辑imgsz640是性价比最高的选择。航拍图里的火点尺度大多数集中在32×32以内用1280能提升小目标召回但训练时间和显存消耗大约翻2.5倍。第一次跑通用640后续再做多尺度对比。batch16在16G显存上配yolov8n可以稳定运行。显存不够就降到8但要把学习率同步调低因为小batch对BN的统计量估计更不稳定。epochs50对这个规模的数据集是合理起点。6116张做50轮相当于模型看了约30万张次图片对小数据集已经算充分。如果50轮还没收敛说明lr或数据增强有问题单纯加epoch治标不治本。训练过程中的监控点日志里每轮输出box_loss、cls_loss、dfl_loss三个损失项。对fire和smoke这种强纹理目标cls_loss如果长时间在2.0以上下不来先怀疑数据集里类别id是否错位其次怀疑正负样本极端不平衡。box_loss持续不降则去看标签坐标是否越界或大量零面积。4.3 训练过程中的反直觉噪声损失震荡、BN崩溃与混淆矩阵异常训练到中途会遇到几个看起来像“模型不行”但其实是配置问题的现象。第一个是BN崩溃也就是yolo训练中偶发的BatchNorm统计量爆炸表现是某个epoch之后loss直接跳成NaN或者mAP从0.7掉到0.2再恢复。原因通常是某个batch里包含一张极端样本——全黑图、全白图、或标注框全是零面积——导致BN的running_mean被拉飞。解决办法是在数据管线里加清洗逻辑过滤掉灰度方差过低的图片在转换脚本里剔除零面积框。不要指望训练器的自动容错。第二个是混淆矩阵总合不唯一。训练结束后看混淆矩阵发现对角线数字没错但“总样本数”和数据集框总数对不上。原因有两类一是验证集里存在没有标注的负样本图片这类图片不参与混淆矩阵分母二是Mosaic增强会拼出4张图的混合图标签也会拼接计数逻辑和单张图不一样。这个现象其实是数据增强的正常结果不是bug别被吓到。第三个是损失曲线震荡剧烈。如果box_loss整体下降但每轮跳动幅度大通常是因为batch size偏小再加上mosaic增强把场景拼得太杂。此时常见做法是提高batch到32把mosaic在最后10个epoch关闭YOLOv8里可设置mosaic0.0。小数据集上mosaic在前中期有助于学习尺度不变性但后期它会反复打乱背景干扰最终收敛。如果继续震荡则检查是否开了fliplr俯拍火灾场景下左右对称性较强但过强的翻转会让烟柱方向语义混乱建调低翻转概率。5. 常见问题与避坑7z解压、标签漂移、过拟合与漏检5.1 7z压缩包密码正确但一直报错这是横跨Windows和Linux用户最高频的问题。现象双击压缩包输入密码后提示“密码错误”或“文件已损坏”但密码明明是对的。我见过有人连续输入十几次密码以为是大小写问题结果问题根本不在密码。原因有几种。7z格式在加密时区分“加密文件名”和“仅加密内容”两种模式。如果创建者启用了“加密文件名”解压工具要求密码的时机和内容校验不一样图形工具有时会误判。其次某些老版本解压工具不支持较新7z版本的压缩算法例如LZMA2字典大小超过512MB会直接报“已损坏”。解决步骤第一不要用双击方式用命令行7z t做测试先确认是密码问题还是文件问题。第二如果确实要输密码用-p参数传不要交互输入避免转义问题。第三如果7z l能列出内容但7z x失败优先换最新版7-Zip或p7zip。第四不要用Windows自带的资源管理器解压7z它对7z格式支持本身不完整后缀看着一样但行为不同。提示解压前先7z l不然后续训练中途发现图片解码失败排查成本会高很多。5.2 VOC转YOLO后的标签漂移id从0开始还是从1开始现象训练结束后发现预测结果里smoke被标成fire或者混淆矩阵显示一个类别的精确率极高、召回率极低。原因转换脚本里class_map定义成{fire: 1, smoke: 2}或训练yaml里names数组顺序与txt第一列不一致。YOLO格式的id从0开始是硬约定任何从1开始的id都会把整个标签空间平移一位而训练器不会报错只是把所有框的语义错位。解决训练前先用解析脚本遍历labels目录统计所有txt里的第一列最小值。如果最小值是0说明id定义正常如果是1直接批量减1重写。这个脚本只有十行比训练完排查错位快得多。还有一种隐蔽的标签漂移同一个图片被旋转增强后标注框没有跟着旋转。YOLO训练器的常规增强会自动同步变换标签但如果你用外部脚本离线做旋转增强就必须自己同步标注。常见做法是只做90度的旋转——因为90度旋转时宽高交换和一个固定公式就能搞定标签变换直接cv2.warpAffine跑一遍图像而不同步标签等于在数据里种毒。5.3 连续训练几十轮后过拟合特征固化与验证集会停滞现象训练到30轮左右训练集loss持续下降但验证集mAP50在28轮后停滞甚至微降。原因6116张图不算多且俯拍影像的纹理高度相似——都是树冠和裸地。模型的低层特征很快就把“树冠纹理”记住对未见过的燃烧场景泛化不足。解决从三方面下手。第一扩展数据增强策略开hsv、翻转、仿射的常规增强并把mosaic开着模拟多场景分布。第二用patience15配合每轮出验证指标一旦发现mAP停滞就不再继续避免把时间耗在注水epoch上。第三用预训练权重做两阶段训练先冻结骨干只训练检测头几个epoch再解冻全部层微调。这个“两步走”能压制过拟合在小数据集上效果尤其明显。5.4 火点漏检与误检不平衡小目标淹没在背景里现象模型对烟柱的召回率很高但对小火点的召回率奇低尤其是在远距离俯拍时。原因火点在图像中相对尺寸过小标注框数量少类别目标尺度分布向“中等目标”偏移小目标的anchor分配不足。解决第一调整输入分辨率或用多尺度训练让模型在不同尺度下都见过小火点。第二用SAHI之类的切图工具把大图切成小块再预测虽然增加推理耗时但对无人机巡检这类离线后处理场景是可行的。第三专门针对小目标做一次hard example mining——把验证集里漏检的图片挑出来在训练数据里重复2到3份再继续微调若干epoch。注意不要重复太多否则又滑回过拟合。这个数据集的重要边界在于它够启动、不够尽头。6116张能让你把YOLO流程跑通、把基线立住但真要部署到生产环境的巡检航线还是要持续累积自己的难样本池。6. 从6116张到落地模型难样本扩充、验证指标与部署检查先讲验证指标。这个数据集训练出来的模型直接用mAP50作为最终考核项是不够完整的。火点漏检的代价远高于误检所以我会额外盯住低置信度下的召回率。做法是把推理置信度阈值从0.25降到0.05统计recall的变化判断模型是把fire藏在低置信度里还是彻底淹没了。前者说明阈值调优、NMS策略有空间后者才是真正的漏检问题。难样本扩充在这个数据集上的收益非常高。常见做法是把训练好的模型拿到新航拍视频上做预测自动抽出置信度低于0.3的预测框对应图像块人工标注后补充进训练集。这样扩充10轮每一轮只挑最难的样本比一次性新增几万张自动标注数据效果稳定得多。扩充时注意保持俯拍视角和拍摄高度的一致性避免引入地面视角样本让模型学歪。部署检查环节我用TensorRT导出时有个血泪教训训练时imgsz640导出时如果改成608或672性能会有一定浮动但一般可以接受真正会翻车的是batch维度的动态变化。TensorRT固定shape引擎对batch1优化最好动态batch引擎会有额外开销。无人机巡检多为单图推理建议导出固定batch1的引擎把省下来的显存放给更高的输入分辨率。另外NMS后处理里fire的框通常比smoke小置信度阈值按类别分开调比全局统一更能平衡漏检与误检。最后说一个习惯我在评估这类数据集时不会只跑一次训练就下结论。一个数据集的价值是否被榨干要看在同一个配置下用不同的随机种子跑3次验证集指标波动是否在1%以内。如果波动超过3%说明数据量或分布不足以支撑稳定收敛这时候再调模型结构意义不大先把数据层面补齐才是正路。希望这套6116张的俯拍航拍森林火灾检测数据集能帮你把冷启动这一步走得顺剩下的坑我们各自数据里去踩。本文还有配套的精品资源点击获取
返回列表