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

资讯详情

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

D-Fire火焰烟雾YOLO数据集:从解压到训练的完整避坑实战

D-Fire火焰烟雾YOLO数据集:从解压到训练的完整避坑实战 简介面向目标检测与火灾智能预警场景的D-Fire火焰烟雾YOLO检测数据集适合计算机视觉、深度学习初学者及安全监测应用开发者使用。数据集涵盖多种环境和光照条件下的火焰、烟雾现场图像并带有精确的边界框与类别标注可直接用于YOLO系列模型的训练、验证与微调有效弥补火灾场景中高质量标注数据不足的问题。压缩包共3个文件包含py脚本、示例图片和md说明文档整体大小仅10.3MB轻量易用。py脚本辅助数据处理、模型训练与验证md文档说明目录结构、标注格式及用法示例图片便于直观评估数据质量。目前已有138人学习下载特别适合希望将YOLO模型落地到监控视频分析、智能安全预警等实际任务的开发者。1. D-Fire火焰烟雾YOLO检测数据集.zip从解压到训练一份可复现的实战路径拿到「D-Fire火焰烟雾YOLO检测数据集.zip」这串名字第一反应不应该是“终于下完了”而是“解压之后能不能直接喂给YOLO开跑”。我见过太多人把时间花在调模型超参上结果是训练loss很漂亮一到夜间或远景就漏检。这不是模型不行而是数据没被正确打开标注格式要转、场景分布要摸清、边界框要清洗。这篇笔记就按做火灾预警项目的真实流程把这个zip从解压、探路、转格式、训练到部署验证全链路走一遍尽可能给出能直接复现的命令和绕开坑位的方法。适合想把火焰烟雾检测快速落地成可演示demo的工程师也适合刚接触目标检测数据集的入门者。2. D-Fire火焰烟雾YOLO检测数据集.zip里到底有什么解压、探路与标注格式2.1 先摸清zip包里的文件结构再决定要不要换工具拿到zip包我一般不会急着用压缩软件全部解压到桌面而是先写个三行脚本把zip里的文件后缀统计一遍。原因很简单D-Fire这类数据集的下载渠道很多有时你拿到的zip被二次打包过里面又多包了一层目录直接解压后路径会变得很深后续YOLO训练时数据加载经常因为路径问题翻车。import zipfile from pathlib import Path from collections import Counter zip_path D-Fire火焰烟雾YOLO检测数据集.zip with zipfile.ZipFile(zip_path) as zf: names zf.namelist() exts Counter(Path(n).suffix.lower() for n in names) print(总文件数:, len(names)) print(扩展名分布:, dict(exts))逻辑说明zipfile是Python标准库不装任何依赖。统计扩展名能立刻看出这个zip是“图片XML标注”的经典VOC风格还是“图片TXT标注”的YOLO风格或者是混合格式。如果看到.jpg和.xml大约一一对应那就是VOC格式如果.jpg和.txt对应说明已经有人帮你转好了一半可以跳过第3章的转换步骤直接进入训练配置。参数说明zip_path填你实际存放路径。用namelist()而不是infolist()也能跑但infolist()能拿到压缩前大小碰到解压后磁盘空间不够的机器可以先预估总大小。如果统计结果里同时出现xml和txt不要高兴太早大概率是其中一部分数据转到了YOLO格式、另一部分没有后面转格式时要按目录分开处理。2.2 从标注文件里读出类别分布和框质量如果解压出来的是XML标注我会先解析几个XML看看里面object的name字段到底有哪些。D-Fire这类火焰烟雾数据集绝大多数标注是fire和smoke两类但保不齐有人把smoke写成Smoke、把fire写成fire_标签名不统一会让后面类别映射出错。import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter xml_dir Path(xml) cls_counter Counter() box_wh_ratios [] for xml_path in xml_dir.rglob(*.xml): root ET.parse(xml_path).getroot() for obj in root.iter(object): name obj.findtext(name, ).strip() cls_counter[name] 1 bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) box_wh_ratios.append((xmax - xmin) / max(ymax - ymin, 1e-6)) print(cls_counter) print(宽高比中位数:, sorted(box_wh_ratios)[len(box_wh_ratios) // 2])逻辑说明用findtext给name字段一个默认值可以防止个别标注缺name导致整个脚本崩掉。宽高比分布用来快速判断这份标注里是不是有大量“烟囱”“火焰柱”这类竖条长框后期转YOLO格式时这类框最容易出现归一化数值异常。参数说明cls_counter会输出类似Counter({fire: 2041, smoke: 1748, Smoke: 32})的结果看到大小写混用就先归并。box_wh_ratios存的是宽/高比中位数大于1说明横向框多如果大量比例超过3转格式时要格外注意框的边缘裁剪。如果你对YOLO的类别编号有疑问可以这样理解YOLO训练时用的names列表就相当于COCO数据集的80个类名0和1只是编号顺序完全自己定关键是data.yaml里的names顺序和转换脚本里的CLASSES顺序要完全一致。2.3 场景切分白昼、夜间、室内外分别有多少火焰烟雾检测最大的坑是夜间。夜间火焰在图像里通常高饱和且发亮但夜间烟雾几乎是半透明灰体和背景融在一起模型很容易当成背景。训练前必须按亮度和来源目录做一次分布统计看看夜间样本到底占多少。import cv2 from pathlib import Path from collections import Counter img_dir Path(images) scene_cnt Counter() for img_path in img_dir.rglob(*.jpg): img cv2.imread(str(img_path)) if img is None: continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) mean_bright gray.mean() if mean_bright 80: scene_cnt[night] 1 elif mean_bright 140: scene_cnt[dusk] 1 else: scene_cnt[day] 1 print(scene_cnt)逻辑说明这是一个非常粗糙的亮度分层100张图跑一遍只要几十毫秒足够给你一个场景分布直觉。如果night样本少于总样本的5%第6章的“双模型备用”就很有必要因为单模型很难同时覆盖白天和夜间两个分布差异巨大的场景。参数说明80和140这两个阈值不是固定的。你可以先取20张图看一眼亮度值再调在夜间监控画面里平均灰度普遍低于80白天烈日场景普遍高于160。如果应用场景是室内阈值还要再往下调。如果某个类别的框数量明显少于另一个类别不要急着删数据先数一下图片数量。比如smoke类只有300张图但每张图里有四五个烟雾框总标注数量其实不少。YOLO按框数量采样类别不均衡比图片不均衡影响更大。这时候可以先用albumentations做亮度扰动补一批夜间smoke图比改损失函数权重更直接。3. 把D-Fire转成YOLO训练格式转换脚本与四个边界坑3.1 为什么不能直接拿D-Fire喂给YOLOD-Fire常见版本里标注是VOC风格的XML而YOLO训练需要的是与图片同名的txt文件每行一条标注五列分别是“类别编号 中心点x 中心点y 宽 高”且所有坐标都归一化到0-1之间。两件事必须做一是把绝对像素坐标换算成相对坐标二是把类别名称映射成编号。很多新手图省事拿XML直接开训结果数据加载器报错或者模型学到错位框。这也好理解XML是给人读的txt是给YOLO数据加载器吃的。不把格式统一成txt后面做数据增强、多尺度训练、切片推理都会踩坑。而且D-Fire里包含大量细长的火焰柱和烟囱状目标转换时如果直接除以图像宽高再截断很容易把框压成一条线。所以这个转换步骤不要省脚本可以一次性解决。3.2 一套可复制的VOC转YOLO脚本以VOC风格XML为例我常用的脚本如下把xml_root和img_root指到对应目录跑完会在out_dir下生成images和labels两个目录图片按软链接方式组织、标签写成txt。import os import cv2 import xml.etree.ElementTree as ET from pathlib import Path CLASSES [fire, smoke] xml_root Path(data/xml) img_root Path(data/images) out_dir Path(data/yolo) out_img out_dir / images out_lbl out_dir / labels out_img.mkdir(parentsTrue, exist_okTrue) out_lbl.mkdir(parentsTrue, exist_okTrue) for xml_path in xml_root.rglob(*.xml): img_path img_root / (xml_path.stem .jpg) if not img_path.exists(): img_candidates list(img_root.rglob(xml_path.stem .jpg)) if not img_candidates: continue img_path img_candidates[0] img cv2.imread(str(img_path)) if img is None: continue h, w img.shape[:2] tree ET.parse(xml_path) lines [] for obj in tree.getroot().iter(object): name obj.findtext(name, ).strip().lower() if name not in CLASSES: continue bndbox obj.find(bndbox) xmin max(0, min(float(bndbox.findtext(xmin)), w)) ymin max(0, min(float(bndbox.findtext(ymin)), h)) xmax max(0, min(float(bndbox.findtext(xmax)), w)) ymax max(0, min(float(bndbox.findtext(ymax)), h)) if xmax - xmin 0.01 * w or ymax - ymin 0.01 * h: continue xc (xmin xmax) / 2 / w yc (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{CLASSES.index(name)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) (out_lbl / (xml_path.stem .txt)).write_text(\n.join(lines)) (out_img / img_path.name).symlink_to(img_path.resolve())逻辑说明大多数D-Fire版本里img和xml按文件名一一对应但偶尔有图片散落在train和test子目录的情况所以脚本里加了一段候选图片搜索。坐标统一先clamp到图像宽高范围内这是防止标注越界导致负坐标或者大于1坐标的关键。参数说明0.01w和0.01h是过滤框宽或高小于图像尺寸1%的标注。火灾场景里这类小框通常是误标或边缘噪点转进YOLO只会让小目标分支过拟合真需要保留可以把阈值降到0.005。注意脚本里用了symlink软链接来组织图片目录。Windows上symlink需要管理员权限如果报错改成copy在Linux服务器上跑最稳。3.3 火焰烟雾场景里最容易翻车的四个边界坑现象一转换后某些txt行里出现负数或大于1的坐标。原因XML里bndbox的xmin、xmax与图片实际尺寸不一致有的标注把坐标写成了相对值有的写错了顺序。解决脚本里已经做了clamp但根本办法是用图像实际宽高覆盖XML里的width和height字段以图像为准。现象二一张xml对应不到jpg。原因部分xml是从其他数据集合并过来的图片文件重命名过。解决按xml stem去img_root全目录找候选图找不到就跳过并打印警告后续人工核对。现象三标签名大小写不一致比如Fire和fire混用。原因不同批次的标注员习惯不同。解决转格式时统一做strip().lower()让CLASSES里的名字全是小写避免漏匹配。现象四txt文件里出现大量空白行。原因这张xml里的object全部被过滤了比如全是person类。解决不要直接删除这张图把它单独归到一个empty目录。训练时用它做负样本对控制误检有很大帮助。3.4 转完之后的校验先数行数再跑一次假训练转格式最怕静默出错某些txt是0字节某些图片是灰度无损压缩格式OpenCV能读但PIL会报错。我一般在训练前写一个五分钟的校验脚本。from pathlib import Path from PIL import Image import cv2 img_dir Path(data/yolo/images) lbl_dir Path(data/yolo/labels) for img_p in img_dir.rglob(*.jpg): img cv2.imread(str(img_p)) if img is None: print(坏图:, img_p) continue lbl_p lbl_dir / (img_p.stem .txt) if not lbl_p.exists(): print(缺标签:, img_p) continue for line in lbl_p.read_text().splitlines(): parts line.split() if len(parts) ! 5: print(异常行:, lbl_p, line) continue vals [float(v) for v in parts[1:]] if not all(0 v 1 for v in vals): print(坐标越界:, lbl_p, line)逻辑说明这个校验能把“缺标签”“坐标越界”“坏图”三类最常见问题一次性筛出来。跑完重点关注“缺标签”的数量级如果图片有两千张缺标签只有十几张可以放行如果缺了一半多半是xml和jpg命名规则不匹配这时候先别训练回去对一下两份文件的命名。参数说明坐标校验会同时检查中心点x、中心点y、宽、高四项是否都在0-1区间内。如果只查前两个数宽度高度越界的问题会被漏掉。4. 用YOLOv8在D-Fire上训练自己的火焰烟雾模型数据集配置与三个必调参数4.1 数据yaml这样写names顺序别乱转换完以后目录结构大概是这样data/yolo/images和data/yolo/labels。接下来写一个data.yaml告诉YOLOv8去哪里找训练集、验证集和类别名。path: ./data/yolo train: images val: images test: images names: 0: fire 1: smoke nc: 2逻辑说明train和val直接指向同一个images目录时YOLO内部会按比例切分但不要真这么干。第一步先用这个yaml跑通pipeline确认环境没有路径问题再做后面的分层切分。names的顺序就是转格式时CLASSES的顺序第3章转出来是0对应fire、1对应smoke这里必须同步。参数说明如果某个类别只有一百多张图nc仍然是2不要试图把罕见类删掉。火焰烟雾项目里fire和smoke通常都是核心业务删掉任何一类都代表业务不完整应该去看要不要做数据增强。4.2 按场景分层切分训练集、验证集和测试集D-Fire的样本如果直接随机切分夜间样本可能全部挤进测试集训练集里白天样本占绝对多数结果验证mAP虚高现场部署时夜间漏检。常见做法是按场景来源做分层抽样。import random from pathlib import Path from collections import defaultdict img_files list(Path(data/yolo/images).rglob(*.jpg)) random.seed(42) groups defaultdict(list) for p in img_files: groups[p.parent.parent.name].append(p) train, val, test [], [], [] for _, files in groups.items(): random.shuffle(files) n len(files) train files[: int(n * 0.7)] val files[int(n * 0.7): int(n * 0.85)] test files[int(n * 0.85):] with open(train.txt, w) as f: f.write(\n.join(str(p) for p in train))逻辑说明groups的键用的是p.parent.parent.name意思是按图片所在子目录名作为场景来源。如果D-Fire的目录结构是data/yolo/images/train/*.jpg那这里取到的是train这个目录名效果不太好。你需要观察实际目录深度换成能区分场景的字段如果文件名前缀带night这类关键字可以直接按文件名前缀分组。参数说明0.7/0.15/0.15是目标检测项目里常见的比例。火焰烟雾跟普通目标检测不同测试集一定要保留夜间和远景样本所以分组键最好再叠加亮度判别把夜间图单独分一层保证val和test里夜间占比不低于训练分布。4.3 训练启动命令与三个必调参数imgsz、epochs、batch配好yaml后直接用ultralytics的命令行训练。实际动手时注意同一份数据不同参数跑出来的结果差很远三个参数必须优先调。yolo detect train \ modelyolov8s.pt \ datad_fire.yaml \ imgsz640 \ epochs150 \ batch16 \ device0逻辑说明imgsz640是速度和精度的平衡点。火焰烟雾的远端目标通常很小如果imgsz降到416小目标漏检会明显上升但直接升到1280又会让训练显存和推理耗时成倍上涨所以先640起跑后面瓶颈在小目标的时候再针对性升到960或1280。参数说明epochs在100到150之间选。D-Fire的样本量在几千张级别训练集只有几百到一千多张图时120个epoch完全够超过200个epoch大概率过拟合白天场景。batch要看显存yolov8s在16GB显存上用batch32没问题8GB卡就老老实实batch16。如果OOM建议先降imgsz而不是batch原因在第5章避坑部分讲。预训练权重的选择上yolov8s.pt比yolov8n.pt强不少。火焰烟雾的框普遍不算大n模型的下采样特征图太粗夜间召回率会明显偏低。如果你服务器只有4GB显存退而求其次用yolov8n但val mAP通常要低3-5个点部署时要做好心理预期。4.4 损失函数权重怎么调先别动看一个指标再决定YOLO损失函数不是玄学但很多人一上来就把box和cls权重改来改去结果mAP更差。以YOLOv8为例训练日志里会看到cls_loss、box_loss、dfl_loss三条曲线。火焰烟雾这种“小目标多、背景占比大”的场景最该关注的是cls_loss是否下降到box_loss的十分之一以下而不是急着调权重。在yolov8.yaml里能看到box: 7.5、cls: 0.5、dfl: 1.5这类默认参数一般不用动。什么时候调如果val侧在fire类上miss率高说明模型对fire的判别力弱可以小幅上调cls权重到1.0同时把box降到5.0让分类分支更敏感如果预测框偏移严重再反过来上调box。每次只调一个量跑20个epoch看趋势别一次全动。YOLOv8的head部分用了DFL所以日志里能看到dfl_loss。很多教程会额外加focal loss的gamma参数但YOLOv8的分类分支已经内置了BCE loss火焰烟雾场景不需要外挂focal。cls_loss下降但val mAP不涨几乎可以断定是标签噪声或坐标错位这时候调损失函数权重等于自欺欺人先把数据查干净再说。5. D-Fire火焰烟雾数据集常见问题与避坑排查解压报错、标签错位与显存不足5.1 解压期的三个坑路径过长、编码问题、损坏的图片现象一Windows下用系统自带解压工具解压到一半报“文件名太长无法解压”。原因zip包里的子目录层级深再加上含中文文件名总路径很容易超过260字符的经典上限。解决用7-Zip解压到短路径比如C:\d_fire不要解压到带中文的深层桌面路径或者用Python的zipfile模块把过深路径拍平后写出。import zipfile import shutil from pathlib import Path with zipfile.ZipFile(D-Fire火焰烟雾YOLO检测数据集.zip) as zf: for info in zf.infolist(): target Path(d_fire_out) / Path(info.filename).name if info.is_dir(): target.mkdir(parentsTrue, exist_okTrue) continue target.parent.mkdir(parentsTrue, exist_okTrue) with zf.open(info) as src, open(target, wb) as dst: shutil.copyfileobj(src, dst)逻辑说明这里用原名name字段而不是完整路径把嵌套目录里的文件全拍平到一个目录下规避路径过长问题。缺点是有同名图片会互相覆盖如果发现文件数对不上再手动按原始前缀分组。现象二解压后文件名或XML内容乱码。原因zip内文件名编码是GBKPython zipfile在Windows默认按UTF-8解码导致乱码。解决对info.filename做encode(cp437).decode(gbk)能救回来一部分实在读不了按路径里的数字序号重命名。现象三jpg全部为0字节。原因下载中断或者zip包本身损坏。解决解压后跑一遍PIL验证并删除坏图校验逻辑和3.4节一样不再重复。5.2 训练期的坑loss下降但val mAP不涨现象训练100个epoch训练loss一路下降但验证集mAP从0.4掉到0.1。原因训练集和验证集分布不一致最常见是分层抽样没做夜间图全进了验证集另一个常见原因是xml转txt时坐标错位。解决用第4章的按场景分层切分重做数据集再用OpenCV把几张验证图的预测框画出来肉眼确认框是否正确落在火焰或烟雾上。画框检查很简单读一张验证图把txt里前两列坐标转回像素用cv2.rectangle画出来扫一遍就能发现坐标到底偏到哪去了。任何一条框错位到图上没有火焰的位置说明转换脚本的坐标系写错了别继续训练。5.3 显存OOM时不要马上降batch现象yolov8m、batch32时报CUDA out of memory改成batch8还是报错。原因YOLOv8训练时默认开启Mosaic和多尺度训练多尺度在训练后期会把输入随机放大到1280像素以上显存峰值出现在这个阶段而不是你设置的batch本身。解决先把imgsz固定640关闭多尺度增强或者把close_mosaic参数设到10让最后10个epoch关闭Mosaic再看显存占用。这一步通常能救回来还不用牺牲batch。如果你用的是多卡训练还要检查DDP的batch是不是按卡数累加的比如batch16在两张卡上实际等于每卡16总batch变成32显存翻倍。5.4 小目标多但recall很低的排查现象验证集整体mAP.5还行但mAP.5:.95很低烟雾框出现大量漏检。原因D-Fire里远处烟雾的框很多只有5到10个像素宽经过YOLO的stride32下采样后特征图上只剩不到1个像素检测头根本看不到。解决最直接的是训练时用imgsz1280让目标在特征图上有足够响应但训练和推理速度都会变慢更省事的方案是推理时用SAHI做切片推理把大图切成带重叠的512小块分别检测能快速提升小目标召回率。6. 进阶夜间和远景的火焰检测怎么做双模型备用与TensorRT提速6.1 按场景给两个模型白天用小模型夜间用大模型如果D-Fire里夜间样本占比很低一个工程上很稳的方案是训练两个模型白天用yolov8s夜间用yolov8m。切换逻辑可以靠在摄像头端算画面平均亮度或者按时间区间定时切换比如晚7点到次日早6点走夜间模型其余时间走白天模型。这种双模型备用比在一个模型里强行塞两种场景要稳得多训练时间也省。夜间模型可以用白天模型的权重作为预训练把夜间样本单独凑一个小训练集做微调。如果想要更细的过火面积统计可以考虑YOLO的实例分割分支但火焰和烟雾边缘变化剧烈分割mask并不稳定检测框加一个矩形范围估算更实用。6.2 用TensorRT把640分辨率推理跑到“能接多少路”火焰烟雾检测经常要接多路摄像头推理速度直接决定单卡能接多少路。第一步先把训练好的pt导出成onnx再用trtexec转成engineyolo export modelbest.pt formatonnx imgsz640 opset12 trtexec --onnxbest.onnx --saveEnginebest.trt --fp16 \ --minShapesimages:1x3x640x640 \ --optShapesimages:1x3x640x640 \ --maxShapesimages:8x3x640x640逻辑说明trtexec是TensorRT自带的转换工具fp16是常规精度优化。min/opt/max三组shapes是给TensorRT做尺寸优化的区间。火焰烟雾场景摄像头分辨率固定最好一开始就按固定batch1的静态shape导出能多榨出一点延迟。参数说明如果后面要用SAHI切片推理输入就不是640而是切片后的小图shapes要改成对应尺寸。多个摄像头并发时优先用batch1的engine开多个stream而不是改optShapes里的max batch。换算路数时按这个公式一路1080p25帧每秒意味着每秒处理25帧单帧耗时假设5ms25帧一共125ms的GPU时间预算一张T4在fp16下640分辨率单帧4到6ms很常见卡上还有转码和预处理开销保守按单帧6ms算1000ms除以150ms约等于6路。这个估算可以作为设备选型起点但务必用自己项目的engine实测后替换数字。6.3 验证方法按场景拆开算mAP不要只看总量最后说一个我自己的血泪教训有次把D-Fire训练完总体mAP看着0.62觉得还不错结果现场demo一跑夜间场景几乎全漏。后来把验证集按白昼、夜间、室内、室外四组分别跑评估才发现夜间mAP只有0.21。从那以后我交付任何火焰烟雾模型都要求评估报告按场景分组出指标总量只是用来汇报分组才是用来决策的。希望帮到你。本文还有配套的精品资源点击获取
返回列表