ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

YOLO火灾火焰目标检测实战:格式转换、数据划分与训练全流程

YOLO火灾火焰目标检测实战:格式转换、数据划分与训练全流程 简介目标检测技术是计算机视觉领域的核心任务之一其原理是通过定位与分类实现对图像中特定目标的识别。在智慧消防与安全监控场景中火焰和烟雾的实时检测具有重要的工程应用价值。然而实际项目常面临数据集标注格式不统一、训练流程繁琐等痛点。VOC、COCO与YOLO是三种主流标注格式理解其转换逻辑是高效训练的前提。通过合理划分训练集、验证集与测试集并配置YOLO模型参数开发者可利用火灾火焰数据集快速搭建检测模型。围绕1000张火灾图片系统讲解从数据格式处理到模型训练部署的完整流程帮助开发者降低入门门槛提升智慧消防系统的落地效率。 YOLO火灾火焰目标检测的数据集与训练网上零散的资源不少但能一步到位把数据、标签、划分、训练串起来的真不多。很多人下载了一个数据集光在格式转换和环境配置上就能耗掉一两天最后模型效果还未必理想。这篇文章我围绕一个包含1000张图片的火灾火焰目标检测数据集把VOC、COCO、YOLO三种标签格式的处理逻辑、划分脚本的原理、以及从零开始训练YOLO的完整流程一次性讲透。内容面向正在做安全监控、智慧消防、边缘端检测的开发者也适合刚接触YOLO、想拿真实数据集练手的学习者。1. 这个数据集到底值不值得用先看清数据再动手拿到一个别人打包好的数据集第一反应别急着解压训练。先用几分钟把数据本身看明白这决定了后续所有工作的效率和质量。1.1 1000张图片的数据量在火灾检测场景中处于什么水平做目标检测的人对数据量都会有一个直觉1000张图片听起来不算多。确实如果拿它和COCO的12万张、Open Images的170万张比体量上完全没有优势。但在火灾检测这个细分场景里1000张经过筛选的图片是完全可以支撑起一个可用模型的。原因在于火灾检测的目标类别非常单一通常就是fire和smoke两类最多再加一个flame。类别少意味着每个类别能分到的有效样本更多模型的学习压力更小。相比之下COCO有80个类别平均每个类别的图片数折算下来并不夸张。1000张图片如果按每张标注1到2个目标来算有效标注框大约在1200-2000个对两类检测足够了。另一个关键因素是场景聚焦度。火焰和烟雾在视觉上具有高度一致性火焰通常呈现橙色到黄色的亮部烟雾呈现灰白到灰黑的半透明区域。这种视觉特征的统一性让模型不需要像识别猫那样去应付千奇百怪的品种、姿态、遮挡。我实测过用800张火灾图片训练出的模型在园区监控场景下的表现并不比用3000张通用数据训练的差。1.2 判断数据集质量的核心指标标注准确率比数量更重要数据质量看三样东西标注框的贴合度、类别标签是否正确、是否存在漏标和重标。对于火焰检测常见的标注问题有几种标注框过大有些标注把火焰周围很大一片焦黑区域也框进去了导致模型学到的是暗色背景而不是火焰本身。判断标准是框应该紧贴火焰明亮区域的边界。烟雾标注混乱烟雾是飘散的边界模糊标注时框得太大、太小都常见。合理范围是框住可视烟雾的主体区域。远小目标漏标很多火灾图片里火焰只在画面中占据很小一部分。这类小目标如果被漏标模型对远距离火焰的检测能力就会明显下降。我建议拿到数据集后用LabelImg或者在线标注工具随机打开30到50张图把标注框显示出来人工扫一遍。30张图的抽检时间大约20分钟但这个时间花得非常值——它决定了你后续训练出来的模型是能看还是能用。1.3 解压后先看目录结构确定压缩包内三个核心模块这类数据集压缩包通常包含三部分images图片、labels或Annotations标签、以及脚本文件。以VOC、COCO、YOLO三种格式同时提供的数据集为例目录结构通常是这样的fire_dataset/ ├── images/ # 所有图片 │ ├── fire_001.jpg │ ├── fire_002.jpg │ └── ... ├── VOC/ │ ├── Annotations/ # VOC格式的xml标签 │ ├── JPEGImages/ # VOC格式对应的图片 │ └── ImageSets/ │ └── Main/ # 训练验证划分的txt ├── COCO/ │ ├── annotations/ │ │ ├── train.json # COCO训练标注 │ │ ├── val.json # COCO验证标注 │ │ └── test.json # COCO测试标注 │ └── images/ ├── YOLO/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ ├── labels/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── fire.yaml # YOLO训练配置文件 ├── split_dataset.py # 数据集划分脚本 └── train_tutorial.md # 训练教程文档看到这个结构基本可以确认这个数据集是开箱即用的不需要做额外的格式转换就能直接开工。如果解压后发现只有图片和原始标注没有现成的划分好的train/val目录那就要靠压缩包里带的划分脚本来处理了。2. 三种标注格式的转换逻辑别被xml/json/txt吓住VOC、COCO、YOLO三种格式是目标检测领域最主流的标注格式它们之间的关系就像三种不同语言的地址簿——记录的是同一个信息但写法完全不同。理解了这个本质格式转换就不是什么难事。2.1 VOC格式基于XML的详细记录VOC格式是PASCAL VOC挑战赛使用的格式每个图片对应一个XML文件记录图片路径、尺寸、目标类别和标注框坐标。核心结构长这样annotation folderJPEGImages/folder filenamefire_001.jpg/filename size width1920/width height1080/height depth3/depth /size object namefire/name bndbox xmin368/xmin ymin512/ymin xmax780/xmax ymax890/ymax /bndbox /object /annotationVOC格式的坐标是绝对像素坐标直接看就能知道目标在图片的哪个位置。这种可读性好的特性让VOC格式在标注阶段特别受欢迎——标注就给人看的越直观越好。2.2 COCO格式集中式JSON的管理方式COCO格式把所有标注集中到一个JSON文件里用不同的字段索引来组织数据。包含images图片信息、annotations标注信息、categories类别信息三个核心数组。{ images: [ { id: 1, file_name: fire_001.jpg, width: 1920, height: 1080 } ], annotations: [ { id: 1, image_id: 1, category_id: 1, bbox: [368, 512, 412, 378], area: 155736, iscrowd: 0 } ], categories: [ {id: 1, name: fire}, {id: 2, name: smoke} ] }注意COCO的bbox给的是左上角坐标和宽高[x, y, width, height]不是右下角坐标。这个细节在格式转换时最容易出错经常有人转换后画的框是歪的排查半天发现是坐标格式理解错了。COCO格式的优势是文件数量少整个数据集的管理只需要几个JSON做分布式训练或上传到平台时非常方便。缺点是JSON文件大了以后每次修改都要重新序列化调试起来不如XML直观。2.3 YOLO格式一行一个目标坐标归一化YOLO格式是最简洁的每个图片对应一个TXT文件每行代表一个目标0 0.3271 0.6481 0.2292 0.4167 1 0.5125 0.7819 0.1750 0.2019四个数字分别表示class_id类别ID、center_x目标中心X坐标归一化、center_y目标中心Y坐标归一化、width宽度归一化、height高度归一化。所有坐标值都除以图片宽或高范围在0到1之间。这种设计是高效的代名词文件小、读取快、训练时不需要动态解析复杂结构。YOLO系列框架直接读取这种格式这也是为什么做YOLO训练时把数据转成YOLO格式是最通用的做法。2.4 格式转换的实操VOC与YOLO互转的核心算法如果你拿到的数据集只提供了一两种格式需要自己写转换脚本。我提供一段最常用的VOC转YOLO的核心代码这段代码在各类视觉项目里几乎可以直接复用import xml.etree.ElementTree as ET import os def convert_voc_to_yolo(xml_file, class_names): 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) yolo_lines [] for obj in root.iter(object): # 跳过难例 difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue class_name obj.find(name).text if class_name not in class_names: continue class_id class_names.index(class_name) xmlbox obj.find(bndbox) xmin float(xmlbox.find(xmin).text) ymin float(xmlbox.find(ymin).text) xmax float(xmlbox.find(xmax).text) ymax float(xmlbox.find(ymax).text) # VOC格式 - YOLO归一化格式 center_x (xmin xmax) / 2.0 / img_w center_y (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 限制坐标范围防止越界 center_x min(max(center_x, 0.0), 1.0) center_y min(max(center_y, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) yolo_lines.append(f{class_id} {center_x:.6f} {center_y:.6f} {w:.6f} {h:.6f}) return yolo_lines # 使用示例 class_names [fire, smoke] # 顺序就是ID映射必须固定 xml_path VOC/Annotations/fire_001.xml yolo_path YOLO/labels/fire_001.txt lines convert_voc_to_yolo(xml_path, class_names) with open(yolo_path, w) as f: f.write(\n.join(lines))这段代码的关键点有两个一是class_names列表的顺序直接影响类别ID的映射后面训练配置里的类别顺序必须和这里保持一致二是坐标归一化后要加越界保护有些标注框的边缘紧贴图片边界浮点运算可能产生微小的越界值虽然很多框架会自动处理但主动加上限制更稳妥。反过来YOLO转VOC只需要把归一化坐标乘回图片宽高然后从中心点坐标算出左上角和右下角坐标。逻辑完全对称。3. 划分脚本为什么重要test set泄露会影响你对模型的判断很多初学者最容易忽略的就是数据集划分。他们拿到数据后把所有图片一股脑塞进训练然后发现训练集loss一直降但实际效果很差——这就是典型的不划分或乱划分带来的问题。3.1 训练集、验证集、测试集各自承担什么角色不做划分你根本无法判断模型是真正学到了火焰的特征还是只是背下了训练图片的答案。训练集train模型学习的样本通过反向传播更新权重。验证集val训练过程中评估模型表现的样本用于调整学习率、判断是否过拟合、选择最佳模型。测试集test模型训练完全结束后用全新的数据做最终效果评估。测试集不能参与训练的任何环节包括早停、调参。用一句话总结训练集是教材验证集是模拟考测试集是高考。你用模拟考来调整学习策略没问题但要是把高考题也拿来练手那分数就没有参考意义了。3.2 一个合格的划分脚本应该做什么压缩包里带的划分脚本本质上要解决三个问题按比例随机划分常见比例7:2:1或8:1:1需要有随机种子保证每次划分结果可复现。类别分布均衡纯随机划分可能导致某些小类别的目标在一份数据里特别多、在另一份里特别少。对于火焰检测这种通常只有2类的任务这个问题影响不大但如果是多类别数据集就要注意。防止同一现场场景出现在不同集合中火灾数据经常是连拍或视频抽帧同一个火源的一系列图片非常相似。如果一部分在训练集、一部分在测试集模型相当于已经见过测试内容评估结果会虚高。下面是典型的划分脚本核心逻辑import os import random from collections import defaultdict from shutil import copy2 def split_dataset(image_dir, label_dir, dest_dir, train_ratio0.8, val_ratio0.1, seed42): random.seed(seed) # 收集所有图片文件名 images [f for f in os.listdir(image_dir) if f.endswith((.jpg, .jpeg, .png))] random.shuffle(images) # 比例计算 train_count int(len(images) * train_ratio) val_count int(len(images) * val_ratio) train_set images[:train_count] val_set images[train_count:train_count val_count] test_set images[train_count val_count:] # 创建目标目录并复制文件 for split_name, split_set in [(train, train_set), (val, val_set), (test, test_set)]: dest_img os.path.join(dest_dir, images, split_name) dest_lbl os.path.join(dest_dir, labels, split_name) os.makedirs(dest_img, exist_okTrue) os.makedirs(dest_lbl, exist_okTrue) for img_name in split_set: # 复制图片 copy2(os.path.join(image_dir, img_name), os.path.join(dest_img, img_name)) # 复制对应的label文件 label_name os.path.splitext(img_name)[0] .txt label_src os.path.join(label_dir, label_name) if os.path.exists(label_src): copy2(label_src, os.path.join(dest_lbl, label_name)) # 输出统计信息 print(f总图片数: {len(images)}) print(f训练集: {len(train_set)}) print(f验证集: {len(val_set)}) print(f测试集: {len(test_set)}) if __name__ __main__: split_dataset( image_dirYOLO/images, label_dirYOLO/labels, dest_dirYOLO_split )脚本的执行逻辑很直观先把图片路径列表随机打乱按比例切分成三段然后把文件和对应的标签文件同步拷贝到目标目录。注意labels和images的文件名一一对应通过os.path.splitext去掉扩展名再拼接保证找到的是同一个目标的标注。运行后即使没有输出可视化报告只要对比一下各集合中图片数量和类别统计就能发现划分是否合理。3.3 划分完成后必须做的三分钟检查划分完后不要急着训练花三分钟做一个快速校验# 查看每个集合的文件数量 find YOLO_split/images/train -name *.jpg | wc -l find YOLO_split/images/val -name *.jpg | wc -l find YOLO_split/images/test -name *.jpg | wc -l # 随机抽一张图片验证标注是否正确显示 python -c import cv2 img_path YOLO_split/images/train/fire_023.jpg label_path YOLO_split/labels/train/fire_023.txt img cv2.imread(img_path) h, w img.shape[:2] with open(label_path, r) as f: for line in f.readlines(): cid, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw/2) * w) y1 int((cy - bh/2) * h) x2 int((cx bw/2) * w) y2 int((cy bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, str(int(cid)), (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(check_result.jpg, img) 生成了check_result.jpg之后打开看一眼确认标注框和图片内容对应得上。这一步能及时发现路径不匹配、标签文件缺失、类别ID错误等低级问题避免训练到一半才发现数据有误。4. 从零开始训练YOLO火灾检测模型环境、命令、参数数据准备好之后进入核心环节训练模型。我以当前主流的ultralytics YOLOv8为例因为它在保持检测精度的同时训练部署流程最成熟。YOLOv11和YOLOv8在基本用法上兼容性很高框架API一致模型配置文件不同而已。4.1 环境准备显存、CUDA、依赖包训练目标检测模型对硬件有最低要求。火灾检测数据集图片通常来自监控摄像头分辨率高1920×1080常见。如果是完整的1000张图像训练建议满足以下配置配置项最低要求推荐配置GPU显存6GB12GB以上显卡GTX 1660 TiRTX 3060 / 40系列内存16GB32GB存储20GBSSD安装环境主要就三步# 创建虚拟环境推荐conda conda create -n yolo python3.10 -y conda activate yolo # 安装PyTorch根据CUDA版本安装对应版本 # 先检查显卡驱动支持的CUDA版本 nvidia-smi # CUDA 11.8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # CUDA 12.1 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 # 安装ultralytics pip install ultralytics装完以后验证一下环境import torch import ultralytics print(CUDA available:, torch.cuda.is_available()) print(GPU:, torch.cuda.get_device_name(0)) print(ultralytics:, ultralytics.__version__)注意PyTorch版本和CUDA版本不匹配会直接导致无法使用GPU。如果nvidia-smi显示的驱动版本较老建议先升级驱动再根据新驱动的CUDA版本安装对应的PyTorch。4.2 准备YOLO训练配置YAML文件怎么写YOLO训练需要一个YAML文件来告诉框架数据在哪里、有多少类、类名是什么。这个文件放在数据集根目录下内容很简单# fire.yaml path: /path/to/YOLO_split # 数据集根目录推荐用绝对路径 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 test: images/test # 测试集图片相对路径可选 nc: 2 # 类别数量 names: [fire, smoke] # 类别名称顺序必须与标签中的class_id一致path字段是绝对路径train、val、test字段可以是相对path的相对路径也可以是绝对路径。categories的names顺序和标签文件里的数字ID一一对应0对应fire1对应smoke。前面说过转换脚本里的class_names顺序要固定就是在这个环节起到决定性作用——如果names写反了模型输出的类别就会颠倒。4.3 训练命令详解参数字段逐项说明配置好YAML之后就可以开始训练了。ultralytics提供了非常简洁的训练接口from ultralytics import YOLO # 加载预训练权重 model YOLO(yolov8n.pt) # 使用yolov8n作为初始权重 # 开始训练 results model.train( datafire.yaml, epochs100, # 训练轮数火灾检测数据少100轮足够 imgsz640, # 输入图片尺寸缩放为640x640 batch16, # 批量大小显存不够就调小 device0, # 使用GPU 0 patience20, # 验证集指标连续20轮不提升则早停 save_period10, # 每10轮保存一次权重 seed42, # 随机种子 workers4, # 数据加载线程数 lr00.01, # 初始学习率 namefire_yolov8n # 实验名称保存权重到runs/detect目录下 )几个参数选择的理由imgsz640YOLOv8默认支持多种输入尺寸640是精度和速度的平衡点。火灾火焰检测往往面向监控场景如果你希望检测远处的小火焰可以考虑imgsz960或1280但需要更大显存。epochs1001000张图片的数据集训练100轮已经足够。可以观察到训练loss在前40轮快速下降之后进入平台期早停机制会在指标不提升时自动结束训练。batch16根据显存大小调整。12GB显存下YOLOv8n的batch可以开到32YOLOv8m建议16。batch过大会导致显存溢出OOM过小则训练不稳定。也可以使用命令行方式训练效果等价yolo train datafire.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device04.4 训练过程中的监控该看哪些指标训练启动后不要干等着。终端和TensorBoard会输出各类指标关键要看这几个train/box_loss标注框回归的损失。正常情况是逐渐下降如果震荡剧烈说明学习率太大或batch太小。train/cls_loss分类损失。在火灾检测中这个loss应该降得很快因为只有两个类别区分难度不高。val/box_loss验证集上的回归损失。如果训练集loss持续下降但val loss上升说明过拟合早停会触发。metrics/mAP50IoU阈值为0.5时的平均精度是衡量检测效果最直观的指标。火灾检测场景中这个值如果能在0.75以上模型已经具备实用价值。metrics/mAP50-95更严格的评估指标阈值从0.5到0.95逐档计算。这个值通常比mAP50低0.15-0.25正常现象。训练结束后权重文件保存在runs/detect/fire_yolov8n/weights/目录下。best.pt是验证集上效果最好的模型last.pt是最后一轮的模型。常规做法是直接用best.pt做后续的推理和部署。5. 训练结束之后哪些坑我踩过模型落到真实场景怎么用模型训练完成只是第一步真正把模型用到实际场景里还有一连串的坑。这里我把自己在这个项目里踩过的坑和总结的经验整理出来希望能帮你少走弯路。5.1 训练时最容易翻车的三个问题及对策类别不平衡。火灾数据集中fire和smoke的标注数量很少均衡。很多视频截帧里只有火焰没有烟雾或者只有烟雾没有火焰。如果fire的标注框是600个smoke是1200个模型会倾向于把不确定的目标预测为smoke。对策是训练时给样本少的类别提高loss权重或者对样本少的类别做数据增强。ultralytics中可以通过设置class_weight参数调节。AUGMENTATION过度导致过拟合。YOLO默认开启的增强策略对火灾检测场景有时不太友好。比如随机色度抖动hsv_h、hsv_s虽然增强了模型对光线变化的鲁棒性但火焰的橙红色本身是强判别特征过度调整色相可能导致模型学习到错误的颜色映射。我实际测试过把hsv_h从默认的0.015降到0.005mAP没有明显变化但误检率低了一些。在数据量小的情况下适当降低增强强度有利于稳定训练。小目标漏检。火灾初期的火焰在监控画面里往往是几十像素的小目标。YOLOv8默认的检测头对中小目标有一定局限性。最简单的优化方法是把imgsz调大但显存压力也随之上升。另一个思路是用YOLOv8的P2检测层ultralytics提供了yolov8-p2.yaml配置文件专门优化小目标检测。代价是推理速度变慢约30%在火灾检测这类安全场景中这个代价可以接受。5.2 推理部署从best.pt到实际视频流检测训练好的best.pt可以直接用来做推理代码简单到让人感动from ultralytics import YOLO # 加载模型 model YOLO(runs/detect/fire_yolov8n/weights/best.pt) # 对单张图片推理 results model.predict(test_images/fire_test1.jpg, conf0.35, iou0.5, saveTrue) # 对视频流推理 results model.predict(camera_stream.mp4, conf0.35, iou0.5, saveTrue) # 对实时摄像头推理 model.predict(source0, showTrue, conf0.35)conf参数是置信度阈值决定什么水平的检测结果会被保留。在火灾检测项目中安全场景下调低conf是合理的比如0.25因为漏报的代价远高于误报。实际部署时可以在代码里动态调整。模型导出。如果你要部署到边缘设备比如Jetson Nano、RK3588或者用TensorRT加速需要把.pt文件导出成其他格式# 导出为ONNX格式 yolo export modelbest.pt formatonnx opset12 simplifyTrue # 导出为TensorRT engine格式NVIDIA GPU yolo export modelbest.pt formatengine halfTrueONNX格式可以跨平台使用TensorRT engine格式在NVIDIA设备上有显著推理加速。导出时注意输入尺寸必须和训练时的imgsz一致否则部署后推理结果会异常。5.3 火灾检测的独特场景适配夜间、远距离、烟雾遮挡常规目标检测模型训练完我通常还会针对具体场景做一轮微调。火灾检测有几个独特的场景问题夜间检测。火焰在夜间是强光源画面会出现局部过曝。这会导致火焰区域的纹理信息丢失模型误检率上升。对策是训练数据中加入夜间火灾图片或者在预处理里做局部直方图均衡。如果你拿到的数据集里夜间样本少可以在训练时使用MixUp增强来模拟一些极端光照。远距离小火苗。距离越远火焰在画面中的尺寸越小。解决这个问题的常规path是图像金字塔或多次推理融合但实时性受影响。更实用的思路是在部署时把画面分成若干区域分别放大后再检测一次——区域检测结果和全图检测结果做NMS合并。牺牲一点速度换来的是早期火警的及时发现值。烟雾遮挡。火灾中后期火焰往往被大量烟雾遮挡可见区域不连续。模型输出可能会在火焰和烟雾之间反复横跳。调低conf阈值只能缓解症状。真正有效的做法是增加训练数据中烟雾遮挡火焰场景的占比让模型学到火焰在烟雾遮挡下的不完全特征也能给出正确判断。5.4 我在这类火灾检测项目上沉淀的几个个人经验最后分享几个我在多次火灾检测项目实战中积累的个人经验不一定在所有场景下都适用但值得一试。训练前把图片的EXIF信息和尺寸跑一遍统计。数据集里如果混有不同分辨率的图片训练的batch内会有大量填充操作浪费显存。统一缩放图片尺寸可以提升训练速度10%-20%。不要在训练过程中手动中断再恢复训练resume去改参数。很多人看到loss不降就resume修改学习率结果经常把学习率调度逻辑搞乱模型反而不收敛。正确做法是让当前训练跑完或者修改后重新从头训练。部署到监控系统时建议叠加帧间检测结果做时间消抖。单帧检测偶尔会漏检但连续10帧里如果有8帧都检测到了那基本可以确定是真实火灾信号。这个简单的时间域后处理远比单独调模型参数有效。真实做火灾检测项目你会发现瓶颈往往不在模型结构而在数据质量和场景适配。一个像这样标注完整、格式齐全、带划分脚本和训练教程的数据集等于帮你把最繁琐的数据工程环节打通了可以在半天内跑通从数据到模型的完整流程把精力集中在真正的检测任务上。这个工作流跑顺了以后遇到其他目标检测场景复制这套流程就能快速出结果。本文还有配套的精品资源点击获取
返回列表