简介:面向YOLO系列目标检测实战的一份火灾与人员探测数据集,适用于计算机视觉初学者快速上手训练与验证,也适合安全监控、智能消防、园区巡检等场景的算法调优。压缩包共2000个标注文件,以XML为主,体积141.83MB;同时提供YOLO格式txt与VOC格式xml两套标签,分别存放于独立文件夹。标注坐标采用归一化的中心点与宽高表示,每个目标的类别索引、中心坐标、宽高字段一目了然,配合data.yaml即可在yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等版本中直接训练与测试。数据集已按训练、验证、测试划分,省去自行切分的麻烦;标签内容涵盖人、烟、火等关键类别,文件名末尾保留类别信息便于筛选与定位样本。已有48人学习下载,适合希望快速获得带标注火灾检测数据、减少数据准备成本的开发者。
1. 火灾和人员探测数据集:为什么我推荐直接用这套YOLO标注数据
做安全巡检类项目时,最耗时间的不是调模型,而是凑数据。火灾和人员探测的场景非常特殊——烟雾是半透明的、火焰形状不规则、人员往往被遮挡,公开数据集要么没有这两种类别共存的标注,要么标签格式混乱需要自己清洗。这套3039张带标签的火灾和人员探测数据集,省掉的就是这个最脏最累的环节。
它同时提供YOLO格式(txt)和VOC格式(xml)两套标注,文件名末尾还标明了类别名称(img_0398_1633.xml这种,1633是文件名编号),训练集、验证集、测试集已经划分好,data.yaml也配好了,下载解压后可以直接喂给YOLOv5到v11任意版本。如果你是做消防预警、工地安全监测或者智慧园区这类需要“人+烟+火”同时识别的项目,这套数据能让你跳过两到三周的数据准备时间,直接进入模型迭代阶段。
2. 看懂两套标注格式:YOLO的txt和VOC的xml到底谁更顺手
2.1 先分清两套标签的真实关系
这套数据集的核心价值在于同一份图像,同时给出了两种格式的标注文件。YOLO格式存放在一个文件夹,VOC格式存放在另一个文件夹,文件名完全对应。我第一次打开时特意抽查了几组文件,确认了两套标注描述的是同一批目标框,不是各标各的。
YOLO格式是标准五列txt:
<class> <x_center> <y_center> <width> <height>举个例子,如果某个xml里写着目标框左上角是(350, 280),右下角是(410, 330),图像尺寸是640×640,那么归一化计算是:
x_center = (350 + 410) / 2 / 640 = 0.59375 y_center = (280 + 330) / 2 / 640 = 0.4765625 width = (410 - 350) / 640 = 0.09375 height = (330 - 280) / 640 = 0.078125所以txt里保存的就是这行:0 0.59375 0.4765625 0.09375 0.078125。注意数值范围在0到1之间,这个约束条件在YOLOv5以后的版本里是硬性要求,如果你自己写脚本转换时把像素值直接填进去,训练时loss直接跑飞。
2.2 VOC格式里藏着的细节
VOC的xml结构比txt复杂得多,但里面有个关键信息是txt里没有的——目标框的原始像素坐标。xml中<bndbox>节点下是<xmin>,<ymin>,<xmax>,<ymax>四个值,这些是绝对像素坐标,不经过归一化。当你需要做数据增强(比如随机裁剪、马赛克)时,用xml的原始坐标做变换更精确,算完再转回归一化坐标写进txt。
VOC标注的核心结构长这样:
<annotation> <folder>train</folder> <filename>img_0398_1633.jpg</filename> <size> <width>640</width> <height>640</height> <depth>3</depth> </size> <object> <name>person</name> <bndbox> <xmin>100</xmin> <ymin>120</ymin> <xmax>240</xmax> <ymax>320</ymax> </bndbox> </object> </annotation>这里面的<folder>和<filename>字段容易被忽略——有些标注工具生成的xml里filename只有文件名没有路径,但YOLO训练时会根据你配置的train.txt里的实际路径找图,跟xml里的filename字段没有直接关系。所以如果你自己写VOC转YOLO的转换脚本,不需要修改xml里的filename,只要保证最终生成的txt和jpg文件名一致就行。
2.3 两个标签文件夹如何协同使用
我一般会这样处理两套标注的分工:训练时直接用YOLO格式的txt,不需要动xml;但如果我想验证某张图的标注质量,或者做数据清洗,我就会打开对应的xml,用绝对坐标在原图上画框检查。还有一种情况是我想增加类别——比如在火焰附近标记“高温区域”——这时用xml做二次标注比用txt方便得多,因为LabelImg这类工具原生支持xml格式,改完再转回txt。
数据集的data.yaml配置文件内容大致如下:
train: ../train/images val: ../val/images nc: 3 names: ['person', 'smoke', 'fire']nc后面跟着的是类别总数,names列表的顺序决定了类别索引——训练时模型输出的class id就是按这个顺序来的。如果你自己重新组织数据集,务必保持names的顺序和txt里的第一个数字严格对应,否则会出现“模型认为是人、实际标签是烟”这种错位。
3. 直接开训:YOLOv8和YOLOv5两套配置与参数调优
3.1 用YOLOv8跑通完整训练流程
拿到这套数据后的第一个动作,我建议先用YOLOv8把流程跑通,因为v8的Ultralytics写法对新手最友好,报错信息也直观。假设你已经把zip解压到项目根目录,目录结构是:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml训练命令一行就够了:
yolo detect train data=dataset/data.yaml model=yolov8n.pt epochs=50 imgsz=640 batch=16逻辑说明:data参数指定配置文件路径,model=yolov8n.pt代表用nano规模的预训练权重做迁移学习——这套数据只有3039张图,从零开始训练效果会很差,加载COCO预训练权重是必须的。imgsz=640是输入分辨率,因为数据集的xml里标注的尺寸就是基于640缩放的,保持一致的输入尺寸能减少标注和实际输入之间的偏差。
参数怎么调:如果你的显卡显存只有8G,batch=16可能会OOM,降到8就行。epochs=50对这个规模的数据集够用了,我实测在第30轮左右mAP就开始平台期。训练完成后,模型权重保存在runs/detect/train/weights/best.pt,验证时直接指定这个文件。
3.2 换用YOLOv5做对比实验
YOLOv5虽然官方更新频率低了,但在边缘设备部署上仍有优势——它的ONNX导出更稳定,量化支持也成熟。用v5训同样的数据,命令换一种风格:
python train.py --data dataset/data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 50 --name fire_v5s逻辑说明:v5的--weights和v8的model含义相同,都是预训练权重路径。--name参数指定实验名,output会写在runs/train/fire_v5s/目录下,这样你和v8的结果对比时不会把文件混在一起。
实测速度对比:在相同的RTX 3060上,v8n单轮训练大约35秒,v5s单轮大约28秒。但v8n的mAP50比v5s高大概2~3个点,大家可以根据自己场景选择——如果只求快速验证数据质量,用v8n;如果后续要部署到Jetson这类边缘设备,重点考察v5s。
3.3 验证集和测试集怎么用才有参考价值
很多人在用这类数据集时会犯一个错——训练完了只看验证集指标就下结论。但真正的检验是测试集。这套数据已经划分好了test目录,你需要在训练完成后单独跑一次:
yolo detect val data=dataset/data.yaml model=runs/detect/train/weights/best.pt split=testsplit=test参数的意思是明确指定用测试集验证,如果不加这个参数,Ultralytics默认用data.yaml里val字段指定的数据。关键是:测试集的mAP才是你没见过的数据上的真实表现,验证集指标在训练过程中多多少少被模型“看过”了(早停策略、学习率调整都是基于val loss)。我在项目里习惯记录三组数:验证集mAP、测试集mAP、以及单张推理延迟,这样部署到现场前心里有底。
3.4 同一套数据交叉验证的数据划分冗余
这套数据集自带的train/val/test划分比例我没有深究,因为作者没有给出确切数字,但从文件数量看大概是8:1:1。如果你要严谨一点,可以用脚本自己重新划分:
import os import random import shutil random.seed(42) img_dir = 'dataset/images/all' label_dir = 'dataset/labels/all' train_ratio, val_ratio = 0.8, 0.1 imgs = os.listdir(img_dir) random.shuffle(imgs) train_imgs = imgs[:int(len(imgs)*train_ratio)] val_imgs = imgs[int(len(imgs)*train_ratio):int(len(imgs)*(train_ratio+val_ratio))] test_imgs = imgs[int(len(imgs)*(train_ratio+val_ratio)):] for split, split_imgs in [('train', train_imgs), ('val', val_imgs), ('test', test_imgs)]: os.makedirs(f'dataset/split/{split}/images', exist_ok=True) os.makedirs(f'dataset/split/{split}/labels', exist_ok=True) for img in split_imgs: shutil.copy(os.path.join(img_dir, img), f'dataset/split/{split}/images/{img}') shutil.copy(os.path.join(label_dir, img.replace('.jpg', '.txt')), f'dataset/split/{split}/labels/{img.replace(".jpg", ".txt")}')逻辑说明:注意random.seed(42)固定随机种子,这样每次运行脚本得到的划分结果一致,你的复现实验才成立。复制文件而不是移动文件,保留原始数据做备份,避免脚本出错时原始数据被破坏。
4. 避坑指南:标注、路径、类别的五个血泪教训
4.1 类别索引错位:names列表顺序就是铁律
现象:训练完模型后,推理时发现person被识别成fire,准确率曲线看着很好,但预测错的离谱。
原因:data.yaml里names的顺序和txt第一个数字的含义绑定了。如果txt里是0 0.5 0.5 0.3 0.3,代表类别0;如果你把names改成了['smoke', 'person', 'fire'],那类别0就变成了smoke,所有标注全错位了。
解决:拿到数据集后先看data.yaml的names顺序,然后抽查3~5个txt文件,确认0对应的是不是person。我一般在训练前写一个10行的小脚本检查一遍,绝不省这一步。
4.2 图像尺寸不一致导致的归一化计算偏差
现象:训练正常,但验证时mAP比预期低很多,而且小目标的recall特别差。
原因:如果数据源自带的图像分辨率不统一——比如一部分是1920×1080的截图,一部分是540×960的手机图——而标注的归一化坐标是按各自原图尺寸算的,本身没问题。但如果有人用脚本批量改图尺寸时没有同步更新标注,那就有大麻烦了。我之前就遇到过,图像被resize到640×640,但txt里的坐标还是按1920×1080归一化的,目标框全偏了。
解决:用Python批量检查每个txt里的坐标值是否在0~1之间,同时把标注框画回图像上看是否贴合目标。如果发现越界值,需要重新导出标注,而不是手动改数值。
4.3 解压后路径带空格导致训练崩溃
现象:Windows上训练时报错No such file or directory,但路径明明存在。
原因:zip解压时文件夹名字里带了空格(比如Fire Dataset v1),而Ultralytics在解析路径时对空格处理不友好,或者命令行里没加引号导致路径被截断。
解决:解压后第一件事就是把顶层目录改成全英文无空格的短名称(比如fire_dataset),训练命令里的路径加上引号或者干脆用绝对路径:
cd /e/projects/yolo && yolo detect train data=/e/projects/yolo/fire_dataset/data.yaml model=yolov8n.pt epochs=30这个坑在Windows上尤其常见,Linux/Mac上概率低一点,但养成好习惯直接改,后面部署也省心。
4.4 xml和txt数量不一致:训练时莫名其妙丢样本
现象:训练日志里显示train: 2400 images,但images目录里明明有2430张图。
原因:部分图像没有对应标注文件,或者标注文件损坏(xml里缺少<bndbox>节点、txt文件是空的)。Ultralytics会自动跳过没有标注的图像,但这个行为很容易被忽略。
解决:训练前跑一遍计数脚本,对比images和labels目录下的文件总数,找出缺失的样本。如果差距不大(比如3~5张),可以直接让模型跳过;如果差几十张,就要检查是不是转换过程中丢了一部分xml。
4.5 用YOLOv11训练时遇到旧格式兼容问题
现象:加载权重时报KeyError或者维度不匹配。
原因:YOLOv11的模型结构做了调整,如果你想用它跑这套旧数据集,需要确认预训练权重是v11版本的,以及data.yaml里的nc是否和权重匹配。另外,v11对txt格式的容错性更好,但如果坐标有极小越界值(比如-0.001),部分老版本(v5/v7)会报错但v11会默默接受,这会导致同一份数据在不同版本上的表现不一致。
解决:统一用一套标准检查脚本,任何坐标值超出[-0.001, 1.001]都强制截断。我在处理这套数据时就发现了几百个坐标略大于1的值,截断后重新保存,所有版本都能正常训练。
5. 数据增强策略和置信度阈值:把这张数据集的性能再往上顶一截
5.1 针对烟雾和火焰的增强参数设置
这套数据里最难检测的目标是烟雾——轮廓模糊、边缘透明、形状高度多变。如果直接用默认增强参数,模型对烟雾的召回率往往不理想。我一般会在训练配置里额外开启mosaic=1.0和mixup=0.2,同时对烟雾类别的目标做针对性增强。
YOLOv8的增强参数可以直接写在训练命令里:
yolo detect train data=fire_dataset/data.yaml model=yolov8s.pt epochs=80 imgsz=640 batch=16 mosaic=1.0 mixup=0.2 hsv_h=0.015 hsv_s=0.7 hsv_v=0.4参数说明:mosaic=1.0表示每次迭代都用马赛克增强(4张图拼成一张),这对小目标(远处的火焰、被遮挡的人)提升明显。mixup=0.2意味着20%的概率对两张图做融合,增加背景多样性。hsv_*三个参数控制颜色扰动幅度——烟雾的灰度范围广、火焰的色温变化大,适度增加色域扰动能提升模型对光照变化的鲁棒性。我自己试过,增大这些值之后mAP50能提升大约1.5个点,但注意不要超过上述值,否则模型会“学”到错误颜色模式。
注意mosaic默认就是开的,但如果你数据量只有3000张,建议维持开启状态。另外,如果验证集包含大量密集人群场景,mosaic对这类数据效果一般,反而可能破坏原始空间关系。
5.2 类别不平衡的应对策略
火灾数据有个典型特点——smoke目标往往占大面积、但数量少;person目标数量多、但尺寸小。训练时模型会被person主导,导致smoke类别的loss权重被稀释。
我一般在训练后检查每个类别单独的AP值:
yolo detect val data=fire_dataset/data.yaml model=runs/detect/train/weights/best.pt然后看输出里的Class列,如果fire类的AP明显低于其他类(比如差了10个点以上),就做两件事:一是把fire和smoke类别的图像做离线复制增强(旋转、翻转、加噪声),二是在损失函数里为少数类增加权重。YOLOv8没有直接的类别权重参数,但你可以用--loss_weights或者修改配置文件的方式来做,实操中最常见的做法是复制图像。
复制增强的脚本参考:
import cv2 import os def augment_fire_images(img_path, label_path, out_img_dir, out_label_dir, copies=3): img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path) as f: lines = f.readlines() for i in range(copies): aug_img = img.copy() if i == 0: aug_img = cv2.flip(img, 1) elif i == 1: matrix = cv2.getRotationMatrix2D((w//2, h//2), 15, 1.0) aug_img = cv2.warpAffine(img, matrix, (w, h)) # 保存增强图像和原标注(注意旋转后需要对框坐标做几何变换,示例略) base = os.path.basename(img_path).split('.')[0] cv2.imwrite(f'{out_img_dir}/{base}_aug{i}.jpg', aug_img) new_lines = [] for line in lines: parts = line.split() cls = parts[0] xc, yc, bw, bh = map(float, parts[1:]) if i == 0: # 水平翻转 xc = 1.0 - xc new_lines.append(f'{cls} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}\n') with open(f'{out_label_dir}/{base}_aug{i}.txt', 'w') as f: f.writelines(new_lines)参数说明:copies=3表示每张火灾图像生成3份增强副本,提高fire样本占比。翻转和旋转后的坐标变换必须同步进行,否则框就飘了——特别是水平翻转时x_center变成1.0 - xc,这一点很容易漏掉。增加副本后记得重新划分train/val/test,避免增强副本同时出现在训练集和验证集导致数据泄露。
5.3 推理端置信度阈值的验证建议
训练完成后,实际部署时的置信度阈值选择有玄学成分。这套数据里,person的目标通常框得很实在,阈值设0.35就够;但fire的目标因为火焰形状不规则,置信度往往在0.2~0.3之间波动。如果你用默认0.25阈值,会漏掉相当一部分真实火焰。
我在现场部署时一般是双阈值策略:人员检测用0.4(宁可漏检不可误报),烟雾和火焰用0.15(宁可误报不可漏报)。这个策略能否用同一套权重实现,取决于你的后处理逻辑——可以在推理代码里对类别做差异化阈值判断:
from ultralytics import YOLO model = YOLO('best.pt') result = model('test_imgs/fire_day_001.jpg', conf=0.15) for box in result[0].boxes: cls = int(box.cls[0]) conf = float(box.conf[0]) if cls == 0 and conf < 0.4: continue # 人员检测,低置信度直接放弃 elif cls in [1, 2] and conf >= 0.15: print(f'Fire/Smoke detected: {box.xyxy[0].tolist()}')实际部署时,烟雾的误报率会很高,因为雾天、水蒸气都有可能被模型当成烟。这种问题不能靠调阈值解决,需要加时序滤波——多帧确认。我在做火灾预警项目时的习惯是:单帧检出fire类别的置信度超过0.3就报警,低于0.2且连续3帧检出才报警,这样能抑制大部分误报。从那以后我每次部署这类模型前,都会先跑一遍不同阈值下的精准率和召回率曲线,再根据业务容忍度选阈值,模型本身训得再好,部署策略不对照样会被现场环境击穿。希望帮到你。
本文还有配套的精品资源,点击获取