尧图精选

YOLO火焰烟雾检测数据集实战:双格式标注对齐与YOLOv8训练避坑指南

🕒 发布时间:2026/10/2 21:59:24 📁 来源:尧图网络
简介面向YOLO目标检测训练与消防预警场景这套火焰烟雾数据集包含18800张真实场景图片配套YOLO和VOC两种格式的标注文件TXT与XML可直接用于训练。压缩包共2000个文件以XML标注为主整体约765MB涵盖室内外火焰、烟雾、明火与阴燃等多样状态XML中记录类别与边界框坐标便于VOC系列流程解析TXT则适合YOLO系列快速加载。数据集目录结构清晰文件名保留来源编号方便回溯场景无需额外清洗格式或整理路径下载解压后即可开始训练。配合迁移学习可有效提升火焰烟雾检测精度适合作为目标检测项目的数据基础。已有1428人学习下载适合具备目标检测基础、需要高质量监督数据的开发者快速开展实验或产品验证同时可显著减少数据准备与人工标注时间。1. YOLO 火焰、烟雾数据集18800张图里真正值钱的不是数量是标签对齐方式做火焰烟雾检测的同行应该都有体会跑通YOLO本身不难难的是凑够一套能让模型在真实场景不翻车的训练数据。这份数据集标题一眼看过去好像只是数字堆叠——18800张图片YOLO和VOC两种格式标注TXT和XML并存。但实际用它训练几轮之后你会发现真正影响mAP的往往不是图片张数而是两个标注体系之间的对齐质量类别名拼写是否一致、box坐标是否越界、空标签文件是不是混在里面。这些细节做不好的话数据清洗占掉的时间比训练还多。这篇笔记就按我拿到这份数据集之后实际会走的路线来写先教你做一次完整的数据体检再讲清楚YOLO/TXT和VOC/XML两套标注怎么落到YOLOv8训练流程里最后把坐标换算、类别映射、小目标漏检这类高频坑一次排完。适合手头有这份数据但训练结果不理想的人也适合拿到任何双格式标注数据集时想少走弯路的人。2. 双格式标注的真实含义TXT与XML背后的两套坐标哲学2.1 YOLO的TXT标签归一化坐标为什么让新手又爱又恨YOLO系列沿用的TXT标注格式每一行对应一个目标物体五个字段依次是类别索引、中心点x坐标、中心点y坐标、目标宽度、目标高度。这四个坐标值有一个容易忽略的前提——它们全部是相对图片宽高的归一化数值取值范围严格落在0到1之间。这意味着同一张1280×720的图片里一个位于第640列、第360行的目标其中心点坐标会被记录成(0.5, 0.5)而不是(640, 360)。很多刚接触这份数据集的人拿文本编辑器打开TXT文件看到一堆0.8、0.12这样的小数就以为标注有问题实际上这是YOLO格式的正常形态。真正的异常信号是坐标值出现负数或大于1的数那才是标注工具或转换脚本出错留下的痕迹。这份数据集给出的TXT文件默认按类别索引排列我需要提醒一点索引对应的类别名是写在数据集的yaml或names配置文件里的。你拿到手要先确认0和1这两个数字分别对应“火焰”还是“烟雾”不确定的话就去看同目录下的XML文件那里面的name标签会给出明文类别名。两条线对不齐的情况我后面会专门展开。2.2 VOC的XML标签左上角加右下角的绝对坐标VOC格式走的是另一套逻辑XML文件里的bndbox节点用xmin、ymin、xmax、ymax四个绝对像素坐标描述一个矩形坐标原点在图片左上角。这套格式对人类更友好打开XML就能直接读出目标在图片中的位置不需要做除法。它的代价是文件体积更大、解析效率更低而且坐标一旦超出图片宽高不像YOLO那样能被小数范围兜住报错——XML里一个xmax写成16384的错误位置图片只有1280×720时是看不出来的直到送进模型训练才会爆。VOC与YOLO的坐标体系换算关系如下表所示字段VOC/XML绝对像素YOLO/TXT归一化中心点x(xmin xmax) / 2中心点x / 图片宽度中心点y(ymin ymax) / 2中心点y / 图片高度宽度xmax - xmin(xmax - xmin) / 图片宽度高度ymax - ymin(ymax - ymin) / 图片高度对于18800张这种体量手工做这种换算不现实一定要脚本化处理。我一般拿到此类双格式数据集的第一步不是直接开始写训练代码而是先写一个check_annotations.py做体检确认三件事图片文件是否都能打开、每张图片的标签文件是否完整存在、标签里的坐标是否落在合理区间。import os from PIL import Image from pathlib import Path # annotation_dir 指向 TXT 所在目录 annotation_dir Path(labels) image_dir Path(images) bad_txt [] no_txt [] for img_path in image_dir.glob(*.jpg): txt_path annotation_dir / (img_path.stem .txt) if not txt_path.exists(): no_txt.append(str(txt_path)) continue for line in txt_path.read_text().splitlines(): parts line.split() if len(parts) ! 5: bad_txt.append(f{txt_path.name}: 字段数不是5 - {line}) continue # 归一化坐标必须全部在0~1之间允许极小浮点误差 cls, cx, cy, w, h parts[0], float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if not (0.0 cx 1.0 and 0.0 cy 1.0 and 0.0 w 1.0 and 0.0 h 1.0): bad_txt.append(f{txt_path.name}: 坐标越界 - {line}) print(f缺失标签文件: {len(no_txt)} 个) for p in no_txt[:10]: print( , p) print(f异常标签行: {len(bad_txt)} 条) for b in bad_txt[:10]: print( , b)这段脚本的核心价值在于把“数据集是否有问题”从玄学变成可量化的体检报告。字段数不为5意味着转换脚本截断了行坐标越界意味着某个目标框超出了图片实际尺寸。注意脚本里我检查的是TXT这份侧写的完整性XML侧完全可以复用同一思路只是要把坐标判断改成xmin xmax且xmax width这类像素边界条件。还有一个实操细节这份数据集的图片可能是.jpg也可能是.png我的习惯是读取图片尺寸时统一用PIL而不是cv2.imread因为OpenCV对某些CMYK色彩空间的JPEG会报错而PIL的容错性更好。另外在逐张读取几千张图片前先按文件扩展名分组能避免在大批量处理中途才发现图片格式比预想的多一种。2.3 面向训练选型这份数据集能不能直接喂给YOLOv8先给结论绝大多数情况下你不需要自己动手把TXT转成XML或者反过来。YOLOv8的ultralytics框架原生支持读TXT标签路径配置到位就能直接开训而那份XML侧标注主要用于三个场景——用LabelImg复检历史标注、转成COCO格式接入MMDetection、或做跨数据集的类别合并。所以第一步确定训练框架再决定要不要做格式转换。如果直接走YOLOv8训练路线数据目录结构长这样flame_smoke_dataset/ ├── images/ │ ├── train/ # 约14000张 │ └── val/ # 约4800张或你自己切分的比例 ├── labels/ │ ├── train/ # 与 images/train 一一对应的TXT │ └── val/ ├── dataset.yaml # 模型读取这个文件dataset.yaml内容path: ./flame_smoke_dataset train: images/train val: images/val names: 0: fire 1: smoke这里有一个真实的翻车点names的顺序必须和TXT文件里的类别索引完全一致。如果TXT里写的是0代表烟雾、1代表火焰而yaml里0配了fire那么训练出来的模型会把火焰和烟雾整个认反mAP看起来还挺高一到现场部署全错。判断方法很简单随机挑几十个TXT文件统计每个索引出现的频率火焰和烟雾样本数量通常差异不大因为火灾现场两者往往同时出现如果某个索引出现次数为0大概率是类别名映射有问题。3. 用这份数据集训练YOLOv8从目录结构到损失函数的全链路3.1 数据切分策略随机抽样还是按场景分桶18800张图片的训练集说大不大说小不小切分比例和策略直接关系到验证集能否真实反映模型泛化能力。我见过很多人直接用train_test_split做随机划分这在多场景数据集上会得到虚高的验证mAP——同一段视频的连续帧被同时分进训练集和验证集模型记住的是场景背景而不是火焰本身。正确的做法是常见做法先看数据集的文件命名规律如果文件名带拍摄地点或时间戳前缀就按前缀做分组切分确保同一场景的所有帧只出现在一侧。import random from pathlib import Path image_dir Path(images) train_dir image_dir / train val_dir image_dir / val train_dir.mkdir(exist_okTrue) val_dir.mkdir(exist_okTrue) images list(image_dir.glob(*.jpg)) random.Random(42).shuffle(images) # 固定随机种子保证可复现 val_count int(len(images) * 0.2) # 简单随机切分示范若文件名带场景前缀请先按前缀分组再切 for img in images[:val_count]: img.replace(val_dir / img.name) for img in images[val_count:]: img.replace(train_dir / img.name) print(ftrain: {len(images) - val_count} 张, val: {val_count} 张)移动图片之后千万别忘了同步移动TXT标签文件漏一个都会让YOLOv8在训练时报corrupt image或直接跳过该样本。移动标签时可以用img.stem .txt反推标签文件名和图片一一对应。二八分对这个数据集规模是够的但如果你做的是工业场景部署建议把验证集比例提到25%——火焰烟雾的负样本形态太多验证集太小的话误检率根本测不准。随机种子固定为42这行代码写出来很简单但它的价值被严重低估。不固定种子的话每次跑出的验证mAP都不一样你可能因为一次随机波动就误判某个模型改动有效实际上只是数据分到了友好的一侧。这是非常典型的“测试集黑匣子”问题。3.2 YOLOv8训练命令与关键参数调节步数、批量与图像尺寸怎么配合直接上训练命令模型选择yolov8s.pt起步。火焰烟雾目标相对不算微小s级模型的感受野匹配度尚可如果你后续发现小目标漏检严重再考虑换yolov8m或切tiled推理这个后面在避坑章里展开。yolo detect train \ modelyolov8s.pt \ data./flame_smoke_dataset/dataset.yaml \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/train \ nameflame_smoke_s参数调节的核心逻辑不是背数值而是理解它们的关系。imgsz640是YOLOv8默认缩放尺寸火焰烟雾的标注框大多占画面比例较大640足够如果你希望在保持训练速度的同时提升小目标召回可以提到768但epoch数建议相应增加30%左右因为更大的输入分辨率意味着每张图的信息量更大需要更多轮次收敛。batch16是8GB显存的常规选择24GB左右的卡可以提到32甚至48观察显存占用率在85%上下比较稳妥。patience20控制早停连续20个epoch验证集mAP没有提升就提前结束。这个值设得太大浪费算力太小又容易在局部波动中过早停止。火焰烟雾这类背景干扰强的任务mAP曲线通常在60到100个epoch之间还有缓慢爬升20的耐心值是经验平衡点。训练途中要盯着两个指标val/box_loss和val/cls_loss前者波动大是正常的后者如果持续上升而mAP不变说明模型在过拟合需要触发早停或加数据增强。lr00.01对150个epoch来说略偏激进前5个epoch的warmup阶段会看到loss快速下降如果下降速率异常快比如第一轮就掉了一半以上多半是类别索引映射错误模型在强行记忆错误标签。3.3 损失函数视角为什么火焰烟雾任务的cls_loss比box_loss更关键YOLOv8的损失函数由分类分支和回归分支组成前者负责判断框里是什么后者负责把框画准。在火焰烟雾这类检测任务里我要强调一个反直觉现象最终的部署效果主要被cls_loss决定因为难的不是框准火焰烟雾的形状相对规整而是把地面反光、红色广告牌、白色水蒸气这三大干扰源跟真正的火焰烟雾区分开。训练时如果想撬动类别层面的学习有两个做法。一是调整正负样本的focal loss权重让模型在背景易混淆区域附近更高频地“犯错并修正”二是在数据层面做hard example mining——把验证集上被误检为火焰的最高频图片挑出来丢回训练集多训几个epoch。第二种方法在火焰烟雾检测上几乎永远比第一种有效因为误检通常集中在特定拍摄角度和光线条件属于场景分布问题不是损失函数能修正的。常见的数据集不会有这些硬负样本文件标注你需要自己从历史预测结果里去挖。提示训练前先跑一次yolo detect predict把验证集的前几十张图片预演一遍确认输出框类别名称正确、坐标没有整体偏移再做正式训练。这一步只需要几分钟能省下几个小时的错训练。4. TXT与XML互转脚本详解类别映射表和坐标换算的三道坎4.1 一份可靠的双向转换代码VOC转YOLO为中心虽然直接跑YOLOv8训练不需要转换XML但当你需要接入MMDetection、做可视化复检或合并其他数据集时转换脚本就是必需品。我给出一个以VOC转YOLO为中心的实现因为这是最常见的诉求方向反向转换只是在坐标公式上取逆思路完全一样我把关键差异点在注释里补全。import xml.etree.ElementTree as ET from pathlib import Path # VOC XML 转 YOLO TXT def voc_to_yolo(xml_path, out_path, class_map): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: # 未知类别直接跳过避免脏数据进训练集 continue cls_id class_map[cls_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) # 中心点与宽高的归一化换算 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h # 坐标边界裁剪防止浮点误差带出0~1区间 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) Path(out_path).write_text(\n.join(lines)) # 类别名到索引的映射务必与实际标签核对 class_map { fire: 0, smoke: 1, } voc_to_yolo(fire_001.xml, fire_001.txt, class_map)这段代码的正确性高度依赖两件事XML里有没有size节点以及class_map和TXT既有索引是否一致。size节点缺失意味着你不知道归一化分母是多少这时候只能打开图片用PIL实际读取宽高代价是每张图都多一次IO我一般优先报错而不是默默跳过因为这种XML基本是从标注工具导出的半成品。class_map不一致则是另一个方向的坑——如果数据集的TXT已经存在但和XML类别不同你的转换脚本会把本来就对齐的数据弄乱所以反转场景下我会先统计既有TXT的类别索引分布做交叉验证。4.2 三个最容易翻车的坐标细节第一道坎是坐标原点。OpenCV和PIL的x轴都从左往右、y轴从上往下但部分标注工具导出XML时用的是y轴向上的数学坐标系不做区分的话转换出的框会整体上下颠倒。检查方法找一张标了高空摄像头视角烟雾的图看XML里目标的ymin数值是否明显小于图片中上部目标的实际像素位置如果差得很离谱就说明需要做y img_h - y的翻转。第二道坎是单像素退化box。一些标注人员在截取小目标时会把xmin和xmax标成同一个值YOLO转换后宽高变成0。YOLOv8对宽度为0的box不算“非法”但会在回归头里产生NaN梯度最稳妥的做法是在转换循环里加一个判定if xmax xmin or ymax ymin: 该目标丢弃并输出警告。18800张图里出现几十个这种退化框非常正常这是标注员的真实习惯不必迁怒数据集。第三道坎是图片尺寸与XML坐标系不匹配。数据集给的是YOLO和VOC双格式但未必保证所有图片的width和height标签值与图片文件本身一致——有些q放缩过的新图XML里还是原始分辨率。这类问题转换时不会报错但训练时box会整组偏移。所以我强烈建议在转换后做一次可视化抽样直接在图上画出转换出的box人眼看十张图比任何代码检查都高效得多。这也是我一直坚持的习惯无论数据集提供方说格式多标准自己的眼睛复核不可替代。5. 火焰烟雾数据集避坑指南翻车现场的5条血泪经验5.1 火焰烟雾类别边界模糊训练时朝雾喷了验证时对灯犯了现象训练loss下降正常但验证集mAP在0.35到0.45之间反复横跳进一步提升不下去。查看预测图发现把白炽灯下的暖光、黄昏太阳反光识别成火焰的概率极高把清晨河面水雾判定为烟雾的比例也不低。原因火焰和烟雾的视觉特征本身高度依赖背景——火焰是发光体加背景色温的叠加烟雾则近乎透明、靠遮挡关系才能被人类辨别。这份数据集在标注阶段必然存在标注员主观判断不一致的问题同一张暖光图在不同批次里可能分别标了fire和nothing模型学到的是噪声而不是特征。解决我的常规做法是挑出验证集里误检概率最高的200张图逐张看原图并重新标注把硬负样本单独放在一个目录用--augment开启马赛克和HSV扰动继续训练。同时把分类阈值从默认0.25调到0.4误检率通常能用阈值压掉一半代价是召回略降。工业部署场景下安全和误报是跷跷板先压误报再补召回是更稳妥的顺序。5.2 空标签TXT文件是静默杀手现象数据集里有个别TXT文件内容为空YOLOv8训练不报错、日志正常、mAP曲线也正常直到你部署后发现某些场景完全检测不出来回查训练数据才发现那部分图片缺少标签、被框架当作纯背景处理了。原因标注工具在导出时偶尔会漏掉最后的保存动作或标注员看到“画面里没目标”就跳过保存。这类文件直接参与训练让模型在对应场景学会预测“无目标”特别是视频帧连续出现时模型会把“无目标”模式关联到整个场景。解决在数据切分代码里加一步空文件检测if txt_path.stat().st_size 0就把对应图片和TXT同时移出数据集。这不是这个数据集独有的问题任何人工标注数据都需要跑这步体检。5.3 XML与TXT的坐标差了半个框到底信谁现象同一张图TXT转成可视框完全正常但XML画出来整体偏向右下角约20像素火焰框每次都把火苗起点框丢了。原因转换XML的脚本读取size节点时写死了某个默认分辨率而这份数据集的原始图片在采集后被批量压缩过XML里存的是压缩前尺寸。TXT侧因为是标注工具直接生成的归一化坐标不受分辨率变化影响所以看着正常。解决以TXT为准。日常训练、验证、导出都在YOLO体系内做只有当你要接MMDetection这类只认VOC/COCO的框架时才做转换而且转换时务必以PIL.Image.open实际读出的宽高作为归一化分母而不是XML里的size节点。5.4 训练集整体太“干净”背景多样性不足现象训练时验证mAP能到0.8以上部署到现场监控画面率直落到0.5尤其夜间和逆光场景漏检严重。原因数据集虽然是18800张但大量图片可能来自同一组监控场景背景纹理、光照条件高度相似。YOLO模型对场景的记忆能力很强它会学到“凡是这片空地背景上的亮斑就是火焰”真正的火焰特征权重被背景特征抢走了。解决用MixUp增强配合随机HSV变化强行打散背景关联更彻底的办法是拉入公开的红外或可见光火焰数据集做跨域合并。还有一个实践技巧把训练级输入分辨率从640提到768让模型看到更细的纹理反而会降低它对背景的整体依赖这在火焰检测上我复现过多次效果显著。5.5 验证集拆分不干净mAP虚高0.1的真相现象训练结束输出验证mAP高达0.92同事拿同一批图片推理发现中段错漏不少。看起来前后矛盾恰恰说明验证集和训练集重叠度太高。原因原始数据按文件夹存放时同一段视频的连续帧被均匀洒在了训练和验证两个目录里。模型在训练时实际上“见过”了验证帧的前后帧背景在推理时天然占据优势。解决拆数据前按文件名前缀或视频ID分组用group_id做分层抽样保证同一视频的所有帧只进一侧。前面3.1节的随机切分脚本我已经写了思路实际落地时给每张图片加一个场景编号字段按编号聚合后再切分。验证集重新切分后mAP掉到0.7附近这才是真实水平。6. 部署前的最后一道工序把验证指标换成你能信的实景测试训练结束后马上要看本地验证集的预测图这个习惯帮我排掉过至少三轮无效调参。做法很简单从val目录挑出有代表的场景夜间、逆光、远距离小目标、强反光各来十几张跑一次yolo predict把所有图上的预测框画出来排到一张拼接大图里。肉眼看一遍比看任何PR曲线都直观你立刻就知道模型的失败模式集中在哪里——是漏检远距离的烟雾还是把路灯当火焰一目了然。看完了再调两个东西。一是类别置信度阈值YOLOv8的默认conf0.25火焰烟雾场景通常要上调到0.35或0.4因为误报成本高二是IOU的NMS阈值默认iou0.7如果你发现同一团火焰上叠了三四个框降到0.5就能压掉冗余。这两个参数在predict命令里用--conf和--iou控制。如果漏检集中在远距离小目标上我的一般做法不是急着换大模型而是先做一次裁剪推理验证原理把输入图按2×2切块每块独立预测后再合并结果。这种方式对小目标召回提升明显代价是推理次数翻四倍确认有效后再决定是上YOLOv8m还是用原生支持的--tiled模式做工程化。另一个常用的技巧是把训练阶段的imgsz设成和部署端输入分辨率一致差别太大的话训练时的感受野和部署时的感受野会错位这是很隐蔽的精度损耗来源经常被人忽略。最后提一句训练时保留checkpoint的习惯。runs/train/flame_smoke_s/weights/里会有best.pt和last.pt我一般保留三个节点第80个epoch的mid权重、best和last。万一新增数据重新训练后效果没提升还能用mid权重回到中间状态重调lr这就是我常说的训练后悔药。多存几个权重文件不占多少空间但卡在调参泥潭里时它是唯一的退路。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →