资讯动态

YOLO火灾人员检测训练指南:数据标注、标签校验与模型调优

发布时间:2026/9/15 4:43:38 来源:尧图企业网站定制
简介面向火灾烟雾与人员检测场景的YOLO目标检测数据集覆盖人、烟、火三类目标适合需要快速训练YOLOv5/v7/v8/v9/v10/v11模型的算法开发者和安防、消防类项目团队。压缩包共约2000个文件整体大小141.83MB标注文件同时提供VOC格式XML与YOLO格式TXT两套标签并已划分好训练集与验证集内置data.yaml配置可直接导入主流YOLO框架省去自行采集、标注和格式转换的重复工作。每个标注框采用归一化坐标记录TXT格式为“类别 中心点x 中心点y 宽度 高度”便于直接加载训练和模型评估。从资源结构看文件命名清晰便于快速定位特定样本适合火灾预警、人员检测等项目的快速迭代。目前已有46人学习下载资源对初学者和工程人员都较为友好下载后即可基于自带标签开展模型的训练、验证与测试。1. 用YOLO训练火灾人员探测模型数据格式和标签质量决定大半效果用YOLO训练火灾人员探测模型时比背网络结构更影响结果的是数据格式和标签质量。实际项目里我在一个监控场景调检测器火焰识别效果尚可烟雾几乎全漏最后定位到标签文件里存在大量空白txt和归一化坐标越界的问题。这份数据集围绕“人在火灾现场”这一场景包含3039张带标签图像覆盖人、烟、火三类目标同时提供yolo格式txt和voc格式xml两套标注。适合正在做智慧消防、工地烟火告警以及准备用yolov5、yolov7、yolov8、yolov9、yolov10、yolo11快速验证模型效果的工程师。2. 数据集结构yolo格式txt与voc格式xml的双轨设计2.1 目录与文件组织解压后你会看到数据集并不是一张图像配一套标签而是两套独立的标注文件并行。项目正文里的img_0398_1633.xml这类文件属于voc格式那套同名的img_0398_1633.txt则放在yolo格式那套。两套标签描述的都是同一批图像区别在于坐标组织方式和归一化基准。采用双格式的好处是用yolov5、yolov8等原生框架训练时直接指向txt目录需要转成mmdetection、detectron2或者做自定义数据增强时从xml里解析object结构更完整不容易丢多边形或属性信息。我一般拿到这类数据集先做一次文件数量统计确认图像、txt、xml三者数量一致再进入训练。# 统计图像文件数 find . -type f -name *.jpg | wc -l # 统计yolo格式txt文件数 find . -type f -name *.txt | wc -l # 统计voc格式xml文件数 find . -type f -name *.xml | wc -l这里wc -l前一步用find过滤后缀不会把标签目录里的文件混进来。如果txt和xml数量不等于图像数量先不要急着训练大概率是某个xml没有转换出对应txt或压缩包传输出问题时丢了文件。如果图像后缀是png而不是jpg把第一行-name *.jpg改成-name *.png即可。注意不要通过修改data.yaml去强行对齐缺失的标签因为训练时读到空标签会直接跳过图像造成类别统计失真。提示数据集虽然已经划分好但仍建议重新校验一次。跨平台解压时文件名可能被截断尤其是Windows下中文路径配合长文件名容易让img_0398_1633.xml变成img_0398_1633.xml.txt。2.2 yolo格式txt标签解析与归一化坐标yolo格式的定义是一行一个目标class x_center y_center width height。四个坐标值全部除以图像宽高做归一化取值范围在0到1之间。例如一行0 0.5168 0.4821 0.2045 0.1783表示某个目标的中心点在图像宽度的51.68%、高度的48.21%位置框宽为图像宽度的20.45%框高为图像高度的17.83%。字段含义取值范围class类别索引从0开始0、1、2x_center框中心点x的归一化坐标0.0 ~ 1.0y_center框中心点y的归一化坐标0.0 ~ 1.0width框宽的归一化值0.0 ~ 1.0height框高的归一化值0.0 ~ 1.0还原成像素坐标时需要从上一张图的尺寸中得到真实宽高。下面的函数把一行yolo标签转成x1, y1, x2, y2形式import cv2 def yolo_to_xyxy(path_txt: str, img_w: int, img_h: int): 将yolo格式txt转为像素级xyxy边界框。 path_txt: yolo格式标签文件路径 img_w: 图像宽度像素 img_h: 图像高度像素 boxes [] with open(path_txt, r) as f: for line in f: # 每行 class x_center y_center width height cls_id, xc, yc, w, h map(float, line.split()) x1 int((xc - w / 2) * img_w) y1 int((yc - h / 2) * img_h) x2 int((xc w / 2) * img_w) y2 int((yc h / 2) * img_h) boxes.append((int(cls_id), x1, y1, x2, y2)) return boxes这里的关键是还原顺序x1用中心点减去半宽x2用中心点加上半宽方向不能写反。使用int()做截断会让右下角坐标偏小1像素对目标检测训练影响极小但如果后续要配合分割mask或标注工具比对建议改用round()因为负坐标会被int()错误截断为0。2.3 voc格式xml标签解析与转换voc格式把标注信息拆进标签节点中典型的img_0398_1633.xml结构如下annotation filenameimg_0398_1633.jpg/filename size width640/width height480/height /size object namesmoke/name bndbox xmin120/xmin ymin80/ymin xmax340/xmax ymax260/ymax /bndbox /object /annotationname是类别名称可能是person、smoke或fire。转换成yolo txt时需要用一个字典把名称映射到整数索引0/1/2。这个映射关系从data.yaml中读取不要硬编码到脚本里否则类别顺序一变训练数据就全乱了。下面代码完成xml到yolo的转换并在归一化前把越界框限制在0到1之间import xml.etree.ElementTree as ET def xml2yolo(xml_path: str, out_txt: str, names_map: dict): 将voc xml转为yolo txt。 names_map: {person:0, smoke:1, fire:2} root ET.parse(xml_path).getroot() # 读取图像原始尺寸 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] # 遍历每个object节点 for obj in root.iter(object): name obj.find(name).text cls_id names_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) # 中心点和宽高归一化 cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 越界裁剪 cx, cy min(max(cx, 0), 1), min(max(cy, 0), 1) w, h min(max(w, 0), 1), min(max(h, 0), 1) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines))这段代码使用了ET.parse和root.iter(object)比findall更安全因为iter会遍历所有子层级避免嵌套结构漏读。一个常见问题是xml的filename节点与实际文件名不一致因此转换时不要依赖该字段直接根据传入的xml_path推导输出txt路径。同一张图有多个object时循环会写入多行对应yolo txt中的多个目标。2.4 data.yaml类别配置数据集根目录的data.yaml是yolov5/v8/v9等框架统一读取的入口。它的大致内容如下# 数据集根路径 path: /home/you/fire_smoke_yolo # 训练集相对path的目录 train: images/train # 验证集相对path的目录 val: images/val # 类别索引和名称映射 names: 0: person 1: smoke 2: fire实际类别顺序以解压后文件为准可能是其他顺序。path字段是框架拼接路径的根train和val都相对于它。如果train写成images/train而你的结构是train/images训练时会直接报路径不存在。保险的方法是训练前先打印一次python -c import yaml; dyaml.safe_load(open(data.yaml)); print(d[path]); print(d[train]); print(d[val])确认输出路径确实指向图像目录而不是标签目录。这个简单的print能省掉后面一长串FileNotFoundError排查时间。3. 训练前校验用Python脚本检查标签、可视化与划分验证3.1 标签与图像配对校验YOLO系列算法目标检测训练对标签格式极其敏感一个小数点错位就会导致NaN loss。在开始训练之前我会先做一个三重校验图像与txt配对、txt是否为空、xml是否同步存在。下面脚本检查训练集目录中的配对情况from pathlib import Path img_dir Path(images/train) txt_dir Path(labels/train) ok [] missing_txt [] empty_txt [] # 遍历每个训练图像 for img_path in img_dir.glob(*.jpg): txt_path txt_dir / (img_path.stem .txt) if not txt_path.exists(): missing_txt.append(str(img_path)) else: # 标签文件存在但没有任何目标行 if not txt_path.read_text().strip(): empty_txt.append(str(img_path)) else: ok.append(str(img_path)) print(paired:, len(ok)) print(missing txt:, len(missing_txt)) print(empty txt:, len(empty_txt))把这段脚本分别跑train和val目录。missing_txt对应有图无标empty_txt是标注文件存在但没有任何目标。这两类问题会导致训练时读到空张量或者模型被迫把空标签当负样本最终mAP偏低。我处理过一批数据里面有几十张空txt直接训练后person类recall掉了8个百分点后来把空文件对应的图像从训练集中剔除才恢复。3.2 可视化检查bounding box数字校验通过不代表标注语义正确。烟这类目标形状不规则标注员很容易把整片烟雾框进大框里或者只标了浓烟中心导致模型学习到错误的尺寸分布。因此需要把框画到图像上肉眼看一遍。下面函数可以批量生成可视化结果import cv2 def draw_boxes(img_path: str, txt_path: str, class_names: list): 将yolo格式标签绘制到图像上。 class_names: [person, smoke, fire] img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: cls_id, xc, yc, bw, bh map(float, line.split()) # 还原像素坐标 x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, class_names[int(cls_id)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return img可视化重点看三类问题边界框是否跑出图像边界、宽高是否变成负数或0、烟和火的框之间是否存在大量重叠导致一个框吃掉另一个目标。如果框整体贴边检查图实际分辨率和xml或txt里隐含的宽高是否一致很多火灾监控图像是1920x1080但标注软件默认按640x480导出转换出的yolo比例会整体偏移。3.3 类别分布与样本统计火灾场景中大多数监控图像可能只有烟没有火也可能全是人类别不均衡是常态。统计每类目标框数量能帮助决定是否需要调整损失权重或做重采样。下面脚本遍历所有txt统计类别索引出现次数from collections import Counter from pathlib import Path txt_dir Path(labels/train) cls_counter Counter() # 遍历每个yolo标签文件 for txt_path in txt_dir.glob(*.txt): for line in txt_path.read_text().strip().splitlines(): if line: # 每行第一个数字是类别索引 cls_id line.split()[0] cls_counter[cls_id] 1 print(cls_counter)一个健康的火灾人员探测数据集中person、smoke、fire三类框数比例不宜超过10:1。如果fire只有几百个框而person有两万个那么模型大概率会把fire当成背景负样本。这时需要调整训练策略一种做法是复制fire样本做重采样另一种是使用yolov5命令行里的--cls权重。统计结果可以整理成类似下表的形式但具体数字以你解压后跑出来的为准类别框数图像占比person1280078%smoke960055%fire210018%如果某一类框数占比过低后面训练时优先考虑调高对应类的loss权重或者对包含该类的图像做额外的mosaic增强而不是简单增加epoch。3.4 训练集/验证集划分验证数据集虽然已经划分好仍要验证train和val之间是否有文件名重叠。火灾监控视频经常出现连续帧同一场景被不同文件名记录如果这些相近帧同时出现在训练集和验证集验证mAP会虚高无法反映真实泛化能力。先做简单重名检查# 提取训练集文件名去掉后缀 ls images/train | sed s/\.[^.]*$// | sort train_names.txt # 提取验证集文件名 ls images/val | sed s/\.[^.]*$// | sort val_names.txt # 找两个文件共有的行 comm -12 train_names.txt val_names.txt | head -20sed去掉后缀sort保证顺序一致comm -12输出共有行。没有输出代表文件名编码完全独立。但文件名不同不代表内容不相似连续视频帧往往放在不同文件中。如果时间允许可以随机从train和val各抽50张图计算感知哈希或直方图相似度把相似度高于0.9的验证集图片剔除。注意剔除验证集图片后需要同时删除对应的yolo txt避免训练脚本报标签不匹配。4. 基于yolov8的训练配置与参数调优4.1 组织数据目录训练开始前把数据集整理成yolov8标准结构。虽然data.yaml可以指定任意路径但统一后的结构让多人协作时不用反复改配置。推荐目录形如fire_smoke_yolo/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamllabels目录里放yolo格式txt。如果xml标签在独立文件夹可以建立一个软链接而不是复制避免复制导致路径依赖错乱ln -s /path/to/xml_labels fire_smoke_yolo/xml_labelsdata.yaml的path字段要改成当前机器上的绝对路径train和val写成相对path的路径。例如path: /home/you/fire_smoke_yolotrain: images/train。yolov8会把两者拼接成/home/you/fire_smoke_yolo/images/train所以不要在path里额外带上images。4.2 训练命令解析整理好后直接运行yolov8训练# 使用yolov8s预训练权重微调 yolo detect train datadata.yaml modelyolov8s.pt \ epochs150 imgsz640 batch16 \ projectruns/fire_smoke nameyolov8s这条命令会做三件事加载yolov8s.pt的coco预训练权重冻结大部分backbone的初始层根据data.yaml里的names重新初始化输出分类头按指定imgsz和batch在GPU上迭代。选择yolov8s是性价比折中它的mAP比n型号高3到5个点显存占用又比m型号小很多。如果GPU显存只有8Gbatch开到16并保持默认AMP混合精度只有4G显存时建议换成yolov8n并把imgsz降到480。4.3 关键超参数表训练中影响收敛最明显的不是学习率而是输入尺寸和数据增强。火灾图像里的烟常是小目标在640输入下一个30x30像素的烟框只占30个像素经过32倍下采样后特征图上不足1个像素几乎不可见。下面是常用参数参考参数推荐值作用适用场景imgsz640输入边长图像分辨率高可升到768batch16梯度更新批大小显存不够降到8epochs150总训练轮次小数据集150~300有效patience20早停等待轮次过拟合时调小mosaic1.0四图拼接增强烟小目标建议保留close_mosaic10最后10轮关闭mosaic防止尺寸分布失真lr00.01初始学习率数据量小时降到0.005mosaic是yolov8里效果最强的数据增强它把四张图拼接成一张训练样本被切割的目标框跨接缝让模型学会识别截断的小目标。但它会人为增加小目标比例导致模型对大目标不敏感因此要在最后10轮通过close_mosaic10关闭让其恢复到正常尺寸分布。lr0调低能缓解loss抖动但会拖慢前期收敛等后面发现loss完全不再下降时再手动调低比一开始就设小更合理。4.4 类别不平衡与损失调整火灾数据集中最常见的失衡是person多、fire少。yolov8的损失由box、cls、dfl三部分组成Ultralytics默认配置中box权重约为7.5cls约为0.5dfl约为1.5。当fire类框数不足person类的十分之一时cls损失会被person主导模型倾向于把所有带火特征的目标都预测为背景。一种有效的处理方式是训练时对fire样本做重采样让每个epoch中fire出现的概率接近person。可用简单的计数来生成采样权重from collections import Counter # 统计每个类别的目标数量 counts Counter() for txt in txt_dir.glob(*.txt): for line in txt.read_text().strip().splitlines(): if line: counts[line.split()[0]] 1 # 生成每个类别的采样权重数量越少权重越高 max_count max(counts.values()) sample_weights {k: max_count / v for k, v in counts.items()} print(sample_weights)生成权重后把它交给自定义的DataLoader做WeightedRandomSampler。注意每个epoch的随机复制会让验证集mAP偏高最终评估时仍要用原始分布做一次测试。如果不想改采样逻辑改用yolov5训练会更直接命令行原生提供--cls和--obj参数# 使用yolov5的类别loss权重调整 python train.py --data data.yaml --weights yolov5s.pt \ --cls 0.7 --obj 0.5 --img 640 --batch 16--cls 0.7让分类错误产生更大梯度对fire这类稀少类帮助明显但注意不要超过1.0否则原本区分不清的烟雾背景会被大量误报。训练输出中的best.pt基于验证集mAP选择通常比last.pt更可靠。训练完成后建议立刻在验证集上跑一次单类评估yolo detect val datadata.yaml modelruns/fire_smoke/yolov8s/weights/best.pt观察日志中per-class mAP50如果fire类低于0.5后面推理阶段就要从置信度阈值和输入切片方向去补而不是继续盲目加epoch。5. 推理验证与常见坑小目标烟、半透明火与误检优化技巧5.1 推理置信度阈值调整训练完模型后直接用best.pt在验证集上输出检测图# 使用best.pt推理降低置信度阈值 yolo predict modelruns/fire_smoke/yolov8s/weights/best.pt \ sourceimages/val saveTrue conf0.1conf0.1是低置信度阈值用于观察模型能召回多少目标。person类通常产生大量0.5以上的框而fire类往往集中在0.3到0.5之间。如果按默认conf0.25一刀切fire类会被大量过滤。火灾告警场景中漏检比误检更危险我通常把置信度压到0.1再在下游业务逻辑里通过同一目标跨帧持续出现来去抖。5.2 针对烟雾漏检的输入切片推理YOLO模型在640x640输入下对图像中央的大块烟雾效果尚可但对角落里小范围火苗和稀薄烟几乎无效。更可靠的做法是把1920x1080图像切成多个640x640 patch每个patch独立推理最后合并结果。常见做法是使用开源的slice-inference库# 使用切片推理库对超大图做patch级检测 from slice_inference import SliceInference detector SliceInference( model_pathruns/fire_smoke/yolov8s/weights/best.pt, slice_size640, overlap0.5, conf_threshold0.1, ) results detector.predict(images/val/img_0398_1633.jpg)slice_size640保持模型最优输入overlap0.5让patch之间有50%重叠避免目标刚好被切在边界上。合并阶段每个patch产生的框要加上该patch的左上角坐标偏移然后再做NMS。这里要注意NMS的IoU阈值火灾场景中多个小火点可能挨得很近阈值设0.5会合并两个独立火点设0.3更安全。5.3 域差异用随机初始化对比coco预训练如果你一直在用coco预训练权重微调却感觉fire和smoke的mAP上不去可以试试随机初始化对比实验。coco数据集里有person类却没有smoke和fire类预训练特征里对火焰纹理几乎没有先验。此时用一个从头训练的yolov8s.yaml跑250个epoch往往fire类mAP比微调更高。训练命令只需要把model换成yaml文件yolo detect train datadata.yaml modelyolov8s.yaml \ epochs250 imgsz640 batch16modelyolov8s.yaml指示框架跳过加载预训练权重完全随机初始化。对比两轮训练输出中的per-class mAP50如果随机初始化在fire类上明显更好说明coco特征空间与火焰场景差异过大后续应改为在自采数据上做自监督预训练或者直接用best.pt作为新数据的起点。再把best.pt的结果和voc xml原始标注做一次边界框对齐检查确认fire类检测框中心点落在bndbox内部基本就能定位是域差异问题还是标注本身有偏移。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价