在另一个线程中保存大图像

我有一个小 GUI,在显示的图像上显示不同的层。在某些时候,我想将带有所有可视化图层的当前图像存储到磁盘,同时继续使用 GUI。图像相当大(存储大约需要 5 秒),所以我想将保存的内容卸载到后台线程中。


我尝试了不同的方法,但都没有奏效。最小的工作示例(仍然需要 PNG 进行测试,抱歉):


import sys

import threading


from PIL import Image

from PIL.ImageQt import ImageQt

from PyQt5.QtCore import QThread, pyqtSignal, QRunnable, QThreadPool

from PyQt5.QtGui import QPixmap

from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel, QSizePolicy, QAction, QToolBar



class StorageQRunnable(QRunnable):


    def __init__(self,

                 pixmap: QPixmap,

                 target_path: str):

        super(StorageQRunnable, self).__init__()


        self.pixmap = pixmap

        self.target_path = target_path


    def run(self):

        print("Starting to write image in QRunnable.")

        self.pixmap.save(self.target_path, "PNG")

        print("Done writing image in QRunnable.")



class StorageQThread(QThread):


    signal = pyqtSignal("PyQt_PyObject")


    def __init__(self,

                 pixmap: QPixmap,

                 target_path: str):

        super(StorageQThread, self).__init__()


        self.pixmap = pixmap

        self.target_path = target_path


    def run(self):

        print("Starting to write image in QThread.")

        self.pixmap.save(self.target_path, "PNG")

        print("Done writing image in QThread.")

        self.signal.emit(0)



class StorageThread(threading.Thread):


    def __init__(self,

                 pixmap: QPixmap,

                 target_path: str):

        super(StorageThread, self).__init__()


        self.pixmap = pixmap

        self.target_path = target_path


    def run(self):


        print("Starting to write image in threading.Thread.")

        self.pixmap.save(self.target_path, "PNG")

        print("Done writing image in threading.Thread.")



简短手册:

  • 开始

  • Ctrl+F查看来自 GUI 线程的控制台输出(一次或多次)

  • Ctrl+B开始将大 PNG 存储在后台线程中

  • 继续按CtrlF,在图像被存储之前什么都没有发生,并且所有事件都只在之后处理(GUI 不可用)

  • Ctrl通过+退出Q

随意评论不同的方法,即 的用法threading.Thread、用法QThread或用法QRunnable,所有结果都相同:将像素图存储为 PNG(实际上应该在后台线程中发生)会阻塞 GUI。


catspeake
浏览 124回答 1
1回答

犯罪嫌疑人X

该问题与线程无关,但 QPixmap 不是线程安全的,不应像文档指出的那样从另一个线程进行操作:GUI线程和工作线程如前所述,每个程序在启动时都有一个线程。该线程称为“主线程”(在 Qt 应用程序中也称为“GUI 线程”)。Qt GUI 必须在这个线程中运行。所有小部件和几个相关的类,例如 QPixmap,都不能在辅助线程中工作。辅助线程通常被称为“工作线程”,因为它用于从主线程卸载处理工作。相反,您应该使用为 I/O 操作优化的 QImage,正如文档指出的那样:Qt 提供了四个类来处理图像数据:QImage、QPixmap、QBitmap 和 QPicture。QImage 是为 I/O 和直接像素访问和操作而设计和优化的,而 QPixmap 是为在屏幕上显示图像而设计和优化的。QBitmap只是一个继承QPixmap的便利类,保证深度为1。如果QPixmap对象真的是位图,isQBitmap()函数返回true,否则返回false。最后,QPicture 类是一个绘制设备,用于记录和重放 QPainter 命令。所以解决方案是:self.imageLabel.pixmap().toImage()代码:import sysimport threadingfrom PyQt5 import QtCore, QtGui, QtWidgetsclass SaveWorker(QtCore.QObject):    started = QtCore.pyqtSignal()    finished = QtCore.pyqtSignal()    def save(self, image, path):        threading.Thread(target=self._save, args=(image, path,), daemon=True).start()    def _save(self, image, path):        self.started.emit()        image.save(path, "PNG")        self.finished.emit()class TrialWindow(QtWidgets.QMainWindow):    def __init__(self, parent=None):        super(TrialWindow, self).__init__(parent)        self.imageLabel = QtWidgets.QLabel()        self.imageLabel.setSizePolicy(            QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored        )        self.imageLabel.setScaledContents(True)        self.setCentralWidget(self.imageLabel)        toolbar = QtWidgets.QToolBar("Controls")        toolbar.addAction(            QtWidgets.QAction(                "Do", self, shortcut="Ctrl+F", triggered=self.continue_in_foreground            )        )        toolbar.addAction(            QtWidgets.QAction(                "To background", self, shortcut="Ctrl+B", triggered=self.to_background            )        )        toolbar.addAction(            QtWidgets.QAction("Exit", self, shortcut="Ctrl+Q", triggered=self.close)        )        self.addToolBar(toolbar)        self.worker = SaveWorker()        self.worker.started.connect(self.on_started)        self.worker.finished.connect(self.on_finished)    def visualize(self, pxmp: QtGui.QPixmap):        self.imageLabel.setPixmap(pxmp)    @QtCore.pyqtSlot()    def continue_in_foreground(self):        print("Doing.")    @QtCore.pyqtSlot()    def on_started(self):        print("started")    @QtCore.pyqtSlot()    def on_finished(self):        print("finished")    @QtCore.pyqtSlot()    def to_background(self):        self.worker.save(self.imageLabel.pixmap().toImage(), "/tmp/target1.png")if __name__ == "__main__":    app = QtWidgets.QApplication(sys.argv)    # load pixmap    pixmap = QtGui.QPixmap("/tmp/sample.png")    window = TrialWindow()    window.show()    window.visualize(pixmap)    sys.exit(app.exec_())
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python