
简介面向YOLO系列目标检测模型训练与验证的数据集包聚焦火灾、烟雾、人员三类目标识别任务适合目标检测入门学习者以及消防安防类算法项目开发者使用可用于毕设、课程设计或实际巡检场景既适合初学者熟悉YOLO的数据处理流程也适合有经验者快速验证算法效果。压缩包共2000个文件以VOC格式的xml标注文件为主并配套提供yolo格式的txt标注整体大小约141.83MB作者已按训练集、验证集、测试集划分完毕。随包附带data.yaml配置文件yolo标注采用“类别索引归一化中心点归一化宽高”的标准写法可直接用于yolov5、yolov8、yolov9、yolov7、yolov10、yolo11等主流算法其中类别索引从0开始中心点坐标与宽高均被归一化到01区间格式规范统一xml标注便于在LabelImg等工具中核验和二次修改。两类标签分别存放文件名末尾带有类别信息下载后即可快速开展训练与测试无需手动整理或转换格式。已有48人学习使用是消防预警、园区安全巡检等需要火灾与人员检测能力项目的现成数据基础便于直接取用和后续扩展。1. 火灾与人员探测数据集为什么这 3039 张带标签图像值得当训练基线做消防监控的都知道找“火灾检测数据集”不难难的是拿到手就能训。那份“YOLO算法-火灾和人员探测数据集-3039张图像带标签-人-烟-火.zip”名字已经把底线交代清楚了3039 张图上打了人、烟、火三类标签格式是 YOLO 算法直接吃的 txt 标注省掉了从 VOC/COCO 里筛图、写转换脚本、重新归一化的麻烦。解压后划分 train 和 val当天就能开第一版训练。这套数据覆盖的典型场景是监控摄像头下的室内外火灾预警人员是否进入危险区域、烟雾是否已经开始聚集、明火是否出现。适合两类读者——一类是安防与消防产品线的算法工程师把这份数据训出的模型当作预警系统的第一版基线另一类是拿目标检测做课程设计或毕业设计的学生想快速走通“数据集→训练→评估→部署”的完整链路。下面按我的落地习惯从解压检查一路讲到部署推理中间穿插做烟火检测被反复教育出来的踩坑经验。2. 解压后的第一件事核对标签格式、类别分布与图像一致性拿到 zip 之后先别急着开训。YOLO 格式的数据集最常见的问题不在算法而在文件本身——标签和图像对不上、类别编号跟预期不一致、归一化坐标越界、空标签文件混在里面。这些坑在训练早期会被 loss 下降掩盖掉等部署到现场才炸出来代价比多花半小时检查大得多。2.1 解压与目录检查images、labels 的对应关系把压缩包放到工作目录后先看顶层目录结构。视频监控和应急管理场景里常见做法是 images 和 labels 两个根目录下面各分 train/val或者直接用 train/images、train/labels 这种平铺结构。无论哪种核心规则只有一条每张图像的标注文件必须同名、不同后缀。# 解压后先看目录树 cd ~/datasets unzip YOLO算法-火灾和人员探测数据集-3039张图像带标签-人-烟-火.zip -d fire_smoke_person find fire_smoke_person -maxdepth 3 -type d # 检查图像和标签是否一一对应以 train 为例 for img in fire_smoke_person/images/train/*.jpg; do labelfire_smoke_person/labels/train/$(basename ${img%.jpg}).txt if [ ! -f $label ]; then echo 缺少标签: $img fi done这段把图像目录里每张 jpg 对应的 txt 是否存在检查一遍。实际项目中图像可能是 png或者标注放在单层 labels 下按真实解压结果调整路径即可。缺失标签的图像不能进训练集否则 dataloader 会随机报错而且错得很隐晦像是显存不够或者 worker 崩溃。这一步值得在脚本里跑完并把缺失清单存下来后续做数据清洗也方便。2.2 读懂 YOLO 标签归一化框、类别编号与一个小脚本YOLO 的 txt 标注每行格式是固定的类别 ID、框中心点 x、中心点 y、框宽 w、框高 h五个值全部相对图像尺寸做了归一化范围在 0 到 1 之间。这份数据集明确写了“人-烟-火”三个标签按多数类似数据集的习惯类别 ID 为 0person、1smoke、2fire但不要默认这个顺序直接读几个文件确认最稳。from pathlib import Path label_path Path(fire_smoke_person/labels/train) for txt_file in sorted(label_path.glob(*.txt))[:3]: print(f--- {txt_file.name} ---) lines txt_file.read_text().splitlines() for line in lines: parts line.split() if len(parts) ! 5: print(异常行:, line) continue cls_id int(parts[0]) x, y, w, h map(float, parts[1:]) # 归一化坐标合法性检查 if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(坐标越界:, line) else: print(f类别{cls_id}: 中心({x:.3f},{y:.3f}) 尺寸({w:.3f},{h:.3f}))这段脚本干两件事打印前几个标签文件的具体数值以及做坐标合法性检查。YOLO 标签里 w 和 h 是相对图像的归一化宽度、高度不是左上角坐标很多人第一次写可视化脚本时把 x、y 当左上角用画出来的框全部偏移半个框位。先跑这个脚本确认数据和格式匹配再往下走。2.3 统计类别与框尺寸分布先知道数据在哪失衡3039 张图像听起来足够起步但类别分布往往不均衡。火灾数据集通常是“人”类样本很多——监控画面里行人本来就常见而“火”和“烟”相对稀缺尤其火灾早期的小火苗和中远景下的烟雾标注框又小又少。如果直接训练模型会倾向学出“人”类的高召回烟火类表现稀烂。先统计再训练后面调参才有方向。import numpy as np from pathlib import Path label_dir Path(fire_smoke_person/labels/train) class_names [person, smoke, fire] counts np.zeros(len(class_names)) box_sizes [] for txt_file in label_dir.glob(*.txt): for line in txt_file.read_text().splitlines(): parts line.split() if len(parts) 5: continue cls_id int(parts[0]) if cls_id len(class_names): print(f未知类别ID: {cls_id} {txt_file.name}) continue counts[cls_id] 1 w, h float(parts[3]), float(parts[4]) box_sizes.append((w, h)) box_sizes np.array(box_sizes) print(各类别标注框总数:, dict(zip(class_names, counts))) print(标注框平均宽高(归一化):, box_sizes.mean(axis0)) print(标注框面积中位数:, np.median(box_sizes[:, 0] * box_sizes[:, 1])) print(小目标占比(面积0.01):, (box_sizes[:, 0] * box_sizes[:, 1] 0.01).mean())框面积小于 0.01 意味着在 640×640 输入下目标面积不足 4096 像素大约是 40×40 的小块。如果这个占比超过 20%后面训练时必须把 imgsz 调大或者做切片推理否则小火焰基本全漏。这一步统计出的数字直接决定第 3 章训练参数怎么设。提示统计越界标签时要区分“轻微越界”比如 0.98 到 1.02和“完全错误”比如负值或大于 2。轻微越界可能是标注人员手滑训练时数据增强会自动裁剪完全错误则必须剔掉否则 loss 会被这几个框带偏。3. 用 YOLOv8 跑通第一个火灾人员检测模型数据检查通过后就可以进入训练环节。目标检测框架现在选择很多但做烟火检测这种边界模糊、形状不规律的目标我的习惯是直接用 YOLOv8理由是它在小目标上的表现稳定且训练、验证、导出一条命令走完省去自己写 dataloader 和评估脚本的时间。下面的步骤按 YOLOv8 官方代码库的常见用法展开换版本时参数名称基本兼容。3.1 数据集划分固定随机种子避免训练集和验证集穿帮3039 张图像不算多划分时最忌随机性不可控。同一个场景的连续帧如果既进了训练集又进了验证集验证指标会虚高部署到新场景立刻原形毕露。我一般按图像文件名哈希或者场景前缀划分而不是纯随机。# 用 Python 做带种子的划分按文件名散列避免同场景串集 python - EOF from pathlib import Path import random, shutil random.seed(42) images sorted(Path(fire_smoke_person/images/train).glob(*.jpg)) random.shuffle(images) val_ratio 0.15 val_count int(len(images) * val_ratio) val_images set(images[:val_count]) for img in images: label Path(fire_smoke_person/labels/train) / (img.stem .txt) if img in val_images: shutil.move(str(img), fire_smoke_person/images/val/) shutil.move(str(label), fire_smoke_person/labels/val/) EOF伪随机洗牌加固定种子能保证每次实验划分一致但这只能保证分布近似。真正的坑是同一个着火点在连续多帧里反复出现被随机分到两个集合。有条件的话最好看一眼图像文件名有没有场景编号或时间戳按这个维度划分验证集才有说服力。划分完重新统计两边的类别分布确认烟、火类没有全部跑到某一边。3.2 最小训练命令从 yolov8n 到你的第一个权重划分好后写一个数据配置文件指向数据集路径和类别名。然后从最小的 n 模型起步先验证整个流程有没有跑通再看指标决定要不要换更大的模型。# fire_smoke.yaml path: /home/user/fire_smoke_person train: images/train val: images/val names: 0: person 1: smoke 2: fire# 流程验证跑 30 epochs用最小模型 yolo detect train \ datafire_smoke.yaml \ modelyolov8n.pt \ epochs30 \ imgsz640 \ batch16 \ device0 \ projectruns/fire_smoke这里有几个参数值得解释。modelyolov8n.pt 是官方预训练权重pt 文件里带着 COCO 上学习的特征对烟火这种颜色纹理目标有很好的迁移基础不建议从头训练。imgsz640 是默认输入尺寸但第 2 章统计里如果小目标占比高要直接改成 1280代价是训练时间变长、显存占用变大。batch16 是按 24GB 显存保守设置的8GB 显存就降到 8显存不够时优先减 batch而不是无脑减小模型。30 epochs 只是跑通流程最终训练要以第 4 章的早停逻辑为准。3.3 关键训练参数imgsz、batch、epochs、学习率与早停数据集的规模决定了参数不能照搬 COCO 那套。3039 张图在目标检测里属于中小规模epochs 太少学不到烟火的边界太多则过拟合。# 正式训练ImageNet 预训练 早停 数据增强 yolo detect train \ datafire_smoke.yaml \ modelyolov8s.pt \ epochs200 \ imgsz1280 \ batch8 \ optimizerSGD \ lr00.01 \ lrf0.01 \ patience20 \ augmentTrue \ projectruns/fire_smoke说下为什么这样调。模型从 n 升到 s是因为烟火目标的纹理信息比人形更复杂yolov8n 的特征提取能力在远距离小火苗上明显不够用如果显存允许m 版还可以再涨两三个点。imgsz1280 对应第 2 章的统计结果——多数烟火框是小目标640 下直接就没了。epochs 给了 200但 patience20 意味着验证集 mAP 连续 20 轮不涨就提前结束避免无谓的算力消耗。优化器用 SGD 而不是默认的 Adam是因为小数据集上 SGD 配合学习率衰减更容易收敛到平缓的极值区域泛化好一些。注意imgsz 从 640 提到 1280显存占用不是线性增长而是约 4 倍。8GB 显存跑不动就把 batch 降到 4再不行回退到 960。宁可图像略压缩也别把 batch 压到 2 以下否则 BN 统计不稳定loss 会剧烈抖动。4. 训练与推理踩坑排查烟火错检、漏检与过拟合的 5 个现场烟火检测跟行人检测完全是两种手感。行人结构化、尺度稳定矩形框对姿态变化有一定容忍度烟火没有固定形状火焰的边界是发散的烟雾是半透明的矩形框本身就携带大量背景噪声。这一章写我实际训练和测试里遇到的 5 个典型问题每个都按“现象→原因→解决”的顺序拆开。4.1 火焰漏检扎堆出现目标太小被缩放到看不见现象训练完成后测试大面积明火都能框中但监控画面角落里的小火苗——比如电箱冒出的初期火——基本全漏。验证集 mAP 看着还行单独挑出小目标样本一测recall 很低。原因YOLO 的输入会把原图缩放到 imgsz一帧 1920×1080 的监控画面缩到 640 后一个原本 40×40 像素的小火苗只剩大约 13×13 像素经过几层下采样后特征图上的响应已经微弱到可以忽略。解决训练时直接改用 imgsz1280让小火苗在输入里保留更多像素如果显存不够退而求其次做数据增强里的随机裁剪模拟小火苗占画面主体的情况。推理阶段用切片把原图切成 640 或 960 的 patch 分别检测再合并对 4.1 这种场景几乎立竿见影。切片推理的代码模板放在第 6 章。4.2 天空白云被当成烟负样本缺失的典型翻车现象模型在室内测试集上表现良好一放到室外监控画面上天空中的层积云被持续检测成 smoke置信度还超过 0.5。原因烟的标签特征是半透明、白色到浅灰色、边缘模糊天空白云在视觉上几乎完全吻合。数据集里的烟样本大多是从燃烧点向上蔓延的形态模型学到了“浅色、大面积、低纹理”的抽象特征云恰好命中。这不是模型坏了是训练数据里缺少“类似烟但不是烟”的负样本。解决收集几十张不同天气的室外天空图放到 images 目录下但不要标任何框。YOLO 训练时无标签图像不会贡献分类 loss所以这种做法无效。正确做法是给这些负样本标注成“无目标”但 YOLO 的 dataloader 不读取空标签文件需要把它们作为背景帧混入训练集并调整 class weights。更简单的方式是把云天图像切块后单独跑一轮 fine-tune加入少量硬负样本模型当前会误检的图和对应的空标签用低学习率矫正决策边界。4.3 标注框太偏或太大标签噪声比算法参数更影响结果现象训练 loss 收敛正常但可视化预测框时发现火苗中心偏移、框尺寸忽大忽小同一段视频里相邻帧的检测框抖动明显。原因烟雾和火焰的边缘是渐变的标注人员在画框时对“边界在哪”的判断不一致。有人把整团烟雾都框进去有人只框最浓的核火焰则因为光照扩散有人框到光晕边缘。这种噪声给回归头引入了不一致的监督信号模型只能学到一个折中的框抖动在所难免。解决用脚本对数据集的框做统计清洗。先剔除面积异常大的框——超过全图面积 20% 的框多半误框背景再剔除中心点严重偏离图像中心的框火焰意外出现在角落是正常的但如果是图像边缘一圈的零散小框大概率是标注残留。清洗后重新可视化一批标注人工扫一遍最离谱的样本这一步花一个小时后面省三天调参时间。4.4 验证集 mAP 高、现场掉点过拟合与场景偏差现象训练结束时 mAP50 达到 0.85内部测试视频表现不错但拿到客户现场走廊、仓库的新摄像头漏检率直接翻倍。原因3039 张图的数据分布和现场实际场景有偏差比如训练集里多数是白天室内自救演练场景现场可能是夜间仓储、低照度走廊模型在验证集上的高指标只说明它记住了这套数据分布没学会对光照变化不敏感的特征。解决先看训练曲线里验证 loss 是否在某个 epoch 后开始回升——回升就是过拟合信号。然后做两件事一是加数据增强把亮度、对比度、高斯噪声的扰动幅度调大尤其对火灾这种光照敏感的目标二是收集现场数据做增量训练哪怕只有两三百张无标注图像用当前模型自动标注后再人工修正也能把现场掉点的问题拉回来一大截。这里没有捷径场景泛化靠的就是数据和增强。4.5 类别不平衡人被过度检出烟火被压住现象混淆矩阵显示 person 的 recall 接近 0.9fire 的 recall 只有 0.4。而且部分浓烟场景下模型把穿深色衣服的人漏掉了专注去检烟说明类别间存在竞争。原因类别不平衡时模型会倾向学多数类。这个数据集里“人”的出现频率远高于“火”和“烟”如果第 2 章统计时发现 counts 差异超过 3 倍训练时要主动干预。解决在 loss 上做文章。YOLOv8 的 loss 里按 cls 分配权重可以给 fire 和 smoke 更高的权重代价是误检增多更稳妥的做法是过采样少数类——把包含火和烟的图像复制几份加入训练集配合增强让同一个样本在每轮 epoch 里都以不同形态出现。注意不要简单重复同一张图否则模型会背样本。我用过 mosaic 把四张烟火图拼成一张既增加了样本量又保持了上下文丰富度效果比调 loss 权重稳定。5. 评估与部署用 mAP、混淆矩阵和 ONNX 把模型落地训练不是终点。火灾预警系统要接给业务侧之前必须用统一的指标在固定测试集上确认模型能力再把权重导出成推理引擎能跑的格式。这一章说评估命令、指标读法以及导出 ONNX 后在 CPU 上的推理验证。5.1 用 val 命令生成指标与混淆矩阵训练完成后先用验证集做一次完整评估保存预测结果和混淆矩阵。yolo detect val \ modelruns/fire_smoke/train/weights/best.pt \ datafire_smoke.yaml \ imgsz1280 \ conf0.25 \ iou0.6 \ save_jsonTrue \ save_confTrue评估结果会用一张大图展示混淆矩阵横轴是真实类别纵轴是预测类别。看这张图时重点看两处一是 background 这一列如果 person 或 fire 大量落到 background说明该类别 recall 不足二是 smoke 和 background 之间是否有明显混淆——户外误检云的问题在这里会直接暴露。mAP50 在烟火场景下通常比 mAP50-95 高不少因为烟火边界本质上不明确IOU 0.75 以上的框很难判定。所以业务侧定指标不要死盯 50-95mAP50 和针对小目标的 recall 才是关键。5.2 可视化预测结果看错检发生在哪类目标上指标只给数字最终判断要靠看图。from ultralytics import YOLO model YOLO(runs/fire_smoke/train/weights/best.pt) results model.predict( sourcetest_images/, conf0.25, iou0.6, saveTrue, save_txtTrue, projectruns/eval_vis, namepredict, )保存的预测 txt 是 YOLO 格式可以直接和第 2 章的标签统计脚本对接批量计算“哪些类别在哪些场景下漏检”。比如把所有漏检图挑出来存成单独目录人工扫一遍就会发现规律——要么是远距离小火苗要么是逆光下的灰烟。这些规律回头对应到第 4 章的排查方案比只看 mAP 数字有用得多。可视化这一步尽量在多个场景目录上做别只跑测试集场景差异带来的错检类型完全不同。5.3 导出 ONNX 并在 CPU 上跑一版推理部署到监控盒子或服务器最常见做法是导出 ONNX再用 ONNX Runtime 或 TensorRT 加载。注意这里的 dynamicTrue 会让导出的图支持动态 batch 和动态分辨率但会增加少量推理延迟如果部署端分辨率固定可以不用。yolo export \ modelruns/fire_smoke/train/weights/best.pt \ formatonnx \ opset12 \ dynamicTrue \ imgsz1280import onnxruntime as ort import cv2 import numpy as np from ultralytics import YOLO onnx_model YOLO(runs/fire_smoke/train/weights/best.onnx) results onnx_model(test_fire.jpg, imgsz1280, conf0.25) print(results[0].boxes.cls, results[0].boxes.conf)第一次在 CPU 上跑 ONNX 时如果发现推理时间比 PyTorch 推理还慢先检查是不是没有用 onnxruntime-gpu 包CPU 推理下输入分辨率 1280 的单帧时间通常在 200ms 级别做实时视频流分析要配合跳帧策略而不是每帧都全分辨率推理。部署端如果对延迟敏感比较快的路线是导出 TensorRT engine但那是另一个话题数据集侧的工作到这里已经闭环了。6. 进阶针对小目标烟火做切片推理与定向增强小目标漏检是烟火场景的第一痛点最后单独拿出一章专门解决它。室内外监控画面普遍是 1080p 甚至更高分辨率一个大场景里火焰初期可能只有几十个像素YOLO 直接全图推理很容易漏掉。切片推理的思路是把原图切成有重叠的 patch分别推理后再合并结果火焰即使很小也会在某个 patch 里占据足够大的比例。import cv2 import numpy as np from ultralytics import YOLO model YOLO(runs/fire_smoke/train/weights/best.pt) def slice_infer(image_path, patch960, overlap0.25, conf0.25, iou0.5): img cv2.imread(image_path) h, w img.shape[:2] step int(patch * (1 - overlap)) boxes [] for y in range(0, h, step): for x in range(0, w, step): patch_img img[y:y patch, x:x patch] # 边缘不足 patch 时填充灰色避免目标变形 if patch_img.shape[0] patch or patch_img.shape[1] patch: canvas np.full((patch, patch, 3), 114, dtypenp.uint8) canvas[:patch_img.shape[0], :patch_img.shape[1]] patch_img patch_img canvas results model(patch_img, confconf, imgszpatch, verboseFalse) for box in results[0].boxes: cx, cy, bw, bh box.xywh[0].cpu().numpy() cls_id int(box.cls[0]) score float(box.conf[0]) # 坐标映射回原图 gx x cx - bw / 2 gy y cy - bh / 2 boxes.append([gx, gy, bw, bh, score, cls_id]) # 跨 patch 的重复检测做一次 NMS from collections import defaultdict final [] for cls_id in set(b[5] for b in boxes): cls_boxes [b for b in boxes if b[5] cls_id] cls_boxes.sort(keylambda b: b[4], reverseTrue) kept [] for b in cls_boxes: if all(ioa(b, k) iou for k in kept): kept.append(b) final.extend(kept) return finaloverlap 设 0.25 是为保证目标不被切成两半后任何一半都小于模型最小可检尺寸。patch 越大推理次数越少但显存占用越高960 是 1080p 画面下的经验值基本能保证一个 40 像素的小火苗在 patch 里占到 4% 以上不会被下采样抹掉。上面代码里 NMS 部分用了简化的交集判断生产环境可以直接用 torchvision.ops.nms 替换避免自己写的循环在目标多时变慢。定向增强方面我的另一个习惯是把烟雾类样本单独抽出来在训练管线里对它们做更强的亮度和模糊扰动。烟雾检测本质依赖纹理和透明度过曝和失焦是最常见的退化因素先让模型见过这些变体现场就不会因为一个镜头起雾就漏检。把这些增强参数写进 data.yaml 的 augment 配置而不是在外部脚本里手工改图——这样训练和验证的预处理链路保持一致不会出现“增强后的分布只在训练集有效”这种脱节。这套数据集真正做到位不只是训出来一个权重文件而是形成一条“检查数据→训练→看错检→回补数据”的闭环。迭代两三轮之后你的模型会比任何公开预训练权重都贴合自己的现场场景。我自己的教训是第一次做烟火检测时跳过数据检查直接训练结果花了三倍时间回来清理标签和补负样本。数据集的检查、统计、清洗这些看起来不产生“模型精度”的步骤恰恰是最后精度能不能落地的关键。希望这篇笔记能帮你少走这段弯路。本文还有配套的精品资源点击获取