如何在更新标签 Pyqt5 上的图像(opencv)时调用其他函数?

How to call other functions while update image(opencv) on a label Pyqt5?

我想用图像更新标签,但是当我开始更新标签时,在更新完成之前没有任何效果,例如我想暂停更新或关闭 window。当我使用 tkinter 时它工作得很好但它在 Pyqt5 中不起作用。这是我的代码

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QDialog, QMainWindow,QMessageBox,QGroupBox,QSlider,QPushButton,QRadioButton,QLabel,QCheckBox,QFrame,QWidget,QTabWidget,QProgressBar,QTextBrowser,QTableWidget
from PyQt5.QtCore import QCoreApplication, Qt, QSize,QThread, QRect,QSize
from PyQt5.QtGui import QIcon, QFont,QPixmap,QIcon
import cv2
import os
import glob



class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(800, 800)
        self.label = QtWidgets.QLabel(Form)
        self.label.setGeometry(QtCore.QRect(165, 125, 61, 16))
        self.label.setObjectName("label")
        self.btn1 = QPushButton(Form)
        self.image_frame = QtWidgets.QLabel(Form)
        self.btn1.setGeometry(QRect(350, 200, 111, 31))
        
        self.btn1.clicked.connect(self.load_images_from_folder)
        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.btn1.setText(_translate("Form","Btn"))
        

    def load_images_from_folder(self):
        for filename in glob.glob('path/*.jpg'):
            img = cv2.imread(filename, cv2.IMREAD_UNCHANGED)
            img = cv2.resize(img,(200,200))
            print(filename)
            self.showimg(img)

    def showimg(self,img):
        self.image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        self.image = QtGui.QImage(self.image.data, self.image.shape[1], self.image.shape[0], QtGui.QImage.Format_RGB888).rgbSwapped()
        self.image_frame.setPixmap(QtGui.QPixmap.fromImage(self.image))
        self.image_frame.setGeometry(QRect(70, 310, 461, 441))
        Form.update()

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Form = QtWidgets.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()

    sys.exit(app.exec_())

点击按钮标签更新后,我无法再次点击按钮,直到更新完成

在 UI 环境中,必须尽快将控制返回到主线程。如果你有很多图像处理,这意味着加载图像的函数(它也调用设置图像的函数)在完成之前不会将控制权释放给主线程。

如果不这样做,结果是 UI 将完全冻结(没有更新,没有用户输入),直到 returns。

解决方案是使用线程,但是还需要考虑到涉及UI个元素,不允许访问UI个元素从外螺纹。因此,actual 解决方案是使用 QThreads 和自定义信号:Qt 信号是线程安全的,并且它们是 queued 只要两个objects 属于不同的线程正在尝试通信。

在这种特定情况下,线程将是具有自定义信号的 QThread(每当新图像准备就绪时必须发出):

class ImageLoader(QtCore.QThread):
    imageReady = QtCore.pyqtSignal(object)
    def __init__(self):
        super().__init__()
        self.images = []

    def run(self):
        while True:
            while self.images:
                img = cv2.imread(self.images.pop(), cv2.IMREAD_UNCHANGED)
                img = cv2.resize(img,(200,200))
                self.imageReady.emit(img)

    def loadImages(self, imageList):
        # if you want to *clear* the current queue
        self.images[:] = imageList
        # otherwise, just append:
        # self.images.extend(imageList)

然后实现需要创建线程实例并将图像添加到 load_images_from_folder 中的 queue:

class Ui_Form(object):
    def setupUi(self, Form):
        # ...
        self.imageLoader = ImageLoader()
        self.imageLoader.imageReady.connect(self.showimg)
        self.imageLoader.start()

    def load_images_from_folder(self):
        self.imageLoader.loadImages(glob.glob('path/*.jpg'))

重要提示:

  1. 编辑使用 pyuic 生成的文件被认为是 糟糕的 做法,因为它几乎总是会导致难以追踪的意外行为和错误;这些文件的 header 中的注释非常明确:您应该 编辑它们;相反,创建这些文件,保持原样,然后将它们导入到您的主脚本中,如关于 using Designer;
  2. 的官方指南中所述
  3. 访问 Form (Form.update()),这基本上是一个 全局 变量,是另一种不好的做法;就像编辑 pyuic 文件一样,这应该 永远不会 完成,除非你 真的 知道你在做什么(如果你知道,你可能根本不会这样做);在任何情况下,在标签上设置像素图会自动安排更新,因此函数调用完全无用,因为我们正确使用了线程,这允许主事件循环请求 UI;[=53 上的更新=]
  4. 设置固定几何图形是另一种通常不鼓励的做法;学习如何使用 layout managers and how to use them in Designer 代替;如果您希望 QLabel 具有固定大小,请使用 setFixedSize();