资讯动态

YOLOv11火灾烟雾检测数据集实战:从标注到部署的完整指南

发布时间:2026/10/13 7:53:46 来源:尧图企业网站定制
简介面向目标检测开发者和火灾监控研究人员这份资源为已标注的YOLOv11火灾烟雾数据集包含3600张覆盖不同场景、光照条件和烟雾密度的图片可直接用于YOLO系列模型的训练与评估。压缩包内共2000个文件以1996个xml标注文件为核心配合4个txt文件提供train、val、test、trainval划分清单整体大小244.77MB解压后即可按清单组织数据流程。目前已有209人学习下载。借助该数据集使用者可走通图片预处理、模型选择、训练调参和泛化测试的完整链路减少自行采集与标注成本对希望落地实时火灾烟雾报警系统的Python开发者而言这份数据既能验证YOLOv3至YOLOv11等不同版本的检测效果也为公共安全、工业与住宅监控场景提供了可靠训练素材。1. 为什么说3600张已标注图片是YOLOv11火灾烟雾检测的及格线做火灾烟雾检测的开发者大多卡在同一个环节数据。目标检测公开集里行人、车辆、工业品一抓一大把专做火焰和烟雾的标注数据却少得可怜。这套3600张已标注图片的YOLOv11火灾烟雾数据集解决的就是从零起步时最疼的“没数据”问题——图片和标注文件都备好了省去自己从监控视频里抽帧、打框、导出的三天工作量。它适合两类人一是想快速跑通火灾烟雾检测Demo、验证可行性的新手二是做园区、工地、仓储监控方案的工程师需要先拿一套干净数据训练基线模型。但3600张只是及格线不是终点。它的场景分布、类别定义和标注风格决定了模型上限后续要做难例回填和数据扩充才敢上正式环境。2. 数据集自查清单YOLO格式、类别标注与3600张图的质量底细2.1 先读懂YOLO标注文件目录结构与txt格式拿到任何一套“已标注”数据集第一步不是急着开训练而是先确认标注格式能不能被YOLOv11直接吃进去。常见做法是images和labels两个目录平级images里放原图labels里放同名txt文件。这个数据集按二类目标处理类别定义通常把0号设为fire火焰1号设为smoke烟雾偶尔也有人把两类合并成一类但那样模型对“火苗vs浓烟”的区分能力会差很多不推荐。txt文件里每一行对应一个目标框格式是五段式类别ID、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。归一化指的是相对图片宽高的比例取值在0到1之间。下面这段脚本可以把标注直接画回原图上肉眼确认标注到底长什么样。import os from PIL import Image, ImageDraw img_dir images label_dir labels img_files [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png))] for img_name in img_files[:10]: # 先抽查前10张 img Image.open(os.path.join(img_dir, img_name)) w, h img.size draw ImageDraw.Draw(img) base os.path.splitext(img_name)[0] txt_path os.path.join(label_dir, base .txt) with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) cx, cy, bw, bh map(float, parts[1:5]) # 归一化坐标换算回像素坐标 x1 (cx - bw / 2) * w y1 (cy - bh / 2) * h x2 (cx bw / 2) * w y2 (cy bh / 2) * h color red if cls_id 0 else blue draw.rectangle([x1, y1, x2, y2], outlinecolor, width3) img.save(check_ img_name) print(已输出 check_ img_name)这段代码的核心是把txt里的归一化坐标乘以图片宽高还原成像素坐标然后画框。注意宽高要拿原图尺寸来算不能拿训练时resize后的尺寸来算。如果发现某张图里框偏移、框住大片背景、或者框和烟雾边缘完全对不上那这张图要么重新标注要么直接删掉否则它会变成训练时的噪音。2.2 用脚本透视数据质量框越界、类别比例与目标尺度人工抽查只能看表面要摸清这3600张图的底细得写一个统计脚本。我一般会统计四个维度每个类别的目标总数、每张图的平均目标数、标注框是否越界、目标框面积占原图面积的比例。最后一个维度尤其重要因为火灾烟雾场景里大量目标是远距离小火苗和稀薄烟雾面积占比经常小于1%这类小目标比例越高训练难度越大。import os from collections import Counter label_dir labels cls_counter Counter() # 类别统计 box_counter Counter() # 每张图的目标数分布 out_of_bounds [] # 越界框记录 area_ratios {0: [], 1: []} # 每个类别的目标面积占比 for txt_name in os.listdir(label_dir): if not txt_name.endswith(.txt): continue txt_path os.path.join(label_dir, txt_name) with open(txt_path) as f: boxes [line.strip().split() for line in f if len(line.strip()) 0] box_counter[len(boxes)] 1 for parts in boxes: cls_id int(parts[0]) cls_counter[cls_id] 1 cx, cy, bw, bh map(float, parts[1:5]) # 检查越界框超出图片范围 if cx - bw / 2 0 or cx bw / 2 1 or cy - bh / 2 0 or cy bh / 2 1: out_of_bounds.append((txt_name, parts)) area_ratios[cls_id].append(bw * bh) print(类别数量, dict(cls_counter)) print(单图目标数分布, dict(box_counter)) print(越界框数量, len(out_of_bounds), out_of_bounds[:3]) for cls_id in area_ratios: ratios area_ratios[cls_id] print(f类别{cls_id} 目标面积占比 平均{sum(ratios)/len(ratios):.4f} 最小{min(ratios):.4f} 最大{max(ratios):.4f})脚本的逻辑很直白全部读一遍txt统计类别出现次数、每张图的目标数、越界框并按类别汇总目标框面积占比。越界的判定依据是归一化坐标下框的左边缘小于0或右边缘大于1就说明框跑出了图片边界。这类框在训练时可能触发坐标裁剪警告严重时会导致训练中断。统计完之后关注三个数字类别是否均衡、目标尺度中位数、越界框数量。如果fire和smoke数量差三倍以上训练时小类会被大类压制如果面积占比大多低于0.01那基本全是小目标后面调参时必须把输入尺寸和锚框往小了压。3. YOLOv11训练实操数据划分、模型选型与参数配置3.1 先解决数据划分按场景分组而不是纯随机打乱3600张图要拆成训练集、验证集和测试集。常规比例是8:1:1也就是2880张训练、360张验证、360张测试。但这里有一个特别关键的细节如果数据来源是监控视频抽帧同一个摄像头的连续帧画面高度相似纯随机打乱会把相似的图片同时送进训练集和验证集最后验证分数虚高部署到新场景就翻车。我一般优先按文件名前缀分组。比如图片名前缀是摄像头编号或视频片段编号就按这个前缀为单位划分保证同一来源的帧全部落在同一份数据集里。如果文件名看不出任何分组信息只能随机切分但训练时要多留个心眼观察验证集loss是否在后期异常反弹。import os import random import shutil images [f for f in os.listdir(images) if f.endswith(.jpg)] # 按文件名前缀分组例如 cam01_0001.jpg 的前缀是 cam01 groups {} for img in images: prefix img.split(_)[0] groups.setdefault(prefix, []).append(img) group_names list(groups.keys()) random.seed(42) random.shuffle(group_names) train_groups group_names[:int(len(group_names) * 0.8)] val_groups group_names[int(len(group_names) * 0.8):int(len(group_names) * 0.9)] test_groups group_names[int(len(group_names) * 0.9):] for split, split_groups in [(train, train_groups), (val, val_groups), (test, test_groups)]: os.makedirs(fdataset/{split}/images, exist_okTrue) os.makedirs(fdataset/{split}/labels, exist_okTrue) for g in split_groups: for img in groups[g]: base os.path.splitext(img)[0] shutil.copy(fimages/{img}, fdataset/{split}/images/{img}) shutil.copy(flabels/{base}.txt, fdataset/{split}/labels/{base}.txt)这个脚本按前缀分组把同一组的图片全部放进同一个split里。random.seed(42)保证了每次运行划分结果一致方便复现实验。如果你确定这套数据的文件名没有可用的前缀就把prefix这一行的拆分逻辑去掉直接对images列表随机打乱后切片。划分完成后要写一个data.yaml给YOLOv11用。注意路径建议写绝对路径避免因为工作目录不同导致“图片不存在”的报错。path: /absolute/path/to/dataset train: train/images val: val/images test: test/images nc: 2 names: 0: fire 1: smoke3.2 模型选型与关键参数yolov11n到yolov11s的取舍3600张图属于中小规模数据模型档位不是越大越好。YOLOv11有n、s、m、l、x五个档位n最小最快x最大最慢。火灾烟雾检测要部署到监控设备的话摄像头数量多、视频路数多单路推理速度跟不上就白搭。我的建议是先拿yolov11n跑通流程确认数据没问题再上yolov11s提升精度。直接上x档3600张图大概率过拟合而且调整参数时每次训练慢得让人失去耐心。参数配置上有几个值值得用心调。imgsz默认是640但如果你的目标大多是远处小烟雾建议开到960小目标在特征图上的像素会多一些。batch大小由显存决定常见16到32。epochs在100左右足够这个数据量训练200轮后期容易过拟合。优化器用默认的SGD即可AdamW收敛快但最终精度不一定更好。训练命令一行就能启动pip install ultralytics yolo detect train \ modelyolov11n.pt \ datafire_smoke.yaml \ imgsz640 \ batch16 \ epochs100 \ device0 \ cacheram \ projectfire_smoke \ nameexp1model参数指定预训练权重YOLOv11会基于这个权重做迁移学习比从零训练收敛快得多。cacheram把图片预加载进内存如果你的机器内存不够32G这行删掉换成cachedisk从磁盘缓存会慢一些但不占内存。device0表示用第一张GPU机器没有CUDA就改成devicecpu但训练速度会慢几十倍不太建议。训练跑起来之后不要太迷信loss曲线。loss下降是必然的真正要看的是验证集mAP和PR曲线。下一章讲的几个坑都是在loss好看之后才冒出来的。3.3 训练过程中的监控指标别只盯着终端输出训练时终端会滚动输出每个epoch的P、R、mAP50、mAP50-95这些数值比loss更接近真实水平。我习惯同时开一个TensorBoard或直接盯results.csv文件在项目文件夹的exp1目录下每行记录一个epoch的完整指标。如果发现第40轮以后mAP50还在涨但mAP50-95开始停滞不用管接着跑如果P和R一个猛涨一个猛跌说明置信度阈值设置不合理但训练本身没问题可以用验证集后期重新找最优阈值。4. 训练期四个常见坑越界框、小目标漏检、验证虚高与误报翻车4.1 训练中断与loss骤升多半是标注框越界现象训练跑到一半控制台突然报出类似“assertion failed”或“labels out of bounds”的错误训练直接中断。有些版本不报错但loss曲线在第10轮左右剧烈波动居高不下。原因txt里存在越界框。标注工具导出时选了错误的格式或者手工标注时把框拖出了画布边缘归一化坐标就会小于0或大于1。YOLOv11训练时会对标签做严格检查越界框要么触发异常要么在进行损失计算时引入错误梯度。解决训练前先跑一遍上面2.2节的越界检查脚本把越界框所在文件全部找出来。能修复就修复不能修复就删除那一行标注。我来提供一个小脚本直接把越界框剔除出txt训练前跑一遍最安心。import os label_dir labels for txt_name in os.listdir(label_dir): if not txt_name.endswith(.txt): continue txt_path os.path.join(label_dir, txt_name) with open(txt_path) as f: lines [line.strip() for line in f if len(line.strip()) 0] new_lines [] for line in lines: parts line.split() cx, cy, bw, bh map(float, parts[1:5]) if cx - bw / 2 0 or cx bw / 2 1 or cy - bh / 2 0 or cy bh / 2 1: continue new_lines.append(line) with open(txt_path, w) as f: f.write(\n.join(new_lines) \n) print(f{txt_name}: 原{len(lines)}个框, 保留{len(new_lines)}个)这个脚本直接把越界行删掉而不是修复坐标是因为我们不知道真实目标位置擅自修正坐标只会引入更大的标注误差。删除有问题的行剩下的干净标签足够把模型训出来。4.2 远处小目标全部漏检imgsz和mosaic增强之争现象模型在验证集上mAP50有0.7但看具体图片远处的浓烟和小火苗全部没检出检测结果只覆盖画面中央的大目标。原因火灾烟雾场景很多目标在画面里只有几个像素。imgsz640时一张1920x1080的图等比缩放后一个原本60x60像素的烟雾目标缩小到20x20在特征图上只剩一个点召回率自然上不去。解决先加大imgsz到960或者1280小目标在深层特征图上的响应更强。但这个操作会让显存占用翻倍batch要相应减半。另一个有效手段是打开多尺度训练YOLOv11会随机在0.5到1.5倍之间缩放输入图相当于给模型看了更多尺度的目标。最后还可以尝试把图片切成四块做滑窗训练每个小图单独推理小目标相对尺寸被放大。这种做法推理耗时翻倍适合离线分析不适合实时监控。4.3 验证集分数虚高同源帧泄漏导致的假象现象训练时验证集mAP50一路涨到0.85以上模型看起来状态很好。部署到真实视频流里换上没见过的摄像头画面检测结果惨不忍睹漏报一堆。原因数据划分时把同一摄像头同一时间段抽出的连续帧随机分到了训练集和验证集。验证集里的图片和训练集图片几乎一样模型等于开卷考试记住场景特征就能拿到高分。解决回到3.1节按照片名前缀或视频段ID做分组划分。没有前缀信息的话先算一下图片间的相似度把相似度过高的帧剔除掉一部分再做随机切分。验证时也可以故意混入几十张完全陌生场景的图片做“人工干扰测试”如果分数掉得厉害就说明泛化能力不足。4.4 夜间和复杂场景误报模型学到的是“亮色”不是“火”现象模型把路灯、车灯、红色告警灯、夕阳反光都识别成火灾。白天场景误报还好夜晚误报率飙升。原因数据里白天场景占多数夜间火和烟雾的样本少模型没见过足够多的负样本只能靠颜色特征糊弄过去。橙色的灯和火焰在视觉上高度相似它区分不开。解决标注时把这类容易混淆的物体框出来如果有“未知类”或者“干扰源”类别可以加上让模型明确知道这是负样本。同时做色彩空间的数据增强把训练图的饱和度、亮度随机调低强制模型去学纹理和形状特征而不是颜色。最后在部署端用连续帧确认策略同一位置连续3帧以上都输出火灾目标才触发报警单帧误报全部忽略这套机制能滤掉大部分闪烁灯光。5. 评估与部署验证从mAP数字到视频流里的真实表现5.1 读懂验证输出mAP50、mAP50-95和PR曲线的优先级训练完成后验证命令会输出一组指标。我先给表格再说怎么用。指标含义火灾烟雾场景的参考P精确率检出框中真正有火或烟的比例低会导致误报率飙升报警没人信R召回率真实目标被检出的比例低会漏报火灾场景最致命mAP50IoU阈值0.5时的平均精确度主要关注指标需要优先压到0.7以上mAP50-95多个IoU阈值下的平均表现越高说明框定位越准但不必过度追求火灾烟雾检测和普通物体检测不一样误报可以靠连续帧确认来压制漏报才是真正的事故。所以评估时R的优先级高于PmAP50看整体水平mAP50-95看边界框质量但不要为追框质量牺牲召回率。验证命令如下yolo detect val \ modelfire_smoke/exp1/weights/best.pt \ datafire_smoke.yaml \ conf0.25 \ iou0.5conf是置信度阈值iou是NMS的IoU阈值。验证时这两个参数保持默认就行调试部署阈值时要回到这一步重新测因为conf一改P和R都会剧烈变化。5.2 导出ONNX并接入推理部署端如何踩住阈值训练得到的best.pt是PyTorch权重接进监控系统前一般要转成ONNX格式再用ONNX Runtime或TensorRT推理。导出命令yolo export modelfire_smoke/exp1/weights/best.pt formatonnx imgsz640format指定导出格式imgsz要和训练时的输入尺寸保持一致否则某些算子会报错。导出后可以先写一段极简的Python推理脚本验证ONNX权重和原PyTorch权重的输出一致性。部署端我第一次吃过大亏单帧检测结果直接触发报警结果一个路过的手电筒灯光就让整个监控中心响起来。之后养成的习惯是必须做时序确认。伪代码如下frame_index 0 hit_count 0 ALARM_FRAMES 3 # 连续3帧命中才报警 def on_detect(frame_index, detections, alarm_box): hit any(d[class_id] 0 and d[conf] 0.35 for d in detections) if hit: hit_count 1 if hit_count ALARM_FRAMES: trigger_alarm(alarm_box) else: hit_count 0ALARM_FRAMES设为3配合20fps的监控帧率意味着目标出现0.15秒以上才触发报警。火焰不会闪烁消失烟雾更不会这个策略能挡掉绝大多数瞬时噪声和光线变化。5.3 部署后的长期验证每周回放一次真实告警记录模型上线不是终点而是监控的开始。我习惯每周把上周所有报警记录截图保存人工过一遍误报和漏报。误报多的场景收集对应图片补标成负样本漏报多的场景把漏检帧回填进训练集。这套循环比调任何超参数都有效因为数据永远比参数更稀缺。运营一个月之后模型在真实场景里的表现会有明显提升这一阶段的收获远超过训练时调参带来的提升。6. 让3600张数据增值的进阶姿势增强、难例回填与蒸馏数据增强要克制不是越猛越好。火焰烟雾和普通物体不一样色调是关键特征。Mosaic拼接对密集小目标有效但把火和烟雾拼到工业车间背景上容易产生语义混乱。HSV增强里hsv_h千万别调大色相偏移会让火焰变成绿色或紫色模型学会了这种假目标到了真实场景反而不认正常的火。我一般只开hsv_s和hsv_v也就是饱和度与亮度的轻微抖动控制幅度在0.2以内。难例回填是这套数据最有实用价值的增值手段。先用当前模型跑一批没标注的视频帧把置信度在0.3到0.5之间的模糊检测挑出来这种大多是模型不确定的目标。人工看一遍误报的存成负样本漏报的补上框每周攒几十张累计一个月就相当于给数据集增加了几百张真实场景的高价值数据。负样本不要直接丢进训练集要单独建一个background目录配一个类别为0的空标签txt文件或者标注成专门的干扰类。蒸馏技术值得最后试一次。用同一份数据训一个大的yolov11x模型作为teacher然后用它对未标注或增强后的图片生成伪标签再用伪标签训练一个小的yolov11n模型。这个做法能保留小模型的速度优势同时继承大模型学到的特征表达能力。前提是teacher模型的质量必须过关如果teacher本身就漏检严重伪标签只会把小模型带偏。回看我自己做过的模拟项目X从拿到3600张图到最终部署最大的教训是数据永远比模型重要。别花一周时间调学习率和锚框先花一周时间把数据质量搞干净。标注质量、场景多样性和验证集划分的合理性这三个点任何一个出了问题模型再先进也补不回来。先跑通一套干净的baseline再慢慢往里面回填难例这条路虽然慢但每一步都在给后续工作打底。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑