Python3 + Pillow + QT5:当我调整包含图像的标签大小时崩溃
Posted
技术标签:
【中文标题】Python3 + Pillow + QT5:当我调整包含图像的标签大小时崩溃【英文标题】:Python3 + Pillow + QT5: Crash when I resize a label containing an image 【发布时间】:2016-11-01 08:29:00 【问题描述】:当我将图像加载到已经可见的窗口中的 QLabel 中时,我收到一个消息框:“Python 已停止工作”。选择调试显示:Python.exe中出现未处理的Win32异常。
如果我在显示窗口之前将图像加载到标签中,它会正确显示。
这是精简后的代码:
#!/usr/bin/etc python
import sys
import os
import stat
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PIL import *
from PIL.ImageQt import *
def update(label):
filename = r"C:\Users\me\Pictures\images\00000229.jpg"
im1 = Image.open(filename)
print ("Read (,)".format(im1.width, im1.height))
im2 = im1.rotate(90, expand=True)
print("Rotate (,)".format(im2.width, im2.height))
im2.thumbnail((1200,1200))
print("Thumbnail(,)".format(im2.width, im2.height))
qimage = ImageQt(im2)
pixmap = QPixmap.fromImage(qimage)
label.setPixmap(pixmap)
app = QApplication(sys.argv)
desktop = QDesktopWidget()
deskGeometry = desktop.availableGeometry()
print("desktop (,)".format(deskGeometry.width(), deskGeometry.height()))
window = QFrame()
# If you let QT pick the sizes itself, it picks dumb ones, then complains
# 'cause they are dumb
window.setMinimumSize(300, 200)
window.setMaximumSize(deskGeometry.width(), deskGeometry.height())
label = QLabel()
#call update here: no crash
caption = QLabel()
caption.setText("Hello world")
box = QVBoxLayout()
box.addWidget(label)
box.addWidget(caption)
#call update here, no crash
window.setLayout(box)
#call update here, no crash
window.show()
#this call to update results in a crash
update(label)
#window.updateGeometry()
print("App: exec")
app.exec_()
输出:
desktop (3623,2160)
Read (1515,1051)
Rotate (1051,1515)
Thumbnail(832,1200)
App: exec
我需要做一些特别的事情来告诉 QT 窗口大小将会改变吗?从这里诊断问题的任何建议......
更新:
如果我复制更新函数的主体并将其粘贴到更新调用的位置,它不会再崩溃——它会按预期工作。
由此我得出结论,存在对象生命周期问题。 QT 和/或 Pillow 在幕后某处保留指向内部缓冲区的指针,而不是复制或“窃取”缓冲区。当包含缓冲区的对象被删除时,指针变得无效并且“Bad Things Happen[TM]”
现在要确定谁在偷懒……
【问题讨论】:
我打算建议您需要保留对基础图像数据的引用 - 但我自己实际上无法重现该问题。但无论如何,这类垃圾收集问题是众所周知的,所以我认为这里没有什么不寻常的事情。<humor>
如果我想要对象生命周期问题,我会用 c++ 而不是 Python 进行编程——哎呀。 `' 感谢您查看问题。
【参考方案1】:
根据更新中提到的观察,我找到了一个解决方案,这似乎是一个对象生命周期问题。
将update
函数中的行从
pixmap = QPixmap.fromImage(qimage)
到
pixmap = QPixmap.fromImage(qimage).copy()
强制复制像素图。这个副本显然有自己的数据缓冲区,而不是从图像中借用缓冲区。
然后,标签会保留对像素图的引用——确保缓冲区的生命周期。 “错误”似乎是 QPixmap.fromImage 捕获指向图像中数据的指针,但不保留对图像的引用,因此如果图像被垃圾收集(这可能是因为它是一个大对象,标签(和像素图)有一个指向未分配内存的指针。
[这个“指向缓冲区的指针”只是我的猜测,但最重要的是程序不再崩溃。]
【讨论】:
我在缩放 QPixmap 对象时遇到了完全相同的问题,我按照指示解决了这个问题,即通过复制:pixmap = QPixmap("tempPic.png")
pixmap = pixmap.copy().scaled([...]) # before: pixmap.copy().scaled([...])
【参考方案2】:
万一其他人偶然发现:
我遇到了一个非常相似的问题,并且能够使用插槽和信号(基于this great article)解决它,因为 .copy() 解决方案对我不起作用。我的解决方案是将 QPixmap 和 QLabel.setPixmap 的创建分成不同的函数。当创建 QPixmap 的函数完成时,它会发出一个信号,触发将 pixmap 设置为标签的函数。
例如,如果您想使用线程:
class WorkerSignals(QObject):
ready = pyqtSignal()
class Worker(QRunnable):
def __init__(self, some_function, *args, **kwargs):
super(Worker, self).__init__()
self.some_function = some_function
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['ready'] = self.signals.ready
@pyqtSlot()
def run(self):
self.some_function(*self.args, **self.kwargs)
def make_pixmap(qimage, ready, **kwargs):
pixmap = QPixmap.fromImage(qimage) # qimage is some QImage
ready.emit()
def set_pixmap(pixmap):
label.setPixmap(pixmap) # 'label' is some QLabel
threadpool = QThreadPool
worker = Worker(make_pixmap, image)
worker.signals.ready.connect(set_pixmap)
threadpool.start(worker)
在这种情况下显然不需要使用线程,但这只是为了表明如果您想在不挂起 GUI 的其余部分的情况下处理和显示图像是可能的。
编辑:崩溃又回来了,忽略上述内容。正在解决另一个问题。
【讨论】:
以上是关于Python3 + Pillow + QT5:当我调整包含图像的标签大小时崩溃的主要内容,如果未能解决你的问题,请参考以下文章