
简介面向YOLO系列目标检测算法的火灾与人员探测数据集适合算法学习者、安防消防领域开发者快速获取带标注的训练数据。内容覆盖人、烟、火等检测类别数据已划分训练/验证/测试集并附data.yaml配置文件可直接适配yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等常见版本省去数据转换与整理环节。压缩包总大小141.83MB标签文件共2000个以VOC格式xml标注为主同时提供YOLO格式txt标注两种格式分别存于独立文件夹便于项目切换标注体系。xml文件名能直观看出对应类别配合图片即可用于模型训练、验证与测试。由于标签格式说明清晰入门者也能较快上手直接搭好训练流程。已有48人学习下载适合需要快速验证火灾/人员识别方案、开展算法对比实验的开发者参考。1. 火灾与人员探测这份YOLO目标检测数据集到底能直接拿来干什么先想清楚一个场景防火监控里真正要紧的检测目标不外乎人、烟、火三类。YOLO算法目标检测数据集市面上不少但多数是通用COCO类目要落地到消防告警还得自己清洗、改标签、重划分费半天劲。这份资源给的是3039张带标签图像同时提供yolo txt和voc xml两种格式自带data.yaml训练集、验证集、测试集已经划分好拿到手不用做标签转换就能直接跑yolov5到yolo11的整个链路。适合谁用做过目标检测但没时间整理消防领域数据的人或者刚入门YOLO、想用真实数据走通全流程的新手。后面会把这套数据的格式、配置、训练命令、常见翻车点逐一拆开都是实际跑过才写出来的。2. 标签格式拆解YOLO txt与VOC xml两种坐标体系2.1 类别分布与文件名命名规律数据集标题已经写明人、烟、火三类对应到标签文件里通常就是person、smoke、fire。项目正文里列出的xml文件名比如img_0398_1633.xml这类编号本身不带类别信息真正的类别在文件内容里。解压后打开任意xml看objectname字段就能确认完整类别列表不要凭文件名猜类别顺序。# 在labels/yolo目录下统计每个类别出现的总框数 cat labels/train/*.txt | awk {print $1} | sort | uniq -c统计结果是理解类别平衡性的第一手资料。消防场景里火焰和烟雾通常占大头人员框数可能只有前者的五分之一这个比例直接影响训练时的类别权重配置。如果person类别只有一两百个框宁可先用数据增强把它拉起来也不要直接上模型硬训。三类目标在监控画面里的特征差异很明显person是刚性物体轮廓稳定smoke是半透明的弥散区域边界模糊fire则是高亮色块颜色饱和度高但形状不稳定。这些差异决定了后面增强参数怎么调。2.2 归一化坐标到底存了什么YOLO格式的txt文件每一行就是:class x_center y_center width height五个数字全部用空格分隔其中x_center、y_center、width、height都是归一化比例值范围在0到1之间。什么意思以一张宽1280、高720的图像为例某个火焰框的像素坐标是左上角(320,180)、右下角(960,540)那么x_center (320 960) / 2 / 1280 0.5 y_center (180 540) / 2 / 720 0.5 width (960 - 320) / 1280 0.5 height (540 - 180) / 720 0.5归一化的好处是坐标不随图像分辨率变化。训练时YOLO统一把图像resize到640x640标签不需要跟着改如果换成了1280x1280的输入标签也原样能用。这一点和VOC的绝对像素坐标有本质区别也是新手最容易混的地方。检查标签越界可以用下面这段Python脚本任何坐标值超出[0,1]都会打印文件名from pathlib import Path for f in Path(labels/train).glob(*.txt): for line in f.read_text().strip().splitlines(): parts list(map(float, line.split())) if len(parts) ! 5: print(f[格式错误] {f.name}: {line}) if parts[1] 0 or parts[1] 1 or parts[2] 0 or parts[2] 1: print(f[越界] {f.name}: {line})这段脚本的思路很简单先把每行拆成5个浮点数再检查中心点坐标是否落在[0,1]区间。实际项目里我还会加一条width和height不能为0的判断空框会让损失函数算出一个奇怪的nan训练直接崩溃。2.3 用Python把XML转成YOLO格式数据集同时给了yolo和voc两种标签但逻辑上txt和xml应该是同一批目标的不同表达。如果想把xml当权威来源、重新生成txt或者反过来检查两种格式是否一致需要写一个转换脚本。核心逻辑是先从xml的size节点读图像宽高再把bndbox里的绝对坐标换算成归一化坐标import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_txt_path, class_map): tree ET.parse(xml_path) root tree.getroot() img_width float(root.find(size/width).text) img_height float(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in class_map: continue cls_id class_map[name] box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) if x1 x2 or y1 y2: print(f[跳过异常框] {xml_path}: {name} {x1} {y1} {x2} {y2}) continue x_center ((x1 x2) / 2.0) / img_width y_center ((y1 y2) / 2.0) / img_height w (x2 - x1) / img_width h (y2 - y1) / img_height lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) Path(out_txt_path).write_text(\n.join(lines), encodingutf-8) class_map {person: 0, smoke: 1, fire: 2} voc_to_yolo(img_0398_1633.xml, img_0398_1633.txt, class_map)几个关键点。class_map的类别顺序必须和data.yaml里的names保持一致否则模型学到的类别索引和实际物体对不上训练完的模型会在推理时把火焰标成人。x_center保留6位小数足够多几位也不会提高精度。脚本里对x1x2做了防御人工标注的xml里偶尔会出现这种脏数据硬转成txt会让模型看到一个宽为负的框训练时梯度直接乱掉。注意转换完成后别急着开训把txt和xml的数量对一遍。一个xml对应一个txt哪个多出来都要查清楚多出来的往往是空标签文件在作祟。3. 训练前的数据配置把data.yaml和目录结构一次弄对3.1 目录结构与data.yaml字段数据集已经划分好标准目录结构是这样dataset/ ├── images/ │ ├── train/ # 约2000张 │ ├── val/ # 约500张 │ └── test/ # 约500张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamlimages和labels必须一一对应每张jpg都要有一个同名txt。划分好的数据集一般不会出错但解压路径变化后yaml里的path字段需要重新核对。data.yaml的内容大致如下path: /root/fire-dataset train: images/train val: images/val test: images/test nc: 3 names: 0: person 1: smoke 2: firepath字段我通常直接写成绝对路径而不是相对路径。原因有两个第一绝对路径在训练、验证、导出onnx三个环节都不会迷路第二如果数据集挪到另一台服务器上只改这一行就够了train、val、test这些后缀路径永远不要动。lc字段和names列表是另一个容易出问题的地方。nc必须等于names的数量names的索引顺序必须和txt标签里的类别索引完全一致。有些数据集会把names写成person、fire、smoke和训练脚本里class_map的顺序不一致最后mAP曲线会非常难看。拿到数据后第一件事就是打开一个txt文件看第一个数字是几再和yaml对照。3.2 用Ultralytics训练YOLOv8的命令装好ultralytics包之后训练命令非常干净pip install ultralytics # 在COCO预训练权重yolov8s.pt基础上迁移学习 yolo train modelyolov8s.pt data/root/fire-dataset/data.yaml \ epochs100 imgsz640 batch16 device0modelyolov8s.pt会自动下载COCO预训练权重。对这份数据来说人员类别在COCO里已经有很强的先验迁移学习后person的AP起步就高烟和火则需要后面几十个epoch自己学。如果机器没有网络提前把权重下载好放在本地再把model参数改成权重文件的路径。参数怎么选imgsz先取640这是精度和速度的平衡点。火焰本身是大目标居多640够用如果想要更精细的小火苗检测可以试768代价是显存占用和推理耗时同步上升。batch大小取决于显存8G显存跑yolov8s配batch 16比较稳妥OOM就直接降到8。epochs建议先给100训练完看val曲线再决定是继续训练还是早停。提示如果用的是yolov5的官方仓库训练命令不同。python train.py --data data.yaml --weights yolov5s.pt --batch-size 16 --img 640是yolov5的标准写法yolov7用户则需要改hyp配置文件。这份数据集的标签格式是通用的换到哪个框架都能直接读。3.3 验证命令与三类评估指标训练完成后验证命令同样简单yolo val modelruns/detect/train/weights/best.pt \ data/root/fire-dataset/data.yaml \ imgsz640验证结果会给出mAP50、mAP50-95、precision、recall四个主要指标。对火灾告警场景来说mAP50的价值远高于mAP50-95因为告警逻辑不要求预测框和真实框完全重合只要大致框住火焰区域就够了。经验值mAP50超过0.85就基本可用mAP50-95超过0.6已经相当能打。只看总体mAP会掩盖类别问题。val命令的输出里会打印每个类别的ap50如果person只有0.3而fire有0.9说明人员框数量太少或标注质量差需要针对person做数据增强而不是盲目调全局参数。这个习惯能节省大量调参时间。4. 避坑指南标注数据训练时最常见的四类问题4.1 现象loss收敛但mAP一直卡在0.4跑完100个epochtrain loss曲线很漂亮val的mAP50却始终在0.4上下浮动。原因主要是类别不平衡加标签噪声。火灾场景里person框数量远少于smoke和fire网络先学会烟和火人员框就是学不进去。另一个常见原因是标签里有漏标或错标验证集上模型预测对了但ground truth是错的AP直接被拉低。解决方法是先看类别分布再逐个检查标签from pathlib import Path import collections cnt collections.Counter() for f in Path(labels/train).glob(*.txt): for line in f.read_text().strip().splitlines(): cnt[int(line.split()[0])] 1 print(cnt)如果person只有一百来个框别急着改网络结构。常见做法是把增强里的copy-paste打开把人员目标复制粘贴到不同背景上或者用crop把包含人的区域放大后单独训练。标签噪声的排查更花时间我的做法是随机抽200张训练图把模型预测结果画出来和原标签对比错得离谱的框十有八九是标注源问题人工补一轮比调参划得来。4.2 现象XML转TXT后坐标出现负值或大于1第一次做格式转换就遇到框跑到画面外面画出来的标签严重错位。原因几乎都是宽度和高度没有分别归一化或者xml里xmin和xmax写反了。还有一种情况是某些标注工具导出的坐标会超出图像边界比如标注时手抖多画了两像素。解决方法是转换脚本里加约束检查任何坐标不在[0,1]区间都打印文件名# 全量扫描标签目录 for f in Path(labels/train).glob(*.txt): for line in f.read_text().strip().splitlines(): parts list(map(float, line.split())) if any(c 0 or c 1 for c in parts[1:]): print(f[越界] {f.name}: {line})我的习惯是转换后全量扫描一遍遇到越界先回xml里确认是脏数据还是公式bug。如果xml里xmax本来就不大于xmin直接丢弃该目标框这种框喂给模型只会让边框回归头学到错误信息。注意扫描脚本只检查坐标范围格式错误如小数位数缺失、负号位置不对也得单独排查自动化脚本挡不住所有脏数据。4.3 现象浓烟区域里的火焰被漏检烟雾和火焰在视觉上天然重叠烟雾半透明会稀释火焰的纹理特征模型把烟和火框重叠时NMS会优先保留置信度更高的smokefire就被压掉了。另一个原因是标注时烟和火框交叠严重两个正样本在同一个位置冲突。解决办法分训练和推理两层训练前检查重叠框当fire框和smoke框的IoU大于0.4时优先保留fire标签同时把smoke框缩到不重叠的区域。推理时把fire的conf阈值放宽到0.3smoke阈值提到0.5给火焰更多通过机会。这样会带来一些误报但告警场景宁可多报也不漏报误报可以靠延迟确认来过滤漏报可能造成实质损失。4.4 现象白天误把白墙、水泥地当烟雾问题根子是负样本太少。数据集里几乎所有图像都包含烟火目标模型没学过“没有烟的普通墙面长什么样”于是把浅色光滑区域都当成烟。解决方法是找几百张不含烟火的普通场景图不打标签只生成空txt文件放进训练目录重新训练。我第一次这么干的时候效果立竿见影误报率降了一半还多。另一个实用手段是在推理链路里加帧间差分或背景建模只处理运动区域内的检测结果静止的白墙天然被过滤掉。5. 从模型到告警推理脚本与阈值策略5.1 单张图片推理与模型选择训练完先跑单张图验证效果from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcetest_fire.jpg, conf0.35, iou0.5, imgsz640, saveTrue, )conf0.35表示置信度低于0.35的预测框被丢弃iou0.5是NMS阶段用的IoU阈值框与框的重叠超过0.5时保留置信度更高的。这两个参数直接影响漏检率和误检率现场调优时可以画一条PR曲线来找平衡点。模型选择上不同规模权重的差异很现实模型参数量单帧耗时参考适合场景yolov8n3.2M5ms边缘AI盒子yolov8s11M12ms常规监控服务器yolov8m26M25ms中心算力充足如果推理设备是低功耗的RK3588或者Jetson Nanoyolov8n配合fp16精度是更合理的选择如果跑在普通GPU服务器上yolov8s是精度和速度的甜点。没必要为了零点几个点的mAP上yolov8m火灾检测这类场景对帧率的要求往往比对精度的要求更硬。5.2 视频流检测与丢帧策略接RTSP流做实时检测时代码要稍微工程化一点import cv2 import time from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) cap cv2.VideoCapture(rtsp://192.168.1.100:554/stream1) while cap.isOpened(): ret, frame cap.read() if not ret: continue results model(frame, conf0.4, iou0.5, verboseFalse) for r in results: for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) if cls_id 2 and conf 0.4: # fire类别索引为2 trigger_alarm(time.time()) cv2.imshow(fire-detect, results[0].plot()) if cv2.waitKey(1) 0xFF ord(q): breaktrigger_alarm建议用异步调用不要在推理线程里做写日志、发短信这类阻塞操作。视频流解码和推理如果共用主线程解码耗时高的时候帧率会骤降。常见做法是开一个解码线程把帧放进队列推理线程只管消费队列超过5就丢最旧的帧保证告警延迟可控。我遇到过解码线程卡住导致推理线程空转的情况后来在循环里加了超时判断连续30帧读不到就重连RTSP。5.3 导出ONNX与部署注意事项模型验证没问题后要部署导出onnxyolo export modelruns/detect/train/weights/best.pt \ formatonnx imgsz640 dynamicFalse导出时imgsz必须和训练尺寸一致否则某些算子的输入维度对不上会直接报错。dynamicFalse表示固定输入尺寸部署到TensorRT时推理速度更快如果输入分辨率会变化才需要dynamicTrue但代价是预处理耗时增加。ONNX导出后还有个容易踩的坑Python的opencv读图默认是BGR而模型训练时用的是RGB。如果推理脚本直接喂BGR帧给onnx烟雾和火焰的颜色特征会偏差很大检测效果明显下降。转换时要么在预处理里加cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)要么在导出时把模型输入改成BGR。6. 数据增强与场景复用把3039张图的边界撑开6.1 增强参数按场景调火灾检测里火焰对颜色敏感烟雾对纹理和透明度更敏感增强参数不能一刀切。我用ultralytics训练时习惯这样配model.train( datadata.yaml, epochs100, imgsz640, batch16, augmentTrue, mosaic1.0, mixup0.2, hsv_h0.015, hsv_s0.7, hsv_v0.4, fliplr0.5, scale0.5, )mosaic1.0表示每轮训练里约100%的batch会做拼图四张图随机缩放拼接成一张对包含小目标的场景很有帮助。hsv_s0.7是饱和度扰动让模型在浓烟灰白和火焰橘红之间学会区分颜色边界。如果误报多我一般会关掉fliplr火灾现场左右对称性并不强水平翻转可能让模型学到错误的空间关系。这些参数不是越大越好mixup超过0.4后烟雾这种纹理本来就模糊的目标会变得更难收敛。6.2 新场景复用与数据闭环拿到这套数据集训练出基线模型后如果要用到另一个车间、隧道或林场不需要重新标注几千张。我的做法是只收集300到500张目标场景的图像其中大部分是不含烟火目标的负样本少量是新增角度的正样本。把这批图按同样格式放进images/train和labels/train然后加载best.pt权重继续训练30到50轮。同一套数据划分不用重做新增样本并进去之后跑同一份训练命令即可。这套数据下回来解压之后也别急着开训先花十分钟把labels和images对齐检查一遍再跑第3章的流程。从那以后我每次拿到新数据集都强制走一遍标签完整性检查加类别分布统计再进训练这套流程不花多少时间但能省下至少半天调参功夫希望帮到你。本文还有配套的精品资源点击获取