目标检测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可视化界面