目标检测YOLOv5-PyQT可视化例程开发

Posted zstar-_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了目标检测YOLOv5-PyQT可视化例程开发相关的知识,希望对你有一定的参考价值。

前言

花了几天功夫做了一个YOLOv5的PyQT可视化程序,主要针对多幅图片训练、自动标注和检测展示。涉及正在进行的项目,暂时不开源。在开发过程中,踩了不少坑,这里简单做一些记录。

项目使用到的开源代码:
YOLOv5(5.0+6.0):https://github.com/ultralytics/yolov5
自动标注程序:https://github.com/cnyvfang/labelGo-Yolov5AutoLabelImg

效果演示

整体效果演示如下:

交互式目标检测软件演示

遇到的问题和解决方案

ui文件转py

使用QtDesigner设计的ui文件,可以通过PyUIC自动生成对应的py文件。


生成的文件仅包含窗体对象,浏览时,可以添加下方的执行程序:

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    Window = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(Window)
    Window.show()
    sys.exit(app.exec_())

pyinstaller多线程崩溃重启

在开发完之后,用pyinstaller打包应用程序,调用子线程训练时,卡在一半不动,然后程序崩溃自动重启。
查阅相关资料,在windows上Pyinstaller打包多进程程序需要添加代码使其支持多线程操作,添加代码如下:

import multiprocessing

if __name__ == "__main__":
    multiprocessing.freeze_support()  # 支持pyinstaller,防止打包闪退

pyqt样式美化

pyqt样式美化有很多种开源css文件,本次开发中尝试了三种方案。

qdarkstyle

安装qdarkstyle:

pip install qdarkstyle

qdarkstyle包括了深色和浅色两种主题,使用方式和效果如下:

深色主题:

import qdarkstyle

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) # Qdark深色样例
    Window = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(Window)
    Window.show()
    sys.exit(app.exec_())

浅色主题

from qdarkstyle.light.palette import LightPalette

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
	app.setStyleSheet(qdarkstyle.load_stylesheet(qt_api='pyqt5', palette=LightPalette()))  # Qdark浅色样例
    Window = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(Window)
    Window.show()
    sys.exit(app.exec_())

QCandyUi

QCandyUi安装:

pip install QCandyUi

QCandyUi使用:

from qt_material import apply_stylesheet

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    apply_stylesheet(app, theme='light_teal.xml')
    Window = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(Window)
    Window.show()
    sys.exit(app.exec_())

这里使用了light_teal.xml这个主题,还有其它主题可供选择:

'dark_amber.xml',
'dark_blue.xml',
'dark_cyan.xml',
'dark_lightgreen.xml',
'dark_pink.xml',
'dark_purple.xml',
'dark_red.xml',
'dark_teal.xml',
'dark_yellow.xml',
'light_amber.xml',
'light_blue.xml',
'light_cyan.xml',
'light_cyan_500.xml',
'light_lightgreen.xml',
'light_pink.xml',
'light_purple.xml',
'light_red.xml',
'light_teal.xml',
'light_yellow.xml'

这些主题都比较简陋,基本上是换个颜色而已

飞扬青云-QSS

下面三套QSS样式取自知乎刘典武
为了方便下载,我上传到了资源中:https://download.csdn.net/download/qq1198768105/86775044

总共有三套样式,对应黑色、白色、蓝色,本项目使用的是lightblue这套样式。

使用方式:

if __name__ == "__main__":
    multiprocessing.freeze_support()  # 支持pyinstaller,防止打包闪退
    app = QtWidgets.QApplication(sys.argv)
    styleFile = 'ui/lightblue.css'
    with open(styleFile, 'r') as f:
        qssStyle = f.read()
    app.setStyleSheet(qssStyle)
    Window = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi()
    ui.show()
    sys.exit(app.exec_())

pyqt基础操作

下面是项目中反复使用的一些基础函数和命令。

设置应用图标

app.setWindowIcon(QIcon('ui/icon.png'))

设置按钮有效状态

self.pushButton.setEnabled(True)

固定窗口尺寸

self.setFixedSize(self.width(), self.height())

设置窗口背景渐变填充

self.setStyleSheet('''
    #mainWindowbackground-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0 rgb(255,255,255),stop:.99 rgb(151, 201, 242));
''')

设置空间背景透明

fill_bg = '''
    QWidget 
        background: transparent;
    
'''
self.scroll.setStyleSheet(fill_bg)

打开子窗口

def open_train_window(self):
    self.train_window = train_window.train_Window()
    self.train_window.show()

打开文件夹

def open_output(self):
    path = os.getcwd() + '\\\\' + 'outputs'
    os.system(f"start explorer path")

子线程传参

子线程通过信号与槽函数机制来传递参数

self.thread = DetectionThread(self)
self.thread.start()
self.thread.progressBarValue.connect(self.callback)

def callback(self, i):
    self.progressBar.setValue(i)

class DetectionThread(QThread):
    progressBarValue = pyqtSignal(int) 
    self.progressBarValue.emit(100)

获取文本框输入信息

self.lineEdit.textChanged[str].connect(self.get_epoch)

def get_epoch(self, epoch):
    self.epoch = epoch

子目录添加路径

调用子目录中的程序,在import之前需添加根路径

sys.path.append("yolov5")

获取当前根路径

Root = os.path.split(os.path.abspath(__file__))[0]

缩略图列表显示

本项目开发之中,遇到的一个重点问题是在ListView中动态添加缩略图,其中分解成两个问题,一个是ListView的使用,另一个是动态缩略图的添加。

QScrollArea

ListView在pyqt中有个对应的控件是QScrollArea,找到了一个使用例程:
参考自:https://blog.csdn.net/Yibaomeimei/article/details/124694955

import sys
from ui_test import *
from PyQt5.QtWidgets import *
import random


class test_ui(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        #生成随机的列表作为我们要显示的输出结果
        self.results = [[random.randint(1, 4) for j in range(1, 5)] for i in range(1, 15)]
        self.topFiller = QWidget()
        self.topFiller.setMinimumSize(500, 1000)
        for i in range(len(self.results)):
            label = QLabel(self.topFiller)
            label.resize(420, 30)
            label.setText(str(":" "<13".format(self.results[i][0]) + (":" "<15".format(self.results[i][1]))
                              + ":" "<13".format(self.results[i][2])
                              + ":" "<13".format(self.results[i][3])))
            label.move(10, 30 * i)
        self.vbox = QVBoxLayout()
        self.scroll = QScrollArea()
        self.scroll.setWidget(self.topFiller)
        self.vbox.addWidget(self.scroll)
        self.frame.setLayout(self.vbox)



if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainwindow = test_ui()
    mainwindow.show()
    sys.exit(app.exec_())

缩略图加载显示

缩略图加载显示找到了一个例程:
参考自:https://blog.csdn.net/weixin_42512684/article/details/103414691

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import os
import sys


class img_viewed(QWidget):
    def __init__(self,parent =None):
        super(img_viewed,self).__init__(parent)
        self.parent = parent
        self.width = 960
        self.height = 500

        self.scroll_ares_images = QScrollArea(self)
        self.scroll_ares_images.setWidgetResizable(True)

        self.scrollAreaWidgetContents = QWidget(self)
        self.scrollAreaWidgetContents.setObjectName('scrollAreaWidgetContends')

        # 进行网络布局
        self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
        self.scroll_ares_images.setWidget(self.scrollAreaWidgetContents)

        self.scroll_ares_images.setGeometry(20, 50, self.width, self.height)
        self.vertocal1 = QVBoxLayout()


        # self.meanbar = QMenu(self)
        # self.meanbar.addMenu('&菜单')
        # self.openAct = self.meanbar.addAction('&Open',self.open)
        # self.startAct =self.meanbar.addAction('&start',self.start_img_viewer)
        self.open_file_pushbutton =QPushButton(self)
        self.open_file_pushbutton.setGeometry(150,10,100,30)
        self.open_file_pushbutton.setObjectName('open_pushbutton')
        self.open_file_pushbutton.setText('打开文件夹...')
        self.open_file_pushbutton.clicked.connect(self.open)


        self.start_file_pushbutton = QPushButton(self)
        self.start_file_pushbutton.setGeometry(750, 10, 100, 30)
        self.start_file_pushbutton.setObjectName('start_pushbutton')
        self.start_file_pushbutton.setText('开始')
        self.start_file_pushbutton.clicked.connect(self.start_img_viewer)

        self.vertocal1.addWidget(self.scroll_ares_images)
        self.show()

        #设置图片的预览尺寸;
        self.displayed_image_size = 100
        self.col = 0
        self.row =0
        self.initial_path =None

    def open(self):
        file_path = QFileDialog.getExistingDirectory(self, '选择文文件夹', '/')
        if file_path ==None:
            QMessageBox.information(self,'提示','文件为空,请重新操作')
        else:
            self.initial_path =file_path

    def start_img_viewer(self):
        if self.initial_path:
            file_path = self.initial_path
            print('file_path为'.format(file_path))
            print(file_path)
            img_type = 'jpg'
            if file_path and img_type:

                png_list = list(i for i in os.listdir(file_path) if str(i).endswith('.'.format(img_type)))
                print(png_list)
                num = len(png_list)
                if num !=0:
                    for i in range(num):
                        image_id = str(file_path + '/' + png_list[i])
                        print(image_id)
                        pixmap = QPixmap(image_id)
                        self.addImage(pixmap, image_id)
                        print(pixmap)
                        QApplication.processEvents()
                else:
                    QMessageBox.warning(self,'错误','生成图片文件为空')
                    self.event(exit())
            else:
                QMessageBox.warning(self,'错误','文件为空,请稍后')
        else:

            QMessageBox.warning(self, '错误', '文件为空,请稍后')



    def loc_fil(self,stre):
        print('存放地址为'.format(stre))
        self.initial_path = stre

    def geng_path(self,loc):
        print('路径为,,,,,,'.format(loc))
    def gen_type(self,type):
        print('图片类型为:,,,,'.format(type))


    def addImage(self, pixmap, image_id):
        #图像法列数
        nr_of_columns = self.get_nr_of_image_columns()
        #这个布局内的数量
        nr_of_widgets = self.gridLayout.count()
        self.max_columns =nr_of_columns
        if self.col < self.max_columns:
            self.col =self.col +1
        else:
            self.col =0
            self.row +=1

        print('行数为'.format(self.row))
        print('此时布局内不含有的元素数为'.format(nr_of_widgets))

        print('列数为'.format(self.col))
        clickable_image = QClickableImage(self.displayed_image_size, self.displayed_image_size, pixmap, image_id)
        clickable_image.clicked.connect(self.on_left_clicked)
        clickable_image.rightClicked.connect(self.on_right_clicked)
        self.gridLayout.addWidget(clickable_image, self.row, self.col)


    def on_left_clicked(self,image_id):
        print('left clicked - image id = '+image_id)

    def on_right_clicked(self,image_id):
        print('right clicked - image id = ' + image_id)


    def get_nr_of_image_columns(self):
        #展示图片的区域
        scroll_area_images_width = self.width
        if scroll_area_images_width > self.displayed_image_size:

            pic_of_columns = scroll_area_images_width // self.displayed_image_size  #计算出一行几列;
        else:
            pic_of_columns = 1
        return pic_of_columns

    def setDisplayedImageSize(self,image_size):
        self.displayed_image_size =image_size




class QClickableImage(QWidget):
    image_id =''

    def __init__(self,width =0,height =0,pixmap =None,image_id = ''):
        QWidget.__init__(self)

        self.layout =QVBoxLayout(self)
        self.label1 = QLabel()
        self.label1.setObjectName('label1')
        self.lable2 =QLabel()
        self.lable2.setObjectName('label2')
        self.width =width
        self.height = height
        self.pixmap =pixmap

        if self.width and self.height:
            self.resize(self.width,self.height)
        if self.pixmap:
            pixmap = self.pixmap.scaled(QSize(self.width,self.height),Qt.KeepAspectRatio,Qt.SmoothTransformation)
            self.label1.setPixmap(pixmap)
            self.label1.setAlignment(Qt.AlignCenter)
            self.layout.addWidget(self.label1)
        if image_id:
            self.image_id =image_id
            self.lable2.setText(image_id)
            self.lable2.setAlignment(Qt.AlignCenter)
            ###让文字自适应大小
            self.lable2.adjustSize()
            self.layout.addWidget(self.lable2)
        self.setLayout(self.layout)

    clicked = pyqtSignal(object)
    rightClicked = pyqtSignal(object)

    def mousePressEvent(self,ev):
        print('55555555555555555')
        if ev.button() == Qt.RightButton:
            print('dasdasd')
            #鼠标右击
            self.rightClicked.emit(self.image_id目标检测利用PyQT5搭建YOLOv5可视化界面

是否有 API 可以检测操作系统正在使用哪个主题 - 深色或浅色(或其他)?

OpenCV 例程 300篇246. 特征检测之ORB算法

OpenCV 例程 300篇246. 特征检测之ORB算法

openmv之特征点检测

youcans 的 OpenCV 例程200篇154. 边缘检测之 Canny 算子