简介:面向毕业设计场景的YOLOv5火灾火焰烟雾检测完整方案,整合了标注数据、训练好的模型与PyQt可视化界面,并实现了TensorRT加速的预处理、推理和后处理流程,适合计算机视觉或嵌入式方向的学生直接复现与二次开发。工程也提供了CBAM、SE等注意力机制改进尝试,以及串口通信联动脚本,方便扩展为实时预警系统。资源共74个文件,以31个yaml配置、28个Python脚本、5个shell脚本为主体,辅以图片示例、Markdown说明与许可证文件,覆盖模型结构定义、训练推理、串口通信、界面展示等模块;压缩包仅718KB,体量轻巧,便于快速下载部署。yaml文件用于模型和数据集配置,py脚本封装检测核心逻辑,sh脚本可一键完成环境准备等操作。目前已有118人学习下载,适合需要完整项目参考、想了解TensorRT加速目标检测落地的学习者,也可作为论文实验或课程设计的基座。
1. YOLOv5火灾火焰烟雾检测:这套资源到底能直接拿来做什么?
做毕业设计最怕的不是写代码,而是数据、模型、界面、部署四件事同时压过来。这套 Python 毕业设计资源主攻 YOLOv5 火灾火焰烟雾检测,把最花时间的数据集、训练好的模型、YOLO 格式标注、PyQt 界面雏形和 TensorRT 加速推理源码一次打包。我拆包后发现它不是一个空壳工程,而是从数据标注到串口联动报警的完整链路:SYS.zip 里是可训练的图片和标签,FIRE-YOLOV5-master 里是改造过的 YOLOv5 工程,里面单独加了 CBAM 和 SE 注意力模块的模型定义,还有 serial communication.py 负责把检测结果发到外部设备。适合正在做火灾检测相关课程设计、毕业设计,或者想把 YOLOv5 快速迁移到火焰烟雾场景的工程师。你不需要从零开始攒数据,重点是弄明白每个文件怎么配合、训练参数怎么调、TensorRT 引擎怎么转,以及最容易翻车的几个位置在哪。
2. 拆解资源包:数据、权重与注意力改进点都在哪
2.1 文件清单里藏着什么:数据、权重、改进点和脚本
我打开压缩包第一感觉是:这确实是能跑起来的完整工程,不是那种只有一个 README 的壳。下面这张表是拆完包后整理的关键文件,建议你按这个顺序去对照自己手里的资源。
| 文件 / 目录 | 作用 | 说明 |
|---|---|---|
| SYS.zip | 原始数据集压缩包 | 解压后一般是 images 和 labels 目录,标注为 YOLO 格式 |
| FIRE-YOLOV5-master | 工程根目录 | 在官方 YOLOv5 结构上改了模型文件和注意力模块 |
| add(CBAM).yaml | 模型结构配置 | 在 YOLOv5 的 backbone 层叠加 CBAM 注意力 |
| add(SE)common.py | SE 模块实现 | 通道注意力,和官方 common.py 配合使用 |
| add(CBAM)common.py | CBAM 模块实现 | 通道注意力 + 空间注意力,主要在输入输出层做替换 |
| serial communication.py | 串口联动脚本 | 用 serial.Serial 把检测结果发给单片机或其他设备 |
| Accelerate the engine.py | TensorRT 推理脚本 | 加载 fire.engine,完成预处理、推理、后处理 |
| train.py | 训练入口 | YOLOv5 官方训练脚本,参数和官方保持一致 |
| models/yolov5s/m/l/x.yaml | 基础模型结构 | 你可以在这个基础上加注意力,也可以直接训练 |
| data/*.yaml | 数据集配置模板 | coco.yaml、VOC.yaml 等,用来对照格式 |
整个资源的工作流是这样的:先用 SYS.zip 里的火灾图片和标注整理出数据集,然后在 add(CBAM).yaml 或加 SE 的模型定义上选一个跑 train.py,训练完会得到 best.pt 权重;如果追求实时性,就用 Accelerate the engine.py 把权重转成 TensorRT engine,最后再用 serial communication.py 做外部联动。这里最值得学习的是注意力模块,因为大部分火灾检测失败案例都出在小目标和低对比度上,CBAM 和 SE 就是冲着这个问题去的。
2.2 CBAM 和 SE 注意力模块:给火焰/烟雾检测带来的实际变化
火焰和烟雾不是普通物体:火焰颜色鲜明但形状多变,烟雾则是半透明、边缘模糊,还经常大面积遮挡背景。YOLOv5 的 CSPDarknet 特征提取能力不弱,但对小面积火焰和浅色烟雾容易漏检。SE 模块的做法是先用全局平均池化把每个通道压缩成一个值,再通过两个全连接层算出通道权重,相当于告诉网络“哪些特征通道更重要”。CBAM 比 SE 多了一个空间注意力路径,它在通道注意力之后还会对每个空间位置做加权,让网络更关注“哪里有异常”。
我在实际训练火焰数据时更偏向 CBAM,因为烟雾没有固定的纹理和形状,空间注意力可以帮助模型聚焦到图像右上角的烟雾扩散区域或者火光周围的热浪区域。在代码层面,add(CBAM)common.py 里一般会定义一个包含 ChannelAttention 和 SpatialAttention 的类。这里我写一个等效的核心结构,方便你理解它插在哪:
import torch import torch.nn as nn class ChannelAttention(nn.Module): def __init__(self, channels, reduction=16): super().__init__() self.mlp = nn.Sequential( nn.Conv2d(channels, channels // reduction, 1, bias=False), nn.ReLU(inplace=True), nn.Conv2d(channels // reduction, channels, 1, bias=False), ) self.sigmoid = nn.Sigmoid() def forward(self, x): avg_out = self.mlp(torch.mean(x, dim=(2, 3), keepdim=True)) max_out = self.mlp(torch.max(x, dim=(2, 3), keepdim=True)[0]) return self.sigmoid(avg_out + max_out)这段代码里,torch.mean 和 torch.max 分别提取全局平均特征和最大特征,两者相加后再经过 sigmoid 得到通道权重。reduction 参数控制压缩比例,一般取 16,如果通道数很少可以改成 8。CBAM 的空间注意力部分会在此基础上再对 HxW 维度做一次卷积和 sigmoid,你可以直接参考资源里 add(CBAM)common.py 的完整实现。实际使用中我建议先用 SE 跑通 baseline,再换 CBAM 对比 mAP 变化,不要同时改结构和数据,否则出了问题很难定位。
2.3 预训练权重与推理脚本:怎么用它快速跑通 demo
如果你不想从头训练,资源包里已经带了训练好的模型权重,直接把权重放到 weights 目录,然后按 YOLOv5 官方方式跑 detect.py。虽然拆包时没有看到独立的 detect.py,但这个工程沿用了官方 YOLOv5 入口,train.py 和 model 结构都在,detect 入口可以正常调用。最快验证效果的命令是:
python detect.py --weights weights/best.pt --source demo.jpg --img 640 --conf 0.25 --iou 0.45这个命令里,--img 640 表示把输入图缩放到 640x640,--conf 是置信度阈值,低于这个值的框会被丢弃,--iou 是 NMS 的 IoU 阈值。第一次跑建议直接用 demo.jpg,这个文件在工程根目录下,是典型的火焰画面,能直观看到检测框是否稳定。如果你想把速度再提一档,就用 Accelerate the engine.py 加载 TensorRT 引擎,输入同样一张图,推理时延会比 PyTorch 模式明显下降。要注意的是,engine 文件绑定具体 GPU 型号,你在自己机器上跑前需要重新导出。
3. 训练自己的火灾检测模型:数据准备、超参数与训练流程
3.1 数据集结构与标注格式:YOLO 标注怎么看、怎么改
SYS.zip 解压后,里面图片和标注的目录结构对 YOLO 来说非常标准。每个 txt 文件名和图片名一致,每行代表一个目标框,格式是:class_id x_center y_center width height。坐标都是归一化到 0 到 1 的小数,不是像素值。比如一行1 0.5182 0.2464 0.1235 0.3180,表示类别 1(烟雾)的中心点在图像水平 51.82%、垂直 24.64% 的位置,宽度约 12.35%,高度约 31.80%。
先解压数据,再用下面这行命令看一张图对应的标注内容:
unzip SYS.zip -d fire_dataset cat fire_dataset/labels/train/fire_001.txt如果你要自己扩充数据,最省事的方式是用 LabelImg 或 LabelStudio 导成 YOLO 格式。我一般会写一个快速脚本检查标注是否越界和是否为空,防止框的中心点大于 1 或者宽高超过图像边界。这里给一个检查思路:
import os label_dir = "fire_dataset/labels/train" for f in os.listdir(label_dir): with open(os.path.join(label_dir, f)) as fp: for line in fp: parts = line.strip().split() cls = int(parts[0]) x, y, w, h = map(float, parts[1:]) if x < 0 or y < 0 or w < 0 or h < 0 or x > 1 or y > 1: print(f"异常标注: {f} -> {line.strip()}")这段代码把每个 txt 的四列坐标拆出来,如果出现负值或者超过 1 就打印文件名。这类错误很隐蔽,如果直接训练,loss 会莫名震荡。接下来在工程 data 目录里新建一个 fire.yaml,指向解压出来的数据集:
path: ../fire_dataset train: images/train val: images/val nc: 2 names: ['fire', 'smoke']path 是数据集根目录,train 和 val 是相对路径,nc 是类别数,names 是类别名。资源里的 data 目录虽然放了 Argoverse.yaml、VOC.yaml 一堆官方配置,但训练火焰烟雾时不要用这些,因为它们标注的是车辆、行人,类别数和你完全对不上。
3.2 train.py 训练参数详解:从预训练权重继续练还是从零开始
火焰烟雾数据量通常只有几千张,不建议从零初始化训练,否则特征提取器需要大量数据才能学到通用纹理,训练时间会很难看。正确做法是从 COCO 预训练权重开始迁移学习。用 add(CBAM).yaml 时,训练命令可以这样写:
python train.py --data fire.yaml --cfg add(CBAM).yaml --weights yolov5s.pt --epochs 100 --batch-size 16 --img 640 --patience 20这里每个参数都有讲究。--cfg 指定模型结构,资源里的 add(CBAM).yaml 是在 YOLOv5s 基础上加了 CBAM 注意力,所以配套权重建议也用 yolov5s.pt,保证通道数对齐。--batch-size 要看显存,16 是 8GB 到 12GB 显卡比较稳的值,如果显存只有 6GB,降到 8。--img 640 是训练分辨率,火灾小目标多,可以试试 896,但训练时间会变长。--patience 是早停轮数,20 表示 20 个 epoch 内 mAP 没有提升就停止,节省时间。
训练过程中会在 runs/train/exp 目录下生成 best.pt 和 last.pt。best.pt 是验证集效果最好的权重,last.pt 是最后一轮的权重,最终部署用 best.pt。如果你发现模型在火焰上表现好但烟雾漏检,不要急着改结构,先检查烟雾类别的标注框是不是太少。类别不平衡是火灾检测的常见问题,一个土办法是在 loss.py 里给烟雾类别的分类损失乘一个权重,资源里的 loss.py 是 YOLOv5 标准实现,你可以在这个文件里找到分类损失部分做修改。
3.3 训练日志与模型选择:怎么判断有没有收敛
训练结束后,不要只看终端输出的平均 loss,要打开 runs/train/exp/results.png 看曲线。这个图包含五条重要曲线:Box Loss、Objectness Loss、Classification Loss、Precision、Recall 和 mAP。对于火灾场景,我建议优先关注 Recall 和 mAP@0.5,因为漏报一个火焰可能造成严重后果,宁可多误报也不能让火烧起来没人管。
| 指标 | 含义 | 火灾检测里的关注度 |
|---|---|---|
| Recall | 所有真实目标中检出的比例 | 高,漏检是关键风险 |
| Precision | 检出框中正确框的比例 | 中,误报多会让人疲惫 |
| mAP@0.5 | IoU 阈值 0.5 下的平均精度 | 高,最直观的精度指标 |
| mAP@0.5:0.95 | 不同 IoU 阈值的综合精度 | 低,框的位置要求更严格 |
如果 mAP@0.5 在 50 个 epoch 后还在缓慢上升,说明数据复杂度高,可以继续训练;如果 loss 已经下降缓慢但 mAP 波动很大,可以调低学习率。资源里的 hyps 目录下是超参数文件,默认 hyp.scratch.yaml 里的 lr0 是 0.01,如果训练后期震荡,可以把 lr0 改到 0.005。还有一个可选操作是用tensorboard --logdir runs/train实时观察曲线,比盯控制台舒服得多。
4. TensorRT 加速与串口联动:把模型从离线演示变成实时预警
4.1 TensorRT 引擎构建:从 PyTorch 权重到 engine 文件的转换
PyTorch 推理在 GPU 上已经不错了,但如果要接摄像头实时检测,或者跑在嵌入式设备上,TensorRT 几乎是绕不开的路。TensorRT 会把网络中的卷积、BN、ReLU 等层融合,还能用 FP16/INT8 精度减少显存消耗和计算量。资源里专门有一个 Accelerate the engine.py,它的核心就是加载和运行 engine 文件,但 engine 文件本身需要先由权重转换出来。
如果你的环境里装了 YOLOv5 官方库,最快的转换方式是:
python export.py --weights weights/best.pt --include engine --device 0 --fp16这个命令会生成 best.engine。如果 export.py 版本和你用的 YOLOv5 工程不完全一致,也可以用资源里 Accelerate the engine.py 里的 TensorRT API 手动加载已经生成的 engine。加载部分的代码逻辑是这样的:
import tensorrt as trt TRT_LOGGER = trt.Logger(trt.Logger.WARNING) runtime = trt.Runtime(TRT_LOGGER) with open("fire.engine", "rb") as f: engine = runtime.deserialize_cuda_engine(f.read()) context = engine.create_execution_context()这里 TRT_LOGGER 负责把 TensorRT 的警告和错误输出到终端,trt.Logger.WARNING 表示只显示警告以上级别,调试时可以改成 trt.Logger.INFO,能看到更多引擎构建细节。runtime 负责把二进制 engine 反序列化成可执行引擎。context 是实际执行推理的对象,每个 engine 可以创建多个 context 做并发推理。
血泪经验:engine 文件和生成它的 GPU 绑定。你在这台机器上导出的 engine,拿到另一张不同型号的显卡上大概率报错。所以项目演示前一定要在演示机上重新导出一次,或者直接用 PyTorch 权重兜底,别让 engine 变成黑匣子。
4.2 预处理、推理、后处理:engine 的输入输出要怎么接
TensorRT 的输入不是原始图像,它需要你把图像转成 CHW 的 float32 数据,并且值域归一化到 0 到 1。YOLOv5 推理标准流程是:先用 letterbox 把图像等比缩放到 640x640,周边用灰色填充,然后做 BGR 到 RGB 转换,再归一化。资源里的 Accelerate the engine.py 用到了 CUDA 的 cuda.Device、make_context、Stream 和 pagelocked_empty,这些工具负责在 GPU 上高效分配内存。
简化后的核心推理循环如下:
import cuda import numpy as np input_shape = (1, 3, 640, 640) output_shape = (1, 25200, 6) d_input = cuda.mem_alloc(1 * 3 * 640 * 640 * 4) d_output = cuda.mem_alloc(1 * 25200 * 6 * 4) stream = cuda.Stream() # 预处理后的图像:归一化 RGB,CHW 排列 image_np = np.ascontiguousarray(preprocessed_image, dtype=np.float32) cuda.memcpy_htod_async(d_input, image_np, stream) context.execute_v2(bindings=[int(d_input), int(d_output)]) cuda.memcpy_dtoh_async(output_array, d_output, stream) cuda.Stream.synchronize(stream)这里 cuda.mem_alloc 在显存上分配输入和输出缓冲,execute_v2 执行推理,bindings 列表里的顺序要和 engine 的输入输出 binding 序号一致。输出形状是 1x25200x6,其中 25200 是 640x640 下三个尺度特征图拼接后的候选框总数,6 是 4 个框坐标加 1 个置信度加 1 个类别数(如果只有两类就是 7)。拿到这堆原始输出后还需要做置信度过滤和 NMS,这部分和 PyTorch 后处理一样,只是输入换成了 numpy 数组。
4.3 串口通信联动:检测到火情后怎么通知外部设备
毕业设计里只把检测结果显示在屏幕上,说服力有限。资源里的 serial communication.py 用 pyserial 实现了串口发送,检测到火焰或烟雾后,可以给单片机、报警器或者继电器发一个指令。基础用法如下:
import serial import time ser = serial.Serial('COM3', 9600, timeout=1) time.sleep(0.1) if max_conf > 0.5: # 帧头 + 指令 + 结束符 ser.write(b'\xAA\x55\x01\x00\xFF') print("发送报警帧")serial.Serial 的第一个参数是串口号,Windows 下常见 COM3、COM4,Linux 下常见 /dev/ttyUSB0;9600 是波特率,必须和接收端一致;timeout 是读写超时。发送的内容是十六进制字节,这里 0xAA 0x55 是帧头,0x01 表示检测到火灾,0x00 是预留的传感器编号,0xFF 是结束符。实际项目里你可以自己定协议,但帧头 + 数据 + 校验的格式一定要保留,否则接收端无法判断一帧从哪里开始。
串口联动最关键的是搞清楚帧格式。我见过有人直接ser.write(b'fire'),接收端单片机按字节解析时完全懵掉,因为字符串没有固定长度,也没有帧头。用十六进制帧的方式最稳,单片机端只需要检测 0xAA 0x55 开头就进入接收状态。
5. 避坑与常见问题:训练、转换、部署最容易翻车的五个位置
5.1 训练时 loss 曲线一直高位震荡,mAP 上不去
现象:训练到 50 个 epoch,Box Loss 和 Objectness Loss 还是忽高忽低,验证集 mAP 在 0.5 附近波动上不去。原因:最常见的是标注框包含太多小目标,或者标注本身有边界越界;其次是学习率偏大,导致后期权重更新步长太大。解决:先用上面 3.1 的脚本检查一遍标注,然后看 hyps 里的 lr0 和 lrf,把 lr0 从 0.01 降到 0.005,lrf 保持 0.2。还可以把 --img 从 640 提升到 896,让模型看到更清晰的小火焰。
5.2 TensorRT 导出 engine 失败,提示显存不足或版本不兼容
现象:执行 export.py --include engine 时,报错Could not create engine或者Cuda engine creation failed。原因:一是 batch-size 设置太大,TensorRT 需要分配对应的显存;二是 TensorRT 版本和显卡驱动、CUDA 算力不匹配。解决:导出时固定 batch-size 1,加--fp16减少显存占用;先用nvidia-smi看驱动支持的 CUDA 版本,再装对应的 TensorRT。如果你是 30 系以后的显卡,尽量用 TensorRT 8.x 以上版本。
5.3 检测框在视频里一帧有一帧没有,跳得很厉害
现象:用摄像头实时检测,火焰框偶尔消失,或者同一个火苗一会儿一个框一会儿两个框。原因:conf_thres 设太低,大量低置信度候选框通过 NMS 后残留;或者 iou_thres 太高,导致重复框合并不彻底。解决:把 detect.py 的 --conf 从 0.25 调到 0.35 或 0.4,--iou 从 0.45 调到 0.3。如果还是跳,可以在后处理里做帧间 EMA 平滑:当前帧置信度 = 0.7 * 当前帧 + 0.3 * 上一帧最大置信度,减少单帧噪声。
5.4 串口乱码或单片机接收不到完整报警帧
现象:上位机能打开串口,但接收端显示乱码,或者经常读到半截数据。原因:波特率不一致是头号原因;其次是发送频率太高,接收端缓冲区溢出。解决:先确认两边的波特率、数据位、停止位完全一致,推荐9600, 8, N, 1。然后在代码里给连续两条报警帧之间加 200ms 延时,并用帧头 + 长度 + 校验的结构。发送前用串口调试助手先收一发,确认二进制内容后再接单片机,这一步能省掉大量定位时间。
5.5 PyQt 界面卡死,拖动窗口都没反应
现象:把推理循环直接写在 PyQt 的按钮回调或主窗口事件里,摄像头画面一开,界面立刻无响应。原因:GPU 推理解锁和帧读取都是耗时操作,它们把 Qt 事件循环堵死了。解决:把摄像头采集和推理放到 QThread 的工作线程里,通过 signal 把检测结果发回主线程更新画面。我一般习惯这样写:工作线程只负责推理,主线程只负责显示,中间用队列传递结果。这也是资源里 PyQt 界面雏形应该遵守的结构,不要在界面上直接调 TensorRT。
6. 进阶技巧:阈值扫描与多尺度推理,把小火焰从远处找出来
最后一个技巧是我每次部署火灾检测模型都会做的固定动作:阈值扫描。很多人直接拿 detect.py 默认的 conf 0.25 上现场,其实这个值对不同场景差异巨大。白天阳光下的火焰很亮,置信度能到 0.8;夜间远处的小火点,置信度可能只有 0.2 左右。与其凭感觉调,不如直接扫描验证集。
import torch model = torch.hub.load('ultralytics/yolov5', 'custom', path='weights/best.pt') for conf in [0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5]: model.conf = conf results = model(val_images) metrics = results.pandas().xyxy # 统计这一组阈值下的召回率和误报数,记录到表格 print(f"conf={conf}, recall={recall:.2f}, false_alarm={false_count}")这段代码遍历一组置信度阈值,在验证集图片上跑推理,统计召回率和误报数。选阈值的原则是:在能接受的误报数量下,尽量让召回率最高。我自己的经验是,火焰烟雾场景宁可把阈值降到 0.15 左右,保证所有可疑区域先被框出来,再通过帧间连续性过滤单帧误报,而不是一开始就设高阈值把目标漏掉。
多尺度推理也很实用。640 的输入对远处小火苗不友好,你可以把同一帧图分别缩放到 640、960、1280 三个尺寸推理,再把输出合并做一次 NMS。这样小目标至少有一次机会落在合适的分辨率上,召回率能提升几个点,缺点就是慢,只适合重要帧或每隔几帧做一次。我之前做夜间火焰检测,默认阈值 0.25 漏掉了 80 米外的小火点,后来改成验证集上扫描阈值,并每隔 5 帧做一次 960 分辨率推理,召回率才真正上去。从那以后我每次部署火灾模型,都会强制走一遍阈值扫描和尺度测试,希望帮到你。
本文还有配套的精品资源,点击获取