前言
为了更便捷地使用YOLOv12;YOLOv11;YOLOv10;YOLOv8等基于ultralytics的目标检测,尤其是对于没有深度编程经验的用户,一个可视化界面(GUI)显得尤为重要。为此,基于 PySide6 开发了一个最新的支持YOLOv12的可视化界面(GUI)(其他低版本YOLO也可用),该界面能够实现对 YOLOv11/YOLOv8模型的简单操作,包括模型选择、图片检测、视频检测、摄像头检测并进行结果展示等功能,且完全兼容官方源代码。单文件即插即用,仅300多行左右,无论是研究人员、工程师,还是学生或 AI 爱好者,都能通过这个工具更加直观和高效地进行模型应用和调试。
本文仅针对图像处理部分给出代码示例,只需要在根目录下新建一个main.py即可正常运行,需要完整功能可私聊或通过公众号购买成品。
改进的模型也能用,只需要把代码插入到改进的文件夹中即可。
本文的可视化UI界面对于Ultralytics(目前的YOLOv12 & YOLOv11 & YOLOv10 & YOLOv8通用)的检测、分割、分类、姿势估算(detection, segmentation, obb, classification, and pose estimation)等均可正常显示。
上个版本链接YOLOv11(Ultralytics)可视化界面GUI设计,基于pyside6,单文件即插即用 兼容官方源码,可打包成软件_yolov11 gui-CSDN博客
此次美化了界面外观布局,添加了检测ip摄像头功能,左侧增加检测信息输出。
使用之前需要配置环境,运行有问题应该先重新按照下文配置环境。
YOLOv12则看这篇文章
pip install pyside6
效果展示
主界面
检测效果
本文代码图片检测运行界面如下
检测效果
ip摄像头可通过这个文章使用手机测试
代码
import cv2,sys
import numpy as np
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtCore import *
from ultralytics import YOLO
from collections import Counter
class GradientWidget(QWidget):
def paintEvent(self, event):
painter = QPainter(self)
gradient = QLinearGradient(0, 0, self.width(), self.height())
gradient.setColorAt(0, QColor(240, 248, 255))
gradient.setColorAt(1, QColor(230, 240, 250))
painter.fillRect(self.rect(), gradient)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.xiaolian_ui()
self.model = None
self.result = None
self.con = 0.25
def xiaolian_ui(self):
self.setFixedSize(1280, 590)
self.setMinimumSize(1280, 590)
self.setMaximumSize(1280, 590)
self.setWindowTitle('@author:笑脸惹桃花')
self.setWindowIcon(QIcon("icon.png"))
# 主布局
main_widget = GradientWidget()
main_layout = QHBoxLayout(main_widget)
main_layout.setContentsMargins(15, 15, 15, 15)
main_layout.setSpacing(15)
# ===== 左侧控制面板 =====
left_panel = QWidget()
left_panel.setMaximumWidth(220)
left_layout = QVBoxLayout(left_panel)
left_layout.setContentsMargins(5, 5, 5, 5)
left_layout.setSpacing(8)
# Logo
logo = QLabel("""<div style='text-align: center; font-family: Arial;
font-size: 14pt; font-weight: bold; color: #2c3e50;'>
YOLOv12 Detection</div>""")
left_layout.addWidget(logo)
# 操作按钮
btn_group = QGroupBox("操作面板")
btn_group.setStyleSheet("""
QGroupBox {
border: 1px solid #95a5a6;
border-radius: 4px;
margin-top: 6px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 6px;
color: #34495e;
font: bold 11px;
}
""")
btn_layout = QVBoxLayout()
buttons = [
("加载模型", self.load_model),
("图片检测", self.select_image),
("停止", self.stop_detect)
]
for text, slot in buttons:
btn = QPushButton(text)
btn.setFixedHeight(32)
btn.setStyleSheet(f"""
QPushButton {
{
font: 12px 'Microsoft YaHei';
background: '#f8f9fa' ;
border: 1px solid #ced4da;
border-radius: 3px;
padding: 4px;
}}
QPushButton:hover {
{ background: #e9ecef; }}
""")
btn.clicked.connect(slot)
btn_layout.addWidget(btn)
btn_group.setLayout(btn_layout)
left_layout.addWidget(btn_group)
# 修改状态指示部分为信息输出框
status_group = QGroupBox("检测信息")
status_group.setStyleSheet(
"QGroupBox { border: 1px solid gray; border-radius: 5px; margin-top: 1ex; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 3px; }")
status_layout = QVBoxLayout()
self.output_text = QTextEdit(self)
self.output_text.setReadOnly(True)
status_layout.addWidget(self.output_text)
status_group.setLayout(status_layout)
left_layout.addWidget(status_group)
left_layout.addStretch()
# 退出按钮
exit_btn = QPushButton("退出系统")
exit_btn.setFixedHeight(30)
exit_btn.setStyleSheet("""
QPushButton {
background: #e74c3c;
color: white;
font: bold 12px 'Microsoft YaHei';
border-radius: 3px;
padding: 4px;
}
QPushButton:hover { background: #c0392b; }
""")
exit_btn.clicked.connect(self.close)
left_layout.addWidget(exit_btn)
# ===== 右侧显示区域 =====
right_panel = QWidget()
right_layout = QHBoxLayout(right_panel)
right_layout.setContentsMargins(0, 0, 0, 0)
right_layout.setSpacing(12)
def create_display(title):
box = QWidget()
box.setStyleSheet("""
background: white;
border: 1px solid #bdc3c7;
border-radius: 5px;
""")
layout = QVBoxLayout(box)
layout.setContentsMargins(0, 0, 0, 0)
# 标题栏
title_bar = QLabel(title)
title_bar.setStyleSheet("""
background: #f8f9fa;
color: #2c3e50;
font: bold 30px 'Microsoft YaHei';
padding: 6px;
border-bottom: 1px solid #ced4da;
""")
title_bar.setAlignment(Qt.AlignCenter)
layout.addWidget(title_bar)
# 图像显示
img_label = QLabel()
img_label.setAlignment(Qt.AlignCenter)
img_label.setMinimumSize(500, 500)
img_label.setMaximumSize(500, 500)
img_label.setStyleSheet("background: #2c3e50;")
layout.addWidget(img_label)
return box, img_label
self.cam_box, self.label1 = create_display("实时画面")
self.result_box, self.label2 = create_display("检测结果")
right_layout.addWidget(self.cam_box)
right_layout.addWidget(self.result_box)
main_layout.addWidget(left_panel)
main_layout.addWidget(right_panel, 1)
self.setCentralWidget(main_widget)
def load_model(self):
self.result = None
model_path, _ = QFileDialog.getOpenFileName(self, "选择模型文件", filter='*.pt')
if model_path:
self.model = YOLO(model_path)
def select_image(self):
self.result = None
image_path, fileType = QFileDialog.getOpenFileName(self, "选择图片文件", filter='*.jpg *.png *.bmp')
if image_path:
img = cv2.imread(image_path)
self.detect_image(img)
def stop_detect(self):
self.result = None
img = cv2.cvtColor(np.zeros((580, 550), np.uint8), cv2.COLOR_BGR2RGB)
img = QImage(img.data, img.shape[1], img.shape[0], QImage.Format_RGB888)
self.label1.setPixmap(QPixmap.fromImage(img))
self.label2.setPixmap(QPixmap.fromImage(img))
self.display_statistics()
def close(self):
exit()
def detect_image(self, img):
if self.model is not None:
frame = img
results = self.model.predict(frame,conf=self.con)
image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
height1, width1, channel1 = frame.shape
bytesPerLine1 = 3 * width1
qimage1 = QImage(image_rgb.data, width1, height1, bytesPerLine1, QImage.Format_RGB888)
pixmap1 = QPixmap.fromImage(qimage1)
self.label1.setPixmap(pixmap1.scaled(self.label1.size(), Qt.AspectRatioMode.IgnoreAspectRatio))
annotated_image = results[0].plot()
annotated_image = cv2.cvtColor(annotated_image, cv2.COLOR_BGR2RGB) # 转换为 RGB
height2, width2, channel2 = annotated_image.shape
bytesPerLine2 = 3 * width2
qimage2 = QImage(annotated_image.data, width2, height2, bytesPerLine2, QImage.Format_RGB888)
pixmap2 = QPixmap.fromImage(qimage2)
pixmap2 = pixmap2.scaled(self.label2.size(), Qt.AspectRatioMode.IgnoreAspectRatio)
self.result = results
self.label2.setPixmap(pixmap2)
self.display_statistics()
def stat(self):
detected_classes = []
target_range = {0, 1} #修改为自己的类别
if self.result == None:
return None
for r in self.result:
classes = r.boxes.cls.cpu().numpy().astype(int).tolist()
# 筛选出在目标范围内的类别,并去重
detected_classes.extend([cls for cls in classes if cls in target_range])
class_counts = Counter(detected_classes)
class_counts_dic = dict(class_counts)
return class_counts_dic
def display_statistics(self):
class_counts = self.stat()
if class_counts == None:
self.output_text.setText('')
return
#修改class_labels为自己的类别对应关系,可中文
class_labels = {
0: "helmet", 1: "vest"
}
# 构建输出字符串
output_string = ""
for class_id, count in class_counts.items():
label = class_labels.get(class_id, f"类别{class_id}") # 如果没有找到标签,则使用默认标签
output_string += f"{label}: {count} 个\n"
self.output_text.setText(output_string)
def closeEvent(self, event: QEvent):
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
自己用需要修改自己数据集对应类别。
需要完整功能可私聊或通过公众号购买成品。有其他需求及想要定制的可以私信或通过公众号联系我,遇到报错可以在评论区交流,关注公众号获取更多资源~