使用 PySide6/QT 实现 YOLOv8 可视化 GUI 页面
在人工智能和计算机视觉领域,YOLO(You Only Look Once)是一种广泛使用的实时目标检测算法。为了直观地展示 YOLO 算法的检测效果,我们可以使用 Python 中的 PySide6 库来创建一个简单的 GUI 应用程序,将检测结果实时可视化。
本文介绍如何使用 PySide6 和 OpenCV 构建基于 YOLOv8 的目标检测图形用户界面(GUI)。内容涵盖环境配置、UI 布局设计、模型加载、文件上传、实时视频流处理及置信度阈值调节等核心功能模块的代码实现与逻辑说明,旨在提供一套完整的桌面端目标检测可视化工具开发方案。

在人工智能和计算机视觉领域,YOLO(You Only Look Once)是一种广泛使用的实时目标检测算法。为了直观地展示 YOLO 算法的检测效果,我们可以使用 Python 中的 PySide6 库来创建一个简单的 GUI 应用程序,将检测结果实时可视化。
本文将指导你如何使用 PySide6 实现这一功能。
PySide6 是一款功能强大的 GUI(图形用户界面)开发框架,它允许 Python 开发者使用 Qt 库的功能来构建跨平台的桌面应用程序。PySide6 作为 Qt 的 Python 绑定版本,继承了 Qt 的跨平台特性,支持在 Windows、macOS、Linux 等多种操作系统上开发和部署应用程序。其具有以下特点:
OpenCV(Open Source Computer Vision Library)是一个广泛使用的开源计算机视觉库,提供了丰富的图像和视频处理功能,以及一些机器学习算法。广泛应用于人脸识别、物体识别、图像分析、视频压缩及深度学习任务等场景。
利用 pip 工具进行依赖项安装(也可以利用 Anaconda 进行依赖包安装),要求 python≥3.8。具体步骤如下:
打开终端或命令行,输入以下代码:
pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple
在 ultralytics 包里,会自动安装适配当前版本的 torch。也可以根据自己的需要安装对应的 torch 版本:
pip install torch=2.1.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install PySide6 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
如果需要额外的功能模块(如 xfeatures2d、stitching 等),可以安装 opencv-contrib-python:
pip install opencv-contrib-python -i https://pypi.tuna.tsinghua.edu.cn/simple
在你的 Python 脚本中,导入所需的库:
import os
from datetime import datetime
import json
import cv2
import torch
from PySide6.QtCore import pyqtSlot, Qt, QDir, QTimer
from PySide6.QtGui import QIcon, QImage, QPixmap
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QTextBrowser, QFileDialog, QSlider, QDoubleSpinBox, QVBoxLayout, QHBoxLayout, QWidget
from ultralytics import YOLO
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.init_gui()
self.model = None
self.timer = QTimer()
self.timer1 = QTimer()
self.cap = None
self.video = None
self.file_path = None
self.base_name = None
self.value = 0.5 # 默认置信度
self.timer1.timeout.connect(self.video_show)
def init_gui(self):
self.folder_path = "./model_file" # 设置模型文件夹路径
self.setFixedSize(1300, 650)
self.setWindowTitle('目标检测')
# self.setWindowIcon(QIcon("logo.jpg")) # 设置窗口图标
central_widget = QWidget(self)
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
左侧为原图/原视频展示区,右侧为检测结果输出展示区。
# 界面上半部分:视频框
topLayout = QHBoxLayout()
self.oriVideoLabel = QLabel(self)
self.detectlabel = QLabel(self)
self.oriVideoLabel.setFixedSize(530, 400)
self.detectlabel.setFixedSize(530, 400)
self.oriVideoLabel.setStyleSheet('border: 2px solid #ccc; border-radius: 10px; margin-top:75px;')
self.detectlabel.setStyleSheet('border: 2px solid #ccc; border-radius: 10px; margin-top: 75px;')
topLayout.addWidget(self.oriVideoLabel)
topLayout.addWidget(self.detectlabel)
main_layout.addLayout(topLayout)
记录操作日志及告警信息(标签名称、置信度、坐标等)。
# 创建日志打印文本框
self.outputField = QTextBrowser()
self.outputField.setFixedSize(530, 180)
main_layout.addWidget(self.outputField)
遍历文件夹并添加文件名到下拉框,绑定加载按钮。
# 假设 selectModel 已定义
for filename in os.listdir(self.folder_path):
file_path = os.path.join(self.folder_path, filename)
if os.path.isfile(file_path) and filename.endswith('.pt'):
base_name = os.path.splitext(filename)[0]
self.selectModel.addItem(base_name)
self.loadModel = QPushButton('🔄️加载模型')
self.loadModel.setFixedSize(100, 50)
self.loadModel.clicked.connect(self.load_model)
load_model 函数代码如下:
def load_model(self):
filename = self.selectModel.currentText()
full_path = os.path.join(self.folder_path, filename + '.pt')
self.base_name = os.path.splitext(os.path.basename(full_path))[0]
if full_path.endswith('.pt'):
self.model = YOLO(full_path)
self.start_detect.setEnabled(True)
self.stopDetectBtn.setEnabled(True)
self.openImageBtn.setEnabled(True)
self.confidence_slider.setEnabled(True)
self.outputField.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - 模型加载成功:{filename}')
self.outputField.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - 请选择置信度阈值')
else:
self.outputField.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - 请重新选择模型文件!')
自定义置信度阈值,value 值会绑定模型预测的 conf。
# 创建一个置信度阈值滑动条
self.con_label = QLabel('置信度阈值', self)
self.slider = QSlider(Qt.Horizontal, self)
self.slider.setMinimum(1)
self.slider.setMaximum(99)
self.slider.setValue(50)
self.slider.setTickInterval(10)
self.slider.setTickPosition(QSlider.TicksBelow)
self.slider.setFixedSize(170, 30)
self.spinbox = QDoubleSpinBox(self)
self.spinbox.setButtonSymbols(QAbstractSpinBox.NoButtons)
self.spinbox.setMinimum(0.01)
self.spinbox.setMaximum(0.99)
self.spinbox.setSingleStep(0.01)
self.spinbox.setValue(0.5)
self.spinbox.setDecimals(2)
self.spinbox.setFixedSize(60, 30)
layout = QVBoxLayout()
hlayout = QHBoxLayout()
layout.addWidget(self.con_label)
hlayout.addWidget(self.slider)
hlayout.addWidget(self.spinbox)
layout.addLayout(hlayout)
self.confidence_slider = QWidget()
self.confidence_slider.setLayout(layout)
self.confidence_slider.setEnabled(False)
# 连接信号和槽
self.slider.valueChanged.connect(self.updateSpinBox)
self.spinbox.valueChanged.connect(self.updateSlider)
支持 JPG 格式图片和 MP4 格式视频。
# 文件上传按钮
self.openImageBtn = QPushButton('🖼️文件上传')
self.openImageBtn.setFixedSize(100, 65)
self.openImageBtn.clicked.connect(self.upload_file)
self.openImageBtn.setEnabled(False)
# 对上传的文件根据后缀名称进行过滤
file_path, _ = QFileDialog.getOpenFileName(self, "选择检测文件", filter='*.jpg *.mp4')
使用 OpenCV 进行视频抽帧,转为 QImage 格式展示。
# 执行预测按钮
self.start_detect = QPushButton('🔍开始检测')
self.start_detect.setFixedSize(100, 50)
self.start_detect.clicked.connect(self.show_detect)
self.start_detect.setEnabled(False)
模型预测的核心代码:
frame = self.model(frame, imgsz=[448, 352], device='cuda' if torch.cuda.is_available() else 'cpu', conf=self.value)
中断检测程序,释放资源。
# 停止检测按钮
self.stopDetectBtn = QPushButton('🛑停止')
self.stopDetectBtn.setFixedSize(100, 50)
self.stopDetectBtn.setEnabled(False)
self.stopDetectBtn.clicked.connect(self.stop_detect)
# 中断检测事件
def stop_detect(self):
if self.timer.isActive():
self.timer.stop()
if self.timer1.isActive():
self.timer1.stop()
if self.cap is not None:
self.cap.release()
self.cap = None
self.video = None
self.ini_labels()
self.outputField.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - 检测中断!')
self.file_path = None
以下是整合后的完整代码示例,包含类定义、UI 布局及核心逻辑:
import os
from datetime import datetime
import cv2
import torch
from PySide6.QtCore import Qt, QDir, QTimer
from PySide6.QtGui import QIcon, QImage, QPixmap
from PySide6.QtWidgets import (
QApplication, QMainWindow, QLabel, QPushButton, QTextBrowser,
QFileDialog, QSlider, QDoubleSpinBox, QVBoxLayout, QHBoxLayout,
QWidget, QComboBox, QAbstractSpinBox
)
from ultralytics import YOLO
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.init_gui()
self.model = None
self.timer = QTimer()
self.timer1 = QTimer()
self.cap = None
self.video = None
self.file_path = None
self.base_name = None
self.value = 0.5
self.timer1.timeout.connect(self.video_show)
def init_gui(self):
self.folder_path = "./model_file"
self.setFixedSize(1300, 650)
self.setWindowTitle('目标检测')
central_widget = QWidget(self)
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# 上半部分布局
topLayout = QHBoxLayout()
self.oriVideoLabel = QLabel(self)
self.detectlabel = QLabel(self)
self.oriVideoLabel.setFixedSize(530, 400)
self.detectlabel.setFixedSize(530, 400)
self.oriVideoLabel.setStyleSheet('border: 2px solid #ccc; border-radius: 10px; margin-top:75px;')
self.detectlabel.setStyleSheet('border: 2px solid #ccc; border-radius: 10px; margin-top: 75px;')
topLayout.addWidget(self.oriVideoLabel)
topLayout.addWidget(self.detectlabel)
main_layout.addLayout(topLayout)
# 中间控制区
control_layout = QVBoxLayout()
# 模型选择
self.selectModel = QComboBox()
self.loadModel = QPushButton('🔄️加载模型')
self.loadModel.setFixedSize(100, 50)
self.loadModel.clicked.connect(self.load_model)
model_row = QHBoxLayout()
model_row.addWidget(self.selectModel)
model_row.addWidget(self.loadModel)
control_layout.addLayout(model_row)
# 文件上传
self.openImageBtn = QPushButton('🖼️文件上传')
self.openImageBtn.setFixedSize(100, 65)
self.openImageBtn.clicked.connect(self.upload_file)
control_layout.addWidget(self.openImageBtn)
# 置信度
self.con_label = QLabel('置信度阈值', self)
self.slider = QSlider(Qt.Horizontal, self)
self.slider.setMinimum(1)
self.slider.setMaximum(99)
self.slider.setValue(50)
self.slider.setTickInterval(10)
self.slider.setTickPosition(QSlider.TicksBelow)
self.slider.setFixedSize(170, 30)
self.spinbox = QDoubleSpinBox(self)
self.spinbox.setButtonSymbols(QAbstractSpinBox.NoButtons)
self.spinbox.setMinimum(0.01)
self.spinbox.setMaximum(0.99)
self.spinbox.setSingleStep(0.01)
self.spinbox.setValue(0.5)
self.spinbox.setDecimals(2)
self.spinbox.setFixedSize(60, 30)
slider_layout = QVBoxLayout()
hlayout = QHBoxLayout()
hlayout.addWidget(self.slider)
hlayout.addWidget(self.spinbox)
slider_layout.addWidget(self.con_label)
slider_layout.addLayout(hlayout)
control_layout.addLayout(slider_layout)
# 开始/停止
btn_layout = QHBoxLayout()
self.start_detect = QPushButton('🔍开始检测')
self.start_detect.setFixedSize(100, 50)
self.start_detect.clicked.connect(self.show_detect)
self.stopDetectBtn = QPushButton('🛑停止')
self.stopDetectBtn.setFixedSize(100, 50)
self.stopDetectBtn.clicked.connect(self.stop_detect)
btn_layout.addWidget(self.start_detect)
btn_layout.addWidget(self.stopDetectBtn)
control_layout.addLayout(btn_layout)
main_layout.addLayout(control_layout)
# 日志区
self.outputField = QTextBrowser()
self.outputField.setFixedSize(530, 180)
main_layout.addWidget(self.outputField)
# 连接信号
self.slider.valueChanged.connect(self.updateSpinBox)
self.spinbox.valueChanged.connect(self.updateSlider)
def updateSpinBox(self, val):
self.spinbox.setValue(val / 100.0)
self.value = val / 100.0
def updateSlider(self, val):
self.slider.setValue(int(val * 100))
self.value = val
def load_model(self):
filename = self.selectModel.currentText()
full_path = os.path.join(self.folder_path, filename + '.pt')
self.base_name = os.path.splitext(os.path.basename(full_path))[0]
if full_path.endswith('.pt'):
self.model = YOLO(full_path)
self.start_detect.setEnabled(True)
self.stopDetectBtn.setEnabled(True)
self.openImageBtn.setEnabled(True)
self.confidence_slider.setEnabled(True)
self.outputField.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - 模型加载成功:{filename}')
self.outputField.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - 请选择置信度阈值')
else:
self.outputField.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - 请重新选择模型文件!')
def upload_file(self):
self.outputField.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - 请选择检测文件')
file_dialog = QFileDialog()
file_dialog.setDirectory(QDir("./valid_file"))
file_path, _ = file_dialog.getOpenFileName(self, "选择检测文件", filter='*.jpg *.mp4')
if file_path:
self.file_path = file_path
self.ini_labels()
def ini_labels(self):
self.oriVideoLabel.clear()
self.detectlabel.clear()
def show_detect(self):
if not self.file_path:
return
if self.file_path.endswith('.mp4'):
self.cap = cv2.VideoCapture(self.file_path)
self.timer1.start(33) # 约 30fps
else:
frame = cv2.imread(self.file_path)
self.process_frame(frame)
def video_show(self):
if self.cap is None or not self.model:
return
ret, frame = self.cap.read()
if not ret:
self.stop_detect()
return
self.process_frame(frame)
def process_frame(self, frame):
if self.model is None:
return
results = self.model(frame, imgsz=[448, 352], device='cuda' if torch.cuda.is_available() else 'cpu', conf=self.value)
result_img = results[0].plot()
q_image = QImage(result_img.data, result_img.shape[1], result_img.shape[0], QImage.Format_RGB888)
pixmap = QPixmap.fromImage(q_image)
self.detectlabel.setPixmap(pixmap.scaled(self.detectlabel.size(), Qt.KeepAspectRatio))
def stop_detect(self):
if self.timer.isActive():
self.timer.stop()
if self.timer1.isActive():
self.timer1.stop()
if self.cap is not None:
self.cap.release()
self.cap = None
self.video = None
self.ini_labels()
self.outputField.append(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - 检测中断!')
self.file_path = None
if __name__ == '__main__':
app = QApplication([])
window = MyWindow()
window.show()
app.exec()

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online