【YOLO5 项目实战】(8)PyQt5 图形界面—PCB缺陷检测系统

news2024/11/14 20:55:03

欢迎关注『youcans动手学模型』系列
本专栏内容和资源同步到 GitHub/youcans


【YOLO5 项目实战】(8)PyQt5 图形界面—PCB缺陷检测系统

    • 1. PyQt5 图形界面开发工具
      • 1.1 PyQt5 的安装
      • 1.2 在 PyCharm 集成 QtDesigner 和 PyUIC
      • 1.3 使用 QtDesigner 开发 PyQt5 图形界面
    • 2. PCB 缺陷检测系统的图形界面设计
      • 2.1 PCB 缺陷检测系统需求分析
      • 2.2 创建图形窗口(MainWindow)
      • 2.3 创建菜单对象(menu)
      • 2.4 布局管理(Label)
      • 2.5 创建内容显示控件(Label)
      • 2.6 创建按钮控件(PushButton)
    • 3. GUI 主程序开发
      • 3.1 从零开始编写主程序
      • 3.2 信号与槽的连接
      • 3.3 导入图像文件子程序
      • 3.4 保存图像文件子程序
      • 3.5 YOLOv5 图像检测子程序
      • 3.6 OpenCV 图像文件转换为 QImage 子程序
      • 3.7 帮助菜单子程序
    • 4. 调用YOLO5 模型进行目标检测
      • 4.1 安装 ultralytics 模块
      • 4.2 ultralytics 的导入和初始化
      • 4.3 使用 YOLO 模型进行检测
    • 5. PCB缺陷检测系统的使用

本节以基于 YOLOv5 的PCB 缺陷检测任务为例,详细介绍使用 Pyqt5 设计 YOLOv5 图形用户界面,实现人机交互的 YOLOv5 PCB 缺陷检测系统。

关于YOLOv5 的PCB 缺陷检测前文已有介绍,如需了解如何使用 PCB 缺陷数据集训练模型,可以阅读我的博客 【YOLO5 项目实战】(3)PCB 缺陷检测 。


1. PyQt5 图形界面开发工具

1.1 PyQt5 的安装

Qt 库是跨平台的 C++ 库的集合,是最强大的 GUI 库之一,可以实现高级 API 来访问桌面和移动系统的各种服务。

安装了 PyQt5 就可以用 Python 语言编写 Qt 程序。

pip install pyqt5 -i https://mirrors.aliuyun.com/pypi/simple

虽然安装 PyQt5 就可以编程实现 GUI,但是学习、编程、调试、修改都是相当复杂和繁琐的。而 Qt Designer 基本是通过人机交互的排版方式进行界面设计,非常方便、直观。所以我在使用 Qt Designer 图形界面设计工具之后,就再也不愿意编写 Python 程序来实现 GUI 了。

Qt Tools 包含了两个重要的工具:

  • 图形界面设计工具 Qt Designer,用于设计图形界面,生成 .ui文件,以 xml 格式存储界面和控件的属性;
  • UI 文件转换工具 PyUic,用于将 .ui 文件解析为 .py 文件的工具。

Qt Tools 工具可以直接使用 pip 方式安装:

pip install pyqt5-tools

或:

pip install pyqt5-tools -i https://pypi.douban.com/simple/


1.2 在 PyCharm 集成 QtDesigner 和 PyUIC

使用 PyCharm 集成开发工具的小白,在安装 QtTools 库以后,还要对 QtDesigner 和 PyUIC 进行环境配置,将其集成到 PyCharm 中。

  • 运行 PyCharm;
  • 从顶部菜单栏选择:File -> Settings,弹出 Seetings 窗口;
  • 从左侧菜单栏中选择:Tools -> ExternalTools,在右侧点击 “+” 弹出 CreateTool 窗口;

(1)添加 QtDesigner 工具
在这里插入图片描述

在 CreateTool 窗口依次填写:

  • Name:填写 “QtDesigner”
  • Program:填写 designer.exe 的路径,例如:

C:\Python\Anaconda3\Lib\site-packages\qt5_applications\Qt\bin\designer.exe

注意:此处填写刚才 pip 安装的 pyqt5-tools 工具包的路径。如果小白的 Python 或 Anaconda3 安装在其他路径下,则从对应的目录找到 “qt5_applications\Qt\bin\designer.exe”,或者在资源管理器中搜索 “designer.exe” 文件找到安装路径。

  • Arguments:不用填写
  • Working directory:填写生成 UI 文件的保存路径
    例如,要将 .ui 文件保存在当前 Project 的路径下,则填写 “$ProjectFileDir$”;要将 .ui 文件保存在当前 Project 路径下的 \program 子目录中,则填写 “$ProjectFileDir$\program”。

填好 CreateTool 窗口后,点击 “OK” 即可完成 QtDesigner 工具的添加。


(2)添加 PyUIC 工具
在这里插入图片描述

在 CreateTool 窗口依次填写:

  • Name:填写 “PyUIC”
  • Program:填写 python.exe 的路径,例如:

C:\Python\Anaconda3\python.exe

注意:此处填写 IDE 使用的 Python Interpreter的路径。如果小白的 Python 或 Anaconda3 安装在其他路径下,或者选择其它路径中的 python.exe 作为 Python Interpreter,可以从 Pycharm -> Settings -> Project -> Python Interpreter 打开配置窗口,从右侧上方 “Python Interpreter:” 选项框找到 python.exe 的路径。

  • Arguments:填写"-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py”
  • Working directory:填写将 .ui 文件转换为 .py 文件的保存路径
    例如,要将 .py 文件保存在当前 Project 的路径下,则填写 “$ProjectFileDir$”;要将 .py 文件保存在当前 Project 路径下的 \program 子目录中,则填写 “$ProjectFileDir$\program”。

填好 CreateTool 窗口后,点击 “OK” 即可完成 QtDesigner 工具的添加。


1.3 使用 QtDesigner 开发 PyQt5 图形界面

使用 QtDesigner 开发 PyQt5 图形界面的基本步骤是:
(1)使用图形界面设计工具 QtDesigner 进行图形界面设计,生成 .ui 文件;
(2)使用 UI 转换工具 PyUIC 将 .ui 文件转换为 .py 文件;
(3)编写一个 Python 应用程序调用 .py 界面文件,就可以实现 Python 平台的 GUI 编程。

具体测试方法可以参见我的博客 【OpenCV-PyQT项目实战(1)安装与环境配置】


2. PCB 缺陷检测系统的图形界面设计

2.1 PCB 缺陷检测系统需求分析

  1. 导入待检测文件(图像,视频,摄像头)
  2. 显示检测图像(待检测图像,检测结果图像)
  3. 显示检测结果(图像检测结果,目标检测数据)
  4. 保存检测结果
  5. 运行控制(检测,保存,帮助)
  6. 帮助
  7. 关闭

2.2 创建图形窗口(MainWindow)

  1. 运行 PyCharm,打开建立的 Python Project,例如 Project 为 YOLOv5UI_PCB。
  2. 从顶部菜单栏选择:Tools -> ExternalTools -> QtDesigner,打开 QtDesigner。
  3. 在 “新建窗体” 窗口的左侧菜单选择 “MainWindow” 新建一个图形窗口。
  4. 在左侧上方 “对象查看器” 窗口选中 “MainWindow” 对象,在左侧中部 “属性编辑器” 编辑:
    (1)将 “宽度” 修改为 1080, “高度” 修改为 720,也可以直接使用鼠标拉伸来调整 “MainWindow” 大小。
    (2)将 “WindowTitle” 属性修改为:YOLOv5目标检测。
    (3)将 “WindowIcon” 属性修改为:用户选择的图标,例如 icons\youcans.png。

2.3 创建菜单对象(menu)

QtDesigner 创建的 “MainWindow” 图形窗口,自动生成了顶部菜单栏 menubar,在图形窗口左上角显示有文本输入框 “在这里输入”。

输入菜单对象(menu)的标题(title):

  • 鼠标点击文本输入框 “在这里输入”,选中文本输入框,控件的边框变为紫色;
  • 再双击选中的控件,出现激活的文本输入框,就可以输入所要建立菜单的标题;
  • 输入菜单标题后回车结束,就建立了一个一级菜单,例如:将菜单标题设为 “文件”。

输入菜单对象的标题,以及修改菜单对象的属性,更通用的方法是:

  • 在 QtDesigner 右侧的 “对象查看器” 中选中对象 “menu”,此时右侧中部的 “属性编辑器” 将显示对象 “menu” 的属性。
  • 在 “属性编辑器” 内选择 “title” 属性,将其修改为菜单标题:“文件”。

建立一级菜单 “文件” 后,菜单栏中在 “文件” 右侧又出现新的文本输入框 “在这里输入” ,按照以上操作可以接着建立更多的菜单对象。

QtDesigner 创建二级菜单,实际上是将动作(action)添加到一级菜单。


2.4 布局管理(Label)

布局管理就是管理图形窗口中各个部件的位置和排列。图形窗口中的大量部件也需要通过布局管理,对部件进行整理分组、排列定位,才能使界面整齐有序、美观大方。

在 QtDesigner 左侧工具栏中,第一组工具 “Layout” 就是布局管理器,分别提供了水平布局、垂直布局、栅格布局和表单布局 4种布局管理的工具按钮。

容器布局将容器控件(Container)与布局管理器结合,先用容器控件将窗口分为若干区域,再在每个区域内加入布局管理器。建立容器控件后,可以直接将其它控件加入容器控件内;也可以在容器控件加入布局管理器,再向布局管理器加入多个控件,使多个控件按布局要求放在容器中。

首先在 QtDesigner 左侧工具栏的 “Containers” 类中,选择 “Frame” 控件或 “Widget” 控件将其拖动至中间的图形窗口中,创建容器控件。
对图形窗口中的容器控件 “Frame” 或 “Widget”,可以选中后用鼠标拖动、拉伸来调整控件的位置和大小,或者在 “属性编辑器” 中设置 (X, Y)、宽度、高度属性。
对于需要进行布局管理的容器控件,从在 QtDesigner 左侧工具栏的 “Laytout” 类中选择所需的布局管理器控件,将其拖动至容器控件中,创建容器控件的布局管理器。
容器布局就像网站、报刊中的栏目、子版,可以按照编辑的要求便捷、自由地进行布局。例如,我们要将程序窗口按照十字分割方案分为上下和左右四个部分,就在窗口先创建四个 “Frame” 容器控件,并调整其位置和大小,然后向一个或几个 “Frame” 容器控件加入所需的布局管理控件。如下图所示,可以实现对程序窗口的自由分割和布局。

在这里插入图片描述


2.5 创建内容显示控件(Label)

在 QtDesigner 左侧的 “WidgetBox” 工具栏中,将常用的控件按类别进行分组。用鼠标将工具栏中的控件图标拖拽到 QtDesigner 中间的图形界面编辑窗口,就在图像界面创建了一个所选择的控件。

内容显示控件QLabel(显示框)是一个只读显示的简易控件,用于显示不可编辑的文本或图像,不提供用户交互功能。

  • 从左侧控件栏的 “DisplayWidget” 中选择 Label 控件,移动鼠标将 Label 控件拖动到新建图形窗口内的任意位置,就在图形窗口位置生成了一个 Label 控件对象。
  • 鼠标左键选中图形窗口中的这个 Label 控件对象,拖动鼠标可以调整控件的位置,对于其它控件也可以通过鼠标拖动来调整位置。
  • 对于添加到 QLabel 显示框,可以通过属性编辑器修改属性,例如通过 “Pixmap” 选择显示的图像文件。默认情况下,QLabel 会对 文本和图像 内容 左对齐、垂直居中显示,也可以通过属性修改进行设置。

类似地,建立 Label 控件对象 “label_2”,但没有为其设置显示的图像文件,因此该区域看起来是空白的。


2.6 创建按钮控件(PushButton)

按钮是最常用的控件类型。在 QtDesigner 左侧的 “WidgetBox” 工具栏中的"Buttons" 组,设有多种不同类型的按钮控件。

QPushButton(按键按钮)是最常用的按钮,按下(或者单击)按钮可以执行某个操作或回答问题,例如:确定,应用,取消,关闭,是,否和帮助。

  • 按钮控件通常显示一个文本标签(text),可以为按钮选择一个图标(icon),还可以选择设置快捷键(shortcut)。
  • 按键按钮的上述属性,都可以在 “属性编辑器” 中相应的属性行中进行编辑修改。
  • 当按键按钮被鼠标或快捷键激活时,按钮会发出 clicked() 信号,可以通过连接槽函数来触发特定的操作。

用鼠标将工具栏中的按钮控件拖拽到 QtDesigner 中间的图形界面编辑窗口,就在图像界面创建了一个按钮控件。从左侧控件栏的 “Buttons” 中选择 PushButton 按钮,移动鼠标将 PushButton 按钮拖动到新建图形窗口内的任意位置,就在图形窗口位置生成了一个 PushButton 按钮对象。

  • 鼠标左键选中图形窗口中的这个 PushButton 按钮对象,拖动鼠标可以调整控件的位置。
  • 鼠标选中 PushButton 按钮对象,控件周围的边界位置上就出现 8个蓝色的点,表示控件被选中,这时可以在右侧的 “属性编辑器” 内对对象的属性进行编辑和修改,例如:
    • 将 PushButton 对象的高度修改为 80,宽度修改为 40;
    • 将 PushButton 对象的 “QAbstractButton->text” 修改为 “1 打开”。
      在这里插入图片描述

于是,我们就完成了本项目的图形界面设计,将其保存为 uiYOLO5_2.ui文件。

在 PyCharm中,使用 PyUIC 将选中的 uiYOLO5_2.ui 文件转换为 .py 文件,就得到了 uiYOLO5_2.py 文件。


3. GUI 主程序开发

3.1 从零开始编写主程序

我们编写一个主程序调用设计的图形界面 YOLOv5PCB_Main1.py,就可以完成一个图形界面应用程序。

在 QtDesigner 将设计的图形界面保存为 uiYOLO5_2.ui。在 PyCharm 选中 uiYOLO5_2.ui 文件,点击鼠标右键唤出下拉菜单,选择:ExternalTools -> PyUIC,点击鼠标左键运行 PyUIC 将选中的 .ui 文件转换为 .py 文件,在该路径生成 uiYOLO5_2.py 文件。

面向对象的程序设计使程序的结构更加清晰,从而易于阅读、理解、开发和维护,非常适合开发大规模、多任务的图形界面应用软件。PyQt5 中的类、对象、控件和方法,都是面向对象的程序设计的概念。

我们采用面向对象的程序设计方法,编写第一个例程 YOLOv5_Main1.py 内容如下。其中,导入模块 uiYOLO5_2 是刚才建立的 uiYOLO5_2.py 文件,要注意输入正确路径和文件名。

# -*- coding: utf-8 -*-
# YOLOv5_Main1.py
# GUI of YOLOv5
# Copyright 2024 Youcans, Xidian
# Crated:2024-08-28

import sys, time
from PyQt5.QtWidgets import QApplication, QMainWindow
from uiYOLO5_2 import Ui_MainWindow  # 导入 uiYOLO5_2.py 中的 Ui_MainWindow 界面类

class MyMainWindow(QMainWindow):  # 继承 QMainWindow类
    def __init__(self, parent=None):
        super(QMainWindow, self).__init__(parent)  # 初始化父类
        self.ui = Ui_MainWindow()  # 继承 Ui_MainWindow 界面类
        self.ui.setupUi(self)  # 继承 Ui_MainWindow 界面类
        # self.initGUI()
        # self.initMain()

if __name__ == '__main__':
    app = QApplication(sys.argv)  # 在 QApplication 方法中使用,创建应用程序对象
    myWin = MyMainWindow()  # 实例化 MyMainWindow 类,创建主窗口
    myWin.show()  # 在桌面显示控件 myWin
    sys.exit(app.exec_())  # 结束进程,退出程序

在这个例程中,我们创建了一个类 MyMainWindow(),它继承了 QtWidgets.QMainWindow 类方法和导入的 uiDemo2.py 中的 Ui_MainWindow 界面类。

编译运行这个程序,生成如下图像界面,与我们在 QtDesigner 设计的 GUI 是一致的。但我们还没有对其中的对象进行定义和关联,所以点击按键并不会动作。

在这里插入图片描述


3.2 信号与槽的连接

在上节的例程 YOLOv5_Main1.py 中,我们使用 QtDesigner 设计了一个简单的图像界面 ,包括菜单、工具栏、图像和按钮。但是,这些按钮和菜单项还是无效的,点击后不会调用相应的处理函数,这是因为我们还没有为这些控件关联动作程序(连接槽函数)。

信号与槽机制是 PyQt 的核心机制,用于对象之间的通信,也就是实现函数之间的自动调用。关于信号与槽的具体内容,请参考我的博客【OpenCV-PyQT项目实战(3)信号与槽机制】。

在 initGUI(self) 子程序中,定义信号与槽的连接。将自定义的槽函数 openImage()、openVideo()、runSlot() 等添加到主程序中,编写第二个例程 YOLOv5_Main2.py 内容如下:

# -*- coding: utf-8 -*-
# YOLOv5_Main2.py
# GUI of YOLOv5
# Copyright 2024 Youcans, Xidian
# Crated:2024-08-28

import sys, time
import numpy as np
import cv2 as cv
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
from uiYOLO5_2 import Ui_MainWindow  # 导入 uiYOLO5_2.py 中的 Ui_MainWindow 界面类

class MyMainWindow(QMainWindow):  # 继承 QMainWindow类
    def __init__(self, parent=None):
        super(QMainWindow, self).__init__(parent)  # 初始化父类
        self.ui = Ui_MainWindow()  # 继承 Ui_MainWindow 界面类
        self.ui.setupUi(self)  # 继承 Ui_MainWindow 界面类
        self.initGUI()
        self.initMain()

    def initGUI(self):
        # --- GUI 菜单栏定义 ---
        # 菜单栏 动作定义
        self.ui.actionOpen.triggered.connect(self.openImage)  # 连接并执行 openImage 子程序
        self.ui.actionRun.triggered.connect(self.runSlot)  # 连接并执行 runRun 子程序
        self.ui.actionSave.triggered.connect(self.saveSlot)  # 连接并执行 saveSlot 子程序
        self.ui.actionHelp.triggered.connect(self.actHelp)  # 连接并执行 actHelp 子程序
        self.ui.actionQuit.triggered.connect(self.close)  # 连接并执行 退出

        # 通过 connect 建立信号/槽连接
        # 点击按钮事件时,发射triggered信号,执行相应的子程序 click_pushButton
        self.ui.btnPic_1.clicked.connect(self.openImage)  # 点击 btnPic_1/导入图片 触发
        self.ui.btnPic_2.clicked.connect(self.openVideo)  # 点击 btnPic_2/导入视频 触发
        self.ui.btnPic_3.clicked.connect(self.openImage)  # 点击 btnPic_3/开启摄像头 触发
        self.ui.Button_1.clicked.connect(self.runSlot)  # 点击 Button_1/目标检测 触发
        self.ui.Button_2.clicked.connect(self.saveSlot)  # 点击 Button_2/保存结果 触发
        self.ui.Button_3.clicked.connect(self.close)  # 点击 Button_3/退出 触发

        # 通过 connect 建立信号/槽连接
        self.ui.spinBox.valueChanged.connect(self.on_spinBox_valueChanged)

    def initMain(self):
        self.mode = "image"
        self.org_path = None
        self.draw_path = None
        self.org_img = np.empty((1,1,3))
        self.numTarget = 0
        self.is_camera_open = False

        # 初始化
        self.ui.spinBox.setRange(0, 0)  # 初始化 目标编号的可选范围
        self.ui.spinBox.setValue(0)  # 初始化 spinbox 的初值为 0
        self.org_img = tools.img_cvread("PCB00.png")  # OpenCV 读取图像文件
        # self.ui.label.setPixmap(self.cvToQImage(self.org_img))  # 刷新显示图片

    def openImage(self):  # 读取图像文件,动作 actionOpen 触发
        return

    def openVideo(self):  # 读取视频文件, 点击 Button_2 触发
        return

    def runSlot(self):  # 动作 actionRun 触发
        return

    def saveSlot(self):  # 动作 actionSave 触发
        return

    def on_spinBox_valueChanged(self):  # 点击目标编号, 切换显示第 index 个目标的数据
        return

    def actHelp(self):  # 帮助子程序, 动作 actHelp 触发
        QMessageBox.about(self, "About",
                          """YOLO5 目标检测系统 v1.0\nCopyright YouCans, IMIU Xidian 2024""")
        return

if __name__ == '__main__':
    app = QApplication(sys.argv)  # 在 QApplication 方法中使用,创建应用程序对象
    myWin = MyMainWindow()  # 实例化 MyMainWindow 类,创建主窗口
    myWin.show()  # 在桌面显示控件 myWin
    sys.exit(app.exec_())  # 结束进程,退出程序

编译运行这个程序,生成图像界面与例程 YOLOv5_Main1.py相同,但现在按键可以点击,并执行相应的子程序。例如点击菜单中的“帮助”可以弹出帮助窗口,点击“退出”按键会退出程序。但是,点击其它按钮和菜单项还是没有执行相应的内容,这是因为我们的任务处理子程序(槽函数)还是空的。


3.3 导入图像文件子程序

从菜单选择“打开”,或点击按键“导入图片”,进入导入图像文件子程序,读取待处理的图像文件,并刷新图像显示,清空检测数据显示区。
用 QFileDialog.getOpenFileName 函数交互式选择要打开的文件,用 cv.imread函数读取图像文件。

    def openImage(self):  # 读取图像文件,动作 actionOpen 触发
        self.mode = "image"  # 图像文件模式
        self.file_path, _ = QFileDialog.getOpenFileName(None, '打开图片', './', "Image files (*.jpg *.jepg *.png)")
        print(self.file_path)

        self.org_path = self.file_path
        # self.org_img = tools.img_cvread(self.org_path)  # OpenCV 读取图像文件
        self.org_img = cv.imdecode(np.fromfile(self.org_path, dtype=np.uint8), cv.IMREAD_COLOR)
        imgPix = self.cvToQImage(self.org_img)  # OpenCV 转为 PyQt 图像格式
        self.ui.label.setPixmap(imgPix)  # 刷新显示图片
        self.ui.label.setAlignment(Qt.AlignCenter)
        self.ui.lineEdit_1.setText(self.file_path)  # 显示导入文件路径
        self.targetUpdate(0)  # 清空检测数据显示区
        return

3.4 保存图像文件子程序

用 cv.imwrite函数写入检测结果图像文件。

    def saveSlot(self):  # 动作 actionSave 触发
        if not self.org_path:  # 不存在需要检测的图像
            print("Org_img not existed.")
            return
        if not self.numTarget:  # 不存在需要保存的图像
            print("Draw_img not existed.")
            return

        save_name = (self.org_path.split("/")[-1]).split(".")[0] + "_out.png"
        print("save_name", save_name)
        self.save_path = "..\\runs\\detect\\" + save_name

        if self.mode == "image":
            cv.imwrite(self.save_path, self.draw_img)
            print("{} saved:".format(self.save_path))

        return

3.5 YOLOv5 图像检测子程序

    def runSlot(self):  # 动作 actionRun 触发
        print(self.org_img.size)
        print("Detect image: ", self.org_path)

        # 调用 YOLOv5 预训练模型检测图片
        t1 = time.time()
        img_path = self.org_path  # 需要检测的图片地址
        self.results = self.model(img_path)[0]  # YOLO 检测结果
        t2 = time.time()
        print("Detect time: {:.3f} s".format(t2 - t1))

        # 显示检测结果图片
        now_img = self.results.plot()  # 检测结果图片, CV格式
        self.draw_img = now_img
        imgPix = self.cvToQImage(now_img)  # OpenCV 转为 PyQt 图像格式
        self.ui.label.setPixmap(imgPix)  # 刷新显示图片
        self.ui.label.setAlignment(Qt.AlignCenter)

        # 目标类别
        cls_list = self.results.boxes.cls.tolist()
        self.numTarget = len(cls_list)  # 目标数量, 检测结果是多目标的数组
        print("number of target:", self.numTarget)  # 目标数量, 1
        self.cls_list = [int(i) for i in cls_list]
        print("cls_list:",self.cls_list)  # 目标类别, [1]
        # 目标位置 [xmin, xmax, ymin, ymax]
        location_list = self.results.boxes.xyxy.tolist()
        self.location_list = [list(map(int, e)) for e in location_list]
        print("location_list:",self.location_list)  # [[599, 1044, 642, 1080]]
        # 置信度
        self.conf_list = self.results.boxes.conf.tolist()
        self.conf_list = ['%.2f %%' % (each*100) for each in self.conf_list]
        print("conf_list:", self.conf_list)  # ['53.12 %']

        self.ui.spinBox.setRange(1, self.numTarget)  # 设置目标编号的 可选范围
        self.targetUpdate(1)  # 默认显示第 1 个目标的数据
        return

3.6 OpenCV 图像文件转换为 QImage 子程序

    def cvToQImage(self, image):
        # 8-bits unsigned, NO. OF CHANNELS=1
        if image.dtype == np.uint8:
            channels = 1 if len(image.shape) == 2 else image.shape[2]
        if channels == 3:  # CV_8UC3
            # Create QImage with same dimensions as input Mat
            qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_RGB888)
            return qImg.rgbSwapped()
        elif channels == 1:
            # Create QImage with same dimensions as input Mat
            qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_Indexed8)
            return qImg
        else:
            QtCore.qDebug("ERROR: numpy.ndarray could not be converted to QImage. Channels = %d" % image.shape[2])
            return QImage()

3.7 帮助菜单子程序

    def actHelp(self):  # 帮助子程序, 动作 actHelp 触发
        QMessageBox.about(self, "About",
                          """YOLO5 目标检测系统 v1.0\nCopyright YouCans, IMIU Xidian 2024""")
        return

至此,我们完成了 GUI 程序的开发,可以实现 GUI 中的各种基本功能。


4. 调用YOLO5 模型进行目标检测

在上一章中,我们介绍的其实都是基于 PyQt 的 GUI 相关操作的子程序的实现,例如读入和保存文件,显示图像和数据,与 YOLO5 模型的使用没有直接关系。
本章以 PCB 缺陷检测任务为例,具体介绍如何在这个框架上应用 YOLOv5 进行 PCB 缺陷检测。

4.1 安装 ultralytics 模块

本文使用 ultralytics 模块实现 YOLO 模型和方法。

Ultralytics是一个开源的计算机视觉和深度学习框架,旨在简化训练、评估和部署视觉模型的过程。该框架提供了一系列流行的视觉模型,包括YOLOv5、YOLOv4、YOLOv3、YOLOv3-tiny、YOLOv5-tiny、EfficientDet、PAN、PP-YOLO等,并提供了训练、评估和推理的工具和实用程序。

Ultralytics 提供了多种安装方法,包括 pip、conda 和 Docker。推荐采用 Pip 安装:

# Install the ultralytics package from PyPI
pip install ultralytics

注意要把 ultralytics 安装在激活环境,并在该激活环境建立和运行本项目。

YOLO Python 界面可无缝集成到项目中,从而轻松加载、运行和处理模型输出。 界面的设计以简单易用为宗旨,用户可以在自己的项目中快速实现对象检测、分割和分类。这使得 的 界面成为任何希望将这些功能纳入其 项目的人的宝贵工具。

例如,用户只需几行代码就可以加载模型、训练模型、评估模型在验证集上的性能,甚至将模型导出为ONNX 格式。

from ultralytics import YOLO

# Create a new YOLO model from scratch
model = YOLO("yolov8n.yaml")

# Load a pretrained YOLO model (recommended for training)
model = YOLO("yolov8n.pt")

# Train the model using the 'coco8.yaml' dataset for 3 epochs
results = model.train(data="coco8.yaml", epochs=3)

# Evaluate the model's performance on the validation set
results = model.val()

# Perform object detection on an image using the model
results = model("https://ultralytics.com/images/bus.jpg")

# Export the model to ONNX format
success = model.export(format="onnx")

4.2 ultralytics 的导入和初始化

主程序 YOLOv5_Main*.py 的开头导入所需的模块时,除了前面例程中导入的系统相关和 GUI 相关模块之外,还要导入 ultralytics 模块如下:

import numpy as np
import cv2 as cv
from ultralytics import YOLO

为了快速对输入图像进行目标检测,主程序初始化时加载预训练模型。在

    def initMain(self):

        # 加载预训练模型
        modelPath = '../models/Yolov5sPCB100.pt'  # 加载的预训练模型路径
        self.model = YOLO(modelPath, task='detect')
        self.model(np.zeros((48, 48, 3)))  #预先加载推理模型

其中,modelPath 是训练好的 PCB缺陷检测模型的路径,可以根据该模型文件的路径和文件名进行设置。注意对于 PCB 缺陷检测项目,该模型并不是官方下载的 YOLOv5s.pt 预训练模型,而是在 PCB 缺陷数据集训练的专用模型。


4.3 使用 YOLO 模型进行检测

如前所述,ultralytics 使用 YOLO 模型检测图片的程序非常简单:

# Perform object detection on an image using the model
results = model(“https://ultralytics.com/images/bus.jpg”)

模型检测调用返回一个 Results 对象。

Results 对象的属性如下(属性,类型,描述):

  • orig_img,numpy.ndarray,原始图像,以numpy数组表示。
  • orig_shape,tuple,原始图像的形状,以(height, width)格式表示。
  • boxes,Boxes, optional,包含检测边界框的 Boxes 对象。
  • masks,Masks, optional,包含检测掩码的 Masks 对象。
  • probs,Probs, optional,包含每个类的概率的 Probs 对象,用于分类任务。
  • keypoints,Keypoints, optional,包含每个对象检测关键点的 Keypoints 对象。
  • obb,OBB, optional,包含定向边界框的 OBB 对象。
  • speed,dict,一个字典,包含预处理、推理和后处理的速度(以毫秒为单位,针对每张图像)。
  • names,dict,一个字典,包含类名。
  • path,str,图像文件的路径。

Results 对象的方法如下(方法,返回类型,描述):

  • update(),None,更新 Results 对象的 boxes、masks 和 probs 属性。
  • cpu(),Results,返回一个 Results 对象的副本,所有张量都在CPU内存中。
  • numpy(),Results,返回一个 Results 对象的副本,所有张量都转换为numpy数组。
  • cuda(),Results,返回一个 Results 对象的副本,所有张量都在GPU内存中。
  • to(),Results,返回一个 Results 对象的副本,张量在指定的设备和数据类型上。
  • new(),Results,返回一个新的 Results 对象,具有相同的图像、路径和名称。
  • plot(),numpy.ndarray,绘制检测结果。返回标注图像的numpy数组。
  • show(),None,在屏幕上显示标注结果。
  • save(),None,将标注结果保存到文件。
  • verbose(),str,返回每个任务的日志字符串。
  • save_txt(),None,将预测结果保存到txt文件中。
  • save_crop(),None,将裁剪的预测结果保存到 save_dir/cls/file_name.jpg 中。
  • tojson(),str,将对象转换为JSON格式。

在本项目中,通过 img_path 传递被检测的图片路径,model(img_path)[0] 返回检测结果的 Results 对象。
需要注意的是,不论被检测图片是一幅图像,还是多幅图像或图像序列,返回值都是一个 list 数组,通过 [0] 得到第一幅图像的检测结果。

        # 调用 YOLOv5 预训练模型检测图片
        img_path = self.org_path  # 需要检测的图片地址
        self.results = self.model(img_path)[0]  # YOLO 检测结果

进一步地,
(1)使用 Results 对象的方法 results.plot() 得到检测结果的标注图像(numpy数组格式,与 OpenCV图像格式相同,需要转化为 PyQt 图像格式才能显示).
(2)使用 Results 对象的属性 Boxes 对象,其属性 cls 返回边界框的类别值,属性 xyxy 返回xyxy格式的边界框坐标,属性 conf 返回边界框的置信度。

需要注意的是,检测结果可能包括零个、一个或多个目标,通过 .tolist 转换为 list 数组,以方便对每一个目标进行操作。


        # 显示检测结果图片
        now_img = self.results.plot()  # 检测结果图片, CV格式
        self.draw_img = now_img
        imgPix = self.cvToQImage(now_img)  # OpenCV 转为 PyQt 图像格式

        # 目标类别
        cls_list = self.results.boxes.cls.tolist()
        self.numTarget = len(cls_list)  # 目标数量, 检测结果是多目标的数组
        self.cls_list = [int(i) for i in cls_list]
        # 目标位置 [xmin, xmax, ymin, ymax]
        location_list = self.results.boxes.xyxy.tolist()
        self.location_list = [list(map(int, e)) for e in location_list]
        # 置信度
        self.conf_list = self.results.boxes.conf.tolist()
        self.conf_list = ['%.2f %%' % (each*100) for each in self.conf_list]

如何使用这些检测结果数据,例如显示、保存,是在 GUI 程序中实现,在此不做赘述。


5. PCB缺陷检测系统的使用

运行 PCB缺陷检测系统,主要操作如下:
(1)通过菜单栏或“导入”按键 导入被检测图片,主图显示被检测图片。
(2)通过菜单栏或“检测”按键 调用YOLOv5 模型对图像进行目标检测,主图显示检测后的标注图像,检测结果区显示被检测目标的列表,检测数据区显示第1个检测目标的数据。
(3)在检测数据区选择“目标编号”,可以显示对应的目标的检测数据。
(4)点击保存,可以保存检测结果图像和数据文件。
(5)通过菜单栏或“退出”按键,退出系统。

在这里插入图片描述

说明:
本系统 GUI 预留了导入视频文件或开启摄像头进行检测,但由于本项目内容太多,本文并未涉及这些功能,将在后续文中进行介绍。有兴趣的读者也可以参考我的博客:
OpenCV-PyQT项目实战(9)项目案例04:视频播放
OpenCV-PyQT项目实战(12)项目案例08:多线程视频播放。

【本节完】


版权声明:
欢迎关注『youcans动手学模型』系列
转发请注明原文链接:
【YOLO5 项目实战】(8)PyQt5 图形界面—PCB缺陷检测系统
Copyright 2024 youcans, XUPT
Crated:2024-08-28


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2084602.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EPLAN在安装完成之后,输入文字时出现卡顿和死机的解决办法

EPLAN在安装完成之后,输入文字时出现卡顿和死机的解决办法 EPLAN在安装完成之后,插入文字时有时会卡顿,甚至出现软件卡死,无任何反映的情况,具体的解决办法可参考以下内容: 找到电脑右下角的输入法,右击进入设置, 如下图所示,点击进入常规设置, 如下图所示,向下找…

终于!我找到了开发的得力助手!阿里云天池云原生编程挑战赛参赛攻略

作者:ysevenk_7 参赛准备 我是机缘巧合在 6 月底了解到了天池云原生编程挑战赛,于是乎搜了一下,之前本人对于比赛并没有太多经验,看了大赛介绍之后莫名兴奋,果断拉了队友报名,完成认证、起队名、下载插件…

【STM32】RS485

RS485是常见的串口接口。 大部分图片来源:正点原子HAL库课程 专栏目录:记录自己的嵌入式学习之路-CSDN博客 目录 1 串口、UART、TTL、RS232、RS422、RS485的关系 1.1 串口 1.2 UART、TTL、RS232、RS422、RS485 1.3 常见串口标准的比较 …

Apache Arrow 的列式内存格式

Apache Arrow 的列式存储格式是一种内存数据组织标准,它通过物理布局、Array(数组)、Schema(模式)和 RecordBatch(记录批次)等,优化了大数据的存储与处理。这种格式以列而非行来存储…

更改网络ip地址时出现错误怎么办

在日常的网络使用中,‌有时我们需要更改IP地址以满足特定的网络需求,‌然而,‌在更改IP地址的过程中,‌可能会遇到各种错误,‌导致无法成功更改或网络连接出现问题。‌‌而更改网络IP地址时出现错误是由于多种原因导致…

二、基于Vue3的开发-环境搭建【Visual Studio Code】扩展组件

Visual Studio Code中的扩展组件 1、安装的扩展工具2、说明2.1 、代码规范性检查EsLint2.2 、代码语法高亮提示工具Vue - Official2.3 、阿里的AI代码开发提示工具 TONGYI Lingma 1、安装的扩展工具 2、说明 2.1 、代码规范性检查EsLint Visual Studio Code 中【设置】-setti…

基于元神操作系统编程写硬盘扇区

1. 背景 本文介绍了“调用元神操作系统API向硬盘扇区写数据”的程序实现及测试结果。 2. 方法 (1)调用元神操作系统API读硬盘扇区 本部分内容已在前面的文章中进行介绍,详细内容请参考“编写程序调用元神操作系统的API”。 (…

二叉树 - 二叉树的所有路径

257. 二叉树的所有路径 方法一:递归法: /*** Definition for a binary tree node.* function TreeNode(val, left, right) {* this.val (valundefined ? 0 : val)* this.left (leftundefined ? null : left)* this.right (rightundefi…

水下目标检测(低光照目标检测)方法-发表在Patter Recognition,代码已开源

这里写自定义目录标题 前言动机贡献Overview一些实验结果数据集主要实验结果实验结果展示 总结 前言 Hi,各位读者,好久不见!现在我已经从北大博士毕业,成为一名小青椒啦!工作还是需要宣传的。今天想分享我在水下目标检测的工作&a…

低代码技术:快速构建应用的未来

在当今快速发展的数字化时代,企业和个人对软件应用的需求不断增长。然而,传统的软件开发过程通常复杂且耗时。这使得低代码技术(Low-Code Technology)成为了越来越多人关注的焦点。本文将探讨低代码技术的基本概念、优势以及如何在…

贾湖刻符——汉字起源的重要线索

关注我们 - 数字罗塞塔计划 - 汉字是世界上唯一沿用至今的古老文字系统,其演变历程承载着中华文明的发展和赓续。那么汉字究竟源自何时?是古代神话传说的“昔者仓颉作书,而天雨粟,鬼夜哭”;还是由华夏先民创制的刻划符…

请你学习:前端布局3 - flex

Flexbox布局也叫Flex布局,弹性盒子布局。它的目标是提供一个更有效地布局、对齐方式,并且能够使父元素在子元素的大小未知或动态变化情况下仍然能够分配好子元素之间的间隙。主要思想是使父元素能够调整子元素的宽度、高度、排列方式,从而更好…

AI变现之Midjourney头像定制

前言 Midjourney | 头像定制 1.项目介绍 个性化头像在如今的社交媒体时代变得越来越重要。传统头像照片有时显得普通,而AI绘画头像则能为自己的社交账号增加独特性和吸引力。 通过AI绘画工具制作头像,可以获得一个充满创意和个性的头像,让…

湖南的智榜样网络安全公司开的培训学校参加学习成为网络安全工程师

学习网络安全可以通过以下步骤进行: 获取基础知识:开始学习网络安全之前,建议先获取一些计算机基础知识,包括计算机网络、操作系统、编程语言等方面的知识。这些基础知识将为你理解和学习网络安全提供必要的背景。 学习网络安全基…

数据中台即将消亡,数智基建取而代之?

数据中台即将消亡,数智基建取而代之? 前言数智基建 前言 在当今数字化浪潮汹涌澎湃的时代,企业的发展如同在浩瀚海洋中航行的巨轮,而数据则是推动这艘巨轮前行的强大动力。然而,如何有效地管理和利用数据,…

2024年第十四届APMCM亚太地区大学生数学建模竞赛思路

2024年第十四届亚太地区大学生数学建模竞赛(以下简称“竞赛”)是由中国国际科技促进会物联网工作委员会和北京图象图形学学会联合主办的亚太地区大学生学科类竞赛,竞赛由亚太地区大学生数学建模竞赛组委会负责组织,欢迎各高等院校按照竞赛章程及有关规定…

MySQL:SQL调优的简单实践

记一次简单的SQL优化实践。 一、初始化数据 1.1 初始化数据-课程表 #课程表 create table Course(c_id int primary key,name varchar(10) );#存储过程:增加课程表100条数据DROP PROCEDURE IF EXISTS insert_Course;DELIMITER $CREATE PROCEDURE insert_Course()…

[线程]***多线程带来的风险-线程安全问题

文章目录 一. 什么是线程安全二. 线程不安全一个经典的例子三. 对上述例子的理解四. 出现线程不安全的原因1. 线程在操作系统中是随机调度, 抢占式执行的2. 当前代码中, 多个线程同时修改同一变量3. 线程针对变量的修改操作, 不是"原子"的4. 内存可见性问题, 引起线程…

使用gradle 移除敏感权限

前言 最近要上架Google Play 但是因为有个敏感权限很容易被拒。 想着把权限依赖的库去掉就行了,但是遇到一个恶心的问题。就是这个权限在Android Studio的Merged Manifest 视图中没有,但是在生成的apk中却包含。这样的就不能通过Android Studio来定位权…

一文搞懂 js 原型和原型链

文章目录 一、前言二、原型2.1 概念2.2 获取原型的方法2.2.1 __proto__获取方式2.2.2 通过构造函数prototype 属性获取2.2.2 ES6 class 通过Object.getPrototypeOf()获取类原型 2.3 通过原型实现继承2.4 原型的作用 三、 原型链四、ES6实现继承五、综述 一、前言 原型和原型链…