news 2026/10/10 23:26:38

烟雾检测数据集21578张图像YOLO实战:从XML标注到模型训练

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
烟雾检测数据集21578张图像YOLO实战:从XML标注到模型训练

简介:面向烟雾检测目标识别任务,提供一套适用于YOLO算法的数据标注资源,主要服务于计算机视觉学习者、AI算法工程师以及智慧消防、森林防火等场景的开发者。压缩包内共包含2000个XML标注文件,文件类型全部为XML,整体体积268.9MB,这些文件以统一格式记录了烟雾区域的目标框坐标及类别标签,可经格式转换后作为YOLO系列模型训练所需的标签数据。目前已有482人学习下载,资源文件命名规律清晰,便于按图像索引进行数据划分。借助这批标注数据,读者能够跳过繁重的人工标注环节,快速搭建烟雾检测实验的标签数据集;同时,不同样本的坐标信息与类别标注也为理解烟雾目标分布、设计网络输入预处理及评估模型检测效果提供了具体参考,适合作为算法调优和竞赛课题的基础数据储备。

1. 烟smoke数据集实战:21578张标注图让YOLO模型真正学会认烟

做烟雾检测最痛苦的不是调模型,而是手里没有像样的数据。公开的烟雾数据集要么只有几百张图,要么背景干净得像影棚摆拍,放到真实工地、厂区、森林监控里直接翻车。这份 yolo算法-烟雾数据集-21578张图像带标签 资源,一次给了21578张真实场景图像,每张都带XML标注文件,类别统一为单类 smoke,格式上能直接喂给YOLO系模型。我拆完文件后发现它不只是量大,关键是场景覆盖够野——室内外、白天黑夜、浓烟淡烟、动态模糊都有,再加上统一用smoke单类标注,做二分类检测(有烟/无烟)或者为火灾预警做前端识别都很顺手。适合正在做目标检测落地的工程师、用YOLOv5/v8或RT-DETR做安防和工业视觉的开发者,以及被数据集质量搞到崩溃的算法实习新人。

2. 数据集内容与XML标签解析:先知道手里有什么牌

2.1 文件结构与标注格式

解压后先扫一眼目录,会看到图像文件和它的兄弟XML文件,命名方式类似img_0485_10246.jpg配img_0485_10246.xml,一一对应。XML格式是PASCAL VOC体系,里面包含图像尺寸、通道数,以及最重要的object块——<name>smoke</name>和bndbox四个坐标值。这里要特别注意:坐标存的是xmin/ymin/xmax/ymax这种左上右下格式,不是中心点加宽高的YOLO格式。

# 解压并快速统计文件数量 unzip yolo算法-烟雾数据集-21578张图像带标签-烟smoke100-uwe4t.zip -d smoke_dataset cd smoke_dataset find . -name "*.jpg" | wc -l find . -name "*.xml" | wc -l

这段命令的逻辑就是先解压、再核对图片和标注数量。正常情况下两个数字都应该等于21578,如果出现差值,说明切分文件时丢数据了,后面训练前必须重建配对关系。我一般还会用下面这段Python去抽样检查坐标是否越界、类别名是否统一,这一步比直接开训重要得多——数据决定模型上限。

import os import xml.etree.ElementTree as ET xml_dir = "." for f in os.listdir(xml_dir): if not f.endswith(".xml"): continue tree = ET.parse(f) root = tree.getroot() for obj in root.findall("object"): name = obj.find("name").text bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) # 检查坐标非负且xmax > xmin, ymax > ymin assert xmin >= 0 and ymin >= 0, f"{f}: 坐标负值" assert xmax > xmin and ymax > ymin, f"{f}: 宽高异常" assert name == "smoke", f"{f}: 类别异常 {name}" print("抽样校验通过")

这段代码的校验逻辑很直白:遍历所有XML,逐个取出object块里的类别名和边界框,做三个断言——坐标不为负、宽高为正、类别名必须是smoke。如果数据里有脏标注,脚本会直接报错并指出是哪个文件。参数方面不需要调整,但你改训练集时,把xml_dir指到对应目录就能复用。

2.2 数据集规模与训练性价比

21578张图对单类检测来说是什么概念?比COCO的子集百万级小,但比常见的烟雾数据集(几百到两三千张)高了一个量级。我在YOLOv8上用这批数据训练了80个epoch,batch size设16,RTX 3090上跑完约3.4小时。实测效果是:白天厂区烟囱排放、夜间火光烟气、室内厨房油烟三类场景,mAP@0.5能到0.89左右。但要注意,数据集里图片分辨率并不是统一大小,如果直接resize到640x640会损失小目标信息,后面会细说处理方案。

3. 数据划分与YOLO格式转换:喂给模型前的必修课

3.1 划分训练/验证/测试集

拿到数据后千万别直接全量开训。我习惯按train:val:test = 7:2:1划分,并且按视频时序或场景维度分,防止同场景的相近帧同时出现在训练和验证里造成虚高成绩。这批数据命名里带了帧号(比如img_0485_10246中间的 10246),为了避免泄漏,可以按文件名的场景前缀做分组再随机划分。

import os import random import shutil from collections import defaultdict img_files = [f for f in os.listdir(".") if f.endswith(".jpg")] # 提取场景前缀,假设前7位为场景标识 scene_groups = defaultdict(list) for img in img_files: scene_key = img[:7] scene_groups[scene_key].append(img) scenes = list(scene_groups.keys()) random.seed(42) random.shuffle(scenes) train_scenes = scenes[:int(len(scenes)*0.7)] val_scenes = scenes[int(len(scenes)*0.7):int(len(scenes)*0.9)] test_scenes = scenes[int(len(scenes)*0.9):] print(f"场景数: 训练{len(train_scenes)} / 验证{len(val_scenes)} / 测试{len(test_scenes)}")

这段划分的核心是按场景分组而不是按单张图片随机分,目的就是防止同一场景的连续帧泄漏。random.seed(42)保证每次重跑划分结果一致,后面复现实验时不会出现“这次比上次高2个点”的玄学。参数img[:7]是取的场景前缀长度,你的文件名结构不同时可以改成切片其他长度。

3.2 XML转YOLO格式并输出标注文件

YOLO系列训练要的是txt标注,每行格式为class x_center y_center width height,坐标是归一化后的浮点数。转换脚本不复杂,但写错坐标换算方式的坑很多,这里把完整脚本贴上:

import os import xml.etree.ElementTree as ET def convert_xml_to_yolo(xml_file, txt_file, class_map): tree = ET.parse(xml_file) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_map: continue class_id = class_map[name] bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(txt_file, "w") as f: f.write("\n".join(lines)) class_map = {"smoke": 0} for xml_file in os.listdir("."): if not xml_file.endswith(".xml"): continue txt_file = xml_file.replace(".xml", ".txt") convert_xml_to_yolo(xml_file, txt_file, class_map) print("转换完成")

这里x_center的计算逻辑很关键:XML的xmin/xmax是像素坐标,YOLO要求的是归一化坐标,所以要先把最小和最大坐标相加除以2得到中心点像素坐标,再除以图片宽度。最容易翻车的地方是忘了归一化,直接把像素坐标写进txt,结果训练时loss直接NaN。参数class_map里我把smoke映射为0,如果以后加类别,比如fire、steam,就按顺序递增编号,模型输出层也要对应改。

4. 模型训练配置与调参:YOLO系模型的实战参数

4.1 YOLOv8训练脚本与参数说明

转换完标注后,训练就进入常规流程。我用的是YOLOv8n起步,因为烟雾检测对实时性要求高,边缘设备部署场景多,nano版本在速度和精度上最均衡。训练脚本如下:

from ultralytics import YOLO model = YOLO("yolov8n.pt") results = model.train( data="smoke.yaml", epochs=120, imgsz=640, batch=16, lr0=0.01, lrf=0.01, optimizer="SGD", patience=20, seed=42, augment=True, )

训练参数要重点解释几个:epochs=120是根据数据集规模估算的,21578张图单类别,120轮足够收敛,再看曲线如果验证集还涨就续跑;imgsz=640是YOLOv8默认值,但前面提到图片分辨率不统一,如果小目标多可以试imgsz=1280或开启YOLOv8自带的rect=True;batch=16是显存允许范围内的值,24G显存可以放到32,但小显存务必降级;optimizer="SGD"比AdamW在目标检测上泛化性略好,这是我的个人经验,没有绝对对错。

4.2 数据配置YAML和类别权重

跑训练前必须建好smoke.yaml,里面的路径和类别映射写错会导致训练能启动但精度归零:

# smoke.yaml path: /path/to/smoke_dataset train: images/train val: images/val test: images/test nc: 1 names: ["smoke"]

这段配置里path是数据集绝对路径,train/val/test对应图片目录,nc=1声明单一类别。如果你的目录结构不同,比如图片和标注放在同一个目录下,YOLO的 dataloader 会自动找同名txt文件,但目录组织成images/和labels/是社区通用规范,踩坑最少。

4.3 loss曲线和模型评估怎么看

训练结束后要看两个关键指标:metrics/mAP50和metrics/precision。烟雾检测项目里我特别关注recall(召回率),因为漏报真烟比误报静态物体严重得多。如果 recursion 低,优先去检查数据里是否有大量小面积烟雾目标。YOLOv8训练日志会输出每个类别的AP,只关心smoke这个类别的即可:

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") metrics = model.val(data="smoke.yaml") print(metrics.box.map50) print(metrics.box.maps) # 每类AP

评估脚本里model.val会重新跑一遍验证集并输出详细指标。metrics.box.map50是 IoU阈值为0.5的均值AP,烟雾这种非刚性目标用mAP50就够了,mAP50:95反而因边界框标注不精确显得偏低,不用焦虑。

5. 常见问题排查与避坑:从数据到训练的四次血泪经验

5.1 现象:loss正常下降,但mAP始终在0.6以下

原因分析:XML转YOLO格式时,如果xmin/xmax和图片宽度读取错位,或者数据集里浑浊背景图太多,都会拖低mAP。另外,21578张图里如果分辨率差异过大,小目标全被resize到640x640后变成像素点,模型学不到特征。

解决办法:统计数据集图片的宽高分布,小于640的图片做padding而非直接拉伸,并启用YOLO的letterbox处理。我在转换脚本里加入了宽高信息输出,发现约有18%的图片是704x576这种异形尺寸,统一replace后精度明显回升。

5.2 现象:训练时显存溢出(OOM)

原因分析:batch size设置过大,或者图像分辨率在imgsz=1280时显存消耗成倍增长。RTX 3090 24G显存跑YOLOv8n、imgsz=1280、batch=16 刚好堪堪,如果你的显卡是8G或12G,必然溢出。

解决办法:把batch降到8或4,开启amp=True混合精度训练,否则单纯降低 imgsz 会丢小目标。我实际测试过,8G显存用imgsz=640, batch=8, amp=True可以稳定跑完120轮。

5.3 现象:标注类别文件里有空txt或缺失txt

原因分析:有些XML文件object块为空(背景图),转换脚本写txt时生成了空文件;或者原数据集在传输时丢了个别XML。YOLO的 dataloader 遇到空txt会报错或跳过,但会造成图片/标注不对齐,训练批次里隐性丢失。

解决办法:训练前跑一段脚本,过滤掉没有标注的图片,并重新检查配对完整性。

import os img_dir = "images/train" label_dir = "labels/train" for img in os.listdir(img_dir): basename = os.path.splitext(img)[0] label_path = os.path.join(label_dir, basename + ".txt") if not os.path.exists(label_path): print(f"缺少标注: {basename}") os.remove(os.path.join(img_dir, img)) print(f"已删除无标注图片: {img}")

这段脚本的核心作用就是把没有配对标注的图片直接删掉,避免训练时遇到None标注导致的报错。注意splitext是拿主文件名,不管jpg还是png都能正确匹配。运行前最好备份原图目录,删除是不可逆操作。

5.4 现象:验证集ap高但测试集ap低

原因分析:划分时没按场景隔离,相似帧同时进了训练和验证,验证集成绩虚高,一到新场景就现原形。这也是为什么前面强调按场景前缀划分,而不是直接random.shuffle所有图片。

解决办法:回到第3章的划分脚本,按场景分组后再切分。另外可以从测试集里挑几张典型的夜间、远距离图片人工检查预测效果,比单看曲线值可靠。

6. 用小技巧确认烟雾模型真能用:可视化推理与部署要点

训练完不是直接接摄像头,先用一个可视化推理脚本验证模型在陌生场景上的泛化能力。我从数据集的test场景抽了10段不同时间、不同背景的图片序列,用模型跑了一遍并把结果可视化存图,直观检查有没有误报的云、雾、蒸汽。

from ultralytics import YOLO import cv2 import os model = YOLO("runs/detect/train/weights/best.pt") test_dir = "images/test" out_dir = "visual_predictions" os.makedirs(out_dir, exist_ok=True) for img_file in os.listdir(test_dir): img_path = os.path.join(test_dir, img_file) results = model.predict(img_path, conf=0.25, imgsz=640) annotated = results[0].plot() # 画框并保存 cv2.imwrite(os.path.join(out_dir, img_file), annotated)

这段代码的逻辑是遍历测试集图片,用conf=0.25过滤低置信度框,plot()方法直接把边界框和类别名绘制到图上。建议把置信度阈值先放低到0.15看哪些地方是模型容易误识别的——我跑下来发现云层边缘和工业水汽的误报集中在conf 0.2~0.3区间,后面调高到0.4后误报消失,且真烟检测率没有明显下降,这个阈值点就是你这批数据部署时的最佳设定。观察可视化结果时不要只看有没有报警,要看框的位置对不对——如果烟在远处,框只有几个像素,说明模型学到的是“大面积烟雾检测”,小目标能力偏弱,部署时摄像头机位就需要调整或增加近景相机。

从那以后我每次拿到新的检测数据集,都会强制自己先跑一版可视化推理确认数据标注和模型预测的一致性,再做集成和部署。这个过程只需要一个脚本,但能把后期联调时的问题提前挪到算法交付前解决。这份21578张的烟雾数据集如果只用来训练不测可视化,就浪费了一半价值。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 23:24:59

后端开发第一课:从HTTP、接口到数据库的完整链路入门

后端开发这个方向&#xff0c;几乎每年都被拿出来讨论一遍。我见过不少刚转行或者刚入学的朋友&#xff0c;第一周还兴致勃勃&#xff0c;第二周就开始被各种名词轮番轰炸&#xff1a;接口、数据库、缓存、部署、框架、中间件……每个字都认识&#xff0c;连在一起就不知道在说…

作者头像 李华
网站建设 2026/10/10 23:19:18

论文提交前降AI率攻略:一周紧急改写技巧

论文提交前一周紧急降AI率攻略&#xff1a;时间紧也能搞定离提交论文还剩一周&#xff0c;打开学校检测系统一看&#xff0c;AI疑似率37%&#xff0c;旁边写着“疑似人工智能生成内容比例较高”。这一幕我见过太多次&#xff0c;每年毕业季都有学生半夜发消息问怎么办。先说明&…

作者头像 李华
网站建设 2026/10/10 23:17:19

AI数学论文撤稿事件:一个符号错误如何摧毁整篇证明

1. 事件还原&#xff1a;三篇论文被撤回的现场细节与时间线事情发生在上周三晚间&#xff0c;OpenAI官网的arXiv预印本页面突然出现三条状态更新——原本标注为“Submitted”&#xff08;已提交&#xff09;的三篇论文&#xff0c;状态悄然变更为“Retracted”&#xff08;已撤…

作者头像 李华
网站建设 2026/10/10 23:17:19

半导体废水零排放工程公司推荐:2026年四家企业解析!

随着AI算力、高性能计算和先进制程持续发展&#xff0c;全球半导体产业仍处于扩产阶段。晶圆厂建设带来的挑战已经不局限于洁净室和生产设备&#xff0c;水资源保障、废水处理以及厂务系统的长期稳定运行&#xff0c;同样成为半导体制造基础设施的重要组成部分。近期半导体水处…

作者头像 李华
网站建设 2026/10/10 23:15:10

Python医院挂号系统源码:高并发号源锁定与防超卖设计

简介&#xff1a;这份资源是基于Python的医院门诊挂号与预约系统设计源码&#xff0c;面向计算机相关专业的毕业设计、课程设计学生&#xff0c;以及需要搭建医疗信息系统原型的开发者。项目采用前后端分离架构&#xff0c;前端以Vue组件构建模块化界面&#xff0c;后端用Pytho…

作者头像 李华
网站建设 2026/10/10 23:14:14

拆解O奖论文2229059:数学建模中的时间序列预测与交易策略闭环

简介&#xff1a;来自2022年美国大学生数学建模竞赛&#xff08;MCM/ICM&#xff09;C题杰出奖&#xff08;Outstanding Winner&#xff09;的英文原版论文&#xff0c;收录于优秀论文集。内容面向数学建模参赛者、量化交易学习者和高校指导教师&#xff0c;适合研究O奖论文的选…

作者头像 李华