【问题标题】:Why does Python crash when my PyQt window becomes inactive/is resized?当我的 PyQt 窗口变为非活动状态/调整大小时,为什么 Python 会崩溃?
【发布时间】:2017-09-26 20:44:23
【问题描述】:

在尝试在 PyQt4 窗口中显示网络摄像头提要(使用 imageio/ffmpeg 获得)的不同方式时,我偶然发现了this answer。在 Python 2.7 中将其实现为 ImageDisplayWidget 类(如下所述)后,一切似乎都运行良好:打开一个窗口,显示我的网络摄像头提要没有故障。如果我关上窗户,一切都会停止并整齐地关闭。

但是...每当我单击此 PyQt 窗口外的任何位置(当它显示网络摄像头馈送时),导致它失去焦点时,Python.exe 会因未处理的 win32 异常而崩溃。当我尝试调整窗口大小时也会发生同样的情况。

我可能犯了某种非常愚蠢的初学者错误,但我只是不明白。谁能指出我正确的方向?我是否违反了 (Py)Qt 甚至 Python 的一些基本规则?

这是一个最小的例子:

import sys
import numpy
from PIL import Image, ImageQt  # pillow
from PyQt4 import QtGui, QtCore


class DummyVideoGrabber(QtCore.QTimer):
    signal_image_available = QtCore.pyqtSignal(QtGui.QImage)

    def __init__(self):
        super(DummyVideoGrabber, self).__init__()
        self.timeout.connect(self.update_image)
        self.counter = 0

    def update_image(self):
        # Dummy rgb image (in reality we get a numpy array from imageio's Reader)
        self.counter += 1
        numpy_image = numpy.zeros(shape=(480, 640, 3), dtype=numpy.int8)
        numpy_image[:, :, self.counter%3] = 255
        qt_image = ImageQt.ImageQt(Image.fromarray(numpy_image, mode='RGB'))

        # Emit image
        self.signal_image_available.emit(qt_image)


class ImageDisplayWidget(QtGui.QWidget):
    """
    Custom widget that displays an image using QPainter.

    Mostly copied from: https://stackoverflow.com/a/22355028/4720018

    """
    def __init__(self, size_wxh=None, parent=None):
        super(ImageDisplayWidget, self).__init__(parent)
        self.image = QtGui.QImage()

    def set_image(self, qimage, resize_window=False):
        self.image = qimage
        self.repaint()

    def paintEvent(self, QPaintEvent):
        if not self.image:
            return
        painter = QtGui.QPainter(self)
        painter.drawImage(self.rect(), self.image, self.image.rect())

app = QtGui.QApplication(sys.argv)

# instantiate a display object
display = ImageDisplayWidget()
display.resize(640, 480)
display.show()

# instantiate a grabber object
grabber = DummyVideoGrabber()
grabber.signal_image_available.connect(display.set_image)
grabber.start(100)  # timer interval in ms

# start the event loop
app.exec_()

我发现可以通过添加wasActiveWindow 标志(在构造函数中初始化为True)并以如下逻辑封装drawImage() 调用来防止崩溃:

        if self.isActiveWindow():
            if self.wasActiveWindow:
                painter.drawImage(self.rect(), self.image, self.image.rect())
            self.wasActiveWindow = True
        else:
            self.wasActiveWindow = False

但是,调整窗口大小仍然会导致 python 崩溃。

【问题讨论】:

  • 崩溃时是否有堆栈跟踪?
  • 我必须承认我不知道如何获取这种崩溃的堆栈跟踪。我收到一个 Windows 7 对话框,显示“Python.exe 停止工作”。但我会调查一下。
  • 能否在 IDE 中运行源代码并在控制台中查看堆栈跟踪?
  • QGraphicsView 的代码是什么样的?它真的等同于其他代码吗? (大概不会,因为它不会崩溃)。
  • @AK47:我确实在 PyCharm 和 windows cmd 中运行过它。没有堆栈跟踪。

标签: python-2.7 crash pyqt4 qpainter python-imageio


【解决方案1】:

通过将qt_image 的引用保留为self.qt_image 来解决问题:

...
# Emit image
self.qt_image = ImageQt.ImageQt(Image.fromarray(numpy_image, mode='RGB'))
self.signal_image_available.emit(self.qt_image)
...    

这样就可以正常工作了。不再需要 self.wasActiveWindow 解决方法。

仍然不确定为什么 保留引用会导致低级 python 崩溃...

【讨论】:

  • 不保留引用是 pyqt 崩溃的常见原因。我认为这个问题在关于 QImage 和/或 PIL 之前已经出现过。我以前可能什至回答过有关它的问题 - 但目前我找不到任何问题。我应该在您的示例中发现这个问题 - 但我无法重现崩溃,因此实际上并没有仔细查看代码。最重要的是,您需要保留图像底层的数据,直到您完成使用它。否则,qt 可能会在数据已被 python 垃圾收集后尝试访问/删除数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-31
  • 2021-10-01
  • 1970-01-01
  • 2020-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多