☰
烟雾检测数据集实战:VOC/COCO/YOLO三格式解析与YOLO训练避坑指南
2026/10/1 23:29:15 网站建设 项目流程

简介:本资源为面向目标检测学习者的YOLO烟雾检测数据集,适用于安防监控、工业巡检、森林防火等场景下的烟雾识别模型训练,兼顾入门与进阶需求。压缩包共2000个文件,约86.54MB,包含1000张真实场景高质量图片,以及voc(xml)、coco(json)和yolo(txt)三种格式标签,分别存放于不同文件夹,可直接用于YOLO系列模型训练;另附yaml配置文件、数据集划分脚本和训练教程网页,方便按需划分训练集、验证集与测试集。目前已有362人学习下载。资源同时提供Windows与Linux环境搭建、训练案例教程及划分脚本,读者可据此快速完成环境配置、数据划分与模型训练,并参考案例修改训练自己的数据集,省去从零整理标注与调试流程的时间。

1. 烟雾检测数据集拿到手之后:1000 张图、三种标签格式到底怎么用

工业现场做烟雾检测,最卡脖子的往往不是模型结构,而是数据。你手上如果有一个「1000 张图片 + VOC + COCO + YOLO 三种格式标签 + 划分脚本 + 训练教程」的压缩包,这其实已经覆盖了从数据准备到模型跑通的大半流程。烟雾目标检测和常规的 COCO 物体检测不太一样:烟雾边界模糊、半透明、形态随气流变化,标注一致性差,同一张图两个人标出来的框可能差出一大截。所以拿到数据集的第一件事不是急着train.py,而是搞清楚三种标签格式各自适合什么环节、划分脚本怎么用才不出错、1000 张图的量级能训出什么水平。

这篇文章面向的是想快速把烟雾检测跑起来的工程师和同学:你可能已经配好了 YOLO 环境,手里有这个压缩包,但不确定 VOC 和 COCO 标签什么时候用、划分脚本会不会把同一场景的图分到训练和验证两边导致指标虚高、1000 张图要不要做数据增强。下面按「先理解格式 → 再动手转换和划分 → 然后训练 → 最后避坑」的顺序讲清楚,每一步都给可复现的命令和参数。

2. VOC、COCO、YOLO 三种标签格式:各自解决什么问题

2.1 三种格式的字段差异与适用场景

VOC 格式的核心是每张图对应一个 XML 文件,里面用<object>节点记录类别名和xmin/ymin/xmax/ymax绝对像素坐标。它的好处是可读性强、工具链老牌,LabelImg 默认就导出这个。缺点是文件多、解析慢,类别名是字符串,训练前必须做一次类别到 id 的映射。

COCO 格式是单个 JSON 文件管全部,images、annotations、categories三个数组通过 id 关联,框用[x, y, width, height]绝对坐标表示。它适合做多任务(检测+分割+关键点)和需要统一管理的场景,但 JSON 文件大、手改容易出错。

YOLO 格式最轻,每张图一个.txt,每行class_id cx cy w h,全部是归一化到 0~1 的相对坐标。YOLO 系列训练直接吃这个格式,解析快、无冗余。代价是可读性差,脱离图片看不出框在哪。

格式存储方式坐标类型类别表示典型用途
VOC每图一个 XML绝对像素字符串名标注、转换中间态
COCO单个 JSON绝对像素数字 id多任务、评测
YOLO每图一个 txt归一化相对值数字 idYOLO 训练

提示:三种格式之间转换时,最容易丢信息的是类别名到 id 的映射关系。转换前先把类别列表固定下来,写进一个classes.txt,后续所有脚本都读它,避免不同脚本映射不一致。

2.2 为什么数据集要同时给三种格式

很多人觉得给一种就够了,但实际项目里三种格式各有不可替代的位置。标注阶段用 VOC 或 COCO,因为标注工具和审核流程围绕它们建;训练阶段用 YOLO,因为省解析开销;做 benchmark 对比或接入 mmdetection 这类框架时又要 COCO。数据集同时提供三种,本质是让你不用自己写转换脚本、不用担心中间转换引入坐标误差。

这里有个容易忽略的点:三种格式如果是从同一份标注导出的,框应该完全一致;但如果是分别标注或转换脚本有 bug,可能出现同一张图在 VOC 里框对、在 YOLO 里偏移几个像素。拿到数据集后建议抽 5~10 张图做交叉验证,把三种格式的框画到同一张图上比对。

2.3 用脚本做格式互转与一致性校验

假设压缩包解压后目录是smoke_dataset/,里面有VOC/、COCO/、YOLO/三个子目录。下面这段脚本把 VOC 转成 YOLO,同时校验已有 YOLO 标签是否一致:

import os import xml.etree.ElementTree as ET # 类别列表,顺序决定 class_id,必须和训练时一致 CLASSES = ["smoke"] CLASS_TO_ID = {c: i for i, c in enumerate(CLASSES)} def voc_to_yolo(xml_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in CLASS_TO_ID: continue # 跳过未定义类别,避免 id 错位 bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 转成中心点+宽高的归一化值 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 裁剪到 [0,1],防止标注越界导致训练报错 cx, cy = min(max(cx, 0), 1), min(max(cy, 0), 1) w, h = min(max(w, 0), 1), min(max(h, 0), 1) lines.append(f"{CLASS_TO_ID[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") return lines

逻辑说明:CLASS_TO_ID决定类别顺序,一旦训练用了这个顺序就不能改,否则推理时类别全乱。坐标归一化用图片实际宽高,所以调用时要先读图拿到img_w/img_h。裁剪到[0,1]是血泪经验——标注时框超出图片边界很常见,不裁剪的话 YOLO 训练会直接报错或产生异常梯度。

参数说明:cx cy w h保留 6 位小数足够,YOLO 内部按 float 处理;如果数据集类别不止 smoke,把CLASSES补全,顺序和 COCO 的categories对齐。

3. 划分脚本怎么用:别让同一场景的图同时进训练和验证

3.1 随机划分的陷阱与分层划分思路

1000 张图如果直接random.shuffle按 8:2 切,很可能同一段视频抽出来的连续帧被分到训练和验证两边。烟雾检测数据集很多是从视频抽帧来的,相邻帧几乎一样,这样划分会让验证集指标虚高——模型在验证集上「见过」几乎相同的图。正确做法是按场景或按视频源分组,保证同一组的图只出现在一边。

如果数据集没给分组信息,退一步用图片文件名前缀或时间戳聚类,把相近的归为一组再划分。划分脚本通常提供--val_ratio和--seed两个参数,seed 固定才能复现。

3.2 划分脚本的命令与输出结构

常见划分脚本用法如下:

python split_dataset.py \ --data_root ./smoke_dataset/YOLO \ --out_root ./smoke_split \ --val_ratio 0.2 \ --seed 42 \ --group_by prefix

执行后输出结构:

smoke_split/ images/ train/ val/ labels/ train/ val/ train.txt val.txt

逻辑说明:--group_by prefix表示按文件名前缀分组,同前缀的图不会被拆到两边;--seed 42固定随机种子,保证每次划分结果一致,方便复现实验。train.txt和val.txt里是图片绝对路径,YOLO 训练时通过--data指向的 yaml 引用。

参数说明:--val_ratio 0.2对 1000 张图意味着约 200 张验证,如果类别极不均衡(比如烟雾图远多于无烟图),建议改成按类别分层抽样,否则验证集可能一张正样本都没有。

3.3 划分后必须做的两项检查

第一项,统计训练集和验证集的框数量、类别分布,两边差异超过 10% 就说明划分有问题。第二项,随机抽几张验证集图片,确认它们和训练集没有视觉上的高度重复。这两项检查用十几行脚本就能做,但能省掉后面「指标很好但上线就废」的后悔药。

import os def count_boxes(label_dir): total, files = 0, 0 for f in os.listdir(label_dir): if f.endswith(".txt"): files += 1 with open(os.path.join(label_dir, f)) as fp: total += len([l for l in fp if l.strip()]) return files, total for split in ["train", "val"]: d = f"./smoke_split/labels/{split}" n_files, n_boxes = count_boxes(d) print(f"{split}: {n_files} images, {n_boxes} boxes, avg {n_boxes/max(n_files,1):.2f}")

逻辑说明:分别统计两个 split 的图片数和框数,平均每图框数差异大就说明分布不一致。参数说明:如果avg在 train 和 val 之间差 0.5 以上,回去检查划分脚本的分组逻辑。

4. 用这 1000 张图训练 YOLO:配置、参数与收敛判断

4.1 数据 yaml 与预训练权重选择

YOLO 训练需要一个 data yaml,指定训练/验证路径、类别数和类别名:

path: ./smoke_split train: images/train val: images/val nc: 1 names: ["smoke"]

nc必须和CLASSES长度一致,names顺序和训练时 class_id 对应。预训练权重建议用 YOLO 官方在 COCO 上训好的模型做迁移,烟雾属于通用纹理特征,迁移能明显加快收敛。1000 张图从零训基本不够,迁移学习是标配。

4.2 关键训练参数怎么设

yolo detect train \ data=smoke.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ augment=True

逻辑说明:model=yolov8n.pt用 nano 版先跑通,1000 张图 nano 足够,显存占用低。epochs=100配合patience=20,20 轮验证指标不升就早停,避免过拟合。imgsz=640是 YOLO 默认,烟雾目标如果偏小可以提到 960,但显存和速度要权衡。augment=True开启内置增强,对 1000 张的小数据集很关键。

参数说明:batch=16在 8G 显存上跑 640 分辨率基本安全,爆显存就降到 8。lr0=0.01是迁移学习的常用初始学习率,如果 loss 一开始就震荡,降到 0.001。patience设太小会早停太早,设太大浪费算力,20 是经验值。

4.3 看什么指标判断训练是否正常

训练日志里重点看三个:box_loss应该稳步下降,如果震荡不降检查学习率和标注质量;mAP50在烟雾检测上能到 0.6~0.8 算正常,低于 0.4 说明标注或划分有问题;cls_loss单类别任务应该很快接近 0。混淆矩阵如果出现大量背景被误判为烟雾,说明负样本(无烟图)不够,需要补。

注意:YOLO 的混淆矩阵在单类别时可能显示不直观,别被「总合不唯一」之类的现象迷惑,直接看 precision 和 recall 曲线更实在。

5. 避坑与排查:烟雾数据集训练最容易翻车的 5 个点

5.1 现象:训练 loss 正常但 mAP 一直上不去

原因:验证集和训练集图片高度重复,模型在验证集上「背答案」,但换一批真实图就废。或者标注框普遍偏大,把背景也框进去,模型学到的是背景特征。

解决:用分组划分重切数据集,确保验证集场景独立;抽查 20 张图的标注框,框应该紧贴烟雾区域,不要为了「保险」把框画大。

5.2 现象:训练中途 BN 层崩溃,loss 变 NaN

原因:batch size 太小(比如 2 或 4)导致 batch 统计量不稳定,或者学习率过高。烟雾数据集如果图片尺寸差异大,归一化没做好也会触发。

解决:batch 至少 8,显存不够就降 imgsz;学习率从 0.01 降到 0.001 重跑;检查数据增强里有没有产生极端值(比如裁剪后框面积接近 0)。

5.3 现象:推理时框位置整体偏移

原因:VOC 转 YOLO 时图片宽高读错,或者训练用的图片和标注不是同一批(比如图片被 resize 过但标注没跟着改)。

解决:转换脚本里强制用cv2.imread读实际图片尺寸,不要从 XML 里的size字段读(有些标注工具的 size 字段是错的);训练前用脚本把每张图和标签画出来目检一遍。

5.4 现象:模型把灯光、云、蒸汽误判为烟雾

原因:负样本不足,或者负样本里缺少这些易混场景。1000 张图如果全是烟雾正样本,模型没见过「不是烟雾」的样子。

解决:补 100~200 张含灯光、云、蒸汽但无烟雾的负样本图,标签为空 txt;训练时这些图参与,模型才能学到边界。

5.5 现象:换一台机器训练结果复现不了

原因:随机种子没固定、数据加载顺序受文件系统影响、CUDA 版本差异导致浮点运算结果不同。

解决:划分脚本和训练脚本都固定 seed;用deterministic=True开启确定性训练(会略慢);记录 CUDA 和框架版本,跨机器复现时对齐环境。

6. 1000 张图之后:把烟雾检测做到能用的两个进阶技巧

第一个技巧是难例挖掘。第一轮训练完,用模型在验证集和一批未标注图上推理,把置信度在 0.3~0.6 之间的框挑出来人工复核,确认是漏检还是误检,把漏检的补进训练集。这个循环做两三轮,mAP 通常能再涨 5~10 个点。1000 张图起步,经过难例挖掘扩到 1500~2000 张,效果比单纯调模型结构明显。

第二个技巧是切片推理(SAHI 思路)。烟雾在监控画面里往往只占很小区域,整图 resize 到 640 后烟雾可能只剩十几个像素。把大图切成带重叠的小块分别推理再合并,小目标召回率提升明显。代价是推理变慢,适合对召回要求高的场景。

# 切片推理的简化逻辑 def slice_infer(img, model, slice_size=640, overlap=0.2): h, w = img.shape[:2] step = int(slice_size * (1 - overlap)) boxes = [] for y in range(0, h, step): for x in range(0, w, step): patch = img[y:y+slice_size, x:x+slice_size] if patch.shape[0] < 32 or patch.shape[1] < 32: continue # 太小的边缘块跳过 res = model(patch) # 把 patch 坐标映射回原图 for b in res.boxes: b.xyxy += [x, y, x, y] boxes.append(b) return boxes

逻辑说明:按step滑动切块,每块单独推理,坐标加回偏移量。overlap=0.2保证切边界的目标至少完整出现在一块里。参数说明:slice_size和训练时imgsz一致效果最好;overlap太小会漏切边界目标,太大推理次数翻倍,0.2~0.3 是平衡点。

我自己做烟雾检测项目时,最大的教训是前期太信验证集指标,没做分组划分,结果上线后误报一堆。后来养成习惯:数据集拿到手先画框目检、划分后先统计分布、训练完先拿真实场景图跑一遍再谈指标。这套流程比调任何超参都管用。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询