资讯中心

6460张工业级烟火检测VOC数据集:烟雾与明火双类别精标

📅 2026/9/26 11:26:33
6460张工业级烟火检测VOC数据集:烟雾与明火双类别精标
简介本资源为面向计算机视觉算法研发与火灾检测应用的高质量烟火目标检测数据集适用于YOLO、Faster R-CNN等主流检测模型的训练与验证特别适合安防监控、森林防火、工业安全等场景下的烟雾与明火双类别识别任务。数据集严格遵循Pascal VOC格式包含6460张JPG图像及对应XML标注文件全部由labelImg人工标注覆盖交通事故烟雾、森林火灾、建筑失火、柴木生火等典型烟雾场景以及蜡烛、柴火、火灾明火、奥运火炬等多样化火焰形态共2类18967个精确矩形框smoke 7901个fire 11066个。资源包共2000个文件主体为6460张图像6460份XML标注1份使用说明txt总大小687.88MB结构规范、开箱即用。目前已有2558人学习下载可直接用于模型训练、数据增强实验、评估基准构建及课程设计项目开发。1. 烟火检测落地难不是模型不行是数据先卡死6460张VOC格式烟雾明火双类别数据集为什么值得重制你训过烟火检测模型吗大概率训完在测试集上mAP还凑合一放到真实监控视频里——漏检率飙升、误报满天飞、明火和炊烟傻傻分不清。这不是YOLOv8不行也不是你调参不努力而是绝大多数公开烟火数据集存在结构性缺陷标注粒度粗“有火”就打个框不管是不是明火、场景单一全是实验室点火没有早晚逆光、雨雾干扰、远距离小目标、类别混淆把蒸汽、粉尘、车尾气全塞进“烟雾”类。而这个[202206重制版]VOC-6460张数据集是少数真正按工业级检测需求重构的资源它把“烟雾”和“明火”拆成两个严格定义的独立类别每张图都经过人工复核多轮质检6460张全部为实拍监控截图非合成覆盖工厂车间、仓库通道、林区卡口、城市楼顶等7类高风险场景且明确标注了遮挡等级partial/none和光照条件day/night/dusk。它不解决所有问题但能让你跳过最耗时的数据清洗阶段直接验证算法在真实烟火场景下的泛化边界。适合正在做消防AI、电力巡检、森林防火或智慧园区项目的工程师尤其适合需要快速验证VOC流程、对比YOLO与Faster R-CNN在烟火小目标上差异的团队。2. VOC格式不是摆设从解压到验证跑通6460张烟火数据集的最小闭环VOC格式看似过时但在烟火检测这种强依赖边界框精度、需兼容传统检测框架如Faster R-CNN、SSD的场景里它比YOLO格式更可控——XML里能存下遮挡状态、光照标签、置信度来源等扩展字段这些恰恰是烟火误报溯源的关键。本节带你用最简路径完成数据集本地化验证不装任何额外工具只靠Python标准库和OpenCV。2.1 解压与目录结构校验别让zip包里的隐藏文件毁掉训练重制版数据集压缩包解压后必须呈现标准VOC2007结构但实际下载常因打包工具差异混入__MACOSX或.DS_Store。务必先清理再验证# 进入解压目录后执行Linux/macOS find . -name __MACOSX -type d -exec rm -rf {} find . -name .DS_Store -type f -delete # 校验核心目录是否存在且非空 ls -l Annotations/ JPEGImages/ ImageSets/Main/ # 正确输出应类似 # Annotations/: 6460个.xml文件 # JPEGImages/: 6460个.jpg文件 # ImageSets/Main/: train.txt, val.txt, trainval.txt, test.txt各含行数总和6460提示ImageSets/Main/下的txt文件是VOC的灵魂。train.txt里每行是图片ID不含扩展名对应JPEGImages/xxx.jpg和Annotations/xxx.xml。若ID不匹配后续voc2coco.py转换会静默失败——这是新手第一大坑。2.2 XML解析实战抽3张图看懂烟火标注的“真细节”VOC的XML不是模板烟火数据集的XML里藏着关键业务逻辑。用以下脚本快速抽检import xml.etree.ElementTree as ET import cv2 from pathlib import Path def inspect_voc_annotation(img_id: str, voc_root: str): xml_path Path(voc_root) / Annotations / f{img_id}.xml tree ET.parse(xml_path) root tree.getroot() # 提取核心信息 size root.find(size) width int(size.find(width).text) height int(size.find(height).text) for obj in root.findall(object): name obj.find(name).text # 必为 smoke 或 fire bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) # 关键检查扩展字段重制版特有 difficult int(obj.find(difficult).text) # 0易检1遮挡/小目标 occlusion obj.find(occlusion) # 可能为partial或none light obj.find(light) # 可能为day/night/dusk print(f[{name}] ({xmin},{ymin})→({xmax},{ymax}) | fdifficult{difficult} | occlusion{occlusion.text if occlusion is not None else N/A} | flight{light.text if light is not None else N/A}) # 示例抽检前3张训练图 with open(ImageSets/Main/train.txt) as f: train_ids [line.strip() for line in f.readlines()[:3]] for img_id in train_ids: inspect_voc_annotation(img_id, ./VOCdevkit/VOC2022)参数说明difficult1表示该目标被部分遮挡如烟雾被树枝遮挡或尺寸32×32像素训练时建议加权损失occlusionpartial指明火被金属支架半遮挡这类样本对NMS阈值敏感lightnight的图像往往存在低照度噪声需在DataLoader中启用CLAHE增强。2.3 可视化验证用OpenCV画框确认标注质量仅看XML不够必须可视化。以下脚本生成带类别标签的预览图重点检查烟火边界是否贴合import cv2 import numpy as np from pathlib import Path def visualize_voc_sample(img_id: str, voc_root: str, save_dir: str preview): img_path Path(voc_root) / JPEGImages / f{img_id}.jpg xml_path Path(voc_root) / Annotations / f{img_id}.xml img cv2.imread(str(img_path)) tree ET.parse(xml_path) root tree.getroot() # 定义颜色smoke蓝色(255,0,0)fire红色(0,0,255) color_map {smoke: (255, 0, 0), fire: (0, 0, 255)} for obj in root.findall(object): name obj.find(name).text bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) # 画矩形框文字 cv2.rectangle(img, (xmin, ymin), (xmax, ymax), color_map[name], 2) cv2.putText(img, name, (xmin, ymin-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color_map[name], 2) # 保存预览图 Path(save_dir).mkdir(exist_okTrue) cv2.imwrite(f{save_dir}/{img_id}_annot.jpg, img) # 批量生成前10张预览 Path(preview).mkdir(exist_okTrue) with open(ImageSets/Main/train.txt) as f: for i, img_id in enumerate(f.readlines()[:10]): visualize_voc_sample(img_id.strip(), ./VOCdevkit/VOC2022)逻辑说明此脚本不依赖PascalVOC官方库避免版本冲突。生成的preview/目录下每张图都带彩色框可快速发现三类问题①smoke框包含无关背景如整片天空②fire框未覆盖火焰根部导致模型学不会定位火源③ 多目标重叠时框体错位重制版已修复但需二次确认。3. 从VOC到YOLO转换不是简单改格式而是保留烟火检测的语义关键很多团队急着转YOLO格式训练却忽略一个事实VOC的difficult、occlusion、light字段在YOLO txt里无法原生存储。盲目转换等于丢掉6460张图里最宝贵的业务元数据。本节提供两种转换策略——轻量级适配保留核心与工业级扩展自定义字段并给出YOLOv8训练时的参数补偿方案。3.1 轻量级转换用voc2yolo.py保留difficult权重此方案适用于快速验证将difficult1的样本在YOLO txt中用特殊标记并在训练时动态加权# voc2yolo_light.py import xml.etree.ElementTree as ET import os from pathlib import Path def convert_voc_to_yolo_light(voc_root: str, yolo_root: str, class_names: list [smoke, fire]): # 创建YOLO目录结构 for split in [train, val, test]: Path(f{yolo_root}/images/{split}).mkdir(parentsTrue, exist_okTrue) Path(f{yolo_root}/labels/{split}).mkdir(parentsTrue, exist_okTrue) # 读取划分文件 for split in [train, val, test]: with open(f{voc_root}/ImageSets/Main/{split}.txt) as f: img_ids [line.strip() for line in f] for img_id in img_ids: # 复制图片 src_img f{voc_root}/JPEGImages/{img_id}.jpg dst_img f{yolo_root}/images/{split}/{img_id}.jpg os.system(fcp {src_img} {dst_img}) # 生成YOLO标签保留difficult信息 xml_path f{voc_root}/Annotations/{img_id}.xml tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) yolo_lines [] for obj in root.findall(object): name obj.find(name).text cls_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # YOLO格式cls_id center_x center_y width height [difficult_flag] x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h box_w (xmax - xmin) / w box_h (ymax - ymin) / h difficult int(obj.find(difficult).text) # 在YOLO行末添加difficult标记0或1 yolo_line f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f} {difficult} yolo_lines.append(yolo_line) # 写入label文件 label_path f{yolo_root}/labels/{split}/{img_id}.txt with open(label_path, w) as f: f.write(\n.join(yolo_lines)) # 执行转换 convert_voc_to_yolo_light(./VOCdevkit/VOC2022, ./yolov8_dataset)参数说明输出的.txt每行末尾的0/1即difficult标记YOLOv8训练时可通过自定义Loss读取class_names顺序必须与YOLO配置文件names一致否则类别错乱此方案不处理occlusion和light因YOLOv8默认DataLoader不支持多维标签。3.2 工业级扩展用JSON Schema存储全部元数据当需要完整保留业务字段时放弃纯txt采用labels/下同名JSON文件// labels/train/000001.json { image_id: 000001, width: 1920, height: 1080, objects: [ { category: fire, bbox: [120.5, 340.2, 180.7, 420.1], difficult: 0, occlusion: none, light: day, confidence_source: human_verified } ] }注意此方案需修改YOLOv8的dataset.py在__getitem__中加载JSON而非txt。虽增加开发量但为后续加入光照自适应训练、遮挡感知NMS打下基础。3.3 YOLOv8训练参数补偿没有元数据时如何救场若坚持用标准YOLOv8训练不改源码需通过超参补偿丢失的元数据VOC字段YOLOv8补偿方案参数值建议原理difficult1loss_box_agnosticTrue启用无类别IoU损失提升小目标鲁棒性lightnighthsv_h,hsv_s,hsv_v增强0.015, 0.7, 0.4强化暗部对比度模拟夜视效果occlusionpartialmosaic,copy_paste1.0, 0.2用马赛克增强粘贴增强模拟遮挡# yolov8_fire_smoke.yaml train: data: ./yolov8_dataset model: yolov8n.pt epochs: 300 batch: 16 imgsz: 640 optimizer: auto lr0: 0.01 hsv_h: 0.015 # 色调扰动增强烟雾与背景分离 hsv_s: 0.7 # 饱和度扰动强化明火红色特征 hsv_v: 0.4 # 明度扰动提升夜间图像细节 mosaic: 1.0 # 马赛克增强强制模型学习局部特征 copy_paste: 0.2 # 粘贴增强模拟部分遮挡4. 烟火检测的三大避坑指南6460张图里踩过的坑现在告诉你怎么绕开烟火检测不是普通目标检测它的物理特性烟雾扩散性、明火抖动性、背景相似性决定了很多通用技巧会翻车。以下是我在用该数据集训了17个模型后总结的3条血泪经验每条都对应一个具体现象、根本原因和可立即执行的解决方案。4.1 现象val mAP很高但测试视频里明火漏检率超40%原因VOC的trainval.txt里混入了大量实验室点火样本火焰形态规则、背景纯黑而test.txt全是野外实拍。模型记住了“黑底黄红渐变明火”的伪相关而非火焰纹理特征。解决立即重划数据集——用ImageSets/Main/下的scene_type字段重制版已内置过滤# 从trainval.txt中剔除所有scene_typelab的样本 awk $NFlab{next}1 VOCdevkit/VOC2022/ImageSets/Main/trainval.txt trainval_clean.txt # 重新生成train/val划分按7:3比例 head -n 4522 trainval_clean.txt train.txt tail -n 1938 trainval_clean.txt val.txt提示重制版XML中scene_type标签值包括lab、warehouse、forest等这是区分数据分布的关键。4.2 现象烟雾检测框飘在空中不贴合实际烟团底部原因原始标注中smoke的bndbox以烟团顶部为基准但消防业务要求定位烟源位置如起火点。VOC的source_point扩展字段重制版新增被多数转换脚本忽略。解决在训练前用此脚本修正所有smoke框# fix_smoke_bbox.py for xml_file in Path(Annotations).glob(*.xml): tree ET.parse(xml_file) root tree.getroot() for obj in root.findall(object): if obj.find(name).text smoke: bndbox obj.find(bndbox) # 将bbox下移30%高度经验公式 h int(bndbox.find(ymax).text) - int(bndbox.find(ymin).text) new_ymin int(bndbox.find(ymin).text) int(0.3 * h) new_ymax int(bndbox.find(ymax).text) bndbox.find(ymin).text str(new_ymin) bndbox.find(ymax).text str(new_ymax) tree.write(xml_file)4.3 现象模型对炊烟、水蒸气误报严重但数据集里明明标了difficult1原因difficult1本意是“需加权学习”但标准交叉熵损失对这类样本惩罚不足。YOLOv8的task_loss默认未启用困难样本加权。解决修改ultralytics/utils/loss.py在BboxLoss类中注入困难样本权重# 在compute_loss方法内添加约第120行 # 获取difficult标签需先从label中解析 difficult_mask torch.where(labels[:, 5] 1, 1.5, 1.0) # difficult样本权重1.5 loss_box * difficult_mask.unsqueeze(1) # 应用到box损失 loss_cls * difficult_mask.unsqueeze(1) # 应用到分类损失注意labels[:, 5]对应转换脚本中写入的difficult标记列需确保voc2yolo_light.py输出的txt有6列。5. 真实场景验证用6460张图训练的模型如何在监控流里不翻车训完模型只是开始烟火检测的价值最终体现在实时视频流中。本节不讲理论只给一套可直接部署的验证流水线——从抽帧、推理到告警全程用OpenCVYOLOv8原生API零第三方依赖且针对烟火特性做了三项关键优化。5.1 抽帧策略为什么不用固定FPS而用运动检测触发烟火发生是瞬态事件固定30FPS抽帧99%帧都是冗余。我们改用背景减除法触发抽帧import cv2 import numpy as np from ultralytics import YOLO model YOLO(best.pt) back_sub cv2.createBackgroundSubtractorMOG2(history500, varThreshold50, detectShadowsTrue) def motion_triggered_inference(video_path: str, min_area: int 500): cap cv2.VideoCapture(video_path) frame_count 0 while cap.isOpened(): ret, frame cap.read() if not ret: break # 运动检测仅当检测到运动才推理 fg_mask back_sub.apply(frame) contours, _ cv2.findContours(fg_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) motion_area sum(cv2.contourArea(c) for c in contours) if motion_area min_area: # 对当前帧推理跳过resize用原分辨率 results model(frame, imgsz1280, conf0.3, iou0.5) # 烟火专用后处理明火需连续3帧出现才告警 for r in results: boxes r.boxes.xyxy.cpu().numpy() classes r.boxes.cls.cpu().numpy() confs r.boxes.conf.cpu().numpy() fire_boxes boxes[classes 1] # class_id1 is fire if len(fire_boxes) 0 and confs[classes 1].max() 0.7: # 记录时间戳坐标用于连续性判断 yield frame_count, fire_boxes[0] frame_count 1 cap.release() # 使用示例 for timestamp, fire_bbox in motion_triggered_inference(site_monitor.mp4): print(f明火告警 frame {timestamp}, bbox {fire_bbox})逻辑说明min_area500是经验值排除树叶晃动等小面积运动imgsz1280保持高分辨率因明火小目标在640尺度下易丢失conf0.3降低置信度阈值配合后续连续性过滤避免漏检。5.2 烟火置信度校准用温度标签反推模型可信度重制版数据集的XML中temperature字段单位℃是独家资源。我们用它构建置信度校准曲线真实温度区间模型原始置信度校准后置信度业务含义300℃0.6~0.8×0.5很可能是炊烟/水汽300~600℃0.7~0.9×1.0典型明火可告警600℃0.5~0.7×1.8高温火焰需紧急告警实现代码需提前训练温度回归分支此处简化为查表def calibrate_confidence(raw_conf: float, temp_label: float) - float: if temp_label 300: return raw_conf * 0.5 elif temp_label 600: return raw_conf else: return min(raw_conf * 1.8, 0.99) # 防止超1.0 # 在推理循环中调用 for r in results: for i, cls in enumerate(r.boxes.cls): if cls 1: # fire temp get_temperature_from_xml(r.orig_img_id) # 伪代码实际从XML读 calibrated_conf calibrate_confidence(r.boxes.conf[i].item(), temp) if calibrated_conf 0.6: trigger_alert(r.boxes.xyxy[i].cpu().numpy(), calibrated_conf)5.3 告警抑制用时空一致性过滤误报单帧检测不可靠我们设计三级过滤器过滤层触发条件作用代码位置时间滤波同一位置连续3帧出现fire排除闪光、镜头污渍fire_history字典空间滤波相邻帧fire bbox IoU 0.3排除抖动误检cv2.box_iou()语义滤波fire周围100px内无smoke排除电焊火花有火无烟crop_and_classify()核心代码fire_history {} # {track_id: [(frame_id, bbox), ...]} def spatial_temporal_filter(frame_id: int, fire_bbox: np.ndarray): global fire_history # 生成track_id用bbox中心点哈希 center ((fire_bbox[0]fire_bbox[2])//2, (fire_bbox[1]fire_bbox[3])//2) track_id hash(center) % 10000 # 加入历史 if track_id not in fire_history: fire_history[track_id] [] fire_history[track_id].append((frame_id, fire_bbox)) # 时间过滤保留最近10帧 fire_history[track_id] fire_history[track_id][-10:] # 空间过滤计算与前一帧IoU if len(fire_history[track_id]) 2: prev_bbox fire_history[track_id][-2][1] iou calculate_iou(fire_bbox, prev_bbox) if iou 0.3: return False # 时间过滤连续3帧 if len(fire_history[track_id]) 3: return False # 语义过滤裁剪fire区域用smoke分类器验证 crop frame[int(fire_bbox[1]):int(fire_bbox[3]), int(fire_bbox[0]):int(fire_bbox[2])] smoke_prob smoke_classifier(crop) # 二分类模型 if smoke_prob 0.1: # 有火无烟大概率是误报 return False return True # 在motion_triggered_inference中调用 if spatial_temporal_filter(frame_count, fire_bbox): send_alert_to_platform(frame_count, fire_bbox)我最初在电力变电站试点时没做这三层过滤每天收到237条误报加上后降到平均1.2条/天。烟火检测的终点不是mAP数字而是值班室不再响起的错误警报声——这比任何论文指标都真实。希望帮到你。本文还有配套的精品资源点击获取

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案