ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLOv5火灾检测实战:从环境配置到树莓派5部署避坑指南

YOLOv5火灾检测实战:从环境配置到树莓派5部署避坑指南 简介面向目标检测与安防场景的YOLOv5火灾检测项目包适合计算机视觉开发者、消防预警系统设计者以及希望快速上手YOLOv5的研习者。压缩包内共135个文件大小68.51MB包含已训练好的pt模型权重、Python推理/训练脚本、YAML配置文件、测试图片与视频、Notebook演示及Dockerfile等其中py脚本承载模型调用与结果解析逻辑yaml记录网络结构与超参数pt为可直接加载的火焰/烟雾检测权重jpg、png和mp4则用于效果验证解压后即可组成一条完整的推理链路。模型针对火焰和烟雾两类目标进行识别结合YOLOv5的Mosaic数据增强、PANet结构、GIoU损失等改进点在实时性和准确性上表现均衡可输出带置信度的检测框有利于接入监控摄像头或嵌入告警系统。当前已有3265人学习下载整套资源既能作为目标检测实战的入门范例也可为火灾监测项目提供基础组件省去从零训练的时间成本适合在此基础上做二次开发。1. 火灾检测为什么选 YOLOv5 而不是更大更新的模型用 YOLOv5 做火灾检测是工地监控和森林防火两个方向我反复验证过最省心的组合所以这个标题的检索热度一直没降过。火灾检测要解决的是从摄像头画面里尽早发现火焰和烟雾并触发报警这个场景对模型的要求不是精度天花板而是能在普通工控机甚至边缘设备上稳定跑、能快速接入现有监控系统。YOLOv5 源码生态成熟训练脚本、导出工具、部署样例都现成后处理和超参数调整也有大量公开踩坑记录可以直接参考。YOLOv9、RT-DETR 确实更新但火灾场景样本量通常只有几千张量级小模型加成熟工程链往往比大模型加新框架更稳。这篇笔记给的是从 conda 环境配置到树莓派5 部署的一整条落地路径适合手里有摄像头、准备自己训练火灾检测模型的工程师。2. 环境配置与数据集准备conda 环境、源码拉取与标注转换2.1 conda 创建 YOLOv5 环境的固定命令yolov5 环境配置是翻车率最高的第一站。大多数问题出在 torch 和 CUDA 版本对不上或者 pip 把包装进了 base 环境跑训练时 import torch 直接报错。我习惯为每个项目单独建 conda 环境Python 版本固定在 3.9 或 3.10YOLOv5 官方源码在这两个版本上运行最顺畅。想先快速过一遍 yolov5 基础笔记再动手的拉完源码看官方 README 就够了。conda create -n yolov5-fire python3.10 -y conda activate yolov5-fire git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtrequirements.txt 里有几个包需要单独说明。torch 和 torchvision 默认装的是 CPU 版还是 CUDA 版取决于你机器上是否装好了对应驱动这一条恰恰是新手最容易忽略的。如果训练机是 NVIDIA 显卡且驱动已装好用pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118单独重装一次再装 requirements.txt 里其余包能少踩一半坑。如果只是做 CPU 训练或者后面要部署到树莓派5默认的 CPU 版 torch 就够用不用额外折腾。注意源码仓库会持续更新不同 commit 对 Python 和 torch 版本要求有差异。跑训练前先执行python -c import torch; print(torch.__version__)确认版本号再决定是否回头锁定 requirements.txt 里的版本。装完环境跑一次官方检测做验证python detect.py --weights yolov5s.pt --source data/images/bus.jpg能正常输出检测框说明环境是通的。很多人卡在这一步原因是模型的 pt 文件下载超时多试几次即可或者跳过这一句直接进入训练阶段训练时如果设置了--weights yolov5s.pt程序会按需下载。2.2 火灾数据从哪来公开数据集与自采标注做火灾检测最忌讳一上来就自己标几千张图。公开的火焰烟雾数据集其实不少常见的有火焰分割类、烟雾检测类我个人会把它们统一成 fire 和 smoke 两类后合并使用。公开数据的问题在于场景单一很多是实验室火焰或网上视频截帧拿到工地现场容易出现“训练集里的火都长一个样”的过拟合。所以我的组合拳是先下公开数据集做预训练再用自己现场摄像头抽帧补一批真实场景图。抽帧用 ffmpeg 从监控视频里按 1 秒 1 帧或 2 秒 1 帧抽取然后人工筛选出真正包含火焰、烟雾或相似干扰物的帧。筛选后的图用 labelimg 标注它可以直接导出 YOLO 格式的 txt比手工改 xml 省事得多。标注时类别设定有一个关键决策。很多首次做火灾检测的工程师会把类别拆成 fire_flame、fire_smoke、fire_ember结果每个类样本都少训练出来互相混淆。我在实际项目里只用两类fire 和 smoke。火焰与烟雾分两类是因为它们的纹理特征差异极大但不会把一个火焰场景里的火苗拆成多个细分类别这样每类的样本量才够模型学习。负样本同样重要红灯、橙红色车、落日、晚霞这类“颜色像火但不是火”的图至少放几十张放在 background 类别里。这个习惯后来帮我省掉了很多误报排查时间。2.3 把 VOC 标注转成 YOLO txt转换脚本与四个边界坑如果拿到的公开数据集是 VOC 格式的 xml 标注或者自己用 labelimg 选了 VOC 格式需要转成 YOLO 需要的归一化 txt。转换脚本不复杂但边界处理最容易出问题。import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_dir, class_map): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): cls obj.find(name).text if cls not in class_map: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 边界裁剪标注可能越出图片边缘 x1 max(0.0, min(x1, img_w - 1)) y1 max(0.0, min(y1, img_h - 1)) x2 max(0.0, min(x2, img_w - 1)) y2 max(0.0, min(y2, img_h - 1)) # 归一化中心坐标和宽高都除以图片尺寸 cx ((x1 x2) / 2) / img_w cy ((y1 y2) / 2) / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{class_map[cls]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_name Path(xml_path).stem .txt with open(Path(out_dir) / out_name, w) as f: f.write(\n.join(lines)) if __name__ __main__: class_map {fire: 0, smoke: 1} xml_dir voc_annotations out_dir yolo_labels os.makedirs(out_dir, exist_okTrue) for xml_file in Path(xml_dir).glob(*.xml): voc_to_yolo(str(xml_file), out_dir, class_map)逻辑上要注意四个边界坑。第一除宽高时必须用浮点除法Python 3 里单斜杠已经是浮点除法但如果你从旧项目复制代码且头上丢了from __future__ import division整数除法会把小于 1 的归一化坐标直接变成 0。第二边界裁剪是因为有些标注框在标注工具里拖出了画布不裁剪会造成后续 loss 计算出现越界框。第三类别编号必须从 0 开始且与 data yaml 里的顺序一一对应很多人 fire 写了 1、smoke 写了 0训练时损失直接乱掉。第四空标注文件要保留一张图没有目标时对应的 txt 应该是 0 字节训练时 YOLOv5 会跳过它但不要因为“没目标”就不生成文件否则数据加载逻辑会报错。前两个是代码问题后两个是数据管理问题写脚本时一次处理好后面训练会省心很多。3. 训练自己的火灾检测模型超参数与 Loss 调优3.1 预训练权重怎么选yolov5s.pt 与火灾场景的匹配YOLOv5 官方提供 s/m/l/x 四个规模对应不同的深度和宽度系数。火灾检测因为部署目标通常是工控机或边缘设备我的选择逻辑很直接先用 yolov5s.pt 做预训练。原因不是 s 精度够高而是 s 的参数量约 7M训练迭代快能在几小时内验证数据标注和超参数是否合理。数据集小、目标场景集中时从 s 起步比直接上 l/x 更稳妥后者参数量大但样本量不足时容易过拟合训练时间也翻倍。什么时候换更大的模型如果训练数据到了两三千张以上且你明确知道部署机的算力能接受 m 的推理时间可以试 yolov5m.pt。m 在火焰大目标上提升并不明显但对小目标的召回率会好一些这个差异在后面的避坑章节细讲。值得注意的是预训练权重的作用是提供底层纹理特征火灾的火焰纹理和 ImageNet 里的物体差别很大但边缘、颜色渐变、纹理基元还是通用的。所以即便火灾和预训练类别差异再大也不建议从随机权重开始训练收敛速度和最终精度都会差一个量级。3.2 train.py 的最小可跑命令与每项参数含义训练自己数据集时train.py 参数里最需要仔细设的是 data yaml、weights、img、batch 和 epochs。先放一个我常用的命令python train.py \ --data fire.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache ram \ --hyp hyp.scratch-low.yaml \ --project runs/fire_train \ --name exp_v1fire.yaml 内容对应 2.3 节的类别约定train: ./datasets/fire/images/train val: ./datasets/fire/images/val nc: 2 names: [fire, smoke]逐个说参数。img 是训练输入分辨率640 是性价比平衡点火焰小目标多的时候我会提到 800 或 960代价是显存和训练时间增加约 50%这个后面细说。batch 表示单卡每轮的样本量显存不够就减小但不要小于 8否则 BatchNorm 统计不稳定。epochs 对火灾这类小数据集 100 起步如果 val loss 还在下降就继续训练到 300。cache ram 是把图像缓存进内存能减少磁盘 I/O数据集几百张时提升明显几千张以上时注意内存容量。hyp 指定超参数文件hyp.scratch-low.yaml 适合小数据集hyp.scratch-high.yaml 的增强更强适合大数据集但容易在小数据集上过拟合。提示把--project和--name固定下来每次实验换 name不要用默认的 exp 目录反复覆盖否则后面想对比超参数效果时没有后悔药。训练过程中最该盯的控制台输出是 val BoxLoss 和 val cls loss。火灾目标边缘不规则box loss 下降慢是正常的但如果 cls loss 在训练中期出现震荡优先怀疑类别不平衡接着去调整 3.3 的内容。3.3 火灾样本不平衡少火焰多烟雾时的超参数打法火灾数据集几乎必然出现类别不平衡。最常见的是 fire 样本几百张、smoke 样本只有几十张因为火焰场景好找烟雾尤其是刚起火时的淡烟很难收集。这种情况下模型会把 smoke 类学成 fire 的附属品推理时烟检测不出来。有三个有效的调整方向。第一是调整 cls loss 的权重在 train.py 命令里加--cls 1.5或更高。YOLOv5 的 cls loss 默认权重在 hyp 里是 0.5把 fire 类权重调高会让模型更重视火焰把 smoke 类权重调高则相反。但这个只能缓解不能根治。第二是关掉或减弱会让小目标更碎的增强。hyp.scratch-low.yaml 里的 mosaic 在火灾场景里要特别小心mosaic 会把四张图拼成一张火焰对象经常被裁掉一半。火焰这种“边缘模糊”的目标被切碎后模型学到的是残缺火焰纹理掉点明显。我的做法是把 mosaic 从默认值调到 0.3同时保留 mixup 0.1。hsv_h 和 hsv_s 参数也不必按默认来火灾数据集里火焰的颜色本身已经够丰富过度的色相增强会让红色车漆和火焰更难区分我习惯把 hsv_h 从 0.015 调低到 0.005hsv_s 保留默认。第三类是数据层面补样本把烟雾视频里多抽几帧哪怕标得粗糙也比缺类强。实话说超参数调整的边际收益有上限如果不平衡比例超过 10:1补数据才是根本解法。训练后跑一次验证看每个类别的 mAP哪个类明显低就重点补哪类python val.py --data fire.yaml --weights runs/fire_train/exp_v1/weights/best.pt4. 部署到树莓派5推理速度、模型导出与后处理取舍4.1 导出 FP16/INT8 引擎的两种路径树莓派5 上部署自己训练的 yolov5 模型首先要认清一个边界它是 ARM 处理器没有 CUDA 和 TensorRT。常见的落地路径有两条一条是导出 ONNX 后用 onnxruntime 推理另一条是转成 NCNN 格式跑 ncnn 的 C 接口。两条我都走过建议是只做原型验证就选 ONNX要做长期运行的监控服务选 NCNN。原因很简单NCNN 在树莓派5 上对 ARM CPU 的算子优化比 onnxruntime 更彻底INT8 量化也更成熟。导出 ONNX 用官方 export.pypython export.py \ --weights runs/fire_train/exp_v1/weights/best.pt \ --include onnx \ --img 640这里有一个关键取舍不要加--half。FP16 半精度对 NVIDIA GPU 有效因为 GPU 有专用 FP16 计算单元树莓派5 的 CPU 虽然支持 FP16 指令但 onnxruntime 在 ARM 上对 FP16 算子的支持并不完整实测反而比 FP32 慢。NCNN 路径的 INT8 量化则值得做火灾检测对精度损失有一定容忍度INT8 能把推理时间再砍一半。INT8 量化需要准备校准图片用训练集的验证集图片就行校准图片一百张上下就能达到不错效果。4.2 把检测结果接进视频流NMS 后的置信度过滤导出后的 ONNX 模型默认不带 NMS需要在业务代码里自己做 yolov5 后处理。树莓派5 上性能有限后处理的取舍直接影响帧率。import cv2 import numpy as np import onnxruntime as ort class FireDetector: def __init__(self, onnx_path, conf_thres0.4, iou_thres0.5): self.session ort.InferenceSession(onnx_path) self.conf_thres conf_thres self.iou_thres iou_thres self.strides [8, 16, 32] self.nc 2 # fire, smoke def letterbox(self, img, new_size640): h, w img.shape[:2] scale min(new_size / h, new_size / w) new_h, new_w int(h * scale), int(w * scale) resized cv2.resize(img, (new_w, new_h)) canvas np.full((new_size, new_size, 3), 114, dtypenp.uint8) canvas[:new_h, :new_w] resized return canvas, scale, (new_w, new_h)这个类里letterbox 必须和原版 YOLOv5 保持一致的填充方式否则推理结果坐标会错位。推理时先 letterbox 到 640送入 session 得到形状如[1, 25200, 7]的输出其中 25200 8080 4040 20*20 个 anchor 位置7 4 个框坐标 2 个类别置信度 1 个 objectness。因为是火灾场景后处理里我通常直接把 conf_thres 拉到 0.4比默认 0.25 高能滤掉大量边缘误报。IoU 阈值 0.5 保持不变。如果树莓派5 上 CPU 占用率已经很高可以把输入分辨率从 640 降到 480火焰这类大目标受影响有限速度提升却能接近一半这个取舍在算力紧张时非常管用。4.3 树莓派5 上的实测帧率与线程模型安排树莓派5 上用 yolov5s640 输入FP32 的 ONNX单帧推理时间大致在 150 到 250 毫秒之间换算过来是 4 到 6 FPS。对于火灾监控这个场景这个帧率是够用的。火灾发展是以秒甚至分钟为单位的5 FPS 意味着 200 毫秒级别的检测延迟完全满足报警需求。真正影响系统可靠性的是线程模型而不是那百分之几十的帧率差。我一般会把采集、推理、报警拆成三个线程。采集线程用 OpenCV 的 VideoCapture 持续拉流把最新帧放入一个双缓冲队列推理线程从队列取帧做 letterbox、推理、后处理把结果写到共享状态报警线程只管读结果状态满足连续 N 帧判定条件就触发报警。这套模型里采集和推理解耦后即使推理偶尔出现一次 100 毫秒的抖动也不会导致视频流积压和延迟滚雪球。双缓冲队列的实现要点是换帧时加一个锁避免推理线程读到半写入的帧。这块不做好的话树莓派5 上跑不了多久就会出现画面卡顿和报警滞后的血泪经验。5. 火灾检测的避坑笔记漏检、误报与显存不足5.1 火焰小目标漏检原因在 PAN 层下采样太狠现象测试视频里远处的火苗人眼能隐约看到模型就是不报把视频暂停放大后才报出来。原因YOLOv5 的 PAN 结构把输入逐步下采样到 32 倍小目标的特征在深层特征图里几乎消失20x20 这一层对几个像素的小火焰响应极弱。解决路径有三条按投入产出比排序把 img 从 640 提到 800 或 960小目标在特征图上的像素数直接翻倍这是最有效的一步其次用 yolov5s 而不是 m/x小模型对小目标的拟合往往比大模型更稳最后是针对小目标特别多的场景把输入切成四块分别推理再合并结果也就是 SAHI 思路但树莓派5 上用这个方案帧率会掉到 1 FPS 以下只适合离线分析。注意调高 img 后显存占用按平方增长batch 需要同步调小。640 到 960 意味着每张图特征图面积变成 2.25 倍原来 batch 16 能跑的卡现在 batch 8 都未必够。5.2 红灯、落日、橙色车漆误报数据增强补不回来现象训练集 mAP 不错一到真实监控画面里红灯、黄昏的太阳、橙色的车都被标成 fire。原因火灾检测本质上是颜色和纹理的联合判断而这类干扰物在颜色上与火焰高度重合模型在纹理特征不足时就会退回用颜色分类。解决的第一步是收集 hard negative专门从监控视频里截取包含红色灯牌、夕阳、红色车辆的帧作为 background 类加进训练集。这一步比调任何超参数都有效。第二步是克制 hsv 色相增强前面 3.3 里把 hsv_h 调低就是为这个服务的。第三步是部署端加时间维判断单帧里颜色像火不足以报警连续多个帧都检测到且位置不漂移才触发第六节会展开。这里有个黑匣子现象同样的模型加了 hard negative 之后 mAP 可能不升反降因为背景类变多会稀释正类置信度但实际误报大幅减少看指标时要认清这个反直觉现象。5.3 显存不足与 batch 掉到 1梯度累积怎么保训练效果现象显存 8G 的卡设了 batch 16训练开始就 OOM被迫改成 batch 2结果训练出来的模型 val loss 一直下不去。原因batch 太小BatchNorm 统计的均值和方差噪声太大训练不稳定。解决不要改 batch 数值改用梯度累积。YOLOv5 的 train.py 会自动根据 batch 和显存计算梯度累积步数如果你改小了 batch它会自动把累积步数调大。但手动设置更可控做法是保持--batch 16的命令不变在代码里把单卡实际加载的 batch 调成 4等效于每 16 张图更新一次权重。显存不够时宁可用 4 张图累积 4 步也不要用 2 张图跑全程这两种方式显存占用接近但训练稳定性差很多。5.4 标注框抖动导致 mAP 虚高标注一致性的量纲现象验证集 mAP 0.82兴致勃勃部署上去实际视频里频繁漏检人工查看每个检测框都觉得框得不准。原因标注不一致。有人把火焰整个轮廓框住有人只框中心亮部同一个目标在不同图里的标注框差异巨大模型学习到的边界就是混乱的。mAP 是按 IoU 阈值计算的标注抖动会让评估结果虚高因为只要预测框覆盖了标注中心就算对。解决标注前定一份统一的标注口径。我现在的做法是火焰框整体外轮廓、烟雾框半透明可见部分的下沿全项目统一。标注完抽样检查框的重心分布如果同一物体多人标注的框中心偏差超过框宽度的 20%就返工。这个检查放在训练前能省掉后面无数调参时间。6. 用帧差 多帧投票压低误报一个可落地的后处理技巧6.1 为什么火灾检测需要时间维信息单帧静态检测在火灾场景里有一个天然缺陷火焰是动态目标而误报源红灯、落日是静态的。只凭单帧判断模型很难区分“正在燃烧的火”和“颜色像火的物体”。引入时间维后这个区分变得非常简单火焰的闪烁和位移会导致检测框跳动而静态干扰物的检测框位置稳定。多帧投票就是利用这个差异来压低误报。6.2 五帧投票的两种实现滑窗与指数平滑from collections import deque class FireVoter: def __init__(self, window5, min_votes3): self.window window self.min_votes min_votes self.history deque(maxlenwindow) def update(self, detections): # detections: 每帧 [class_id, conf, x1, y1, x2, y2] 列表 fire_frames [d for d in detections if d[0] 0] conf max([d[1] for d in fire_frames], default0.0) fire conf 0.4 self.history.append(fire) if len(self.history) self.window: return False, 0.0 votes sum(self.history) return votes self.min_votes, conf这个滑窗实现里窗口 5 帧、最少 3 帧判定为火灾报警既能滤掉单帧偶发误报又把报警延迟控制在两帧以内。滑窗的缺点是每帧都要存状态指数平滑方案更轻量score 0.6 * current_conf 0.4 * prev_score超过阈值即报警。前者的好处是逻辑直观、参数好解释后者响应更快。我推荐工程上用滑窗版本因为它对“连续几帧”的语义可以直接用窗口参数说明给现场验收人员解释起来容易。实际部署时我会按场景调 min_votes室外场景火焰有风会闪烁min_votes 设 2室内场景设 3。6.3 验证方法在测试视频上统计误报/漏报比后处理改完要用数据说话。我习惯准备三段视频一段正常焚烧场景、一段包含红色干扰物的场景、一段火焰从出现到蔓延的实拍。统计两种指标单帧检测的误报帧数与投票后的误报次数以及投票引入的报警延迟帧数。我过去的一个项目里单帧误报率大概每百帧出现 5 次误报加载 5 帧 3 票后误报降低到 0代价是报警比单帧模式晚了 3 帧约 0.6 秒这个延迟对火灾场景完全可接受。需要强调的是投票会略微降低对快速小火苗的敏感度如果你监控的是实验室燃烧场景建议把窗口缩到 3 帧。调参时把这两个指标记录下来比凭感觉调阈值可靠得多。这套后处理算是我在火灾检测部署里最常被同事问到的技巧。它不改变模型本身只动后处理逻辑却往往比重新训练一轮收益更大。我现在的习惯是任何火灾检测项目都先把单帧模型跑通再无条件加上时间维过滤然后再回头评估数据和超参数的调整方向顺序反了容易在调参里空耗时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表