引言

  • 简单介绍下YOLO模型的图像分类深度训练和预测功能示例,
  • 了解Python中的PyQt5库的信号槽,QThread线程类,QCharView图表的简单使用
  • 结合Pyqt5与YOLO模型图像分类,实现训练的损失率的图表和预测结果显示。

环境准备

所需的软件和库:PyQt5、YOLO、Python 3.8
涉及库的安装:

#YOLO库
pip install ultralytics
#PyQt5
pip install pyqt5
#QCharView
pip install PyQt5 PyQtChart

YOLO模型 图像分类

  • 训练

通过YOLO模型自带的yolov8n-cls.pt模型进行训练,
通过绑定on_fit_epoch_end回调函数获取训练的损失率和验证的损失率,绘制成报表。
这里介绍常用的训练参数

参数名称描述
model如果预训练就指向预训练模型 模型文件路径,例如 yolov8n.pt, yolov8n.yaml
data执行数据集地址 数据文件路径,例如 coco128.yaml
epochs训练的周期数
patience早停训练等待的最大无改善周期数
batch每批次的图像数量(-1 表示自动批次)
imgsz输入图像的尺寸
save是否保存训练检查点和预测结果
lr0初始学习率(例如 SGD=1E-2, Adam=1E-3)
lrf最终学习率(初始学习率 * 最终学习率)
weight_decay优化器权重衰减 5e-4
project项目名称
name实验名称
device运行设备,例如 cuda device=0 或 device=0,1,2,3 或 device=cpu
plots训练/验证期间是否保存图表和图像

如果没有设置验证数据集,那么设置split=val属性会自动创建20%原有数据作为验证数据

  • 代码示例:
	# 加载一个预训练的分类模型
    model = YOLO('yolov8n-cls.pt')
    print("YOLO Model load successfully")
    #model.add_callback("on_fit_epoch_end", on_fit_epoch_end)
    # 指定设备
    tra_device = "cpu"
    try:
        results = model.train(
            data="F://kaggle//train_alittle",
            epochs=5,
            imgsz=[224,224],
            batch=32,
            device=tra_device ,
            split='val',  # 自动创建验证集

            # === 数据增强优化 ===
            augment=True,  # 数据增强(小数据必备,提升泛化)
            mosaic=0.5,  # 马赛克强度
            mixup=0.1,  # 启用mixup增强
            scale=0.5,  # 缩放增强

            # === 学习率优化 ===
            lr0=0.01,  # 初始学习率
            lrf=0.01,  # 最终学习率
            weight_decay=0.0005,  # 权重衰减

            # === 训练策略优化 ===
            patience=20,  # 早停(n轮无提升终止,防过拟合)
            project="./result",
            name="train_exp",
            resume=False,

            # === 禁用GUI调用 ===
            plots=False,  # <<< 关键,禁用图表
            show=False,  # <<< 关键,禁用显示
            workers=0  # <<< 关键,禁用子进程加载
        )
    except KeyboardInterrupt:
        print("Training interrupted by user")
#目录结构
##train_alittle
####train
######cat
######dog
####val
######cat
######dog

需要引用的模块:

# 模型和训练模块,核心
from ultralytics import YOLO
from torch import Tensor
import torch
# 设置模块
from ultralytics import settings
from copy import copy, deepcopy
from ultralytics.utils.torch_utils import (
    TORCH_2_4,
    EarlyStopping,
    ModelEMA,
    attempt_compile,
    autocast,
    convert_optimizer_state_dict_to_fp16,
    init_seeds,
    one_cycle,
    select_device,
    strip_optimizer,
    torch_distributed_zero_first,
    unset_deterministic,
    unwrap_model,
)

  • 预测

预测就是调用模型,
然后判断每张图片属于各个分类的概率:

  • 完整代码:
model = YOLO('E://QINGTUUGUANGDIAN//Example_Demo//MtNas//YOLO_PROJECTCASE//TextModel//classify//result//train_exp-5//weights//best.pt')
image_folder = "F://kaggle//test1//test1"
# 方法1:获取文件夹中的所有图片,用切片限制数量
all_images = list(Path(image_folder).glob("*.jpg"))[:50]  # 只取前50张
# 执行预测并配置参数
results = model.predict(
    source=all_images,  # 可以是图片、文件夹、视频等
    save=True,  # 保存带有预测标注的图片
    save_txt=True,  # 将预测结果保存为 .txt 文件
    conf=0.25,  # 设置置信度阈值(分类任务中常用)
    imgsz=224  # 设置输入图片尺寸,应与训练时一致
)

# 遍历所有结果
sum=0;
successed=0
for result in results:
    sum+=1
    class_name = result.path.split('\\')[-1]
    print(class_name[:3])
    # 每个 result 包含了单张图片的所有预测信息
    print(f"{result.path}:{result.names[result.probs.top1]}")  # 打印每个图片的预测类别名
    if(class_name[:3]==result.names[result.probs.top1]):
        successed+=1
    # # probs 是一个包含所有类别概率的张量
    # probs_tensor = result.probs.data
    #
    # # 获取概率最高的前5个类别
    # top5_indices = probs_tensor.topk(5).indices.cpu().numpy()
    # top5_confidences = probs_tensor.topk(5).values.cpu().numpy()
    #
    # print("Top-5 预测结果:")
    # for i, (idx, conf) in enumerate(zip(top5_indices, top5_confidences)):
    #     class_name = model.names[int(idx)]
    #     print(f"  第{i + 1}名: {class_name} (置信度: {conf:.4f})")
print(f"总共{sum}条数据,其中预测正确{successed}条,正确率{successed/sum*100:.2f}%")

总的来说,YOLO的图像分类模型的使用并不复杂,
具体的YOLO模型介绍请看文章后面的参考资料,
接下来就开始设计一个可视化的界面。

PyQt5界面设计

这时候就需要用到Designer 5.15.2 (MSVC 2019 64-bit)工具,具体版本随意,
通过工具创建一个QMainWindow界面文件,设计布局,像这样:
在这里插入图片描述

  • 在通过pyuic5 命令行,将ui文件转成py文件:

pyuic5 TextModel/ui/YOLO_Classification.ui -o TextModel/ui/YOLO_Classification.py
生成后的py文件需要修改继承类和添加继承类,
同时调用setupUi函数
像这样:

from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        #super(Ui_MainWindow, self).__init__()
        self.setupUi(self)
        #其他修改
        #self.InitView()

    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(789, 596)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout_2.setSpacing(0)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.widget = QtWidgets.QWidget(self.centralwidget)
        self.widget.setMinimumSize(QtCore.QSize(0, 50))
        self.widget.setObjectName("widget")
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.widget)
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.pushButton_ToTrain = QtWidgets.QPushButton(self.widget)
        self.pushButton_ToTrain.setMinimumSize(QtCore.QSize(60, 35))
        self.pushButton_ToTrain.setCheckable(True)
        self.pushButton_ToTrain.setObjectName("pushButton_ToTrain")
        self.horizontalLayout_3.addWidget(self.pushButton_ToTrain)
        self.pushButton_ToVerify = QtWidgets.QPushButton(self.widget)
        self.pushButton_ToVerify.setMinimumSize(QtCore.QSize(60, 35))
        self.pushButton_ToVerify.setCheckable(True)
        self.pushButton_ToVerify.setObjectName("pushButton_ToVerify")
        self.horizontalLayout_3.addWidget(self.pushButton_ToVerify)
        spacerItem = QtWidgets.QSpacerItem(606, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_3.addItem(spacerItem)
        self.verticalLayout_2.addWidget(self.widget)
        self.stackedWidget = QtWidgets.QStackedWidget(self.centralwidget)
        self.stackedWidget.setObjectName("stackedWidget")
        self.page = QtWidgets.QWidget()
        self.page.setObjectName("page")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.page)
        self.verticalLayout.setObjectName("verticalLayout")
        self.widget_tite = QtWidgets.QWidget(self.page)
        self.widget_tite.setMaximumSize(QtCore.QSize(16777215, 50))
        self.widget_tite.setObjectName("widget_tite")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_tite)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label = QtWidgets.QLabel(self.widget_tite)
        self.label.setMinimumSize(QtCore.QSize(0, 28))
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.lineEdit_dirpath = QtWidgets.QLineEdit(self.widget_tite)
        self.lineEdit_dirpath.setMinimumSize(QtCore.QSize(0, 28))
        self.lineEdit_dirpath.setObjectName("lineEdit_dirpath")
        self.horizontalLayout.addWidget(self.lineEdit_dirpath)
        self.toolButton_choose_dirpath = QtWidgets.QToolButton(self.widget_tite)
        self.toolButton_choose_dirpath.setMinimumSize(QtCore.QSize(0, 28))
        self.toolButton_choose_dirpath.setObjectName("toolButton_choose_dirpath")
        self.horizontalLayout.addWidget(self.toolButton_choose_dirpath)
        self.pushButton_StartTrain = QtWidgets.QPushButton(self.widget_tite)
        self.pushButton_StartTrain.setMinimumSize(QtCore.QSize(0, 28))
        self.pushButton_StartTrain.setObjectName("pushButton_StartTrain")
        self.horizontalLayout.addWidget(self.pushButton_StartTrain)
        self.verticalLayout.addWidget(self.widget_tite)
        self.widget_connect = QtWidgets.QWidget(self.page)
        self.widget_connect.setObjectName("widget_connect")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_connect)
        self.horizontalLayout_2.setContentsMargins(15, 15, 15, 15)
        self.horizontalLayout_2.setSpacing(15)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.widget_charview = QChartView(self.widget_connect)
        self.widget_charview.setStyleSheet("background-color: rgb(246, 246, 246);")
        self.widget_charview.setObjectName("widget_charview")
        self.horizontalLayout_2.addWidget(self.widget_charview)
        self.verticalLayout.addWidget(self.widget_connect)
        self.stackedWidget.addWidget(self.page)
        self.page_2 = QtWidgets.QWidget()
        self.page_2.setObjectName("page_2")
        self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.page_2)
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.widget_tite_2 = QtWidgets.QWidget(self.page_2)
        self.widget_tite_2.setMaximumSize(QtCore.QSize(16777215, 50))
        self.widget_tite_2.setObjectName("widget_tite_2")
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.widget_tite_2)
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        self.label_2 = QtWidgets.QLabel(self.widget_tite_2)
        self.label_2.setMinimumSize(QtCore.QSize(0, 28))
        self.label_2.setObjectName("label_2")
        self.horizontalLayout_4.addWidget(self.label_2)
        self.lineEdit_imagepath = QtWidgets.QLineEdit(self.widget_tite_2)
        self.lineEdit_imagepath.setMinimumSize(QtCore.QSize(0, 28))
        self.lineEdit_imagepath.setObjectName("lineEdit_imagepath")
        self.horizontalLayout_4.addWidget(self.lineEdit_imagepath)
        self.toolButton_choose_file = QtWidgets.QToolButton(self.widget_tite_2)
        self.toolButton_choose_file.setMinimumSize(QtCore.QSize(0, 28))
        self.toolButton_choose_file.setObjectName("toolButton_choose_file")
        self.horizontalLayout_4.addWidget(self.toolButton_choose_file)
        self.pushButton_StartVerify = QtWidgets.QPushButton(self.widget_tite_2)
        self.pushButton_StartVerify.setMinimumSize(QtCore.QSize(0, 28))
        self.pushButton_StartVerify.setObjectName("pushButton_StartVerify")
        self.horizontalLayout_4.addWidget(self.pushButton_StartVerify)
        self.verticalLayout_3.addWidget(self.widget_tite_2)
        self.splitter = QtWidgets.QSplitter(self.page_2)
        self.splitter.setOrientation(QtCore.Qt.Horizontal)
        self.splitter.setObjectName("splitter")
        self.label_3 = QtWidgets.QLabel(self.splitter)
        self.label_3.setAlignment(QtCore.Qt.AlignCenter)
        self.label_3.setObjectName("label_3")
        self.tableWidget = QtWidgets.QTableWidget(self.splitter)
        self.tableWidget.setObjectName("tableWidget")
        self.tableWidget.setColumnCount(0)
        self.tableWidget.setRowCount(0)
        self.verticalLayout_3.addWidget(self.splitter)
        self.stackedWidget.addWidget(self.page_2)
        self.verticalLayout_2.addWidget(self.stackedWidget)
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        self.stackedWidget.setCurrentIndex(0)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton_ToTrain.setText(_translate("MainWindow", "训练"))
        self.pushButton_ToVerify.setText(_translate("MainWindow", "验证"))
        self.label.setText(_translate("MainWindow", "路径设置:"))
        self.toolButton_choose_dirpath.setText(_translate("MainWindow", "选择..."))
        self.pushButton_StartTrain.setText(_translate("MainWindow", "开始训练"))
        self.label_2.setText(_translate("MainWindow", "图片路径:"))
        self.toolButton_choose_file.setText(_translate("MainWindow", "选择..."))
        self.pushButton_StartVerify.setText(_translate("MainWindow", "验证"))
        self.label_3.setText(_translate("MainWindow", "Image"))

不建议在setupUi函数和retranslateUi里面修改参数或类型,
毕竟每次修改了ui文件都会重新生成py文件,这两个函数都会被重新覆盖。
同时也建议把生成后的例如YOLO_Classification.py中的界面复制到新的py文件中,
不建议在原有文件中修改,说起来这都是泪。。。

  • 调用主界面窗体

调用窗体时一定要在QtWidgets.QApplication事务中调用,
要不然如果有绑定信号槽的操作,是不会生效的。
像这样:

if __name__ == '__main__':
    qApp = QtWidgets.QApplication(sys.argv)
    QApplication.setStyle(QStyleFactory.create('Fusion'))

    ui = Ui_MainWindow()
    ui.show()
    sys.exit(qApp.exec_())

运行效果展示:
在这里插入图片描述

界面搞完了,接下来就需要对YOLO模块进行调用了,
但是为了训练时不堵塞界面的显示,所以需要加个线程类封装训练的操作。

重写PyQt5 QThread线程类

于是重写Pyqt5 中QThread线程类,基本和写Qt操作类似
将YOLO的深度训练的和预测都放到这个线程类中执行。

  • 重写QThread类

设计使用Qt的信号槽来获取训练的损失率数据,
所以也需要同时继承QObject类
然后使用pyqtSignal(参数,参数....)定义信号槽,获取线程的运行状态,和训练的损失率数据:
定义一个ThreadFlags枚举类,用来表示需要进行的操作
添加def run(self)函数,运行上面YOLO模块的训练或预测函数
在PyQt5中def __init__(self): 就相当于QT中类的构造函数。
如下图示:

#线程类
class YOLO_CLS_WORKER(QThread, QObject):
    """最简单的 QObject 子类"""
    """定义一个YOLO训练或预测的线程类"""
    # 当前进程处理状态
    TreatmentStated = pyqtSignal(int, float, float)
    # 当前线程运行状态
    IsThreadRun = pyqtSignal(bool)
    # 预测图片返回标注图片
    Predict_Results = pyqtSignal(str)
    def __init__(self, parent=None):
        #super().__init__(parent)  # 必须调用父类构造器
        super(YOLO_CLS_WORKER, self).__init__(parent)
        #线程状态
        self.__threadFlags = ThreadFlags.NONE

 	#线程运行
    def run(self):
        self.IsThreadRun[bool].emit(True)
        if(self.__threadFlags & ThreadFlags.THREAD_TRAIN):
            self.__Train()
            self.__threadFlags &= ~ThreadFlags.THREAD_TRAIN
        elif (self.__threadFlags & ThreadFlags.THREAD_PREDICTION):
            self.__Predict()
            self.__threadFlags &= ~ThreadFlags.THREAD_PREDICTION
        else:
            print("Jump The Thread")
        self.IsThreadRun.emit(False)

#操作状态 
class ThreadFlags(IntFlag):
    """操作标志(支持位运算)"""
    NONE = 1 << 0
    THREAD_TRAIN = 1 << 1
    THREAD_PREDICTION = 1 << 2
  • Py界面调用

在PyQt5中通connect绑定信号槽,
通过@pyqtSlot(参数,参数,参数....)定义槽函数。
如下图示:

 def InitView(self):
     self.resize(800, 600)
     self.setWindowTitle("YOLO 图像分类")
     self.setWindowIcon(QApplication.style().standardIcon(42))
    
     #初始化线程类
     self.Worker=YOLO_CLS_WORKER()
     self.Worker.TreatmentStated.connect(self.Train_state)
     self.Worker.Predict_Results.connect(self.Predict_Results)
     self.Worker.IsThreadRun.connect(self.IsThreadRun)
     
 @pyqtSlot(int,float,float)
 def Train_state(self,epoch, total_train_loss, total_val_loss):
     print(f"epoch: {epoch}, train_loss: {total_train_loss}, val_loss: {total_val_loss}")
     self.series_train.append(epoch,total_train_loss)
     self.series_validator.append(epoch, total_val_loss)
     self.widget_charview.repaint()

 @pyqtSlot(str)
 def Predict_Results(self,imagepath):
     #self.label_3.setPixmap(QPixmap(imagepath))
     pixmap = QPixmap(imagepath)
     # 缩放图片以适应标签大小
     scaled_pixmap = pixmap.scaled(
         self.label_3.size(),
         Qt.KeepAspectRatio,
         Qt.SmoothTransformation
     )
     self.label_3.setPixmap(scaled_pixmap)
  • 完整设计

通过标志的切换,切换训练与预测

#引用
from time import sleep
from PyQt5 import QtCore
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QTimer, QThread, QFileInfo
from PyQt5.QtWidgets import QApplication
import sys
from enum import IntFlag
#YOLO相关引用
import os
from pathlib import Path
# 模型和训练模块,核心
from ultralytics import YOLO
from torch import Tensor
import torch
# 设置模块
from ultralytics import settings
from copy import copy, deepcopy
from ultralytics.utils.torch_utils import (
    TORCH_2_4,
    EarlyStopping,
    ModelEMA,
    attempt_compile,
    autocast,
    convert_optimizer_state_dict_to_fp16,
    init_seeds,
    one_cycle,
    select_device,
    strip_optimizer,
    torch_distributed_zero_first,
    unset_deterministic,
    unwrap_model,
)

#操作状态
class ThreadFlags(IntFlag):
    """操作标志(支持位运算)"""
    NONE = 1 << 0
    THREAD_TRAIN = 1 << 1
    THREAD_PREDICTION = 1 << 2

#线程类
class YOLO_CLS_WORKER(QThread, QObject):
    """最简单的 QObject 子类"""
    """定义一个YOLO训练或预测的线程类"""
    # 当前进程处理状态
    TreatmentStated = pyqtSignal(int, float, float)
    # 当前线程运行状态
    IsThreadRun = pyqtSignal(bool)
    # 预测图片返回标注图片
    Predict_Results = pyqtSignal(str)
    def __init__(self, parent=None):
        #super().__init__(parent)  # 必须调用父类构造器
        super(YOLO_CLS_WORKER, self).__init__(parent)
        #线程状态
        self.__threadFlags = ThreadFlags.NONE
        self.__train_Dir="F://kaggle//train_alittle"
        self.__predict_file=""
        self.__LastWeights="E://QINGTUUGUANGDIAN//Example_Demo//MtNas//YOLO_PROJECTCASE//TextModel//classify//result//train_exp//weights//best.pt"

    #获取线程状态
    def getThreadFlags(self):
        return self.__threadFlags

    def To_Train_Start(self,dirpath):
        self.__train_Dir=dirpath
        self.__threadFlags|=ThreadFlags.THREAD_TRAIN
        self.start()

    def To_Predict_Start(self,filepath):
        self.__predict_file=filepath
        self.__threadFlags |= ThreadFlags.THREAD_PREDICTION
        self.start()

    #线程运行
    def run(self):
        self.IsThreadRun[bool].emit(True)
        if(self.__threadFlags & ThreadFlags.THREAD_TRAIN):
            self.__Train()
            self.__threadFlags &= ~ThreadFlags.THREAD_TRAIN
        elif (self.__threadFlags & ThreadFlags.THREAD_PREDICTION):
            self.__Predict()
            self.__threadFlags &= ~ThreadFlags.THREAD_PREDICTION
        else:
            print("Jump The Thread")
        self.IsThreadRun.emit(False)

    #开始训练
    def __Train(self):
        def setup_device():
            print(f"CUDA available: {torch.cuda.is_available()}")
            if torch.cuda.is_available():
                print(f"Using GPU: {torch.cuda.get_device_name(0)}")
                device = '0'
            else:
                print("Using CPU")
                device = 'cpu'
            return device

        # 每个拟合 epoch(训练+验证)结束时,验证之后(此时验证指标可用)
        def on_fit_epoch_end(trainer):
            print("on_fit_epoch_end")
            # 1. 获取训练损失
            train_losses = {}
            total_train_loss = 0.0

            if hasattr(trainer, 'loss_items'):
                train_losses = trainer.loss_items
                total_train_loss = float(trainer.loss_items)

            # 2. 获取验证损失(如果执行了验证)
            val_losses = {}
            total_val_loss = 0

            if hasattr(trainer, 'validator') and trainer.validator:
                validator = trainer.validator
                # 方法1: 从validator获取损失
                if hasattr(validator, 'loss'):
                        total_val_loss =float(validator.loss)

            self.TreatmentStated[int,float,float].emit(trainer.epoch,total_train_loss,total_val_loss)

        model = YOLO('yolov8n-cls.pt')
        print("YOLO Model load successfully")
        tra_device = "cpu"
        device = setup_device() if tra_device != "cpu" else "cpu"
        model.add_callback("on_fit_epoch_end", on_fit_epoch_end)
        try:
            results = model.train(
                data=self.__train_Dir,
                epochs=20,
                imgsz=[224, 224],
                batch=32,
                device=device,
                split='val',  # 自动创建验证集
                # split_ratio=0.2, # 关键参数:从训练集中抽取20%作为验证集

                # === 数据增强优化 ===
                augment=True,  # 数据增强(小数据必备,提升泛化)
                mosaic=0.5,  # 马赛克强度
                mixup=0.1,  # 启用mixup增强
                scale=0.5,  # 缩放增强

                # === 学习率优化 ===
                lr0=0.01,  # 初始学习率
                lrf=0.01,  # 最终学习率
                weight_decay=0.0005,  # 权重衰减

                # === 训练策略优化 ===
                patience=20,  # 早停(n轮无提升终止,防过拟合)
                project="./result",
                name="train_exp",
                resume=False,

                # === 禁用GUI调用 ===
                plots=False,  # <<< 关键,禁用图表
                show=False,  # <<< 关键,禁用显示
                workers=0  # <<< 关键,禁用子进程加载
            )
        except KeyboardInterrupt:
            print("Training interrupted by user")

        #WindowsPath('E:/QINGTUUGUANGDIAN/Example_Demo/MtNas/YOLO_PROJECTCASE/TextModel/classify/result/train_exp-9')

        self.__LastWeights=  results.save_dir / 'weights' / 'best.pt'
        print(f"full_path:{self.__LastWeights}")
        print("__Train_Over__")

     #开始预测
    def __Predict(self):
        if self.__predict_file=="" :
            return
        model = YOLO(self.__LastWeights)
        results = model.predict(
            source=self.__predict_file,  # 可以是图片、文件夹、视频等 这里设置为单张图片路径
            save=True,  # 保存带有预测标注的图片
            save_txt=False,  # 将预测结果保存为 .txt 文件
            conf=0.25,  # 设置置信度阈值(分类任务中常用)
            imgsz=224  # 设置输入图片尺寸,应与训练时一致
        )
        info=QFileInfo(self.__predict_file)
        result=results[0].save_dir+"//"+info.fileName()
        self.Predict_Results[str].emit(result)

        # for result in results:
        #     print(result)


  • 运行测试:

运行上面的类设计,Pyqthon的脚本模式就是好用。

#*第二种写法
# if __name__ == '__main__':
#     app = QApplication(sys.argv)  # 必须创建 QApplication
#
#     # 不要使用 @pyqtSlot,直接定义普通函数
#     def Train_state(epoch, total_train_loss, total_val_loss):
#         print(f"epoch: {epoch}, train_loss: {total_train_loss}, val_loss: {total_val_loss}")
#
#     thread = YOLO_CLS_WORKER()
#     thread.TreatmentStated.connect(Train_state)
#     thread.To_Train_Start("F://kaggle//train_alittle")
#     # 确保信号连接正确(去掉 @pyqtSlot 装饰器)
#     # 或使用默认连接
#     # thread.TreatmentStated.connect(Train_state)
#     # thread.start()
#     # 使用 QTimer 或 app.exec() 而不是 stop_event.wait()
#     sys.exit(app.exec_())  # 进入 Qt 事件循环


#*第二种写法 预测
# if __name__ == '__main__':
#     app = QApplication(sys.argv)  # 必须创建 QApplication
#
#     thread = YOLO_CLS_WORKER()
#     thread.To_Predict_Start("F://kaggle//test1//test1//2.jpg")
#     sys.exit(app.exec_())  # 进入 Qt 事件循环

结果可视化

  • 训练:

目前测试的用的数据和训练次数都很小,所以训练效果波动很大。
请添加图片描述

  • 预测:

直接获取模型标注的图像显示。准确率还是很高
请添加图片描述
请添加图片描述

总结后记

总得来说,Pyqt与QT实际上的写法/用法基本都改动不大,只要会Qt,那么了解了pyqthon的语法,PyQt使用起来就很容易。
由于近几年没写过PyQt了,就想着在学习YOLO模型的时候复习一下,
于是写了这个案例复习一下PyQt的关键用法,
同时总结下YOLO的图像分类模型。

参考资料

  1. 超详细YOLOv8图像分类全程概述:环境、训练、验证与预测详解
  2. 用YOLOv8一站式解决图像分类、检测、分割
  3. #零基础 从 yolo8 入门计算机视觉超简单:物体识别、图像分类、轨迹追踪、姿势识别

ps: 最近有些懈怠了
(╯°□°)╯︵ ┻━┻

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐