尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

YOLO烟雾检测数据集实战:21578张VOC标注转YOLO格式与训练避坑指南

发布时间:2026/9/28 17:24:01

资讯中心
01
ARTICLE

YOLO烟雾检测数据集实战:21578张VOC标注转YOLO格式与训练避坑指南

YOLO烟雾检测数据集实战:21578张VOC标注转YOLO格式与训练避坑指南
简介这份资源是面向目标检测学习者的YOLO烟雾数据集适合从事火灾预警、工业安全监控、环境烟雾识别等方向的算法工程师与高校学生使用可解决烟雾目标检测训练样本不足、标注质量参差的问题。压缩包共收录2000个文件以xml格式的标注文件为主对应21578张烟雾图像整体约268.9MB标注文件可直接用于YOLO系列模型的训练与验证流程。目前已有482人学习下载说明该数据集在烟雾检测任务中具备一定参考价值。数据集聚焦烟smoke100类别标注信息完整读者可据此快速搭建烟雾检测实验环境用于模型训练、精度对比与消融实验也可作为课程设计或毕业项目的训练数据来源省去自行采集与标注图像的时间成本。1. 烟雾检测数据集落地21578 张带标签图像到底能跑出什么效果消防通道的摄像头拍到一缕白烟传统阈值报警器要等烟雾浓度累积到触发线才响中间可能已经烧了三四分钟。换成 YOLO 做视觉烟雾检测理论上能在烟雾刚出现的头几帧就框出来——但前提是你手里有一份标注质量过关、场景覆盖够广的数据集。这次拆的yolo算法-烟雾数据集-21578张图像带标签-烟smoke100-uwe4t.zip就是干这个的21578 张图像全部带 XML 标签单类别 smoke标注格式是 Pascal VOC 那套。它解决的不是模型怎么搭的问题而是拿什么数据训的问题。适合两类人一是想快速验证烟雾检测可行性、不想从零标数据的算法工程师二是做安防、森林防火、工业巡检方向需要一份能直接转 YOLO 格式的现成标注集。下面按数据长什么样 → 怎么转格式 → 怎么训 → 坑在哪的顺序拆开讲。2. 先看清数据底子VOC XML 标注与 21578 张图的组织方式2.1 解压后目录结构与文件命名规律拿到压缩包第一件事不是急着训是先摸清目录。这类数据集常见组织方式是JPEGImages/放图、Annotations/放 XML文件名一一对应。从项目正文给的样本看XML 命名是img_0485_10246.xml这种「前缀_批次_序号」结构说明图像大概率也是img_0485_10246.jpg同名。先跑一遍统计确认图和标签是否配对齐全这一步能提前发现丢标签、丢图的问题。# 解压后进入根目录先看结构 unzip yolo算法-烟雾数据集-21578张图像带标签-烟smoke100-uwe4t.zip -d smoke_dataset cd smoke_dataset # 统计图像和标注数量 find . -name *.jpg | wc -l find . -name *.xml | wc -l # 找出有图无标签、有标签无图的文件 comm -3 (find . -name *.jpg -printf %f\n | sed s/.jpg// | sort) \ (find . -name *.xml -printf %f\n | sed s/.xml// | sort)comm -3那行是关键它把图像名和标签名各自排序后做差集输出任何一边多出来的文件。正常情况应该没有任何输出如果刷出一堆名字说明数据集本身有缺失训练前必须处理掉否则 YOLO 加载时会直接报找不到标签。-printf %f\n只取文件名不带路径避免路径差异干扰比对。2.2 单类别 smoke 的标注内容与坐标格式VOC 格式的 XML 核心就几块size记录图像宽高object里name是类别名bndbox是xmin/ymin/xmax/ymax四个像素坐标。这份数据集是单类别name应该统一是smoke。先抽几个 XML 看看确认类别名没有拼写不一致比如smoke和Smoke混用这是血泪经验里最常见的翻车点YOLO 会当成两个类。import glob, xml.etree.ElementTree as ET from collections import Counter names Counter() sizes [] for f in glob.glob(Annotations/*.xml)[:500]: # 抽样500个够看分布 root ET.parse(f).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) sizes.append((w, h)) for obj in root.findall(object): names[obj.find(name).text] 1 print(类别分布:, names) # 期望只有 smoke 一个键 print(尺寸样本:, Counter(sizes).most_common(5))这段代码做两件事统计类别名是否唯一统计图像分辨率分布。如果names里冒出第二个键说明标注不纯得先统一。分辨率分布决定你训练时的imgsz怎么设——如果绝大多数是 640×640 附近直接imgsz640如果普遍偏小比如 320×240硬拉到 640 只会插值糊掉反而掉点。Counter(sizes).most_common(5)能一眼看出主流尺寸。提示抽样 500 个足够判断分布全量遍历 2 万多个 XML 会慢正式训练前再全量校验一次即可。3. VOC 转 YOLO 格式转换脚本与归一化坐标的四个边界坑3.1 为什么必须转YOLO 要的是归一化中心点格式YOLO 训练不认 VOC 的绝对像素坐标它要的是每行class_id cx cy w h且全部归一化到 0~1。cx cy是框中心点w h是框宽高都除以图像对应边长。转换本身不难难在边界处理框超出图像、宽高为 0、坐标是浮点、类别名映射错位这四个坑任何一个都能让训练 loss 直接 NaN 或者 mAP 归零。下面这份脚本把校验和转换揉在一起转完直接能用。import os, glob, xml.etree.ElementTree as ET CLASS_MAP {smoke: 0} # 单类别映射到 0 IMG_DIR, XML_DIR, OUT_DIR JPEGImages, Annotations, labels os.makedirs(OUT_DIR, exist_okTrue) def convert(xml_path): root ET.parse(xml_path).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.findall(object): cls obj.find(name).text.strip() if cls not in CLASS_MAP: continue b obj.find(bndbox) xmin float(b.find(xmin).text); ymin float(b.find(ymin).text) xmax float(b.find(xmax).text); ymax float(b.find(ymax).text) # 边界裁剪防止框超出图像导致归一化后越界 xmin, xmax max(0, xmin), min(w, xmax) ymin, ymax max(0, ymin), min(h, ymax) bw, bh xmax - xmin, ymax - ymin if bw 1 or bh 1: # 过滤无效框 continue cx (xmin xmax) / 2 / w cy (ymin ymax) / 2 / h lines.append(f{CLASS_MAP[cls]} {cx:.6f} {cy:.6f} {bw/w:.6f} {bh/h:.6f}) return lines for xml in glob.glob(f{XML_DIR}/*.xml): stem os.path.splitext(os.path.basename(xml))[0] lines convert(xml) with open(f{OUT_DIR}/{stem}.txt, w) as f: f.write(\n.join(lines))逻辑说明max(0, xmin)和min(w, xmax)是边界裁剪VOC 标注里偶尔会有标注员手抖把框拖出图像不裁的话归一化后 cx 可能大于 1YOLO 直接判为非法标签。bw 1 or bh 1过滤掉宽高不足 1 像素的框这种框在缩放后基本消失留着只会污染 loss。:.6f保留六位小数YOLO 官方推荐精度太少会累积误差。3.2 生成 train/val 划分与 data.yaml转完标签还得划分训练验证集并写一份 YOLO 认的data.yaml。划分别用随机烟雾数据如果同一段视频抽帧随机划分会导致训练集和验证集高度相似验证 mAP 虚高。常见做法是按文件名前缀比如img_0485这种批次号分组划分保证同批次进同一侧。import glob, os, random random.seed(42) imgs sorted(glob.glob(JPEGImages/*.jpg)) # 按前缀分组避免同源帧泄漏 groups {} for p in imgs: key os.path.basename(p).rsplit(_, 1)[0] groups.setdefault(key, []).append(p) keys list(groups.keys()); random.shuffle(keys) split int(len(keys) * 0.9) train [p for k in keys[:split] for p in groups[k]] val [p for k in keys[split:] for p in groups[k]] for name, items in [(train, train), (val, val)]: with open(f{name}.txt, w) as f: f.write(\n.join(os.path.abspath(p) for p in items)) print(ftrain{len(train)} val{len(val)})data.yaml内容如下path写数据集根目录绝对路径nc是类别数单类别就是 1path: /abs/path/to/smoke_dataset train: train.txt val: val.txt nc: 1 names: [smoke]注意names的顺序必须和转换脚本里CLASS_MAP的 id 严格对应smoke是 0 就写在第 0 位写反了模型学出来的全是错的。4. 用 YOLOv8 跑通训练参数怎么设、显存怎么省4.1 环境与启动命令环境用 ultralytics 官方包最省事pip install ultralytics一把梭。训练入口就一行命令但参数得按数据规模调。21578 张图不算小单卡 8G 显存跑yolov8n或yolov8s比较稳yolov8m以上得看显存。yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ workers8 \ cacheTrue \ patience20 \ projectruns/smoke \ nameexp1参数逐个说modelyolov8s.pt是从预训练权重起步烟雾这种单类别任务预训练能省一半以上收敛时间别从yaml空权重训。batch16是 8G 显存下的保守值显存够可以往上加但 batch 太大对小目标烟雾反而不利。cacheTrue把图像缓存到内存2 万张图大概吃 6~8G 内存内存够就开能明显提速。patience20是早停20 轮验证指标不涨就停省时间。4.2 关键超参imgsz、学习率与数据增强imgsz640不是随便定的。烟雾在监控画面里往往只占一小块如果原始分辨率高缩到 640 后烟雾可能只剩十几个像素模型根本学不到。这种情况要么提高imgsz到 960/1280显存翻倍要么在数据增强里关掉大幅缩放。默认增强里mosaic和scale对烟雾检测是把双刃剑mosaic 拼图能增加小目标样本但烟雾本身纹理弱拼图后容易和背景混淆。# 如果发现小目标漏检严重调低缩放增强、提高输入尺寸 yolo detect train datadata.yaml modelyolov8s.pt epochs100 \ imgsz960 batch8 scale0.3 mosaic0.5 lr00.005scale0.3把随机缩放幅度压到 ±30%mosaic0.5表示一半概率用 mosaiclr00.005比默认 0.01 略低单类别任务不需要太激进的学习率太大容易在早期把预训练特征冲垮。这些值不是标准答案是给一个可调的起点具体看训练曲线里cls_loss和mAP50的走势再微调。4.3 训练过程该盯哪几个指标跑起来后别只等结果盯runs/smoke/exp1/results.csv。重点看三个train/box_loss是否稳定下降、metrics/mAP50是否在涨、val/box_loss有没有和训练 loss 背离。如果训练 loss 一直降但验证 mAP 卡住不动八成是过拟合或者验证集分布和训练集差太远。如果box_loss出现 NaN回去查第 3 章的转换脚本大概率是归一化坐标越界或空标签文件。提示空标签文件图像里没有 smoke在 YOLO 里是合法的代表纯背景负样本别删。但如果一个数据集里空标签占比超过 30%说明标注可能漏标得回头核对。5. 避坑与排查烟雾数据集训练最常见的五个翻车点5.1 现象训练启动就报 No labels found原因data.yaml里train/val指向的 txt 文件路径不对或者 txt 里写的是相对路径但训练时工作目录变了。YOLO 找标签的规则是把图像路径里的images替换成labels、后缀换成.txt。如果你的目录不叫images它就找不到。解决要么把目录规范成images/和labels/结构要么在 txt 里写绝对路径。最稳的是用绝对路径第 3.2 节脚本里os.path.abspath就是干这个的。5.2 现象mAP 一直是 0 或者极低原因类别 id 对不上。转换时smoke映射成 0但data.yaml里names写成了别的顺序或者 XML 里类别名有大小写混用导致部分框被continue跳过。解决跑一遍全量类别统计确认 XML 里只有一种写法再检查data.yaml的names和转换脚本的CLASS_MAP是否一致。这两个地方对齐mAP 不可能为 0。5.3 现象显存爆了报 CUDA out of memory原因batch或imgsz设太大或者cacheTrue时内存不够被系统杀进程。解决先把batch降到 8 或 4再考虑降imgsz。如果开了cacheTrue还爆关掉它用磁盘 IO 换内存。另外workers别设太高设成 CPU 核数就行设太高反而抢内存。5.4 现象验证集 mAP 虚高实际部署漏检严重原因训练验证划分时同一段视频的帧被分到了两边验证集和训练集高度相似模型等于在背答案。解决按文件名前缀或时间戳分组划分保证同源帧进同一侧。第 3.2 节的rsplit(_, 1)[0]就是按前缀分组如果你的命名规律不同改成对应的时间戳或批次号。5.5 现象烟雾框位置偏移框偏大或偏小原因XML 里size记录的宽高和实际图像尺寸不一致。有些数据集在裁剪或缩放图像后忘了同步更新 XML 里的size导致归一化基准错了。解决转换前用 PIL 读一遍实际图像尺寸和 XML 里的size比对不一致的以实际图像为准重新计算。这一步多花几分钟能省掉后面几小时的排查。6. 进阶用混淆矩阵和 PR 曲线判断这份数据到底够不够用训完一轮别急着收工runs/smoke/exp1/下会自动生成confusion_matrix.png和PR_curve.png这两张图比 mAP 一个数字信息量大得多。混淆矩阵看的是分类对不对单类别任务里矩阵是 2×2smoke 和 background对角线是分对的非对角线是漏检和误检。如果 background 被大量误判成 smoke假阳性高说明模型把云、雾、蒸汽这类纹理当成了烟得往训练集里补负样本。如果 smoke 大量被判成 background漏检高说明烟雾特征学得不够要么加数据要么提高输入分辨率。PR 曲线看的是不同置信度阈值下的查准查全权衡。曲线下的面积就是 mAP但更实用的是看曲线拐点拐点靠右上说明模型在较高召回下还能保持查准适合安防场景宁可误报不可漏报拐点靠左下说明模型保守适合需要低误报的场景。根据你的业务选阈值别直接用默认 0.25。from ultralytics import YOLO model YOLO(runs/smoke/exp1/weights/best.pt) metrics model.val(datadata.yaml, conf0.4, iou0.5) print(mAP50:, metrics.box.map50) print(每类AP:, metrics.box.ap50) # 单类别就一个值conf0.4是验证时用的置信度阈值比默认高能看出模型在高置信下的真实水平。iou0.5是判定预测框和真值框匹配的 IoU 门槛标准做法。跑完对比不同conf下的map50选一个业务上可接受的平衡点。我自己的习惯是每次拿到新数据集先跑一遍基线把混淆矩阵和 PR 曲线存下来当参照后面任何改动都跟这个基线比。有一次偷懒没存基线改完增强策略发现 mAP 涨了 2 个点结果回头一查是验证集划分变了白高兴一场。从那以后我每次动数据或改划分都强制先固定一份 val 集不动只调训练侧。希望这份拆解能帮你少走点弯路数据集拿到手先校验再转格式转完先跑小样本过拟合测试确认流程通了再上全量。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。