YOLOv12可视化界面;YOLOv11;YOLOv10;YOLOv8等通用可视化界面GUI设计,基于pyside6,单文件即插即用,更新ip摄像头检测,美化布局及信息输出

前言

为了更便捷地使用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摄像头功能,左侧增加检测信息输出。

使用之前需要配置环境,运行有问题应该先重新按照下文配置环境。

目标检测:YOLOv11(Ultralytics)环境配置,适合0基础纯小白,超详细_yolov11环境配置-CSDN博客文章浏览阅读5.6w次,点赞211次,收藏983次。深度学习目标检测YOLOv11小白教程环境搭建,超级简单的教程,纯新手方便理解,一看就会。_yolov11环境配置 https://blog.csdn.net/qq_67105081/article/details/143270109?spm=1001.2014.3001.5502

YOLOv12则看这篇文章

目标检测:YOLOv12环境配置,超详细,适合0基础纯小白-CSDN博客文章浏览阅读1.2k次,点赞30次,收藏26次。小白也可以看懂的YOLOv12教程!YOLOv12 是 YOLO 系列中首个打破传统基于卷积神经网络(CNN)方法的模型,它通过将注意力机制直接集成到目标检测过程中实现了这一突破。因此YOLOv12需要额外配置FlashAttention,此前的YOLO环境均不可用,需要按照最新的教程配置。此外,30系显卡以前的架构不支持较新的FlashAttention2.x,只能通过更换显卡解决。 https://blog.csdn.net/qq_67105081/article/details/146316615?spm=1001.2014.3001.5501基于上文环境之外,只需要额外多安装一个pyside6库即可。输入如下指令

pip install pyside6

效果展示

主界面

检测效果

本文代码图片检测运行界面如下

检测效果

ip摄像头可通过这个文章使用手机测试

通过ip摄像头软件将手机变成云ip摄像头,并用Python读取视频流,可用于实时目标检测摄像头调用_ip摄像头lite-CSDN博客文章浏览阅读731次,点赞5次,收藏8次。通过将手机模拟成IP摄像头,我们可以实现将手机摄像头的画面通过网络传输,进而通过Python程序读取视频画面进行分析、处理等。本文将介绍如何通过一款局域网IP摄像头软件将手机模拟成云摄像头,并使用Python进行视频流的读取,同时运用目标检测算法进行实时物体识别。使用手机软件模拟ip摄像头相比购买专门的IP摄像头设备,成本大大降低。这里我们选择局域网的第一个MJPEG进行连接,可以将网址输入浏览器中,再输入账号密码admin进行查看,或者通过以下python代码在python上调用视频流画面,代码如下。_ip摄像头lite https://blog.csdn.net/qq_67105081/article/details/143668267?spm=1001.2014.3001.5501

代码

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())

自己用需要修改自己数据集对应类别。

需要完整功能可私聊或通过公众号购买成品。有其他需求及想要定制的可以私信或通过公众号联系我,遇到报错可以在评论区交流,关注公众号获取更多资源~