学习记录01:使用pyqt5搭建yolo3目标识别界面

使用pyqt5搭建yolo3目标识别界面

已有重制版,yolo3检测界面重制版,更简单,完善。

由于这是我第一次写这种博客,其目的也不是为了赚取积分,主要是为了记录我的学习过程中的一些方法,以便以后我再次需要用的时候可以知道我当时是怎么做的。所以文中会有很多地方并不会解释其原理(主要是我自己压根也没搞明白,当时只想知道怎么用就行了,遇到需要用其他的再百度),主要着重于怎么运用。如有不当之处,请指出我当改正。
Alt!
代码地址:
github:https://github.com/lavenderlove52/YOLOV3-detection-interface-pyqt5
gitee:https://gitee.com/lavenderlove52/YOLOV3-detection-interface-pyqt5

搭建pyqt5环境

我用的IDE是PyCharm,深度学习环境搭建可以参考其他博主的教程。
pyqt5的环境搭建流程参考的是b站up主@刘金玉编程。

  1. 安装Anaconda3,搭建好虚拟环境,在虚拟环境里配置好yolo3所需的库,并在这个虚拟环境里安装pyqt5。

  2. win + R打开快速运行,搜索cmd打开命令行窗口。
    在这里插入图片描述
    打开命令提示符窗口,输入’activate’+空格+所在虚拟环境的名称。然后输入’pip install pyqt5’+回车,就会开始安装pyqt5。
    在这里插入图片描述

  3. 在PyCharm中配置好pyqt5。首先打开设置,在’External Tool’中添加ptqt5组件。点击 ‘+‘号,
    在这里插入图片描述
    点击 ‘+‘号,创建工具,工具名字是自己设定的,第一个可以命名为’QTDesinger’,Program路径选择Anaconda安装路径下的’Ananconda3\Library\bin\designer.exe’,因为我是直接装在D盘的,所以完整路径为’D:\Ananconda3\Library\bin\designer.exe’。点击Program这一行末尾的’+‘号即可选择路径。选择好之后,Argument不用填,Working directory会自己填好。
    在这里插入图片描述
    然后再添加一个工具,名字可以命名为’PyUIC’,Program选择路径为虚拟环境的路径,一般都安装在Ananconda3下的evns文件夹里,下一级文件夹就是虚拟环境的名称,找到你创建的虚拟环境文件夹,在里面找到 python.exe文件,完整路径为’D:\Ananconda3\envs\python3-6\python.exe’,Argument填入

-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py

Working directory填入

$FileDir$

完成以上步骤,即可配置好所需工具,开始设计界面了。
可以右键项目名称选择External Tool里的QTDesinger可以直接开始设计界面,最后需要保存在项目根目录里,会生成一个后缀为’ui’的文件,然后右键点击这个文件选择External Tool里的PyUIC即可将其转换为’py’文件,就可以从代码中对界面进行调用和编辑。
在这里插入图片描述
在这里插入图片描述
我没有从直接设计界面开始,而是从代码中设计界面。

程序流程

主界面

Created with Raphaël 2.3.0 开始 选择训练模型 上传数据集 开始训练模型 检测 退出? 结束 yes no

子界面

Created with Raphaël 2.3.0 检测 选择检测模型 选择检测图片 开始检测 退出? 结束 yes no

设计界面

1.主函数

需要导入以下常用的库:

from PyQt5.QtWidgets import *					#这两个是pyqt5常用的库
from PyQt5.QtGui import QIcon, QPixmap			#可以满足小白大多数功能
import os					#这两个是其他的库
import sys					#可以完成一些打开文件保存文件的功能
from yolov3 import train, predict			#这是我自己将yolov3模型整体放在我项目的子文件夹中,从中调用我的训练和预测函数

下面是主程序:

# 创立一个主界面,并保持它,从各种按钮或者组件中接受信号完成界面功能,相当于无限循环
# 只有选择退出后才会关掉程序退出循环
if __name__ == '__main__':
    app = QApplication(sys.argv)
    mc = MyClass()		#这里相当于实例化一个主界面,myclass是自己定义的主界面类
    sys.exit(app.exec_())		#监听退出,如果选择退出,界面就会关掉

2.定义主界面的类

(1)主界面

首先是初始化,需要继承父类。

class MyClass(QWidget):
    def __init__(self):
        super().__init__()		#继承父类
        self.initUI()			#自己定义的函数,初始化类界面,里面放着自己各种定义的按钮组件及布局
        self.child_window = ChildClass()		#子界面的调用,本质和主界面一样是一个类,在这里将其声明为主界面的成员

(2)主界面布局

功能实现在代码注释都有详细说明,关于self.TModelSelectSignal = [0, 0] self.TModel= [1, 0],可以理解为一种主界面和子界面的信号传递,由于我对深层次的参数传递不清楚,所以只能采用一种的最笨的方法,在主界面的类里活得已训练好模型的序号,放在数组self.TModel里,数组的第一位为1表示第一个模型已训练,为0表示未训练。同理第二位则表示第二个模型,如果有更多模型,可以设置更多位。数组的设置实在模型训练之后,因此将赋值数组的代码放在训练函数里。

     def initUI(self):
        self.setWindowTitle("COC缺陷检测")		#设置界面名称
        # self.setWindowIcon(QIcon("iconimg/zhou.png"))		#设计界面的图标,图片放在项目文件夹的子文件夹里就不会出错,名字也要对应
        self.resize(350, 200)		#设置界面大小
        self.TModelSelectSignal = [0, 0]   #选择按钮对应的模型
        self.TModel= [0, 0]                 #表示已经训练好的模型编号

        myframe = QFrame(self)      #实例化一个QFrame可以定义一下风格样式,相当于一个框架,可以移动,其内部组件也可以移动
        btn2 = QPushButton("开始训练模型", self)		#定义一个按钮,括号里需要一个self,如果需要在类内传递,则应该定义为self.btn2
        btn2.clicked.connect(self.TestModel)	#将点击事件与一个函数相连,clicked表示按钮的点击事件,还有其他的功能函数,后面连接的是一个类内函数,调用时无需加括号
        btn3 = QPushButton("上传数据集", self)
        btn3.clicked.connect(self.DataExplorerSelect)   #连接一个选择文件夹的函数
        btn5 = QPushButton("退出程序", self)
        btn5.clicked.connect(self.close)	#将按钮与关闭事件相连,这个关闭事件是重写的,它自带一个关闭函数,这里重写为点击关闭之后会弹窗提示是否需要关闭
        btn6 = QPushButton("检测", self)
        btn6.clicked.connect(self.show_child) #这里将联系弹出子界面函数,具体弹出方式在函数里说明

        combol1 = QComboBox(myframe)   #定义为一个下拉框,括号里为这个下拉框从属的骨架(框架)
        combol1.addItem("                      选择模型")	#添加下拉选项的文本表示,这里因为没有找到文字对齐方式,所以采用直接打空格,网上说文字对齐需要重写展示函数
        combol1.addItem("                     YOLOv3")
        combol1.addItem("                     YOLOv4")
        combol1.activated[str].connect(self.TModelSelect)	#|--将选择好的模型序号存到模型选择数组里
        													#|--后面的训练函数会根据这个数组判断需要训练哪个模型
															#|--[str]表示会将下拉框里的文字随着选择信号传过去
															#|--activated表示该选项可以被选中并传递信号
        vlo = QVBoxLayout()  #创建一个垂直布局,需要将需要垂直布局的组件添加进去
        vlo.addWidget(combol1) 	#添加相关组件到垂直布局里
        vlo.addWidget(btn3)
        vlo.addWidget(btn2)
        vlo.addWidget(btn6)
        vlo.addWidget(btn5)
        vlo.addStretch(1)	#一个伸缩函数,可以一定程度上防止界面放大之后排版不协调
        hlo = QVBoxLayout(self)	#创建整体框架布局,即主界面的布局
        hlo.addLayout(vlo)	#将按钮布局添加到主界面的布局之中
        hlo.addWidget(myframe)	#将框架也加入到总体布局中,当然也可以不需要这框架,直接按照整体框架布局来排版
        #之所以这里有这个myframe,是因为尝试过很多种布局,其中一个布局就是将其他组件都放到这个myframe中,移动这个myframe
        #其里面的组件布局相对位置不会改变,后面又尝试了多种布局,所以这个myframe最后里面其实就剩下一个下拉框
        self.show()   #显示主界面

(3)主界面的功能函数(槽函数)

  1. 选择数据上传,其本质是打开一个文件夹,然后将相关照片按照规定排列好。这里采用的是绝对路径,按理来说相对路径较好,但是没有找到具体实现方法,一般的相对路径方法打不开对应的文件夹,所以暂时选择用这个。
 def DataExplorerSelect(self):
        path = r'D:\pycharm\QTYOLOV3\yolov3\VOCdevkit\VOC2007'
        os.system("explorer.exe %s" % path)
  1. 打开子界面函数
    def show_child(self):
        TModel1 = self.TModel					#|--这是子界面的类内函数
        self.child_window.GetTModel(TModel1)	#|--将训练好的模型序号传到子界面的类内参数里面
        self.child_window.show()		#|--子界面相当于主界面的一个类内成员
        								#|--但是本质还是一个界面类,也有show函数将其展示
  1. 选择需要训练的模型序号
    如果这里报错,有可能是下拉框中文本信息与这里的判断文本信息不同。
    def TModelSelect(self, s):		#s是形参,表示传回来的选中的选项的文字
        if s == '                     YOLOv3':
            self.TModelSelectSignal[0] = 1		#如果选中的是YOLOv3-COC就将第一位置1
            # print(self.TModelSelectSignal[0])
        elif s == '                     YOLOv4':
            self.TModelSelectSignal[1] = 1		#如果选中的是YOLO-Efficientnet就将第二位置1
            # print(self.TModelSelectSignal[1])
  1. 训练函数。因为这里只导入一个训练函数,所以只有一个判别选项,训练完之后会将self.TModelSelectSignal的对应位置零以便下一次可以继续训练。
    def TestModel(self):
        if self.TModelSelectSignal[0] == 1:
            train.run()
            self.TModelSelectSignal[0] = 0
        else:
            print("没有该模型")
  1. 关闭函数。这里是将其重写,多了一个关闭时会有弹窗出现的功能。前一个文本参数时弹出框的名字,后一个文本参数是显示在窗口的文本。
   def closeEvent(self, event):
        result = QMessageBox.question(self, "提示:", "您真的要退出程序吗", QMessageBox.Yes|QMessageBox.No, QMessageBox.Yes)
        if result == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

3.定义子界面

(1)子函数

同样需要初始化并继承父类。

class ChildClass(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.TModel = []	#用来接收主界面的训练好的模型的序号
        self.openfile_name_image = ''	#存储原始图像的地址
        self.result_name_image = ''		#存储检测好的图像的地址

(2)子界面布局

    def initUI(self):
        self.resize(1100, 450)	#缩放界面大小
        self.setWindowTitle("目标检测")	#设置界面标题
        # self.setWindowIcon(QIcon("iconimg/zhou.png"))		#设置界面图标
        self.PModelSelectSignal = [0, 0]	#设置需要预测模型的序号,在下拉框里选择

        myframe = QFrame(self)
        self.label1 = QLabel("检测模型", self)
        combol1 = QComboBox(myframe)
        combol1.addItem("选择检测模型")
        combol1.addItem("YOLOV3")
        combol1.addItem("YOLOV4")
        combol1.activated[str].connect(self.PModelSelect)	#链接预测模型序号选择函数
        btn1 = QPushButton("选择检测图片", self)
        btn1.clicked.connect(self.select_image)		#链接检测图片选择函数,本质是打开一个文件夹

        btn2 = QPushButton("开始检测", self)
        btn2.clicked.connect(self.PredictModel)		#链接预测模型函数

        self.label2 = QLabel("", self) 			#创建一个label,可以存放文字或者图片,在这里是用来存放图片,文本参数为空就会显示为空,留出空白区域,选择好图片时会有函数展示图片
        self.label2.resize(400, 400)
        self.label3 = QLabel("", self)
        self.label3.resize(400, 400)
        label4 = QLabel("                                         原始图片", self)		#用来放在图片底部表示这是哪一种图片
        label5 = QLabel("                                         检测图片", self)
        vlo2 = QHBoxLayout()		#创建一个子布局,将图片水平排放
        vlo2.addWidget(label4)
        vlo2.addWidget(label5)

        vlo = QHBoxLayout()		#创建一个子布局,将按钮水平排放
        vlo.addStretch()
        vlo.addWidget(self.label1)
        vlo.addWidget(combol1)
        vlo.addWidget(btn1)
        vlo.addWidget(btn2)
        vlo.addStretch(1)

        vlo1 = QHBoxLayout()	#创建一个水平布局,将两个提示标签竖直排放
        vlo1.addWidget(self.label2)
        vlo1.addWidget(self.label3)

        hlo = QVBoxLayout(self)		#创建一个总的垂直布局,将三个子布局垂直排放
        hlo.addLayout(vlo)
        hlo.addLayout(vlo1)
        hlo.addStretch(1)
        hlo.addLayout(vlo2)
        hlo.addStretch(0)
        hlo.addWidget(myframe)

(3)子界面功能函数(槽函数)

  1. 一个赋值函数,在外部调用给类内成员赋值
    def GetTModel(self, a):
        self.TModel = a
  1. 关闭事件,和主界面一样是重写之后的。
    def closeEvent(self, event):
        result = QMessageBox.question(self, "提示:", "您真的要退出程序吗", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if result == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()
    def select_image(self):
        self.openfile_name_image, _ = QFileDialog.getOpenFileName(self, "选择照片文件",
                                                             r"./yolov3/imgtest/")
      	#弹出一个对话窗,是一个文件夹,可以选择一个文件然后返回地址到 self.openfile_name_image中
        print('加载照片文件地址为:' + str(self.openfile_name_image))
        self.label2.setPixmap(QPixmap(str(self.openfile_name_image))) #将选中的文件名字传入QPixmap()中,括号内为文件地址,就会读取这个图片
        self.label2.resize(300, 400)
        self.label2.setScaledContents(True)	#表示这个label可以可以自适应窗口大小,可以让图片随窗口大小而变化
  1. 选择需要预测的模型,s是形参,传回的是下拉框中的文本信息。
   def PModelSelect(self, s):
        if s == 'YOLOV3':
            if self.TModel[0] == 1:
                self.PModelSelectSignal[0] = 1
                self.PModelSelectSignal[1] = 0
                print(self.PModelSelectSignal[0])
            else:
                print("模型YOLOV3未训练")	##如果已经训练好的模型数组里对应的位置为0,则表示该模型未训练
                self.PModelSelectSignal[1] = 0		#同时也要讲模型选择信号清零,以便下次可以继续选择赋值
        elif s == 'YOLOV4':
            if self.TModel[1] == 1:
                self.PModelSelectSignal[1] = 1
                self.PModelSelectSignal[0] = 0
                print(self.PModelSelectSignal[1])
            else:
                print("模型YOLOV4未训练")
                self.PModelSelectSignal[0] = 0
  1. 图像预测。由于只导入了一个模型,所以只有一个判别程序,我写的预测函数是可以读取文件路径的图片,所以我只需将需要预测的图片的路径传入预测函数,就会将预测好的图片保存在指定文件夹,然后后面用程序将其读出展示在界面里。
   def PredictModel(self):
        if self.PModelSelectSignal[0] == 1:
            predict.predict(self.openfile_name_image)	#将需要预测的图片传入导入的预测函数
        elif self.PModelSelectSignal[1] == 1:
            print('YOLOV4正在检测')  #这里应该放入另外一个模型
        else:
            print('没有该模型')
        a = self.openfile_name_image
        a = a.split('/')	#将预测图片里的编号分离出来
        a = './yolov3/imgtestresult/' + a[-1]	#将指定路径与图片编号组合,即可得到预测好的图片的路径
        self.label3.setPixmap(QPixmap(a))	#直接读取预测好的图片
        self.label3.resize(300, 400)
        self.label3.setScaledContents(True)
        print(a)

界面展示

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

一些尚未解决的问题

由于保存图片路径变量覆盖的问题,会导致选择第二张检测图片之后,检测图片的结果仍然展示的是第一张,可以在选择检测图片的函数里加上每当选择一张新的图片,即可清除上一张图片。
另外一个问题就是无法连续两次检测,具体原因还没有查明,可能是用的同一个保存图片文件的变量,最后并没有传入预测函数。
另外一个还没有实现的功能就是实时展示训练进程,在原始训练函数里,是会实时打印出训练进程,所以应该可以做到读取训练函数里的打印的文本,然后传递到界面类里的一个函数,然后展示在界面里。

猜你喜欢

转载自blog.csdn.net/qq_43180908/article/details/114639985