尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

YOLOv8火灾检测实战:数据集清洗、训练与置信度调优全指南

发布时间:2026/9/14 2:19:54

资讯中心
01
ARTICLE

YOLOv8火灾检测实战:数据集清洗、训练与置信度调优全指南

YOLOv8火灾检测实战:数据集清洗、训练与置信度调优全指南
简介面向火灾与人员检测场景的YOLO系列算法目标检测数据集适用于巡检监控、消防预警、智慧园区等视觉识别任务特别适合需要快速训练和验证YOLOv5、Y7、V8、V9、V10、YOLO11模型的开发者与学习者。压缩包共2000个文件以XML标注文件为主同时提供YOLO格式TXT标签覆盖人、烟、火三类目标并已划分好训练集、验证集和测试集附带可直接引用的data.yaml配置文件注释格式规范方便迁移到不同版本。数据集大小141.83MB已有46人浏览学习。使用时可分别读取对应文件夹中的标注YOLO格式采用class x_center y_center width height归一化坐标适合不熟悉格式转换的初学者直接上手训练XML格式则便于使用LabelImg等工具二次检查。整体目录结构清晰可帮助读者省去大量标注和整理时间专注模型调参与效果对比。1. 火灾和人员探测数据集3039张图像带标签背后的YOLO格式与脏数据问题摄像头前突然出现烟雾报警再早几秒就可能是完全不同的故事。火灾和人员探测就是这种对“漏报”零容忍的任务人形多变、烟雾透明无边界、火焰小且闪烁监控相机的角度和光照又千差万别。这个数据集给出了3039张已标注图像人、烟、火三类标签是YOLO格式的txt文件——看起来拿到就能跑YOLO算法实际做过训练的人都知道“带标签”和“能训出可用模型”之间还隔着标签清洗、数据划分、类别不平衡和阈值选择四道坎。下面按YOLO算法训练自己的数据集的流程走一遍先解析标签确认人/烟/火的数量与框质量再组织成YOLOv8可训练目录并跑通训练命令接着做清洗与增强最后落在验证集的分类别置信度调优上。适合正在准备火灾识别数据集的工程师也适合刚把数据集下载下来第一次接触YOLO格式的同学。2. 解压后先别训练看懂YOLO标签文件并统计人/烟/火分布2.1 YOLO txt标签的坐标规范与ZIP目录两种常见结构YOLO算法使用的标签不是VOC的XML也不是COCO的JSON而是每张图对应一个同名txt。txt里每一行是五个数字类别id、归一化中心点x、归一化中心点y、归一化框宽w、归一化框高h。比如一张640×480的图上人的框左上在(220,120)、右下在(380,340)那么中心点是(300,230)框宽160、框高220归一化后写进txt的一行就是0 0.46875 0.47917 0.25 0.45833。所有坐标先除以图像宽高再写入所以这套标签不依赖输入分辨率这也是YOLO格式比VOC格式在扩缩图像时少一步转换的原因。ZIP解压后先看目录常见两种结构结构示例后续处理已分好train/valimages/train/.jpg labels/train/.txt直接写data.yaml只有images与labelsimages/.jpg labels/.txt先划分训练集和验证集不管是哪种图像与标签都靠“文件名相同、后缀不同”配对frame_001.jpg对应labels/frame_001.txt。有些压缩包还会单独放一个classes.txt里面按行写类别名没有也别慌YOLOv8只用txt第一列的整数id不强制读取类别文件。先把整个包解压到一个无中文、无空格的路径下再用脚本确认图像扩展名是否统一为.jpg有的人图会混入.jpeg或.png这个差异会在配对阶段暴露出来。2.2 用Python批量解析标签统计人/烟/火框数与框尺寸这一类数据集最怕的不是训练慢而是类别分布偏到只剩人。用下面脚本把每类的框总数、每张图的目标数、以及面积占比小于1%的小目标框数一次算出来from pathlib import Path from collections import Counter, defaultdict import cv2 label_dir Path(labels) img_dir Path(images) class_names {0: person, 1: smoke, 2: fire} label_counter Counter() # 每个类别的框总数 per_image_counter Counter() # 每张图的目标数量分布 wh_stats defaultdict(list) # 每个类别的归一化宽高用来算小目标占比 for txt_path in sorted(label_dir.rglob(*.txt)): if txt_path.name classes.txt: continue img_path img_dir / (txt_path.stem .jpg) if not img_path.exists(): img_path img_dir / (txt_path.stem .png) image cv2.imread(str(img_path)) if image is None: print(txt_path, 对应图像缺失) continue h_img, w_img image.shape[:2] lines txt_path.read_text(encodingutf-8).strip().splitlines() per_image_counter[len(lines)] 1 for line in lines: parts line.split() if len(parts) ! 5: continue cls_id int(parts[0]) w float(parts[3]) h float(parts[4]) label_counter[class_names.get(cls_id, unknown)] 1 wh_stats[class_names.get(cls_id, unknown)].append((w, h)) print(各类别框数, dict(label_counter)) print(每张图目标数出现最多的五种情况, per_image_counter.most_common(5)) small_ratio { name: sum(1 for w, h in wh if w * h 0.01) / len(wh) for name, wh in wh_stats.items() if len(wh) 0 } print(各类别中面积占比小于1%的框比例, small_ratio)这段脚本有两个作用一是确认三类目标都真实存在二是看小目标比例。如果smoke或fire框数远低于person后面训练时模型会天然偏向人尤其是火苗只有十几个像素时w*h算出来经常低于0.01。per_image_counter输出里如果大量出现“0行”标签说明很多空标签文件占着位置这也是火灾数据的常见问题画面干干净净但标注人员没给“无火”帧写任何内容。2.3 用OpenCV随机抽图可视化核对标签是否贴着烟和火统计暴露数量却暴露不了质量。随机抽10张图把标注框画出来看常见情况有三种人的框问题最小烟框经常把一大片半透明区域全包进去火苗框则经常比实际火焰大出两三倍。烟雾没有明确边界标注员主观判断差异很大这会直接让回归loss在多轮训练里震荡。import cv2 import random from pathlib import Path image_paths list(Path(images).rglob(*.jpg)) random.seed(42) sample_paths random.sample(image_paths, 10) colors [(0, 255, 0), (0, 165, 255), (0, 0, 255)] # person绿 smoke橙 fire红 check_dir Path(check) check_dir.mkdir(exist_okTrue) for img_path in sample_paths: img cv2.imread(str(img_path)) h_img, w_img img.shape[:2] txt_path Path(labels) / (img_path.stem .txt) with open(txt_path, encodingutf-8) as fp: for line in fp: cls_id, x_c, y_c, bw, bh map(float, line.split()) x1 int((x_c - bw / 2) * w_img) y1 int((y_c - bh / 2) * h_img) x2 int((x_c bw / 2) * w_img) y2 int((y_c bh / 2) * h_img) cv2.rectangle(img, (x1, y1), (x2, y2), colors[int(cls_id)], 2) cv2.imwrite(str(check_dir / (img_path.stem _check.jpg)), img)抽出来的图不要看一眼就删存到check/目录逐张过。如果发现大量烟框只框住了烟雾中心而漏了边缘扩散区或火苗框比实际火焰大很多那么后续训练前要先回堆标签。此时要做的不是上来就换YOLO算法改进、加注意力机制而是先解决数据标注偏移否则每个epoch都在拟合错误的框中心。3. 把数据集组织成YOLOv8可训练格式data.yaml划分与训练命令3.1 训练集/验证集划分按场景而不是纯随机只有3039张图常用比例是8:2或9:1。ZIP里如果已经带train/val目录这步直接跳过只有一级images/和labels/时拿到数据的第一步就是划分。划分时最忌讳纯随机如果这些图来自几十段连续视频监控帧纯随机会把同一段视频的相似画面一半放进train、一半放进val验证mAP虚高换一个真实场景立刻掉点。按文件名前缀或目录名代表场景去分组再把整个场景组划入train或val。import random import shutil from pathlib import Path from collections import defaultdict image_dir Path(images_flat) label_dir Path(labels_flat) train_ratio 0.9 random.seed(0) groups defaultdict(list) for img_path in sorted(image_dir.rglob(*)): if img_path.suffix.lower() not in (.jpg, .jpeg, .png): continue prefix img_path.name.split(_)[0] # 按场景前缀分组 groups[prefix].append(img_path) group_names list(groups.keys()) random.shuffle(group_names) cut int(len(group_names) * train_ratio) train_groups set(group_names[:cut]) val_groups set(group_names[cut:]) train_img Path(images/train) val_img Path(images/val) train_lbl Path(labels/train) val_lbl Path(labels/val) for d in (train_img, val_img, train_lbl, val_lbl): d.mkdir(parentsTrue, exist_okTrue) def move_sample(img_path, target_img_dir: Path, target_lbl_dir: Path): txt_path label_dir / (img_path.stem .txt) if txt_path.exists(): shutil.move(str(img_path), str(target_img_dir / img_path.name)) shutil.move(str(txt_path), str(target_lbl_dir / txt_path.name)) for group in train_groups: for img_path in groups[group]: move_sample(img_path, train_img, train_lbl) for group in val_groups: for img_path in groups[group]: move_sample(img_path, val_img, val_lbl)img_path.name.split(_)[0]只是一个容易落地的规则具体前缀怎么取要看你文件名结构。如果图片是fire001_0001.jpg这种前缀fire001能代表视频片段如果文件名没有意义就按文件所在子目录划分。shutil.move而不是Path.rename是因为跨磁盘卷复制时rename会报错move更稳。3039张图量不大完整跑一遍即使有重复前缀分组数量也不会太少。3.2 编写data.yaml路径、类别名与nc顺序的关系YOLOv8的data.yaml描述数据接口最小可用版本长这样path: /opt/datasets/fire_person train: images/train val: images/val nc: 3 names: 0: person 1: smoke 2: firepath写绝对路径单人用最省事但这文件如果放进仓库和同事共享建议改成相对路径YOLOv8会把相对路径解析成yaml文件所在位置的相对地址。names列表的顺序必须和txt里第一列的类别id严格一致如果标注文件约定0是人、1是烟、2是火names就按这个顺序写写反了不会报错训练出来的模型会把烟火识别成互相调换的类别。nc: 3在yaml里容易漏YOLO不会自动从names长度推断漏写会在启动时报错。还有一种不常见的组织方式是用train.txt和val.txt列出所有图片绝对路径data.yaml里写train: train.txt、val: val.txt。如果ZIP包内已经给了这种列表也可以直接沿用但注意列表里的路径如果指向打包前的旧位置需要全文替换路径前缀。3.3 跑通最小训练命令并理解epochs/batch/imgsz的取舍第一次跑不要直接100个epoch先用小模型和短周期验证数据链路正常yolo detect train \ datafire_person.yaml \ modelyolov8s.pt \ epochs50 \ imgsz640 \ batch16 \ cacheTrue \ projectruns/detect \ namefire_person_exp \ patience10modelyolov8s.pt会下载COCO预训练权重并改用3类输出层person在COCO里存在所以第一轮就能继承一部分人形特征smoke和fire则要从头学。imgsz640是速度与框精度的折中点如果前面的小目标比例很高后期可以抬高到960但显存和训练时间会明显上涨。cacheTrue会把3039张图提前缓存进内存极大减少小数据集每epoch的读盘等待3060 12GB上开batch16通常不爆显存老卡可以先试8。参数建议值说明modelyolov8s.pt先试small别一上来用xlepochs50起最终100看val曲线收敛情况再翻倍batch8/16/32显存允许尽量大但不要为凑batch降分辨率imgsz640或960小目标多时加大否则维持640patience10连续10轮val mAP不涨就停省时间cacheTrue3039张图完全能缓存打开训练过程中不要只看train loss要盯val box_loss和mAP50。这个数据集的person类会先涨起来smoke/fire可能到第20个epoch才有明显起色。如果到第40个epoch smoke class mAP还在0附近一般不是训练轮次不够而是第2章的标签分布或框质量出了问题回去看small_ratio和可视化图。4. 数据清洗与增强处理小目标、漏检与类别不平衡4.1 用脚本找出坏样本标签越界、空标签、图像损坏YOLO官方在训练时对越界框有裁剪容忍但空标签和损坏图像会直接导致loss变成nan或训练进程中断。3039张图像说多不多训练前用脚本做一次全量体检比训练到一半再去排查高效得多。下面脚本会对每一对图-标签返回错误类型import cv2 from pathlib import Path def inspect_sample(img_path: Path, txt_path: Path): if not img_path.exists() or not txt_path.exists(): return missing_pair image cv2.imread(str(img_path)) if image is None: return broken_image lines txt_path.read_text(encodingutf-8).strip().splitlines() if not lines: return empty_label for line in lines: parts line.split() if len(parts) ! 5: return bad_format cls_id, x_c, y_c, bw, bh map(float, parts) if cls_id not in (0, 1, 2): return bad_cls if bw 0 or bh 0 or x_c 0 or x_c 1 or y_c 0 or y_c 1: return bad_box if (x_c - bw / 2) -0.02 or (x_c bw / 2) 1.02 or \ (y_c - bh / 2) -0.02 or (y_c bh / 2) 1.02: return out_of_bound return None for txt_path in Path(labels).rglob(*.txt): img_path Path(images) / (txt_path.stem .jpg) err inspect_sample(img_path, txt_path) if err: print(f{txt_path} - {err})out_of_bound给了0.02的容错余量这是考虑标注员在图像边缘手抖时框可能略微超出画布。超过2%的越界属于标签错误应该把那个txt剔除或手动修正。bad_cls出现时先别急着删很可能是类别顺序和我们想的不一样你需要回去核对原压缩包的classes.txt。空标签在火灾数据里要单独看纯背景帧没有人和火留着对训练无益但也不致命真正危险的是图像和标签命名错位导致本来有烟的框落在了一张无关图上。4.2 用增强补充烟火样本而不是无脑调YOLO算法当第2章的统计结果显示smoke只有几百框、person有几千框时模型会倾向于把置信度预算全给人。这时候别急着改网络结构先补充烟火正样本。YOLOv8自带Mosaic、HSV变化、随机翻转等在线增强但像Copy-Paste这类对小目标特别有效的增强不一定默认开启。常见做法是离线生成一批烟火增强图让同一张烟火灾图出现在更多样化的背景下。用albumentations在训练目录外生成增强图import albumentations as A import cv2 from pathlib import Path transform A.Compose([ A.HueSaturationValue(hue_shift_limit12, val_shift_limit35, p0.8), A.RandomBrightnessContrast(brightness_limit0.15, contrast_limit0.15, p0.8), A.HorizontalFlip(p0.5), ], bbox_paramsA.BboxParams(formatyolo, label_fields[category_ids])) src_txt_dir Path(labeled_extra/txt) src_img_dir Path(labeled_extra/img) out_img_dir Path(augmented/img) out_txt_dir Path(augmented/txt) out_img_dir.mkdir(parentsTrue, exist_okTrue) out_txt_dir.mkdir(parentsTrue, exist_okTrue) for txt_path in src_txt_dir.rglob(*.txt): img_path src_img_dir / (txt_path.stem .jpg) image cv2.imread(str(img_path)) boxes, labels [], [] with open(txt_path, encodingutf-8) as fp: for line in fp: parts line.strip().split() labels.append(int(parts[0])) boxes.append(list(map(float, parts[1:]))) transformed transform(imageimage, bboxesboxes, category_idslabels) cv2.imwrite(str(out_img_dir / (aug_ img_path.name)), transformed[image]) with open(out_txt_dir / (aug_ txt_path.name), w, encodingutf-8) as fp: for label, bbox in zip(transformed[category_ids], transformed[bboxes]): fp.write(f{label} { .join(f{v:.6f} for v in bbox)}\n)formatyolo告诉albumentations输入输出都是归一化的中心点宽高坐标库内部会转成像素坐标做变换再转回归一化格式。HorizontalFlip对人员目标完全适用但在带风向的烟雾场景要谨慎翻转后烟柱方向会变模型可能学到对称偏差。离线生成的增强图要放在训练目录里一并参与train/val划分避免同一张图的不同变体同时出现在train和val中造成评估乐观。4.3 增强参数表与烟火小目标的典型设置增强策略推荐参数适用说明MosaicYOLOv8默认开启混合四图帮助模型适应不同尺度烟/火Copy-Paste离线脚本实现把火苗小目标粘贴到无火背景缓解类别不平衡RandomBrightnessContrastbrightness≤0.15, contrast≤0.15模拟夜间监控低照度火灾场景很常用HorizontalFlipp0.5人形适用烟火方向对称时再开HueSaturationValuehue_shift≤12, val_shift≤35火焰是橙红色色相变化过大会产生紫色火这些参数不是越大越好。火焰的橙红色是强先验hue范围超过15会把火苗变紫模型学会的不是“火”而是“橙红色物体”val_shift太大会让火焰和背景一起过曝。每个参数改完用plotsTrue或重新可视化生成一批增强预览图确认增强后的box仍然贴着目标边缘。如果增强后框偏移明显说明某个变换和box参数冲突应该关掉那个变换而不是继续加强度。5. 验证集上的人/烟/火分割调优conf阈值与离线难例回流5.1 用val评估分类别的mAP训练完用best.pt做验证重点看三个类别各自的mAP50yolo detect val \ datafire_person.yaml \ modelruns/detect/fire_person_exp/weights/best.pt \ imgsz640 \ conf0.001 \ iou0.5conf0.001是关键。默认验证阈值是0.25烟火小目标置信度普遍低用0.25会把许多实际检出的框当成负样本mAP被压得很惨。先降到0.001看模型的召回上限部署时再单独调阈值。输出结果里如果person的mAP50在0.8以上smoke只有0.3这通常不是网络容量问题而是第2章统计里烟框数量太少或框得不准。不要急着换大模型先把smoke标签质量修一遍往往比多跑50个epoch有效。5.2 分类别置信度阈值漏烟漏火的代价远大于误报验证结束后部署推理脚本里不要用同一个conf阈值处理所有类别。人的误检只是多画几个框烟和火的漏报却可能让系统错过报警。常见做法是把person阈值抬高烟/火阈值放低from ultralytics import YOLO model YOLO(runs/detect/fire_person_exp/weights/best.pt) results model.predict(camera_frame.jpg, conf0.05, iou0.5, verboseFalse) class_conf_threshold {0: 0.30, 1: 0.05, 2: 0.08} final_boxes [] for box in results[0].boxes: cls_id int(box.cls) conf float(box.conf) if conf class_conf_threshold[cls_id]: final_boxes.append((box.xyxy[0].tolist(), cls_id, conf))model.predict里的conf0.05只是为了防止推理阶段过早过滤掉低分框真正决定是否上报的是后面按类别二次判断。烟和火的阈值低意味着会带来更多误报所以一般还要叠加一个帧间确认规则连续两到三帧同一位置出现低置信度烟火框再触发报警。5.3 难例回流把烟火低置信度帧变回训练数据巡检真实监控视频时把最终判定为smoke或fire但置信度低于0.1的帧按视频时间戳保存每周挑一批交给标注人员补框加入训练集重训。这是比持续调整YOLO算法改进更常见的烟火检测迭代方式标注数据永远比网络重写更容易成为瓶颈。3039张图能训出可用的火灾和人员检测模型但要让烟火漏报率继续下降后续的难例回流与二次标注才是整个pipeline里回报最高的部分。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。