简介:面向森林防火与计算机视觉应用场景,这份基于YOLOv8的森林烟雾火焰检测工程包内置源码、数据集、说明文档与测试视频,适合希望快速搭建烟雾火焰识别模型的开发者、研究者和学生参考。压缩包共2003个文件,约373.97MB,主体为987张jpg图片与981个txt标注文件,配套4个pt权重、2个yaml配置、11个mp4及1个3gp测试视频,另有模型说明docx、results.csv、验证集预测/标注图等,覆盖数据准备、模型训练到效果验证的完整流程。该工程包已有1496人学习下载,目录结构清晰,便于按模块检索。借助模型说明文档可了解YOLOv8火焰检测、烟雾识别与森林火灾实时监控的关键原理,训练事件文件与多段测试视频能辅助对照预测效果,便于直接基于近千张标注样本开展训练、评估与二次开发。
1. YOLOv8 森林烟火检测:这份工程包的完整构成
森林防火巡检里,烟雾和火焰的检测越来越依赖视觉模型,但很多人卡在第一步:数据集难找,标注格式五花八门,训练到一半发现自己整理的数据根本跑不起来。这份资源正好撮合了这个断层——它是一个能直接训起来、直接推理的 YOLOv8 森林烟火检测工程,源码里带了数据集、训练脚本、推理脚本和说明文档,解压之后目录结构清楚,训练流程是完整的。它的价值不在于算法多新,而在于这本身就是一套能落地的基线:你拿到它,不必从零收集数据,不必纠结标注格式,直接在预训练权重上微调或从头训练,就能得到一个检测火焰和烟雾的模型。适合的人群很具体:一是需要给无人机或监控摄像头配一个烟火识别模块的开发者,二是刚学目标检测、想要一个真实场景数据集来练手的学生。这套东西不承诺那种动辄 99% 的夸张精度,而是老老实实给你一条能复现的训练链路,让你在现有基础上做调优。
2. 数据集与标注规范:训练前先把训练素材摸透
2.1 目录结构与文件组成
压缩包解压后,第一眼看到的是相对规整的目录,典型的组织方式是这样的:
fire_smoke_dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 训练用的 txt 标注 │ └── val/ # 验证用的 txt 标注 └── data.yaml # YOLOv8 需要的数据集配置images 和 labels 是严格一一对应的,每张图片对应一个同名 txt 文件。这个对应关系是后续所有脚本的根基,换自己数据时千万别把这两层的对应关系改掉,否则训练时加载器经常会报found no labels,或者更隐蔽地静默跳过一批图,让你以为训练数据很多,实际有效数据少了一截。
我第一次拿到这种数据集时习惯先跑一个校验脚本,确认图片能打开、标注文件不是空的、类别编号没有越界。常见的做法是写一个 Python 脚本刷一遍:
import os from PIL import Image img_dir = "images/train" lab_dir = "labels/train" for name in os.listdir(img_dir): # 只处理图片文件 if not name.endswith((".jpg", ".png", ".jpeg")): continue # 找同名标注文件 label_file = os.path.join(lab_dir, name.rsplit(".", 1)[0] + ".txt") if not os.path.exists(label_file): print(f"[缺失标注] {name}") img = Image.open(os.path.join(img_dir, name)) img.verify() # 只校验文件头,速度快 print(f"[OK] {name} size={img.size}")这段脚本做的事情很简单:遍历每张图片,检查标注文件是否存在,再用 PIL 打开图片确认不是损坏文件。参数上要注意img.verify()只做基础校验,不会真正解压全部像素,所以跑全量数据也不会太慢。如果发现某张图本身就是 0 字节,训练时 YOLO 会直接跳过它,但推理时一旦碰到就会报错,最好在训练前把这类脏数据清掉。
2.2 标签格式与类别设定
YOLO 格式的标签不是框坐标的绝对值,而是归一化后的相对值,一行代表一个目标:
class x_center y_center width height这四项全是小数,范围在 0 到 1 之间,是相对于图片宽高的比例。比如一个火焰在 640x480 的图片里,中心点像素坐标是 (320, 240),宽度占了一半,高度占了三分之一,那对应的一行就写成:
1 0.5 0.5 0.5 0.333333这份资源里的烟雾和火焰一般定义成两个类别,通常是0: smoke, 1: fire,具体以 data.yaml 里的 names 字段为准。训练前要把类别顺序记死,因为推理时输出的类别编号就是按这个顺序来的,排错了,画框的时候标签就会张冠李戴。
有一种常见错误是把背景当成了第三个类别。YOLO 格式不允许出现 background 类别,背景是靠没有任何标注的像素隐含表达的。如果标注文件里出现了第 2 类,训练能硬跑,但最终模型的输出维度会多一个永远学不明白的类,精度会受到实质影响,而且不容易察觉。
2.3 数据划分与重复帧检查
拿到数据集先看 train 和 val 的比例,这个资源给出的划分一般是 8:2 或 9:1,具体数量以 labels 目录下文件数为准。如果自己重新划分数据,需要保证 train 和 val 之间没有同源图片——比如同一段视频连续抽帧得到的帧,一部分进了 train 一部分进了 val,验证集就形同虚设了,mAP 会显得虚高,等真上现场就露馅。我一般把近重复照片的排查写进脚本:
import hashlib import os def file_md5(path): h = hashlib.md5() with open(path, "rb") as f: for chunk in iter(lambda: f.read(8192), b""): h.update(chunk) return h.hexdigest() train_imgs = [n for n in os.listdir("images/train") if n.endswith((".jpg", ".png"))] val_imgs = [n for n in os.listdir("images/val") if n.endswith((".jpg", ".png"))] train_hash = {file_md5(os.path.join("images/train", n)) for n in train_imgs} dup = [n for n in val_imgs if file_md5(os.path.join("images/val", n)) in train_hash] print("重复文件:", dup)MD5 校验比较慢,但一次能抓出真正的重复文件。像素内容完全相同但文件名不同的图片,MD5 一致;如果图片被重新压缩过,MD5 会变,那种情况可以叠加感知哈希(pHash)查近似重复,属于进阶做法。数据校验这件事看着琐碎,其实决定了整个训练流程后面会不会翻车,我见过不止一次因为训练和验证集污染导致指标虚高、上线后被现场数据反噬的案例,这一层过滤值得在动手训练之前做掉。
3. 训练环境与参数:把第一个模型跑起来
3.1 环境安装与依赖版本
这份源码基于 ultralytics 的 YOLOv8 实现,训练环境主要依赖 Python、PyTorch 和 ultralytics 包。最简单的安装方式是用 pip 建一个干净环境,Python 3.8 到 3.10 都行,我一般用 3.10。PyTorch 版本要注意和 CUDA 驱动匹配,比如 CUDA 11.8 对应 torch 2.1.x,装错了会运行时报libcudart之类的动态库错误。
python -m venv venv source venv/bin/activate pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python pillow pyyaml逻辑上分成两段:先是虚拟环境隔离,再是装训练相关的包。ultralytics包本身集成了 YOLOv8 的训练、验证、导出命令,不需要额外装复杂的训练框架。--index-url指定 CUDA 版本的 pytorch 源,CPU 机器就把后缀去掉直接装默认版本。装完后跑一下yolo detect predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpg验证链路,能出框说明环境没毛病。
3.2 数据集配置文件与训练启动
训练前需要一份 data.yaml,这份资源已经内置了,关键内容长这样:
path: fire_smoke_dataset train: images/train val: images/val nc: 2 names: 0: smoke 1: firepath是数据集根目录,train 和 val 是相对根目录的图片文件夹路径,nc是类别数,names的编号必须和标注文件的 class 字段对得上。很多人把 path 写成绝对路径,项目目录移动之后又要改一次,我习惯直接用相对路径并保证终端从项目根目录启动训练。
训练命令:
yolo detect train \ data=fire_smoke_dataset/data.yaml \ model=yolov8s.pt \ imgsz=640 \ epochs=100 \ batch=16 \ device=0 \ project=runs \ name=fire_exp1简单拆解一下参数:model=yolov8s.pt是yolov8s的预训练权重,resource 里应该也有对应的初始权重文件;imgsz=640会把训练图片统一缩放为 640x640;batch=16是批量大小,取决于显存;epochs=100是训练轮数;project和name决定权重和日志的输出目录。如果显存吃紧,先把 batch 调成 8 或 4,也可以换yolov8n.pt起步,但检测精度会低一些。
3.3 训练监视与损失曲线解读
训练过程中,ultralytics 会在runs/fire_exp1/下生成results.png和权重文件。results.png 里画了四类曲线,主要看 box_loss、cls_loss 和 dfl_loss。box_loss 是框回归的误差,cls_loss 是分类误差,dfl_loss 是分布焦点损失。随着训练推进,train 的三个 loss 应该稳定下降,val loss 降到某个点后如果开始反弹,就是过拟合的早期信号。
tensorboard --logdir runs或者在 python 里直接读 results 目录下的 CSV 文件自己画,我习惯用 tensorboard 看曲线,方便对比多次实验。训练完看runs/fire_exp1/weights/下有两个文件:best.pt和last.pt。best.pt是验证集上 mAP 最好的权重,last.pt是最后一轮的权重。部署和继续训练的基准都选best.pt,不要手滑拿 last.pt 去推理,效果差不少。
4. 推理与部署:从单张图片到视频巡检
4.1 图片与视频推理脚本
训练出的权重直接可以用于推理,这份源码里提供的是基于 ultralytics 的调用方式:
from ultralytics import YOLO # 加载训练好的权重 model = YOLO("runs/fire_exp1/weights/best.pt") # 推理单张图片 results = model.predict( source="test_images/forest_01.jpg", conf=0.25, iou=0.45, save=True, imgsz=640 )conf=0.25是置信度阈值,低于 0.25 的框会被丢掉;iou=0.45是 NMS 交并比阈值,控制重叠框的合并力度。这两个参数是推理阶段最需要调的。save=True会直接把画了框的结果图存到当前目录,实测时方便肉眼看效果。
处理视频或者摄像头的逻辑几乎一样,唯一区别是source换成视频文件路径或者摄像头编号:
results = model.predict( source="videos/forest_camera.mp4", conf=0.3, iou=0.5, imgsz=640, stream=True, save=True )加了stream=True后,模型会按帧生成结果,内存不会一次性被视频帧占满。如果发现某个视频里烟和火检测断断续续,多半是单帧检测漏检,这种场景建议把conf调到 0.2 以下,宁可多画几个误检框,也别放过真的火情。
4.2 导出 ONNX 与边缘设备部署
训练完的.pt权重只能在 PyTorch 环境里运行,实际项目要么部署到服务端,要么下放到 RK3588 这类边缘 NPU 板子上。常见做法是先导出 ONNX:
yolo export model=runs/fire_exp1/weights/best.pt format=onnx imgsz=640 dynamic=Truedynamic=True允许动态输入尺寸,方便后期切换分辨率。导出后可以顺手用onnxruntime验证一下导出的模型输出:
import onnxruntime as ort import numpy as np from PIL import Image session = ort.InferenceSession("best.onnx") img = np.array(Image.open("test.jpg").resize((640, 640))) img = img[:, :, ::-1].transpose(2, 0, 1) # HWC 转 CHW img = np.expand_dims(img, 0).astype(np.float32) / 255.0 outputs = session.run(None, {session.get_inputs()[0].name: img}) print(outputs[0].shape)验证 ONNX 模型有两点很关键:一是输入维度和归一化方式要和训练时一致,YOLOv8 一般输入NCHW的 RGB 图,除以 255;二是输出是1x84x8400的矩阵,其中 84 表示 4 个框坐标加上 80 个类别概率,但从这个源码数据集训练出来的模型类别数只有 2,所以输出维度是1x6x8400左右,解析时要注意按实际的nc调整。
如果目标平台是 RK3588,常见流程是 ONNX 再转成 RKNN 格式,精度的损失一般能控制在 1%~2% 的 mAP 范围内,森林烟火检测这种相对大目标的任务可以接受。
4.3 输出解析与结果过滤
拿到模型输出后,还需要自己完成 NMS 之后的解析,完整的处理代码如下:
import numpy as np def postprocess(output, conf_thres=0.25, iou_thres=0.45): # output shape: (1, 6, 8400) preds = np.squeeze(output, 0) # (6, 8400) # 前4行是 cx, cy, w, h,后两行是类别概率 boxes = preds[:4, :].T # (8400, 4) scores = preds[4:, :].T # (8400, 2) class_ids = np.argmax(scores, axis=1) confs = scores[np.arange(len(scores)), class_ids] keep = confs > conf_thres boxes, class_ids, confs = boxes[keep], class_ids[keep], confs[keep] # 简单 NMS 逻辑,可换成 cv2.dnn.NMSBoxes from cv2 import dnn indices = dnn.NMSBoxes( boxes.tolist(), confs.tolist(), conf_thres, iou_thres ) return boxes[indices], class_ids[indices], confs[indices]这段是在边缘端用 C++ 或纯 Python 推理时的通用后处理模板。这里的 8400 是 640x640 输入下三个尺度特征图的预测框总数,如果用别的输入尺寸,这个数会变。模型输出的坐标是归一化的中心点和宽高,换算成像素时记得乘回原图的宽高。
5. 避坑排查:森林烟火检测最容易翻车的五个问题
5.1 坑一:训练 loss 很漂亮,验证 mAP 却惨不忍睹
现象:训练三条 loss 曲线都在下降,best.pt也保存了,但验证集 mAP 只有 0.2 左右,肉眼检查结果图发现大量漏检。
原因:大概率是训练集和验证集存在数据污染。很多森林烟火数据来自视频抽帧,相邻帧之间高度相似,如果不做去重就随机划分,验证集里几乎全是训练集里看过的图,模型记住了帧的内容而不是烟火特征。另一个原因是标注错误,某一类标签混入了大量噪声框。
解决:用前面提到的 MD5 去重脚本把重复帧清掉,再重新划分 train 和 val。标注层面随机抽样 20 张图人工核对,重点看标签框是否贴合目标、类别是否标反。森林画面里远处的烟雾经常被标成 fire,这类错误会直接把精度拖下来。
5.2 坑二:烟雾怎么调都检测不出来
现象:火焰检测效果还行,但半透明的烟雾基本不触发,尤其是清晨林间薄雾和白色背景下的浅淡烟雾。
原因:烟雾是半透明目标,边缘模糊,特征没有火焰那么明确。数据集里如果烟雾样本偏少,或者标注框把大范围的弥散烟雾框得太大导致标注质量差,模型就很难学出稳定的纹理特征。
解决:降低推理阶段conf到 0.15 左右先看有没有检出;同时收集更多正样本,专门补充薄雾、远山背景下的烟雾图。训练时对烟雾类别做 mosaic 增强,把它放到不同背景里合成多样本。常见的做法是把数据增强的 hsv_h、hsv_s 参数调大,改变烟雾在色相和饱和度上的分布,让模型不要只认固定颜色。
5.3 坑三:小目标火焰漏检率高
现象:远处的零星火点、几百米外的明火,人眼能看到,模型当成背景直接忽略。
原因:森林场景多为广角大图,火焰像素占比往往小于 1%。YOLOv8 在 640x640 输入下,小目标可能在下采样后只剩几个像素,特征图上的响应微弱,自然检不出来。这是目标检测的经典小目标问题,不是配置参数玄学。
解决:有两个方向。一是训练时把imgsz提到 960 或 1280,小目标保留的像素多了,精度会明显上升,训练时间也会成倍增加。二是部署时对原始大图做切块,把一张 4K 图切成 4 个 640x640 的小块分别推理,再合并结果。后者在无人机巡检场景里很实用,代价是推理耗时变大。
5.4 坑四:训练时显存溢出
现象:启动训练后没几个 epoch,直接报CUDA out of memory,进程被杀。
原因:batch size 和 imgsz 组合起来超出了 GPU 显存。6000 张图听起来不多,但 YOLOv8s 在 640 输入下,batch 16 大约需要 10GB 显存,如果是 8GB 的显卡,这个问题会立刻爆出来。
解决:一是把 batch 降到 8 或 4;二是加amp=True开启混合精度训练,显存占用能降 30% 左右;三是用梯度累积,ultralytics 里没有内置参数,可以通过调低 batch 并手动改训练循环实现。在 GTX 1660 Ti 这种入门卡上,我一般用 batch 8、amp 开启的方式,能跑完 100 个 epoch。
5.5 坑五:白天的效果不错,黄昏和夜晚却失效
现象:白天测试的检测框很准,换成黄昏、逆光或夜间红外画面,模型大面积漏检。
原因:训练数据的光照分布太集中。森林烟火检测的数据大多来自日间监控截图,黄昏和夜间样本稀疏,模型没有见过足够多的低照度特征,自然会失效。
解决:训练时把语义相关的增强打开,hsv_h、hsv_s调大是其一,更直接的是给数据集补充低照度样本。图像预处理可以用histogram equalization做一次 CLAHE 增强,把夜间画面的对比度提上来再喂给模型。如果做的是夜间森林防火,建议单独采集红外或热成像数据训练专门的模型,明暗特征差别太大,一个模型通吃白天黑夜本身就有点强人所难。
6. 进阶验证:用热力图定位模型的注意力盲区
这一节说一个我强烈建议做的验证动作:热度图可视化。模型训练完,mAP 也看了,但你仍然不知道它到底在看照片的哪个区域。森林烟火场景里,火往往只占画面的很小一块,模型可能学歪了,靠画面里一些背景特征判断,只在特定的山地背景下有效,换个场景就漏检。热力图就是把模型对检测框的决策做反向传播,算出每个像素对输出的贡献权重,最终以伪彩色叠加在原始图上,看看模型注意力集中在哪个位置。
举个例子,如果一张森林图里有一团烟雾和一栋小房子,模型把烟雾框出来后,热力图却显示注意力集中在小房子的边缘,那模型学到的可能是地形特征,而不是烟本身。这种情况下调整数据比调参数更有效——增补不同背景的烟雾样本,让模型必须学会烟雾本身的纹理和颜色特征。
实现热力图不需要自己从零写反向传播,常见做法是宿舍里跑一个基于梯度类激活映射的思路,用 ultralytics 的模型对象取中间层特征,再做梯度加权汇总。简化版的思路是这样的:
import torch from ultralytics import YOLO model = YOLO("runs/fire_exp1/weights/best.pt") m = model.model.float().eval() # 注册前向钩子,抓取最后一层骨干特征 features = {} def hook_fn(module, input, output): features["out"] = output.detach() handle = m.model[-4].register_forward_hook(hook_fn) img = torch.rand(1, 3, 640, 640) # 实际换成真实图像张量 output = m(img) handle.remove() # 反向传播取梯度,计算加权激活图 act = features["out"] grad = torch.autograd.grad( output.sum(), act, retain_graph=True )[0] weights = grad.mean(dim=(2, 3), keepdim=True) heatmap = (act * weights).sum(dim=1).squeeze() heatmap = heatmap.clamp(min=0).numpy()这段代码只是一个最小的框架,实际使用时,需要把显式构建的图像经过合适的归一化处理;model.model内部的结构在不同 ultralytics 版本里有差异,可以打印 model 结构定位最后一层骨干特征图的位置。热力图不需要每张测试图都跑,选几十张有代表性的场景,烟雾、近火、远火、误检图各抽几张,就能大致判断数据够不够全面。
从那以后,我每次迭代一版数据集,都会先抽 30 张图跑一遍热力图再决定要不要训练,省下大量反复训练的时间。训练之前先看模型在旧权重上的注意力分布,比训练后才发现问题要高效得多。这一步虽然简单,但能避免你把几天时间花在一个对背景过拟合的模型上。希望这份基于 YOLOv8 的森林烟火检测工程和这些排查思路能帮到你,少踩几个我当年踩过的坑。
本文还有配套的精品资源,点击获取