变电站烟雾明火检测实战:VOC/YOLO双格式数据集训练与部署避坑
2026/9/9 2:00:55 网站建设 项目流程

简介:面向变电站火灾检测场景,这份数据集收录140张真实拍摄的电力场景图片,标注火焰与烟雾两类目标,总框数239个,可为目标检测模型训练提供贴近实际的样本。数据同时提供Pascal VOC格式的xml文件和YOLO格式的txt文件,每张jpg一一对应同名标注,配合labelImg工具生成的矩形框,能够直接接入YOLO、SSD、Faster R-CNN等常见检测框架。整个压缩包共423个文件,除140组图片与标注外,另有少量说明性txt,资源大小5.85MB,下载与解压都比较轻量。目前已有433人完成学习下载。类别框数上,fire共109框、smoke共130框,覆盖不同距离与光照条件下的火灾烟雾形态,适合电力设施巡检、消防预警等方向的算法验证与数据增强。需注意的是,该数据集只保证标注规范合理,不对模型精度做额外承诺,推荐搭配扩充样本后进行训练。 看到这个文件名的时候,我第一反应是:140张图,2个类别,VOC和YOLO双格式,这样一个数据集到底能干什么?但等我真正把它下载下来,做完格式梳理、数据勘验、模型训练和坏例分析之后,我的看法变了。这个数据集的价值不在“量”,而在于它把“变电站真实场景”和“烟雾、明火”这两类目标绑在了一起。对做电力视觉、应急消防、工业安监算法的人来说,这种贴近真实场景的数据反而比动辄几千张的通用数据集更稀缺,也更能检验模型能不能落地。

这篇文章我会用一次完整的使用过程来讲:拿到这个数据集后怎么拆包、怎么看标注、怎么转格式、怎么训练YOLO模型,以及实际部署时容易踩哪些坑。适合正在做目标检测相关项目、手头有小样本专用数据集、或者准备进入电力安监算法方向的工程师参考。

1. 数据集的定位:为什么“变电站烟雾明火检测”值得单独做一份数据

1.1 电力场景目标检测的难点在哪

变电站这个场景有两个明显特点:一是设备密集,二是背景复杂。高压套管、绝缘子、母线、隔离开关、避雷器,大量金属构件的轮廓和纹理都是强干扰。烟雾在画面里是半透明、无固定形状的,火苗又会随着风力和燃烧物变化而闪烁,这两种目标和设备本身的轮廓线混在一起时,通用检测模型很容易漏检或误检。

更麻烦的是,变电站火灾属于典型的小概率大后果事件。日常运行中很难等到真实火灾再去采集图像,所以多数公开的烟火检测数据要么是户外森林火灾、要么是普通室内场景,放在变电站里迁移效果并不好。这个数据集的价值就在于它提供的全是电力设备环境下的真实画面,不是网图拼出来的,也不是简单背景替换的合成图,模型在这个域上微调,泛化性会明显好一些。

1.2 真实场景数据和合成数据的本质差别

很多入门项目喜欢用合成数据做训练,因为生成快、标注干净。但合成数据最大的问题是“域差”:合成烟雾往往形态规整、透明度统一,火焰颜色过于饱和,背景光照也是一致的,模型在这种数据上学到的特征到了真实监控画面里就失效。

我拿到这份数据后特意抽查了几张图,光线有逆光、有阴天、有夜间,烟雾有浓有淡,火焰有的出现在设备底部、有的在电柜缝隙里。这种真实分布的多样性是合成数据很难模拟出来的。对做算法的人而言,真实数据少但“像”,合成数据多但“假”,小样本微调阶段优先保“像”。

2. VOC和YOLO双格式:从标注文件反推数据集结构

2.1 VOC标注的结构长什么样

解压之后,按常见VOC组织方式,应该能看到JPEGImages目录放原图,Annotations目录放XML标注文件。VOC格式的每个XML对应一张图,核心内容大致如下:

<annotation> <folder>JPEGImages</folder> <filename>station_001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>smoke</name> <bndbox> <xmin>410</xmin> <ymin>260</ymin> <xmax>760</xmax> <ymax>610</ymax> </bndbox> </object> </annotation>

这里关键是object节点里的name和bndbox。name对应类别,bndbox给出的是检测框左上角和右下角的绝对像素坐标。如果你要自己在LabelImg里复核标注,直接打开这个XML就能看到完整的框信息。

这个数据集是2个类别,通常会把smoke设为0、fire设为1,但具体以你解压后看到的label文件或classes.txt为准。拿到数据集先看一眼类别映射,不要默认smoke就是第0类,训练之前这一点能避免很多低级错误。

2.2 YOLO格式与坐标换算逻辑

YOLO训练不认XML,它要求每个图片对应一个同名txt文件,每行格式是:

class_id x_center y_center width height

注意这里四个坐标值全是归一化的,范围在0到1之间。x_center是目标框中心点的横向比例,width是框宽占图片宽的比例。很多初学者在这里栽过跟头,把VOC的绝对坐标直接喂给YOLO,训练出来loss全部变成nan。

从VOC转YOLO的换算并不复杂,核心公式就是:

x_center = (xmin + xmax) / 2 / img_width y_center = (ymin + ymax) / 2 / img_height box_width = (xmax - xmin) / img_width box_height = (ymax - ymin) / img_height

如果你拿到的数据集里只有VOC标注,可以用下面这段脚本快速转出YOLO格式:

import xml.etree.ElementTree as ET from pathlib import Path class_names = ["smoke", "fire"] def voc2yolo(xml_path, out_path): tree = ET.parse(xml_path) 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.iter("object"): name = obj.find("name").text if name not in class_names: continue cls_id = class_names.index(name) box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") Path(out_path).write_text("\n".join(lines)) # 批量转换 xml_dir = Path("Annotations") out_dir = Path("labels") out_dir.mkdir(exist_ok=True) for xml_file in xml_dir.glob("*.xml"): voc2yolo(xml_file, out_dir / f"{xml_file.stem}.txt")

2.3 为什么数据集要同时给两种格式

主要原因有两个。第一,不同训练框架的输入要求不一样,YOLO系列要用txt,而很多可视化工具和开源项目默认读VOC,双格式让使用者省去一步转换的功夫;第二,保留VOC格式也方便二次复核,XML里既能看框坐标又能看类别名,人工检查时比看一串数字直观得多。

另外一个实际原因:很多标注平台导出的原始格式就是VOC,所以一手数据往往是VOC,再通过脚本派生YOLO格式。数据集作者把两种都放出来,相当于同时交付“底稿”和“训练专用产物”,这对做数据管理的人来说更规范。

3. 用这份数据跑通一次完整的YOLO训练

3.1 先做数据勘验,再谈训练

拿到数据集的第一件事不是直接训练,而是先做数据勘验。140张图虽然不多,但也要看类别分布是否均衡、图片尺寸是否统一、标注框是否存在越界或空文件。

我习惯写个小脚本统计:

from pathlib import Path label_dir = Path("labels") stats = {"smoke": 0, "fire": 0} empty_files = [] for txt in label_dir.glob("*.txt"): lines = txt.read_text().strip().splitlines() if not lines: empty_files.append(txt.name) continue for line in lines: cls_id = int(line.split()[0]) if cls_id == 0: stats["smoke"] += 1 elif cls_id == 1: stats["fire"] += 1 print("类别统计:", stats) print("空标注文件:", empty_files)

用这个脚本你能很快发现两个问题:一是如果两个类别目标数差距过大,训练时就要考虑给小样本类别加权重;二是如果有空标注文件,放到训练集里会额外增加背景样本,影响不算坏但需要知道。

3.2 数据划分与目录准备

YOLOv8训练要求的目录结构很明确:

station_fire/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/

140张图我建议按85比15划分,也就是train放110张左右、val放30张左右。如果之后准备做交叉验证,也可以拆成多份,但140张这个体量做5折交叉验证更稳妥,虽然会多训练几次,但评估指标更可信。

同时建议写好对应的data.yaml:

path: /data/station_fire/ train: images/train val: images/val nc: 2 names: ['smoke', 'fire']

3.3 训练命令与参数建议

数据集量小,不推荐从零训练,直接用YOLOv8官方预训练权重做迁移学习是更合理的做法。我实际用的命令:

yolo detect train data=station_fire.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 patience=20 device=0

这里几个参数值得解释一下。模型选yolov8s而不是yolov8m或yolov8l,是因为数据量不大,模型复杂度太高容易过拟合;imgsz用640是速度与精度的平衡点,如果你的原始图片分辨率比较高而且目标比较小,可以试试800或960,但要留意显存。patience=20的意思是20个epoch内验证集指标如果没有提升就早停,小数据集训练经常40到50轮就到了瓶颈,没必要硬跑满100轮。

如果你是CPU环境或者显存不足的显卡,可以先把batch降到8甚至4,模型换成yolov8n。我在低配机器上测过,训练时间会变长,但流程是一样的。

4. 小样本训练的关键:迁移学习与数据增强怎么配合

4.1 不要从头训练,特征复用远重要于重新学习

很多人拿到140张图就直接model=yolov8s.yaml从随机初始化开始训练,结果就是loss降不下去、mAP极低。原因很好理解:深度卷积网络底层学到的是边缘、纹理、色块这类通用特征,这些特征在大规模数据集上已经学得很好了,小数据集只需要在高层语义上做适配。

所以训练时建议分两个阶段。第一阶段冻结backbone,只训练检测头,让模型先学会把“通用特征”映射到smoke和fire两个类别上;第二阶段解冻全部层,用小学习率做整体微调。YOLOv8命令行不直接支持分阶段冻结,但你可以先训练几十轮保存权重,然后加载这个权重再训练一轮,并配合learning_rate和weight_decay把学习率调低。

4.2 数据增强参数怎么调

YOLOv8内置了很多数据增强策略,对小数据集来说这是救命稻草,但不是所有增强都适合烟火检测。我实际用的增强组合里,比较有效的是:

  • hsv_h、hsv_s、hsv_v:适当调高可以让模型适应变电站不同光照和不同色温下的烟雾颜色变化
  • scale和translate:模拟摄像头不同焦距和位置偏移
  • fliplr:左右翻转,这个对烟火数据没有方向性影响,可以放心开

需要稍微注意的是mosaic增强。虽然多图拼接能大幅扩充样本,但对小目标密集场景,mosaic容易把不同图的颜色空间混在一起,烟雾这种半透明目标在拼接边界容易出现“切了一半”的假样本。YOLOv8里可以用close_mosaic参数设置在最后10到20轮关闭mosaic,给训练一个稳定收敛的过程。

4.3 评估指标怎么解读

小数据集上不要只看mAP50和mAP50-95这两个数。烟雾目标边界模糊、透明度高,标签框本身就带有一定主观性,所以mAP50会相对好看,mAP50-95低一些是正常的,两者的差距越大说明检测框和标签框的贴合度越差,这往往意味着烟雾目标常被“大概框住”但不够精确。

另外要单独看每个类别的AP。火苗面积小、特征明显,AP通常比较高;烟雾形态变化大、容易和背景融为一体,AP会低不少。如果fire的AP已经到0.85而smoke只有0.6,就别急着调网络结构,优先看看smoke的误检和漏检集中在哪些图上,再决定要不要补数据。

5. 部署到真实电力场景时,我的避坑记录

5.1 变电站现场的“伪火源”和“伪烟雾”

模型在验证集上跑得不错,不代表现场没问题。变电站这种环境里最常见的误报来源有三个:一是阳光斜射到金属构架上的高光反射,颜色和火焰非常接近;二是设备散热口排出的热气、蒸汽,甚至夏天柏油路面上的热浪,会被模型当成烟雾;三是夜间巡检时照明灯具直射镜头的强光,偶尔也会被误检为明火。

应对办法是:部署时把置信度阈值从默认的0.25提到0.4以上,同时引入时序确认逻辑。单帧检测到fire先不报警,连续5到10帧都检测到且位置相近再触发告警,这样可以过滤掉大量瞬时干扰。很多安监平台的误报率超标,不是模型不够好,而是后处理太简单。

5.2 低配GPU和CPU环境能不能跑

不少读者用老显卡,比如AMD RX 580,问能不能跑YOLO。只要驱动和深度学习框架能正常调用GPU,显存够用就可以训练和推理,只不过RX 580只有8GB显存,跑yolov8s加batch 16会吃力,建议batch降到8,或者干脆用yolov8n。CPU也能跑,但训练速度很慢,适合做推理测试,不适合反复调试参数。

关于CUDA的问题,严格来说AMD显卡不走NVIDIA CUDA生态,但PyTorch有对应的ROCm版本可以支持AMD显卡。如果你用的刚好是这类卡,建议先确认自己装的PyTorch是不是ROCm版本,否则即使能识别显卡也无法真正调用算力。

5.3 从140张到可落地:后续如何扩充数据

140张的真实数据只能作为种子集,真正要落地到项目里,还需要继续扩充。我自己习惯的扩充路径有三条:

第一,从现场监控视频里抽帧,优先抽白天、夜晚、逆光、阴天这些不同条件下的画面,用已经训练好的模型做自动标注,然后人工复核,这是最省力的做法;第二,针对误报较多的场景,专门采集反光、蒸汽、灯具直射这类“难负样本”加入训练集;第三,如果拿得到不同变电站的点位,尽量把不同设备型号和布局都覆盖进去,因为每个变电站的设备外观差异都会影响模型泛化。

按照这个思路,原本140张的数据集可以在一个月内扩充到500到1000张,模型精度会有明显提升。不要把数据集当作固定的东西,它更像是项目启动的第一块基石。

最后再分享一个小经验。做这类电力烟火检测项目时,别把精力全花在模型结构上,先把数据质量管好:标注框有没有歪、类别有没有标错、有没有把设备反光标成fire,这些细节对最终精度的影响,往往比换一个更大更深的backbone还大。我在这份数据集上踩过的最大的坑就是一开始没做标注复核,结果训了两轮之后发现val集里有个别错误标签,白白浪费了不少时间。数据集越小,人工核对每一张图就越值得。

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

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

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

立即咨询