一、项目介绍
摘要
本项目基于先进的YOLOv8目标检测算法,开发了一套专门用于森林火灾早期预警的红外烟雾检测系统。系统采用双类别检测框架(nc=2),能够准确识别"fire"(火焰)和"smoke"(烟雾)两类关键目标。项目构建了包含2000张红外图像的专业数据集,其中训练集1600张、验证集200张、测试集200张,确保了模型训练的充分性和评估的可靠性。该系统通过处理红外摄像头采集的实时图像流,能够实现森林区域的24小时全天候监测,在火灾初期即可发现火源和烟雾迹象,为森林防火提供了一种高效、精准的智能化解决方案。实验结果表明,本系统在测试集上达到了较高的检测精度和实时性能,能够满足森林防火监测的实时性要求。
项目意义
森林火灾是全球范围内最具破坏性的自然灾害之一,每年造成巨大的生态损失和经济损失。传统的森林火灾监测主要依靠人工巡逻和瞭望塔观察,存在监测范围有限、反应滞后、夜间监测困难等问题。本项目的开发具有多方面的重要意义:
-
早期预警价值:系统能够在火灾初期阶段(通常是最关键的扑救窗口期)检测到微弱的烟雾和火源,比传统方法提前发出警报,为灭火行动争取宝贵时间。烟雾检测尤其重要,因为烟雾往往比明火更早出现且传播范围更广。
-
全天候监测能力:采用红外成像技术克服了可见光成像在夜间、雾天等低能见度条件下的局限性,实现了真正意义上的24小时不间断监测。红外传感器对热辐射敏感,能够穿透一定程度的烟雾和植被遮挡,提高火灾检测的可靠性。
-
大范围覆盖优势:结合无人机或固定监控点部署,单套系统可覆盖数平方公里范围的林区,监测效率远高于人工方式,特别适合地形复杂、人迹罕至的重点防火区域。
-
智能分析功能:基于YOLOv8的深度学习算法不仅能检测火灾特征,还能通过烟雾扩散方向、火焰强度变化等分析火势发展趋势,为指挥决策提供数据支持。系统可集成风速、湿度等环境参数,实现更全面的火灾风险评估。
-
生态保护价值:及时有效的火灾防控能够保护森林生态系统,减少碳排放,维护生物多样性,显著降低生态破坏程度。
-
经济效益显著:相比火灾造成的直接经济损失和后续生态恢复成本,部署智能监测系统的投入产出比极高。系统还可减少人力巡逻成本,优化防火资源分配。
-
技术示范作用:本项目将最先进的YOLOv8算法应用于专业领域,为计算机视觉在环境保护中的创新应用提供了范例,对推动AI技术在灾害防治领域的落地具有参考价值。
未来,该系统可通过增加多光谱成像、三维定位等功能进一步升级,并与卫星遥感、地面传感器网络形成立体化监测体系,全面提升森林火灾防控的智能化水平。项目的技术路线也可拓展应用于草原火灾、工业火灾等其他场景,具有广阔的应用前景和社会价值。
目录
七、项目源码(视频简介内)
基于深度学习YOLOv8的森林火灾烟雾红外检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)_哔哩哔哩_bilibili
基于深度学习YOLOv8的森林火灾烟雾红外检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)
二、项目功能展示
系统功能
✅ 图片检测:可对图片进行检测,返回检测框及类别信息。
✅ 视频检测:支持视频文件输入,检测视频中每一帧的情况。
✅ 摄像头实时检测:连接USB 摄像头,实现实时监测。
✅参数实时调节(置信度和IoU阈值)
-
图片检测
该功能允许用户通过单张图片进行目标检测。输入一张图片后,YOLO模型会实时分析图像,识别出其中的目标,并在图像中框出检测到的目标,输出带有目标框的图像。批量图片检测
用户可以一次性上传多个图片进行批量处理。该功能支持对多个图像文件进行并行处理,并返回每张图像的目标检测结果,适用于需要大规模处理图像数据的应用场景。
-
视频检测
视频检测功能允许用户将视频文件作为输入。YOLO模型将逐帧分析视频,并在每一帧中标记出检测到的目标。最终结果可以是带有目标框的视频文件或实时展示,适用于视频监控和分析等场景。
-
摄像头实时检测
该功能支持通过连接摄像头进行实时目标检测。YOLO模型能够在摄像头拍摄的实时视频流中进行目标检测,实时识别并显示检测结果。此功能非常适用于安防监控、无人驾驶、智能交通等应用,提供即时反馈。
核心特点:
- 高精度:基于YOLO模型,提供精确的目标检测能力,适用于不同类型的图像和视频。
- 实时性:特别优化的算法使得实时目标检测成为可能,无论是在视频还是摄像头实时检测中,响应速度都非常快。
- 批量处理:支持高效的批量图像和视频处理,适合大规模数据分析。
三、数据集介绍
数据集概述
本项目构建了森林火灾红外专项数据集,共包含2000张高质量标注红外图像,按照8:1:1的比例划分为训练集(1600张)、验证集(200张)和测试集(200张)。数据集涵盖两类关键目标:
-
火灾(fire):不同发展阶段(阴燃、明火、大火)的火场区域
-
烟雾(smoke):各类形态(薄雾、浓烟、上升烟柱等)的烟雾区域
数据集特点
-
多源数据融合:数据集整合了无人机航拍、固定监控设备、卫星遥感等多种来源的红外图像,确保数据多样性。
-
全时段覆盖:包含白天、夜晚、黄昏等不同时段的样本,验证模型的时间鲁棒性。
-
多气候条件:涵盖晴天、雨天、雾天、雪天等多种气象条件下的火灾样本。
-
多地形场景:包含山地、平原、林地、灌木丛等不同地形环境下的火灾特征。
-
多光谱数据:部分样本包含长波红外(LWIR)和中波红外(MWIR)双波段数据,提供更丰富的热特征信息。
-
精细标注:采用像素级标注边界,对部分重叠目标和半透明烟雾进行了精确区分标注。
-
干扰样本丰富:包含阳光反射、热源设备、动物热源等常见干扰样本,增强模型抗干扰能力。
数据集配置文件
数据集采用YOLO格式进行组织,配置文件内容如下:
# YOLOv8森林火灾红外检测数据集配置文件
path: ../datasets/forest_fire_IR
train: images/train # 1600张
val: images/val # 200张
test: images/test # 200张
# 检测类别
nc: 2
names: ['fire', 'smoke']
数据集制作流程
-
原始数据采集:
-
使用专业红外热像仪采集原始数据
-
在多个自然保护区建立固定监测点进行长期数据收集
-
联合消防部门获取真实火灾现场的红外影像资料
-
通过无人机搭载红外相机进行航拍数据补充
-
-
数据预处理:
-
温度值校准:根据环境温度校正原始红外数据
-
动态范围调整:优化温度显示范围以突出火情特征
-
时间同步:对齐多源数据的采集时间戳
-
地理坐标标注:记录每张图像的GPS位置信息
-
-
数据增强策略:
-
空间增强:随机旋转、翻转、裁剪
-
辐射增强:模拟不同大气透射率下的辐射衰减
-
噪声注入:添加传感器噪声和大气扰动噪声
-
多尺度训练:采用4种不同分辨率进行训练
-
-
四、项目环境配置
创建虚拟环境
首先新建一个Anaconda环境,每个项目用不同的环境,这样项目中所用的依赖包互不干扰。
终端输入
conda create -n yolov8 python==3.9
激活虚拟环境
conda activate yolov8
安装cpu版本pytorch
pip install torch torchvision torchaudio
pycharm中配置anaconda
安装所需要库
pip install -r requirements.txt
五、模型训练
训练代码
from ultralytics import YOLO
model_path = 'yolov8s.pt'
data_path = 'datasets/data.yaml'
if __name__ == '__main__':
model = YOLO(model_path)
results = model.train(data=data_path,
epochs=500,
batch=64,
device='0',
workers=0,
project='runs/detect',
name='exp',
)
根据实际情况更换模型 yolov8n.yaml (nano):轻量化模型,适合嵌入式设备,速度快但精度略低。 yolov8s.yaml (small):小模型,适合实时任务。 yolov8m.yaml (medium):中等大小模型,兼顾速度和精度。 yolov8b.yaml (base):基本版模型,适合大部分应用场景。 yolov8l.yaml (large):大型模型,适合对精度要求高的任务。
--batch 64
:每批次64张图像。--epochs 500
:训练500轮。--datasets/data.yaml
:数据集配置文件。--weights yolov8s.pt
:初始化模型权重,yolov8s.pt
是预训练的轻量级YOLO模型。
训练结果
六、核心代码
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QImage, QPixmap, QIcon
from PyQt5.QtWidgets import (QFileDialog, QMessageBox, QTableWidgetItem,
QStyledItemDelegate, QHeaderView)
import cv2
import numpy as np
from ultralytics import YOLO
import os
import datetime
import sys
class CenteredDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
option.displayAlignment = Qt.AlignCenter
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1400, 900)
MainWindow.setWindowTitle("YOLOv8 目标检测系统")
# 设置窗口图标
if hasattr(sys, '_MEIPASS'):
icon_path = os.path.join(sys._MEIPASS, 'icon.ico')
else:
icon_path = 'icon.ico'
if os.path.exists(icon_path):
MainWindow.setWindowIcon(QIcon(icon_path))
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
# 主布局
self.main_layout = QtWidgets.QHBoxLayout(self.centralwidget)
self.main_layout.setContentsMargins(10, 10, 10, 10)
self.main_layout.setSpacing(15)
# 左侧布局 (图像显示)
self.left_layout = QtWidgets.QVBoxLayout()
self.left_layout.setSpacing(15)
# 原始图像组
self.original_group = QtWidgets.QGroupBox("原始图像")
self.original_group.setMinimumHeight(400)
self.original_img_label = QtWidgets.QLabel()
self.original_img_label.setAlignment(QtCore.Qt.AlignCenter)
self.original_img_label.setText("等待加载图像...")
self.original_img_label.setStyleSheet("background-color: #F0F0F0; border: 1px solid #CCCCCC;")
original_layout = QtWidgets.QVBoxLayout()
original_layout.addWidget(self.original_img_label)
self.original_group.setLayout(original_layout)
self.left_layout.addWidget(self.original_group)
# 检测结果图像组
self.result_group = QtWidgets.QGroupBox("检测结果")
self.result_group.setMinimumHeight(400)
self.result_img_label = QtWidgets.QLabel()
self.result_img_label.setAlignment(QtCore.Qt.AlignCenter)
self.result_img_label.setText("检测结果将显示在这里")
self.result_img_label.setStyleSheet("background-color: #F0F0F0; border: 1px solid #CCCCCC;")
result_layout = QtWidgets.QVBoxLayout()
result_layout.addWidget(self.result_img_label)
self.result_group.setLayout(result_layout)
self.left_layout.addWidget(self.result_group)
self.main_layout.addLayout(self.left_layout, stretch=3)
# 右侧布局 (控制面板)
self.right_layout = QtWidgets.QVBoxLayout()
self.right_layout.setSpacing(15)
# 模型选择组
self.model_group = QtWidgets.QGroupBox("模型设置")
self.model_group.setStyleSheet("QGroupBox { font-weight: bold; }")
self.model_layout = QtWidgets.QVBoxLayout()
# 模型选择
self.model_combo = QtWidgets.QComboBox()
self.model_combo.addItems(["best.pt"])
self.model_combo.setCurrentIndex(0)
# 加载模型按钮
self.load_model_btn = QtWidgets.QPushButton(" 加载模型")
self.load_model_btn.setIcon(QIcon.fromTheme("document-open"))
self.load_model_btn.setStyleSheet(
"QPushButton { padding: 8px; background-color: #4CAF50; color: white; border-radius: 4px; }"
"QPushButton:hover { background-color: #45a049; }"
)
self.model_layout.addWidget(self.model_combo)
self.model_layout.addWidget(self.load_model_btn)
self.model_group.setLayout(self.model_layout)
self.right_layout.addWidget(self.model_group)
# 参数设置组
self.param_group = QtWidgets.QGroupBox("检测参数")
self.param_group.setStyleSheet("QGroupBox { font-weight: bold; }")
self.param_layout = QtWidgets.QFormLayout()
self.param_layout.setLabelAlignment(Qt.AlignLeft)
self.param_layout.setFormAlignment(Qt.AlignLeft)
self.param_layout.setVerticalSpacing(15)
# 置信度滑块
self.conf_slider = QtWidgets.QSlider(Qt.Horizontal)
self.conf_slider.setRange(1, 99)
self.conf_slider.setValue(25)
self.conf_value = QtWidgets.QLabel("0.25")
self.conf_value.setAlignment(Qt.AlignCenter)
self.conf_value.setStyleSheet("font-weight: bold; color: #2196F3;")
# IoU滑块
self.iou_slider = QtWidgets.QSlider(Qt.Horizontal)
self.iou_slider.setRange(1, 99)
self.iou_slider.setValue(45)
self.iou_value = QtWidgets.QLabel("0.45")
self.iou_value.setAlignment(Qt.AlignCenter)
self.iou_value.setStyleSheet("font-weight: bold; color: #2196F3;")
self.param_layout.addRow("置信度阈值:", self.conf_slider)
self.param_layout.addRow("当前值:", self.conf_value)
self.param_layout.addRow(QtWidgets.QLabel("")) # 空行
self.param_layout.addRow("IoU阈值:", self.iou_slider)
self.param_layout.addRow("当前值:", self.iou_value)
self.param_group.setLayout(self.param_layout)
self.right_layout.addWidget(self.param_group)
# 功能按钮组
self.func_group = QtWidgets.QGroupBox("检测功能")
self.func_group.setStyleSheet("QGroupBox { font-weight: bold; }")
self.func_layout = QtWidgets.QVBoxLayout()
self.func_layout.setSpacing(10)
# 图片检测按钮
self.image_btn = QtWidgets.QPushButton(" 图片检测")
self.image_btn.setIcon(QIcon.fromTheme("image-x-generic"))
# 视频检测按钮
self.video_btn = QtWidgets.QPushButton(" 视频检测")
self.video_btn.setIcon(QIcon.fromTheme("video-x-generic"))
# 摄像头检测按钮
self.camera_btn = QtWidgets.QPushButton(" 摄像头检测")
self.camera_btn.setIcon(QIcon.fromTheme("camera-web"))
# 停止检测按钮
self.stop_btn = QtWidgets.QPushButton(" 停止检测")
self.stop_btn.setIcon(QIcon.fromTheme("process-stop"))
self.stop_btn.setEnabled(False)
# 保存结果按钮
self.save_btn = QtWidgets.QPushButton(" 保存结果")
self.save_btn.setIcon(QIcon.fromTheme("document-save"))
self.save_btn.setEnabled(False)
# 设置按钮样式
button_style = """
QPushButton {
padding: 10px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
text-align: left;
}
QPushButton:hover {
background-color: #0b7dda;
}
QPushButton:disabled {
background-color: #cccccc;
}
"""
for btn in [self.image_btn, self.video_btn, self.camera_btn,
self.stop_btn, self.save_btn]:
btn.setStyleSheet(button_style)
self.func_layout.addWidget(btn)
self.func_group.setLayout(self.func_layout)
self.right_layout.addWidget(self.func_group)
# 检测结果表格组
self.table_group = QtWidgets.QGroupBox("检测结果详情")
self.table_group.setStyleSheet("QGroupBox { font-weight: bold; }")
self.table_layout = QtWidgets.QVBoxLayout()
self.result_table = QtWidgets.QTableWidget()
self.result_table.setColumnCount(4)
self.result_table.setHorizontalHeaderLabels(["类别", "置信度", "左上坐标", "右下坐标"])
self.result_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.result_table.verticalHeader().setVisible(False)
self.result_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.result_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
# 设置表格样式
self.result_table.setStyleSheet("""
QTableWidget {
border: 1px solid #e0e0e0;
alternate-background-color: #f5f5f5;
}
QHeaderView::section {
background-color: #2196F3;
color: white;
padding: 5px;
border: none;
}
QTableWidget::item {
padding: 5px;
}
""")
# 设置居中代理
delegate = CenteredDelegate(self.result_table)
self.result_table.setItemDelegate(delegate)
self.table_layout.addWidget(self.result_table)
self.table_group.setLayout(self.table_layout)
self.right_layout.addWidget(self.table_group, stretch=1)
self.main_layout.addLayout(self.right_layout, stretch=1)
MainWindow.setCentralWidget(self.centralwidget)
# 状态栏
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setStyleSheet("QStatusBar { border-top: 1px solid #c0c0c0; }")
MainWindow.setStatusBar(self.statusbar)
# 初始化变量
self.model = None
self.cap = None
self.timer = QTimer()
self.is_camera_running = False
self.current_image = None
self.current_result = None
self.video_writer = None
self.output_path = "output"
# 创建输出目录
if not os.path.exists(self.output_path):
os.makedirs(self.output_path)
# 连接信号槽
self.load_model_btn.clicked.connect(self.load_model)
self.image_btn.clicked.connect(self.detect_image)
self.video_btn.clicked.connect(self.detect_video)
self.camera_btn.clicked.connect(self.detect_camera)
self.stop_btn.clicked.connect(self.stop_detection)
self.save_btn.clicked.connect(self.save_result)
self.conf_slider.valueChanged.connect(self.update_conf_value)
self.iou_slider.valueChanged.connect(self.update_iou_value)
self.timer.timeout.connect(self.update_camera_frame)
# 设置全局样式
self.set_style()
def set_style(self):
style = """
QMainWindow {
background-color: #f5f5f5;
}
QGroupBox {
border: 1px solid #e0e0e0;
border-radius: 5px;
margin-top: 10px;
padding-top: 15px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 3px;
}
QLabel {
color: #333333;
}
QComboBox {
padding: 5px;
border: 1px solid #cccccc;
border-radius: 3px;
}
QSlider::groove:horizontal {
height: 6px;
background: #e0e0e0;
border-radius: 3px;
}
QSlider::handle:horizontal {
width: 16px;
height: 16px;
margin: -5px 0;
background: #2196F3;
border-radius: 8px;
}
QSlider::sub-page:horizontal {
background: #2196F3;
border-radius: 3px;
}
"""
self.centralwidget.setStyleSheet(style)
def load_model(self):
model_name = self.model_combo.currentText().split(" ")[0]
try:
self.model = YOLO(model_name)
self.statusbar.showMessage(f"模型 {model_name} 加载成功", 3000)
self.image_btn.setEnabled(True)
self.video_btn.setEnabled(True)
self.camera_btn.setEnabled(True)
except Exception as e:
QMessageBox.critical(None, "错误", f"模型加载失败: {str(e)}")
def update_conf_value(self):
conf = self.conf_slider.value() / 100
self.conf_value.setText(f"{conf:.2f}")
def update_iou_value(self):
iou = self.iou_slider.value() / 100
self.iou_value.setText(f"{iou:.2f}")
def detect_image(self):
if self.model is None:
QMessageBox.warning(None, "警告", "请先加载模型")
return
file_path, _ = QFileDialog.getOpenFileName(
None, "选择图片", "",
"图片文件 (*.jpg *.jpeg *.png *.bmp);;所有文件 (*)"
)
if file_path:
try:
# 读取图片
img = cv2.imread(file_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 显示原始图片
self.display_image(img, self.original_img_label)
self.current_image = img.copy()
# 检测图片
conf = self.conf_slider.value() / 100
iou = self.iou_slider.value() / 100
self.statusbar.showMessage("正在检测图片...")
QtWidgets.QApplication.processEvents() # 更新UI
results = self.model.predict(img, conf=conf, iou=iou)
result_img = results[0].plot()
# 显示检测结果
self.display_image(result_img, self.result_img_label)
self.current_result = result_img.copy()
# 更新结果表格
self.update_result_table(results[0])
self.save_btn.setEnabled(True)
self.statusbar.showMessage(f"图片检测完成: {os.path.basename(file_path)}", 3000)
except Exception as e:
QMessageBox.critical(None, "错误", f"图片检测失败: {str(e)}")
self.statusbar.showMessage("图片检测失败", 3000)
def detect_video(self):
if self.model is None:
QMessageBox.warning(None, "警告", "请先加载模型")
return
file_path, _ = QFileDialog.getOpenFileName(
None, "选择视频", "",
"视频文件 (*.mp4 *.avi *.mov *.mkv);;所有文件 (*)"
)
if file_path:
try:
self.cap = cv2.VideoCapture(file_path)
if not self.cap.isOpened():
raise Exception("无法打开视频文件")
# 获取视频信息
fps = self.cap.get(cv2.CAP_PROP_FPS)
width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 创建视频写入器
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = os.path.join(self.output_path, f"output_{timestamp}.mp4")
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
self.video_writer = cv2.VideoWriter(output_file, fourcc, fps, (width, height))
# 启用停止按钮,禁用其他按钮
self.stop_btn.setEnabled(True)
self.save_btn.setEnabled(True)
self.image_btn.setEnabled(False)
self.video_btn.setEnabled(False)
self.camera_btn.setEnabled(False)
# 开始处理视频
self.timer.start(30) # 30ms间隔
self.statusbar.showMessage(f"正在处理视频: {os.path.basename(file_path)}...")
except Exception as e:
QMessageBox.critical(None, "错误", f"视频检测失败: {str(e)}")
self.statusbar.showMessage("视频检测失败", 3000)
七、项目源码(视频简介内)
完整全部资源文件(包括测试图片,py文件,训练数据集、训练代码、界面代码等),这里已打包上传至博主的面包多平台,见可参考博客与视频,已将所有涉及的文件同时打包到里面,点击即可运行,完整文件截图如下:
演示与介绍视频:
基于深度学习YOLOv8的森林火灾烟雾红外检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)_哔哩哔哩_bilibili
基于深度学习YOLOv8的森林火灾烟雾红外检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)