YOLOv8火灾烟雾人员检测:数据集标签质量与训练调优实战

发布时间:2026/9/14 2:18:49
YOLOv8火灾烟雾人员检测:数据集标签质量与训练调优实战
简介YOLO系列算法的火灾与人员探测数据集面向目标检测开发者和算法学习者主要解决智能监控、消防预警、应急巡查等场景下的人、烟、火三类目标识别问题。压缩包内共2000个标注文件包含VOC格式的XML与YOLO格式的TXT两种标签每个目标框均记录类别索引与归一化中心点、宽高坐标并划分好训练集与验证集压缩包整体141.83MB便于直接投入YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等主流版本的训练与测试。数据集附带data.yaml配置文件省去自行整理路径和类别映射的步骤同时标签文件名末尾带有类别关键词便于快速了解目标类型方便人工抽检与二次清洗。目前已有46人学习下载适合正在备赛、做毕业设计或需要快速验证目标检测效果的开发者可帮助缩短数据预处理时间、专注算法调优与模型部署。1. 火灾和人员探测数据集3039张带标签图像人、烟、火三类怎么选做火灾检测的同行都有体会公开数据集里要么是纯火苗特写要么是实验室环境下的烟雾图真正贴近监控视角、包含人员、烟雾、火焰三类目标且带干净标签的很少。这个标题里的数据集一共3039张图像标注按人、烟、火三分类给出图像量和类别粒度都处于一个适合算法调优的量级——既不像几万张的COCO那样训练周期长也不是几百张那种过拟合到没法看。实际用YOLO算法跑这类任务时模型结构往往不是瓶颈数据集的标签质量和类别平衡才是决定mAP上限的关键。这篇就沿着数据体检、YOLOv8训练、烟火目标调优、验证与部署这条线把一套能落地的流程讲透适合正在做消防预警、安防监控、无人机巡检或相关算法POC的人参考。2. 拿到zip先别训练数据集结构与标签体检2.1 先解压并核对目录与类目顺序常见的YOLO格式数据集压缩包解压后通常长这样unzip fire_person_dataset.zip -d ./fire_data cd ./fire_data tree -L 2fire_data/ ├── train/ │ ├── images/ │ └── labels/ ├── valid/ │ ├── images/ │ └── labels/ └── test/ ├── images/ └── labels/train、valid、test三个目录都各自包含images和labels子目录标签文件是txt格式与图像文件同名。先不要急着跑训练脚本第一步应该在打开几个标签文件确认类别id的真实含义cat train/labels/000001.txt输出可能是0 0.44140625 0.5234375 0.072265625 0.111328125 1 0.6822916667 0.2916666667 0.060546875 0.08203125 2 0.8359375 0.4453125 0.0390625 0.06640625这里每一行的第一个数字是类别id。绝大多数火灾数据集习惯把“人”放第0类但也有一些来自Roboflow或自标注平台转换的数据集把“烟”设为0把“火”设为1。如果类目顺序和你的预期不一致直接套用预训练模型做迁移学习时分类头会错位轻则训练混乱重则推理结果完全没法用。我一般会先写一个一行命令统计标签类别分布cat train/labels/*.txt | awk {print $1} | sort | uniq -c看到三个类别0、1、2的数量比例接近时说明数据基本平衡如果某一类的数量远少于其他类后面就要考虑做类别加权或针对性增强。2.2 YOLO标签格式的四个归一化坐标YOLO系列的txt标签格式是固定的和PascalVOC的XML、COCO的JSON格式都不同。每行内容为类别id x_center y_center width height后四个数值全部基于图像宽度和高度做了归一化取值范围在0到1之间。列一个字段说明表字段含义取值范围典型值第1列类别id从0开始0 ~ 类别数-10代表人1代表烟2代表火第2列目标中心的x坐标除以图像宽度0.0 ~ 1.00.44第3列目标中心的y坐标除以图像高度0.0 ~ 1.00.52第4列目标框宽度除以图像宽度0.0 ~ 1.00.07第5列目标框高度除以图像高度0.0 ~ 1.00.11理解归一化坐标还有一个实际用处当你做图像超分辨率重建或裁剪增强时标注必须跟着图像尺寸同步变换。比如把原图放大2倍归一化坐标值不用改但一旦你做了crop就必须按裁剪区域重新计算每个目标框的坐标和面积占比否则训练时损失函数计算出的IoU全是错的。2.3 用脚本做一次标签体检写一段脚本把整个数据集的标签质量过一次重点查三类问题坐标越界、标签与图像文件名错位、空标签文件。import os from PIL import Image IMG_DIR train/images LBL_DIR train/labels IMG_EXTENSIONS {.jpg, .jpeg, .png, .bmp, .webp} bad_files [] img_name_set set() label_count {} for fname in os.listdir(IMG_DIR): stem, ext os.path.splitext(fname) if ext.lower() in IMG_EXTENSIONS: img_name_set.add(stem) for lbl_name in os.listdir(LBL_DIR): stem, ext os.path.splitext(lbl_name) if ext ! .txt: continue # 检查同名图像是否存在 if stem not in img_name_set: bad_files.append((image_missing, lbl_name)) continue lbl_path os.path.join(LBL_DIR, lbl_name) img_path None for ext in IMG_EXTENSIONS: candidate os.path.join(IMG_DIR, stem ext) if os.path.exists(candidate): img_path candidate break if img_path is None: bad_files.append((image_file_not_found, lbl_name)) continue img Image.open(img_path) W, H img.size with open(lbl_path, r) as fp: lines fp.readlines() if len(lines) 0: bad_files.append((empty_label, lbl_name)) continue for idx, line in enumerate(lines): parts line.strip().split() if len(parts) ! 5: bad_files.append((bad_line_format, f{lbl_name}:{idx1})) continue cls_id int(parts[0]) xc, yc, w, h map(float, parts[1:]) label_count[cls_id] label_count.get(cls_id, 0) 1 # 归一化坐标不应越界 if xc 0 or xc 1 or yc 0 or yc 1 or w 0 or h 1: bad_files.append((coord_out_of_range, f{lbl_name}:{idx1})) # 边界框预测目标中心应落在图像内 cx, cy xc * W, yc * H if cx 0 or cx W or cy 0 or cy H: bad_files.append((center_out_of_image, f{lbl_name}:{idx1})) # 宽度高度占比与实际匹配粗略检查 if w * W W or h * H H: bad_files.append((box_larger_than_image, f{lbl_name}:{idx1})) print(各类别统计:, label_count) print(异常数量:, len(bad_files)) for item in bad_files: print(item)这段脚本做了四层检查文件名是否一一对应、是否有空标签、是否格式错位、归一化坐标与边界框是否合理。值得注意的是中心点在图像内但边界框超出图像范围的情况在火灾数据集里很常见因为烟雾是扩散状的标注员把边缘烟雾标进去时不自觉把框拉到了画幅外。遇到这种样本可以保留中心点在图像内、框面积超过图像但交并比损失仍能正确计算的标签也可以直接删掉这种框取决于你要不要利用画幅外的上下文语义。建议优先保留下载时的原始标签只把“中心点落在图片外”的样本过滤掉。3. 用YOLOv8训练自己的数据集配置、命令与关键参数3.1 数据集YAML配置与类名映射YOLOv8训练自定义数据集需要一个data.yaml文件。针对这个火灾人员探测数据集可以写成path: /home/user/fire_data train: train/images val: valid/images test: test/images names: 0: person 1: smoke 2: fire这里要注意path建议用绝对路径。用相对路径时YOLOv8的解析逻辑以当前工作目录为基准如果你在别的目录启动训练脚本经常会出现dataset not found的报错。另外train和val指向的是images目录而不是labels目录框架会自动在同级目录下找labels文件夹。3.2 训练集划分别把同一场景的连续帧切到两个集合标题里说3039张图像如果下载的压缩包已经是train/valid/test拆分好的直接用即可。但如果给你的是全部图像加标签的混合目录需要自己划分时务必注意火灾视频抽帧出来的数据相邻帧之间的背景高度相似随机打乱切分会导致同一场景的帧同时出现在训练集和验证集里val的mAP虚高很多。正确的做法是先按文件名前缀分组再把整组分配到train或valimport os import random from collections import defaultdict import shutil IMG_DIR all_images LBL_DIR all_labels TRAIN_RATIO 0.85 # 假设文件名格式为 scene001_frame0001.jpg groups defaultdict(list) for fname in os.listdir(IMG_DIR): if fname.endswith(.jpg): scene_id fname.split(_)[0] groups[scene_id].append(fname) random.seed(42) scene_ids list(groups.keys()) random.shuffle(scene_ids) train_size int(len(scene_ids) * TRAIN_RATIO) train_scenes set(scene_ids[:train_size]) for i, (scene_id, files) in enumerate(groups.items()): split_dir train if scene_id in train_scenes else val for fname in files: stem os.path.splitext(fname)[0] shutil.copy(os.path.join(IMG_DIR, fname), f{split_dir}/images/{fname}) shutil.copy(os.path.join(LBL_DIR, stem .txt), f{split_dir}/labels/{stem}.txt)监控摄像头固定机位下火灾可能从一个小火苗慢慢蔓延到整个画面前几帧的火焰面积远小于后几帧。如果按帧随机划分模型会从训练集学到“小火苗”又从验证集看到同一个场景同一时间段的大火苗导致val loss波动巨大。按照场景分组划分更贴近真实部署场景——模型要面对的是没见过的视角和没见过的时间段。3.3 训练命令与关键参数表基于YOLOv8最小可用的训练命令是yolo detect train \ datafire_person.yaml \ modelyolov8s.pt \ imgsz640 \ epochs100 \ batch16 \ patience20 \ optimizerAdamW \ lr00.001 \ augmentTrue从yolov8s.pt开始训练是迁移学习的做法。COCO上预训练好的特征提取器对边缘、纹理、颜色响应的初始化较好火灾场景虽然不在COCO类别里但低层特征的复用价值很高。如果直接从yolov8n.pt开始且数据量又小容易在训练初期就出现loss震荡。以下参数在这个数据集上值得手动调参数推荐值说明imgsz640或768火焰小目标多时用768但显存占用按平方增长epochs100起步看val loss是否还有下降趋势不要机械拉长batch能放进显存的最大值显存不足时优先降imgsz不要过度降batchpatience15~20验证集指标连续不提升时提前停止mosaic1.0最后10轮设0小目标容易在mosaic中裁掉最后阶段关闭可稳定精调hsv_h / hsv_s0.015 / 0.7烟火颜色受光照影响大适当的颜色增强提升泛化fliplr0.5垂直翻转水平翻转会改变火焰上升方向不建议对火使用copy_paste0.3将烟/火目标粘贴到其他背景上缓解小目标样本不足训练结束后模型权重保存在runs/detect/train/weights/best.pt。在火灾检测这类样本量较小的场景里不要只看best.pt的val mAP还要对比last.pt如果两者差距很小说明训练收敛平稳如果best和last的mAP差了2个点以上说明验证集划分可能偏小或数据分布不均。3.4 训练日志里值得盯的三个信号终端输出的loss分成box_loss、cls_loss、dfl_loss。经常出现的异常是box_loss一直降cls_loss却卡在某个平台期。这种情况在这类人员/烟火数据集上尤其常见——因为“人”这个类别外型差异小但尺度变化大而“烟”恰好相反半透明、无固定轮廓、颜色从白到黑都有。cls_loss不降说明分类头没有把烟和普通雾气、水蒸气区分开。常见的应对是增加一个epoch的Warmup或把cls损失的系数调大一点。YOLOv8里可以通过改loss系数实现但更省事的做法是回到数据层面专门给烟这个类别加样本或做裁剪增强。还有一种做法是换yolov8m甚至yolov8l模型把模型容量提上来但火灾数据集通常只有几千张大模型很容易在烟这个类别上过拟合反倒是s和m这两个档位处于平衡点。4. 误检与漏检重灾区烟火的形态学特征与增强策略4.1 烟火检测最难处理的三种样本形态用YOLO算法跑通一次训练并不难真正让工程落地困难的场景集中在三类样本上。第一类是半透明烟雾与背景的边界。烟雾没有锐利边缘标注员在画框时本身就带有主观性同一个烟团两个人标出来的框可能差出30%的面积。这类标签噪声属于“固有噪声”模型学到的是标注框的重心位置而不是真实的烟雾边界所以你在验证集上看到的定位误差统计会明显偏大。第二类是灯光、云层、白色建筑反射与火焰/烟雾的混淆。傍晚的阳光反射、路灯下的暖黄色光晕、监控画面里的白色水汽在YOLO的特征空间里往往距离火焰很近。这不是靠改模型结构能完全消除的需要对训练集做负样本增强把这类易混淆图片作为背景图或粘贴源加入训练。如果数据包里没有提供负样本可以自己从监控视频中截取包含灯光/云层的画面放到train/images下且不配标签文件让模型学会“这些不是目标”。第三类是真正的早期小火苗面积可能只有整张图的0.5%甚至更小。YOLOv8在640分辨率下8倍下采样后原图中16×16像素以下的目标在feature map上只剩2×2个格点特征响应极弱。针对这类小目标有一个实用的前置手段是对标签面积占比较小的那一批图像做超分辨率重建把图像放大到768或1024同时用标签框面积按比例放大——这里要注意归一化坐标本身不随缩放变化但模型输入分辨率变大后小目标占据的像素自然变多能有效提升召回。如果数据量允许也可以把这个超分模型集成到训练pipeline里而不是只做离线增强。4.2 用训练后的曲线反推最优置信度阈值默认的置信度阈值是0.25但这个值不一定适合每个类别。火和烟的样本特征差异很大烟的外观变异性远大于火同一个confidence阈值下烟的precision可能已经掉到0.7而火还在0.9以上。常见的做法是在验证集上对每个类别单独画Precision-Recall曲线选择F1分数最大的点作为推理阈值。可以用YOLOv8的模型验证结果来做from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val( datafire_person.yaml, imgsz640, conf0.001, # 故意给低阈值让所有预测框都参与评估 iou0.6 ) print(metrics.box.f1_curve)把conf设成0.001跑一次验证得到的是完整F1曲线数据。YOLOv8的f1_curve返回的是不同置信度下的F1分数数组配合metrics.box.ap_class_index可以定位每个类别的最优阈值。实际部署时我一般会为“人”设定一个阈值0.4左右为“火”设定更高的阈值0.5以上为“烟”设定较低阈值0.2~0.3因为火灾预警场景里烟的漏检代价远高于误检代价。4.3 训练后洗标签把置信度高的预测框反查成新标签这里的思路是第一轮训练出的模型对多数样本已经有不错的检测能力那些模型给出高置信度预测、但原始标签里没有对应框的位置很可能就是标注遗漏。把这些候选位置提取出来和原标签做IoU比对小于0.1的视为“疑似漏标”import cv2 import numpy as np from ultralytics import YOLO model YOLO(best.pt) results model.predict(train/images, conf0.6, iou0.5, verboseFalse) for res in results: img_path res.path stem img_path.split(/)[-1].split(.)[0] label_file ftrain/labels/{stem}.txt orig_boxes [] if os.path.exists(label_file): with open(label_file) as fp: for line in fp: parts list(map(float, line.split())) orig_boxes.append(parts) orig_boxes np.array(orig_boxes) if orig_boxes else np.zeros((0, 5)) for box in res.boxes: conf box.conf[0].item() if conf 0.6: continue cls_id int(box.cls[0].item()) xywhn box.xywhn[0].tolist() # 和已有标签做IoU检查 max_iou 0.0 for ob in orig_boxes: if int(ob[0]) cls_id: # 简单IoU计算逻辑面积用xywhn直接算 inter max(0.0, min(xywhn[0] xywhn[2], ob[1] ob[3]) - max(xywhn[0], ob[1])) \ * max(0.0, min(xywhn[1] xywhn[3], ob[2] ob[4]) - max(xywhn[1], ob[2])) union xywhn[2] * xywhn[3] ob[3] * ob[4] - inter max_iou max(max_iou, inter / union if union 0 else 0) if max_iou 0.1: print(f疑似漏标: {img_path}, class{cls_id}, conf{conf:.3f}, box{xywhn})这里要控制conf阈值0.6以上才作为候选否则预测框本身存在大量误检洗标签反而会引入新的噪声。产出的疑似漏标列表建议人工过一遍再写回标签文件不要全自动合并。这个洗标签过程跑一轮就够重复多轮会导致标签体系向模型自身预测偏移越洗越丧失对“真实边界”的刻画能力。如果你的数据包里还附带未标注的原始图像也可以先让模型预测再人工筛选后作为新增训练样本喂回去这比只围绕原标签打转效果好得多。5. 验证与部署用混淆矩阵和ONNX Runtime把模型压到边缘设备5.1 验证结果不只是看一个mAP数字训练完用下面的命令可以得到完整的验证结果yolo detect val \ modelruns/detect/train/weights/best.pt \ datafire_person.yaml \ splitval结果目录里的confusion_matrix.png是第一个要看的图不要只看mAP50。混淆矩阵能直观展示烟被误分为火、人被漏检的频率。火灾场景下最常见的混淆不是人和烟火之间的混淆而是“烟”与背景之间的误检——矩阵里烟那一行除了真正例背景占比往往很高说明模型把大量树影、水汽、工厂排放当成了烟。一旦看到这种模式优先加负样本而不是急着调NMS参数。5.2 导出ONNX与边缘部署推理把训练好的权重导出为ONNX格式yolo export modelbest.pt formatonnx imgsz640 opset12 simplifyTrue导出时opset建议用12或13版本太高在Jetson等设备的TensorRT上可能不受支持。ONNX Runtime推理代码如下import cv2 import onnxruntime as ort import numpy as np sess ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider]) input_name sess.get_inputs()[0].name input_shape sess.get_inputs()[0].shape # [1, 3, 640, 640] img cv2.imread(test.jpg) img_resized cv2.resize(img, (640, 640)) blob img_resized[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 blob np.expand_dims(blob, axis0) preds sess.run(None, {input_name: blob})[0] # [1, 84, 8400]这里拿到的输出维度是[1, 84, 8400]84表示4个坐标信息加80个COCO类别分数换成这个数据集就是4加3类后面处理时需要按自己的类别数切片。YOLOv8导出ONNX后输出端自带NMS——这里特别容易踩坑。如果你的版本导出的ONNX仍包含NMS节点在边缘设备上这个节点会拖慢整体推理速度如果要追求低延迟可以导出时去掉聚合NMS把原始preds拿回到后处理里用普通非极大值抑制逻辑处理这样能省下几十毫秒代价是后处理代码变多。对火灾预警这种多路摄像头上屏场景CUDAExecutionProvider不一定比CPU快因为小batch推理时数据拷贝的开销可能吃掉加速收益优先在目标设备上各跑一遍再定provider。5.3 现场优化技巧与快速验证清单在边缘设备上把推理跑通之后如果发现FPS不达标我先查的不是模型本身而是这三个点检查项预期结果不达标时的调整输入分辨率640x640降到544或480mAP损失通常0.3%以内推理后端TensorRT FP16优先于ONNX RuntimeJetson上用trtexec重新优化预处理耗时单帧小于5ms用cv2.resize加GPU上的仿射变换替代逐像素操作NMS后处理单帧小于2ms用向量化numpy替代for循环或降低max_det一组图像验证完成之后可以专门截取一段监控视频按帧丢给模型观察连续帧之间检测框的抖动程度。火焰和烟雾在视觉上是动态变化的如果连续两帧之间同一个火源的框中心跳跃超过目标宽度的30%说明模型对帧间漂移敏感这时可以在后处理里对检测框做时间维度的平滑而不是再去重新训练一个模型。最后提醒一个容易被忽视的细节从压缩包直接获取的数据集原图分辨率不一定统一。YOLOv8训练时会自动做letterbox缩放但验证集里如果有极端宽高比的图像比如2.35:1的宽银幕抽帧letterbox会引入大量黑边影响小目标检测精度。这类图像建议单独处理要么裁掉黑边再送入模型要么在训练配置的letterbox参数里调整填充策略别用默认的灰边填充硬扛。本文还有配套的精品资源点击获取