简介:这是一套面向毕业设计场景的火灾检测可视化系统项目,基于YOLOv5目标检测框架与PyQt5界面库实现,面向计算机、电子信息、数学等相关专业学生,可用于课程设计、期末大作业或毕业论文的参考。压缩包共156个文件,大小约141.44MB,内含YOLOv5训练与推理源码(py/pyc)、模型配置文件(yaml/cfg)、预训练权重(pt)、图片样本(jpg/jpeg)、Dockerfile及环境依赖说明等,能够支撑从数据集准备、模型训练、模型评估到可视化界面集成的完整链路。项目还配有说明文档(md/txt)与jupyter示例,便于梳理模块结构、理解检测逻辑并开展二次开发。sh脚本可用于一键训练或推理,降低环境配置成本。已有482人学习下载,整体目录清晰、类型丰富,对希望快速搭建可演示火灾检测系统或撰写毕业设计的同学而言,是一份值得参考的完整资料。
1. 从模型到界面:YOLOV5+PyQt5火灾检测可视化系统的真实工作流
把YOLOV5训练好的权重文件和PyQt5做成的桌面程序拼在一起,这个标题看起来像是两个独立技术栈的简单叠加,真正做的时候却能拆出数据准备、模型训练、界面设计、线程通信、报警联动一大串问题。火灾检测可视化系统的核心并不在模型本身,而在于把检测结果以稳定、可演示、可解释的形式送进一个GUI窗口里。适合正在做课程设计或毕业设计、打算快速搭出一套能跑通完整流程的工程的人;也适合想了解YOLOV5检测服务如何与桌面端集成的开发者。下面从训练到界面,按一条可复现的路线展开。
2. 先把模型立住:YOLOV5火灾数据集的准备与训练参数
2.1 火灾检测不直接迁移COCO权重:数据分布决定了模型上限
很多第一次做的人会直接拿yolov5s.pt官方的COCO预训练权重去检测火焰,结果发现画面里的火苗识别不出来,或者把橙色的路灯、晚霞误报成火灾。原因不难理解:COCO的80个类别里并没有fire和smoke这两个类别,模型虽然学到了通用特征,但火焰的纹理、边缘和透明度与常见目标差异非常大,而且小尺寸火苗在640分辨率下只有几十个像素,正负样本极不均衡。
因此在这个项目中,训练环节的优先级要高于界面。我一般会先收集火焰和烟雾的图片,公开的火灾检测数据集在代码托管站点和学术资源站上能搜到不少,优先找已经带有YOLO格式标注的版本,省去自己转换的麻烦。如果原始数据只有VOC格式的xml标注,需要写一个转换脚本把xml里的bounding box坐标转成归一化的txt格式,转换时注意归一化用的是矩形中心点坐标和宽高,不是左上角右下角。
数据量方面,火焰加烟雾各2000张左右是入门门槛,类别不平衡会直接拉低mAP。图片里要涵盖白天、夜晚、室内、户外、小目标火源、大面积烟雾等场景。如果原始数据里火焰占绝大多数、烟雾样本很少,可以在训练时对烟雾类别施加更高的损失权重,或者对含烟雾的图片做更多的随机裁剪和亮度抖动,让模型见过更多烟雾形态。
2.2 YOLOV5训练自己的数据集:目录规划、标注格式与yaml配置
YOLOV5对数据集目录没有强约束,但按官方推荐的布局可以少踩很多坑。目录结构如下:
datasets/fire_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── fire.yamlimages和labels下各放图片与对应的txt标注文件,每个txt文件名和图片名一致。标注文件每一行是class_id x_center y_center width height,四个坐标值都是相对图片宽高的归一化浮点数,范围0到1。类别编号从0开始,这个项目里只有fire和smoke两类,所以class_id只能是0或1。如果类别顺序换成了smoke在前,后面推理时显示的名称也要跟着换。
接下来写数据集配置fire.yaml:
# fire.yaml 数据集配置 train: ./datasets/fire_dataset/images/train val: ./datasets/fire_dataset/images/val nc: 2 names: ['fire', 'smoke']train指定训练图片目录,val指定验证图片目录,nc是类别数,names是类别名。写完后可以用下面命令快速检查标注和图片是否能被正确读取:
python train.py --data fire.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --cachetrain.py是YOLOV5仓库主目录下的训练脚本,--data指定数据集配置,--weights指定初始权重,用预训练权重做迁移学习收敛明显更快。--img 640表示输入分辨率,火灾检测里的火焰常以远处的小目标出现,分辨率低于640时漏检率会明显上升。--batch 16需要约8GB显存,如果显存不够就降到8,--cache参数可以把所有训练图片预加载进内存,省掉大量磁盘IO时间,第一次跑会等图片缓存构建完,之后速度提升明显。
2.3 训练时值得盯的三个指标与超参数选择
训练过程中不要只看loss曲线,loss下降不代表检测效果够用。需要同时盯住precision、recall和mAP@0.5,这三个值会在每轮epoch结束后的验证集上输出。
| 指标 | 含义 | 火灾场景里的容忍度 |
|---|---|---|
| mAP@0.5 | IoU阈值0.5时各类别平均精度 | 0.8以上才能作为演示版本 |
| recall | 真实火灾目标被找回的比例 | 宁可误报也不能漏报,recall优先 |
| precision | 检出目标中真实目标的比例 | 过低会频繁误报,至少0.7 |
| F1-score | precision与recall的调和平均 | 演示时看这个值更直观 |
按经验,这个任务从yolov5s开始调,hyp.scratch-low.yaml里的lr0保持默认0.01就行,优先调整的是数据增强参数:mosaic设为1.0可以提升小目标表现,但训练后期建议关掉,否则部分目标被裁剪得过于碎片化,会破坏真实火焰的连续性;如果发现验证集上的mAP在最后20轮不再上升,可以增大mixup系数或减少epochs来抑制过拟合。
训练结束后用best.pt做推理测试,测试别只挑训练集里的图,找一些网上下载的真实火灾照片看误报情况。这一步决定了后面界面开发时模型是否可靠,如果到界面阶段再发现模型选错,返工成本会成倍增加。
3. 用PyQt5把检测结果变成可操作的监控界面
3.1 PyQt5界面骨架:主窗口、视频画布与控制区
拿到训练好的best.pt之后,下一步就进入界面开发。PyQt5安装用pip即可,在PyCharm的终端里直接执行:
pip install pyqt5当前PyQt5版本停留在5.15系列,不会自动跳到PyQt6,可以放心使用。界面骨架我一般用QMainWindow搭,左侧是控制面板,中间是视频画布,画布用一个QLabel控件,通过setPixmap把每一帧更新上去,而不是用QPainter直接绘制整个窗口。核心代码结构如下:
from PyQt5.QtWidgets import QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget class FireDetectWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("YOLOV5+PyQt5 火灾检测可视化系统") # 视频画布:用QLabel展示帧图像,尺寸随窗口缩放 self.canvas = QLabel() self.canvas.setMinimumSize(800, 600) # 控制按钮:开始检测、停止检测、选择视频源 self.start_btn = QPushButton("开始检测") self.stop_btn = QPushButton("停止") self.start_btn.clicked.connect(self.start_detection) self.stop_btn.clicked.connect(self.stop_detection) layout = QVBoxLayout() layout.addWidget(self.canvas) layout.addWidget(self.start_btn) layout.addWidget(self.stop_btn) container = QWidget() container.setLayout(layout) self.setCentralWidget(container)这个骨架把界面和业务逻辑分开了。canvas负责显示画面,start_btn和stop_btn负责控制开关,clicked信号连接到对应的槽函数。真正的检测逻辑不会写在这一层,QMainWindow只做两件事:响应用户操作、刷新显示结果。如果在这里直接写YOLOV5推理循环,界面会在每帧推理期间完全卡死,鼠标拖不动窗口,按钮没有反馈,所以第3.3节必须引入独立线程。
3.2 在界面上叠加检测框:把YOLOV5的坐标换算成Qt坐标
YOLOV5的后处理输出x1, y1, x2, y2,这四个值是像素坐标,可以直接在原始帧上画框,再把画好框的帧转成QImage显示。不要在渲染到界面时再做缩放,那样坐标会偏移。
import cv2 from PyQt5.QtGui import QImage, QPixmap def draw_detections(frame, results, model): # results是YOLOV5后处理后的结果,每个元素是[x1, y1, x2, y2, conf, cls] for *xyxy, conf, cls in results: x1, y1, x2, y2 = map(int, xyxy) label = f"{model.names[int(cls)]} {conf:.2f}" # 在OpenCV的BGR帧上画框,颜色BGR红色对应火焰告警 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(frame, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return frame def frame_to_pixmap(frame): # BGR转RGB,再包装成QImage,否则画面颜色会偏蓝偏绿 rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qimg = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) return QPixmap.fromImage(qimg)画框直接使用OpenCV而不是Qt的QPainter,是因为帧本身就是OpenCV的ndarray,用cv2.rectangle成本低,也避免在Qt绘画事件里做耗时操作。QImage.Format_RGB888要求数据是RGB顺序,所以必须先cvtColor。如果画面在界面上被缩放,QPixmap显示时会自动拉伸,但检测框已经画在原始帧上,所以不会错位。需要留意的是results里的cls值是numpy.float32,取类别名时要先强转成int,否则model.names的下标会报错。
3.3 用QThread跑推理,避免界面卡死的三个关键点
界面卡死的根因是推理阻塞了Qt主线程。解决办法是把视频读取和模型推理放到QThread里,通过信号把处理完的帧传回主线程显示。下面是一个最小可运行的线程封装:
from PyQt5.QtCore import QThread, pyqtSignal import cv2 class DetectThread(QThread): # 自定义信号:发送当前帧,供界面刷新 frame_ready = pyqtSignal(object) def __init__(self, model, source=0): super().__init__() self.model = model self.source = source self.running = True def run(self): cap = cv2.VideoCapture(self.source) while self.running: ret, frame = cap.read() if not ret: break # 调用YOLOV5的推理,输入是原始BGR帧 results = self.model(frame) annotated = draw_detections(frame, results, self.model) self.frame_ready.emit(annotated) cap.release() def stop(self): self.running = False self.wait()这段代码里有两个关键点。第一,frame_ready是跨线程信号,触发后主线程的槽函数会排队执行,中间不会阻塞run里的视频循环。第二,stop方法用self.wait()等待线程退出,防止界面关闭后子线程还在访问已经释放的模型对象。还需要注意QThread的线程亲和性:不要在DetectThread里直接调用任何QWidget方法,所有界面刷新都必须通过信号回到主线程。第三个关键点是异常处理,视频文件损坏或摄像头被拔出时cap.read()会连续返回False,run里要加一个最大失败次数,连续失败30次就主动退出线程并发信号通知界面,否则线程会在空循环里空转。
4. 把模型和界面接起来:实时视频流、报警联动与性能优化
4.1 从视频帧到检测结果的数据管线设计
实时视频流和单张图片检测之间最大的差别在于:摄像头每秒产生25到30帧,而YOLOV5在CPU上推理一帧往往需要200到500毫秒,在GPU上也要30到80毫秒。如果每一帧都送进模型,实时性必然崩塌。常见的做法是把视频读取和推理拆成两个环节,用跳帧策略控制推理频率。
# 数据管线核心:跳帧推理 + 保留最近一次检测结果 frame_index = 0 infer_interval = 2 # 每2帧推理一次 last_boxes = [] while cap.isOpened(): ret, frame = cap.read() if not ret: break if frame_index % infer_interval == 0: results = model(frame) last_boxes = results # 保存结果,供后续帧显示 # 非推理帧也画框:把上一帧的检测结果贴到新帧上 annotated = draw_detections(frame, last_boxes, model) emit_frame(annotated) frame_index += 1这里的逻辑是:推理帧和显示帧分离。每隔infer_interval帧做一次检测,中间跳过的帧直接复用上一次检测到的框,画面保持连续但推理次数减半。infer_interval的取值要看硬件能力,GPU运行时设2或3,CPU运行时设5或10,否则界面流畅度和检测实时性会互相打架。注意保存的检测框坐标要和当前帧尺度一致,如果视频分辨率没变,直接复用没问题;如果中途切换了视频源或分辨率变化,必须清空last_boxes,否则旧坐标会画在错误位置上。
4.2 报警别做成阻塞:倒计时、防抖与声音提示
火灾检测系统里最核心的联动是报警。简单粗暴的做法是只要检测到fire就立刻弹出QMessageBox弹窗,结果视频每几分钟就弹一次,用户要点无数次确定,反而阻碍了观察现场。更好的设计是引入连续帧命中计数和触发阈值:
# 报警防抖逻辑:连续fire_hit_threshold帧检测到火焰才触发 fire_count = 0 fire_hit_threshold = 5 # 连续阈值,建议5~10 is_alarm_active = False while True: frame = read_frame() boxes = detect(frame) fire_detected = any(model.names[int(b[5])] == 'fire' for b in boxes) if fire_detected: fire_count += 1 else: fire_count = 0 is_alarm_active = False if fire_count >= fire_hit_threshold and not is_alarm_active: is_alarm_active = True start_alarm() # 只触发一次fire_count连续累加,一旦中途有一帧没有检测到火焰就清零。这样做可以过滤掉单帧误检,比如镜头快速晃动、光照瞬时变化导致的假阳性。is_alarm_active保证报警只触发一次,直到连续未命中才复位。阈值设太大会导致真实小火苗出现后延迟报警,设太小又容易闪断,建议在答辩演示时用本地视频实测确定,一般5帧以内既能防抖又不明显迟钝。
报警之后还应该自动截图存档,保存带检测框的帧到本地目录,并记录时间戳。这个功能在毕业设计答辩时很加分,因为说明文档里可以写“系统具备报警证据留存能力”。
4.3 推理速度上不去时,先查这四个地方
界面能跑通之后,常见的问题就是帧率太低。下表是我在调试这类系统时优先排查的四个位置。
| 排查点 | 现象 | 调整方式 |
|---|---|---|
| 输入分辨率 | 推理耗时集中在640分辨率上 | 测试img 480,先看漏检是否明显增加 |
| 模型尺寸 | yolov5s仍然太慢 | 换yolov5n,精度下降但速度快一倍以上 |
| 推理框架 | 单帧推理时GPU利用率低 | 用TensorRT或ONNX Runtime提高吞吐 |
| OpenCV解码 | cap.read()本身占CPU | 降低摄像头采集分辨率或改用FFMPEG后端 |
最后一个容易被忽略的地方是CPU推理时的线程配置。YOLOV5的export.py导出的ONNX模型在CPU上可以考虑用OpenVINO执行器,PyQt5界面完全不用改,只需把推理session的provider改成OpenVINO,帧率往往能提高两三倍。如果项目只要求演示,直接在DetectThread里加载优化后的模型即可,注意导出后要重新跑一次相同的测试视频,确认精度没有明显下降。
5. 毕业设计答辩前必做的五项验证与两个容易被追问的边界
5.1 用一段固定视频做演示,比现场接摄像头更稳
毕业设计答辩当天最怕摄像头驱动异常或现场光线不对,导致整个演示卡住。我一般会把测试视频提前录制成mp4,界面里加一个“本地视频”下拉选项,从文件路径读取视频流。这样在操作系统环境多变的多媒体教室,不用依赖摄像头驱动也能完整体验检测流程,评委提问时还可以重放关键片段,展示报警联动过程。
5.2 五项验证覆盖演示全流程
第一个验证是长时间运行的稳定性。让系统连续运行30分钟以上,观察内存占用是否持续增长。如果QThread里的帧队列没有被及时清空,内存会慢慢涨上去,最后界面失去响应,可以把任务管理器截图放进说明文档作为证据。
第二个验证是无标签真实视频的误报率。下载几段没有标注过的火灾现场视频,用界面完整跑一遍,记录误报和漏报。这一步可以提前发现模型对特定场景的过拟合,比如傍晚的红色灯光、墙面上的橙色装饰板。
第三个验证是视频中断的恢复。在播放视频过程中手动删除视频文件或拔出摄像头,系统不能崩溃,要能回退到等待状态并弹出错误提示。很多毕业设计演示都挂在这一步上。
第四个验证是三种报警触发方式分别测试:声音、弹窗、截图存档。注意弹窗不要阻塞推理线程,可以用信号槽把弹窗请求发到主线程处理。
第五个验证是切换设备。拿一台没有NVIDIA显卡的笔记本跑一遍CPU推理,确认帧率不低于5。如果低于5,要把输入分辨率降到480或换yolov5n,否则答辩现场的笔记本带不动,演示效果会非常尴尬。
两个容易被追问的边界:第一个是模型只对训练数据分布内的火焰和烟雾有效,对于镜面反光、橙红色塑料物体和蒸汽,模型可能会误判。答辩时要主动说明数据增强策略和误报控制方法,不能声称该系统可以覆盖所有火灾场景。第二个是PyQt5只是可视化外壳,检测精度完全由YOLOV5权重决定,如果评委要求提高mAP,回应方向是先扩充数据集而不是调界面代码。把这两个边界写进说明文档,答辩时会被认为是真正理解了自己做的东西。
本文还有配套的精品资源,点击获取