为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

catspeake 2022-07-19 20:29:56
我有一个小 GUI,在显示的图像上显示不同的层。在某些时候,我想将带有所有可视化图层的当前图像存储到磁盘,同时继续使用 GUI。图像相当大(存储大约需要 5 秒),所以我想将保存的内容卸载到后台线程中。我尝试了不同的方法,但都没有奏效。最小的工作示例(仍然需要 PNG 进行测试,抱歉):import sysimport threadingfrom PIL import Imagefrom PIL.ImageQt import ImageQtfrom PyQt5.QtCore import QThread, pyqtSignal, QRunnable, QThreadPoolfrom PyQt5.QtGui import QPixmapfrom PyQt5.QtWidgets import QMainWindow, QApplication, QLabel, QSizePolicy, QAction, QToolBarclass 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 存储在后台线程中继续按Ctrl+ F,在图像被存储之前什么都没有发生,并且所有事件都只在之后处理(GUI 不可用)Ctrl通过+退出Q随意评论不同的方法,即 的用法threading.Thread、用法QThread或用法QRunnable,所有结果都相同:将像素图存储为 PNG(实际上应该在后台线程中发生)会阻塞 GUI。
查看完整描述

1 回答

?
犯罪嫌疑人X

TA贡献2080条经验 获得超4个赞

该问题与线程无关,但 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 sys

import threading


from PyQt5 import QtCore, QtGui, QtWidgets



class 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_())


查看完整回答
反对 回复 2022-07-19
  • 1 回答
  • 0 关注
  • 123 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信