YOLOv8森林烟雾火焰检测实战:从数据准备到RK3588部署
简介面向森林防火场景的YOLOv8烟雾火焰检测资源包包含完整项目源码、配套数据集与预训练模型权重适合计算机视觉学习者、算法工程师以及需要快速搭建火灾预警系统的开发者使用。压缩包共2003个文件核心数据为987张jpg图像与981个txt标注文件另有11个mp4演示视频、4个pt权重文件、2个yaml配置、模型说明docx和训练结果csv整体约373.97MB结构清晰便于按数据集、训练配置、权重文件等维度分类查阅。已有1496人学习/下载。通过这份资料可了解YOLOv8在森林烟雾与火焰检测中的数据标注格式、训练流程和验证效果能直接加载pt权重推理也可基于自有数据重新训练配合验证集预测图像与结果记录可对比不同阶段的训练精度为算法优化、实验复现或毕业设计提供完整参考。1. 森林烟雾火焰检测为什么必须用 YOLOv8从可用性到落地成本林场监控杆上那些摄像头白天对着漫山遍野的树晚上对着黑漆漆的山坡最怕的不是没有火情而是屏幕上每隔几分钟弹一次“疑似火焰”值班员去人工确认后发现只是夕阳或红色车辆尾灯。森林烟雾火焰检测这个方向真正难的不是识别出“烧起来”的图像而是要在近乎无限的自然背景里把“一小团烟、一小簇火苗”从云、雾、光影和树叶抖动中切出来。YOLOv8 能在这类任务里站住脚核心原因不是它比上一代涨了多少 mAP而是它的训练、验证、导出、量化工具链足够完整拿到带标注的数据集后一个工程师能在两三天内跑通从训练到部署的闭环出了问题也知道往哪一层排查。这篇笔记就按我自己的落地路径把数据准备、训练、评估、避坑和边缘设备部署一起讲清楚适合正在带森林防火项目的算法工程师、做安防监控集成的开发者以及刚接触检测任务准备拿这个方向练手的新人。2. 选型与数据烟雾火焰检测的难点、YOLOv8 哪个模型值得用、数据集怎么准备2.1 烟雾火焰为什么是检测难点形态、颜色、背景三重夹击目标检测任务通常假设目标有相对稳定的轮廓和纹理比如车辆、行人、交通标志。但烟雾和火焰恰好相反烟雾没有固定形状边缘是渐变的颜色从白到灰到黑都能出现而且透明度高背景透过烟雾依然可见这对模型提取特征很不友好。火焰稍微好一点有颜色和动态变化但在检测单帧静态图时火焰的形态可以是团状、条状、点状加上林间阴影里的暗红色和很多自然物体高度相似。在森林场景里还有一层额外干扰树的枝叶在风里抖动阳光透过树冠形成斑驳光点清晨的薄雾在山坳里聚集这些都会让只依赖颜色和纹理的模型产生误判。所以做这个项目不能只把 YOLOv8 当黑匣子得先理解它内部依赖什么——它检测靠的是“框住区域的特征”特征越稳定越容易被学起来。烟雾这种边界模糊的目标需要在数据里用大量不同背景、不同距离、不同光照下的样本把模型强行“喂”出来。正因如此数据质量比模型结构的影响更大。我见过有人拿 500 张网上图片训练验证集 mAP 到了 0.9放到现场一周就崩原因就是训练集里没有自己监控点位那种低角度、过曝、树枝遮挡的视角。森林烟雾火焰检测的数据集建设核心目标不是“图多”而是“覆盖现场可能出现的不确定性”。2.2 YOLOv8n/s/m 怎么选先按算力和帧率倒推YOLOv8 官方在同一个结构下提供了 n/s/m/l/x 几个尺寸参数和推理延迟依次上升。选哪个不取决于“越大的模型越准”这个直觉而取决于你最终把模型部署在哪。如果是纯服务器离线分析模型大点没关系如果是边缘盒子实时跑每一毫秒延迟都直接压缩帧率。我按常见落地经验整理了一张选型表可以按自己的硬件先查定位模型参数量输入尺寸 640 时的相对延迟适合场景YOLOv8n约 3.2MCPU 可勉强跑GPU 很快边缘盒子原型验证、低功耗设备YOLOv8s约 11.2MCPU 吃力GPU 流畅6G 显存显卡训练、RK3588 等盒子部署YOLOv8m约 25.9MGPU 流畅盒子需量化服务器推理、对精度要求高且不差算力YOLOv8l/x约 43M/68MGPU 有压力离线批量分析、学术实验以森林防火场景来说主流是 s 或者 m。s 在大多数摄像头场景下能保证实时性和召回率m 在小目标和远距离烟雾上表现更好但边缘设备不量化基本跑不动。如果团队里只有一张 GTX 1660 Ti 这种 6G 显存显卡老老实实用 yolov8sbatch 设小一点训练时间还能接受强行上 m 调不出 batch反而拖慢迭代。我的习惯是先跑 n 验证数据是否有明显问题再用 s 做正式训练最后只有在上板后 recall 明显不足时才去试 m 和量化。2.3 数据准备最小闭环目录结构、标签格式与划分脚本拿到一个森林烟雾火焰检测数据集首先要确认它是什么标注格式。常见的有两类一类是 YOLO 格式即每张图片一个同名 txt 文件每行class x_center y_center width height四个坐标值都是 0~1 的归一化浮点数另一类是 VOC 格式即每张图片一个 xml写的是左上角和右下角的绝对像素坐标。很多下载的数据集打包时会混着两种甚至有的检查视频抽帧后只有图片没标签。第一步永远是先把标签统一成 YOLO 格式否则 ultralytics 包直接拒绝训练。下面是一个把 VOC 格式转换成 YOLO 格式的常见脚本我自己每次拿到新数据集都会先跑一遍import os import glob import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_dir, class_list): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) image_name Path(xml_path).stem lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_list: continue class_id class_list.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path Path(out_dir) / f{image_name}.txt out_path.write_text(\n.join(lines)) class_list [smoke, fire] xml_dir datasets/Annotations out_dir datasets/labels os.makedirs(out_dir, exist_okTrue) for xml_file in glob.glob(f{xml_dir}/*.xml): voc_to_yolo(xml_file, out_dir, class_list)这段脚本的核心逻辑是把 VOC 的绝对坐标换算成归一化坐标先取图片宽高再把 xmin/xmax 加起来除以 2 得到中心点横坐标最后除以图片宽度。这里有两个容易出错的地方第一VOC 里有的框是越界的xmin 可能是负数、xmax 可能大于图片宽度转换前最好做一次xmin max(0, xmin)的裁剪第二class_list的顺序必须固定之后训练用到的data.yaml里 classes 顺序要完全一致否则标签对不上。图片和标签都准备好之后还需要把它们划分成训练集、验证集和测试集。我一般按 8:1:1 划分注意按“视频片段或拍摄地点”分组而不是随机打散所有图片否则同一个视频的连续帧会同时混进训练和验证集验证指标虚高。划分脚本不复杂但有一个细节值得注意要随机采样且要把 shuffle 的随机种子固定住方便别人复现你的结果。import os import random from pathlib import Path random.seed(42) image_dir Path(datasets/images) label_dir Path(datasets/labels) images sorted(image_dir.glob(*.jpg)) sorted(image_dir.glob(*.png)) # 按视频或场景前缀分组避免同一段视频的帧跨集合 groups {} for img_path in images: prefix img_path.name.split(_)[0] # 假设文件名以地点或视频片段开头 groups.setdefault(prefix, []).append(img_path) group_keys list(groups.keys()) random.shuffle(group_keys) train_ratio, val_ratio 0.8, 0.1 train_keys group_keys[: int(len(group_keys) * train_ratio)] val_keys group_keys[ int(len(group_keys) * train_ratio): int(len(group_keys) * (train_ratio val_ratio)) ] test_keys group_keys[int(len(group_keys) * (train_ratio val_ratio)):] def write_split(keys, out_file): with open(out_file, w) as f: for key in keys: for img_path in groups[key]: f.write(str(img_path) \n) write_split(train_keys, datasets/train.txt) write_split(val_keys, datasets/val.txt) write_split(test_keys, datasets/test.txt)这个脚本里的_前缀分组是约定实际数据集可能用目录名或者视频文件名需要看情况改。划分完至少检查一件事统计每一类的 bbox 数量确认测试集里每个类都出现了不能出现测试集中只有 smoke 没有 fire导致某个类没有被评估。3. 用 YOLOv8 训练森林烟雾火焰模型训练命令、参数含义与损失曲线怎么读3.1 训练环境与依赖先装好再谈别的YOLOv8 的官方实现都集成在 ultralytics 这个包里不需要自己写训练循环。环境配置整体不复杂但很多人翻车翻在 PyTorch 版本和 CUDA 版本不匹配。我的建议是直接用 PyTorch 官方推荐的命令装比如pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118然后再pip install ultralytics。注意 ultralytics 对 numpy 版本有要求如果装完导入报错np.int这类问题把 numpy 降到 1.x 就能解决。森林烟雾火焰检测这个任务对显存的要求并不高但如果像标题配套的数据集一样包含几千张图片训练时间还是不可忽视。GTX 1660 Ti 这类 6G 显存的卡可以跑只是 batch size 要克制。开训前先跑一个 batch 的预热确认图片解码、标签加载、loss 计算都没有问题再离开电脑去干别的这是我吃过亏以后养成的习惯。3.2 训练命令与关键参数epochs、batch、imgsz 和 patienceYOLOv8 的训练入口是命令行yolo detect train把数据和参数都显式写清楚比写 Python 脚本更容易复现。下面是一个适合森林烟雾火焰检测的起步命令yolo detect train \ datafire_smoke.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch8 \ workers4 \ device0 \ patience15 \ lr00.01 \ cos_lrTrue \ projectruns/train \ nameforest_fire_v1 \ pretrainedTrue这里每个参数都不是摆设data指向数据集配置文件里面至少要写train、val的路径和names类别列表modelyolov8s.pt会自动下载 COCO 预训练权重让模型把基础特征先学起来再迁移到烟雾火焰imgsz640是训练分辨率这个值对烟雾小目标影响很大后面避坑章节细说batch8是由 6G 显存倒推出来的如果显存大可以加到 16 或 32但不能无限加大因为 batch 太大容易收敛到尖锐极小值patience15表示验证集指标连续 15 个 epoch 不提升就提前停止省时间也防过拟合cos_lrTrue让学习率按余弦曲线衰减配合lr00.01在多数迁移任务里比固定学习率稳定。在 1660 Ti 上跑 yolov8s、640 分辨率、batch 8一个 epoch 大概几十秒到几分钟100 个 epoch 通常半天内能跑完。如果数据集小比如不到 1000 张图建议直接把epochs降到 60因为小数据集跑到后面基本在过拟合。如果你习惯写 Python等价的脚本是这样from ultralytics import YOLO model YOLO(yolov8s.pt) model.train(datafire_smoke.yaml, epochs100, imgsz640, batch8, patience15, lr00.01, cos_lrTrue, projectruns/train, nameforest_fire_v1)两种方式没有本质区别命令行的好处是参数一眼能看完方便把训练日志发给别人对照。3.3 训练中看什么损失曲线、验证指标与过拟合判断训练开始后runs 目录下会自动生成 weights 和 results.csv。不要只盯着终端里刷新的 loss 数字要把results.csv拉出来画曲线。很多人问“yolov8 画损失函数曲线图怎么画”其实很简单results.csv 的每一行是一个 epoch 的均值指标用 pandas 读进来把 train/cls_loss 和 val/cls_loss 画在一起一眼就能看出问题。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/train/forest_fire_v1/results.csv) df[epoch] df.index 1 fig, ax plt.subplots(2, 2, figsize(12, 8)) ax[0, 0].plot(df[epoch], df[train/box_loss], labeltrain_box) ax[0, 0].plot(df[epoch], df[val/box_loss], labelval_box) ax[0, 1].plot(df[epoch], df[train/cls_loss], labeltrain_cls) ax[0, 1].plot(df[epoch], df[val/cls_loss], labelval_cls) ax[1, 0].plot(df[epoch], df[metrics/precision(B)], labelprecision) ax[1, 0].plot(df[epoch], df[metrics/recall(B)], labelrecall) ax[1, 1].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) ax[1, 1].plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) for a in ax.ravel(): a.legend() a.grid(True) plt.tight_layout() plt.savefig(training_curves.png, dpi150)这个脚本逻辑很直接把训练和验证曲线叠在同一张图里。判断标准是train box loss 缓慢下降且 val box loss 也下降表示模型还在正常学习如果 val loss 先降后升train loss 继续降就说明过拟合开始回看 patience 触发停止时间或提前 reduce epochs如果 train loss 从一开始就震荡不降优先怀疑标签有问题再考虑学习率是否过大。我见过有人只截一张训练日志里最后几行就开始部署这是最危险的做法。模型在最后一个 epoch 不一定最好YOLOv8 会保存best.pt它的选择标准通常是验证集综合指标最优。所以看曲线时重点看“拐点”和“间距”而不是绝对值。曲线画完训练这一环才算真正结束。4. 模型评估与可视化mAP、PR 曲线、混淆矩阵和热力图怎么看才不算自我感动4.1 评估指标为什么 mAP50 高不代表现场好用训练结束后终端会打出 precision、recall、mAP50、mAP50-95 几行数字。森林防火场景里最关键的指标不是 mAP50而是 recall。漏报一炷小火苗比误报一块夕阳的代价大得多。mAP50 表示预测框和真实框的 IoU 超过 0.5 时算命中这个阈值比较宽松适合烟雾这种边界模糊的目标mAP50-95 则把 IoU 从 0.5 逐步提高到 0.95对框的精细程度更敏感过度追求它容易让模型把精力花在“框得更准”上反而不利于召回远距离小目标和烟雾。实际项目里我会把 recall 和 precision 分开看而不是只看综合 mAP。如果 recall 有 0.9 但 precision 只有 0.6说明一半左右的检测框是误报需要提高置信度阈值或者补负样本如果 precision 很高但 recall 低说明模型漏检多要加大 imgsz、加小目标样本或者换更大尺寸模型。评估时还要按场景维度拆开火灾初期的远距离轻烟、白天强光下的火焰、夜间红外模式各算各的指标混在一起的平均值没有落地参考价值。4.2 用 YOLOv8 自带工具出评估报告YOLOv8 的 val 命令可以直接在验证集上跑出评估结果yolo detect val \ modelruns/train/forest_fire_v1/weights/best.pt \ datafire_smoke.yaml \ imgsz640 \ batch1 \ conf0.25 \ save_jsonTrue \ save_hybridTrue \ projectruns/val \ nameforest_fire_valsave_jsonTrue会生成一个 COCO 格式的 results.json里面记录了每个置信度阈值下的准确率和召回率方便自己画 PR 曲线。save_hybridTrue会把预测框和真实标签画在同一张图上直接看模型在哪些样本上漏检。跑完后去 runs/val 目录里看混淆矩阵和 F1 曲线图。混淆矩阵的横轴是真实类别纵轴是预测类别对角线越亮越好但森林场景里更值得看的是 background 类那一行误检率往往都集中在这里。我在评估时还会加一个“最坏情况检查”把验证集里置信度在 0.1 到 0.4 之间的预测框单独输出人工翻一遍。这些低置信度框通常就是误检的温床能看到模型到底是被云的形状带偏还是被石头反光带偏。4.3 可视化预测跑视频、保存结果和热力图评估通过后下一步是拿真实视频或图片做端到端预测。YOLOv8 的 predict 命令很顺手yolo detect predict \ modelruns/train/forest_fire_v1/weights/best.pt \ sourcetest_videos/ \ imgsz640 \ conf0.25 \ saveTrue \ save_txtTrue \ save_confTrue \ show_labelsTrue \ show_confTrue \ projectruns/predict \ nameforest_fire_videosave_txtTrue会输出每张图的框坐标和类别方便接到告警系统save_confTrue把置信度也写进 txt后续做二次过滤。跑完先不要看视频直接看输出目录里有没有光画了框但没有 conf 的文件。如果模型输出大量 conf0.3 左右的“火焰”回到第 4.2 节把阈值表拉出来找到 precision 和 recall 的平衡点重新设置 conf。这个阈值建议在验证集上调不要拿现场视频去凑否则会过拟合到少数几个场景。热力图也是常用的可视化手段。YOLOv8 本身不带可视化热力图的官方命令常见的做法是使用 Grad-CAM 钩子提取特征图。操作方法加载模型后在网络最后的检测头之前做 forward hook取最后一个尺度的特征响应度叠加到原图上。不过森林场景的热力图通常只作为论文配图业务排查直接看预测框就够了不用在这里花太多时间。5. 森林烟雾火焰检测最常见的 5 个坑现象、原因和解决5.1 小目标烟雾漏检树梢一缕烟就是找不到现象训练时 mAP50 有 0.85但用现场视频测试三五百米外的轻烟完全没有框甚至画面上占了十几像素的烟柱也没反应。原因主要有三个。其一YOLOv8 在 640 分辨率下骨干网络下采样到 1/32最小的检测特征图只有 20x20一个 10x10 像素的小烟雾在这个尺度上几乎只剩一个点。其二标注里的人可能主观忽略了远处的小烟雾标签本身就没有小目标。其三数据增广里的随机裁剪可能把小目标裁掉。解决先把训练和推理的imgsz从 640 提到 960 或 1280小目标特征更明显但显存和推理耗时都会涨如果数据集中小目标占比确实少可以对包含小目标的图片做复制粘贴即把图像中已有的小烟雾区域复制若干份贴到不同背景位置生成新样本。若还不行考虑修改模型结构在 YOLOv8 的 backbone 后加一个输出 stride 为 4 的检测头专门负责小目标但这会拉长训练时间不一定适合新手。5.2 火焰颜色误检夕阳、红车、红色屋顶全被当成火焰现象预测视频里有大量红色区域被标成 fire而真实火焰反而因为过曝被漏检。最常见的是傍晚的夕阳把天空和云染红模型直接把整片橙色天空框出来。原因火焰检测通常依赖颜色特征训练集的标注框里火焰几乎都是橙红色模型学到的是“红 火”而森林监控中红色往往代表的是自然光或人造物。此时背景类别没有足够多的“红色但不是火”的负样本模型没有机会区分。解决采集大概率会遇到的负样本比如夕阳、红色屋顶、红色车辆、红棕色土壤单独建一个 negative 目录标注为空背景加入训练集。训练参数里把 HSV 数据增强的色调扰动调高比如hsv_h0.05让火焰颜色在训练中更多样避免模型对纯橙红色过拟合。另外把预测的conf从 0.25 提高到 0.4 或 0.45误检通常会下降但这可能伤召回需要重新看 PR 曲线。5.3 标签错位一个图里漏标一个框模型就被带偏现象训练 loss 前几个 epoch 下降得很猛但到后面验证集 recall 始终上不去重新检查输出图片时发现很多真实目标根本没被监督到。原因很多标注工具导出的标签里目标被漏标、类别名写错或者一张图里有多个目标但只标了其中一个。YOLO 训练时没被标注的目标不会参与 loss 计算模型自然学不到它。解决训练前先做标签统计。写一个小脚本读所有 txt 文件统计每类 bbox 的数量、尺寸分布、图片平均目标数如果 smoke 和 fire 的标注数量差了三倍以上优先补少数类的样本。再做可视化检查把标注框画到图片上快速翻一遍找漏标。如果数据集很大可以用预训练的 YOLOv8 生成初步框再人工确认。养成“先统计、后训练”的习惯能避免浪费一整个下午。5.4 训练 OOM显存溢出代码还没跑完就崩现象输入yolo detect train后没几分钟就报RuntimeError: CUDA out of memory或者训练到中途突然崩掉。原因batch size 太大imgsz 太大或者开了太多 dataloader worker 导致内存爆掉。GTX 1660 Ti 这种 6G 卡尤其明显yolov8s 在 640 分辨率下 batch 32 必崩。解决最直接的办法是把 batch 降到 8 或 4配合imgsz640。另一个常用做法是开启梯度累积把有效 batch size 放大而不增加显存占用YOLOv8 里没有直接的累积参数可以用小的 batch 多跑几步来近似或者降低图片分辨率到 512 做基准实验调好参数后再回到 640。注意 OOM 不一定在第一步发生可能发生在换到下一个 epoch 的数据增广时。如果中途崩把resumeTrue加到命令里从最近的 checkpoint 续跑但换设备时 checkpoint 的显存状态不一定兼容跨显卡续跑容易出怪问题。5.5 验证集 mAP 高、现场误报频发域漂移是最大黑匣子现象同一份权重验证集上 precision 0.9部署到现场一整天误报几十次大多是树影晃动、云层移动和摄像头红外模式切换时触发的。原因验证集和训练集来自同一个数据分布即使划分合理也和“现场真实环境”有差异。现场摄像头的视角更低、画面经过压缩传输、有运动模糊、光线条件与数据集里的网络图片完全不同。YOLOv8 的权重学的是分布特征而不是绝对规则一旦输入分布偏移输出就不可控。解决从第一天就收集现场视频抽帧加入数据池尤其是晴天、阴天、清晨、黄昏、夜晚红外等不同时段。部署时不要直接用单帧检测结果去告警加一个时间维度的确认连续 N 帧检测到同一个位置才触发。还可以对画面做感兴趣区域掩码把道路、水面、天空等不可能是火源区域的误检直接滤掉。调阈值也有效但要按时间段做不同的 conf夜里红外模式的误检率通常比白天高单独设一套参数比全局改 conf 更强。6. 把模型部署到 RK3588或边缘盒子的落地经验ONNX、量化和帧率森林防火监控最终要落到边缘设备上常见做法是 RK3588 这类带 NPU 的开发板。YOLOv8 的部署链路不算短但只要按下面三步走基本能跑通。6.1 先把训练好的best.pt导出成 ONNXyolo export modelruns/train/forest_fire_v1/weights/best.pt formatonnx opset12 imgsz640导出成功后用onnxsim做一遍简化能去掉一些冗余算子。RNN 转 RKNN 时对 opset 版本敏感我一般固定用 12。6.2 在 PC 上用 rknn-toolkit2 把 ONNX 转成 RKNN 格式int8 量化需要准备一张校准集从训练集里随机抽一百张图片要覆盖不同亮度、远处和近处的烟雾火焰直接用验证集量化会高估精度。6.3 板端推理时注意输入格式是 NHWC归一化方式和 YOLOv8 的[0,1]不一定一致YOLOv8 输出的 8400 个候选框按“中心点 宽高 各类别得分”排列写后处理时不要搞错索引。我一开始直接拿 m 模型上板结果只有 3 帧换成 s 加 int8 量化后到了 15 帧左右误报率也靠置信度阈值压下去了。这个“先量化、再调参”的顺序是我的教训也希望帮到你少走这段弯路。本文还有配套的精品资源点击获取