QImage内存泄漏

Posted

技术标签:

【中文标题】QImage内存泄漏【英文标题】:QImage memory leak 【发布时间】:2016-04-16 11:48:06 【问题描述】:

我编写了一个 OpenCV 应用程序,它基本上从相机中抓取帧,进行一些图像处理并以两个编辑的变体显示图像。首先,我使用 cv2.imshow() 来显示图像,但是虽然 OpenCV(不支持 Qt 的构建)无法提供现代 GUI 元素,但我决定使用 PySide 作为我的 GUI。

但是因为这个我在处理大约 830-850 帧后得到了这个错误(不管我使用什么计时器速率,或者我做了多少图像处理):

QImage: out of memory, returning null image

对于我在 GUI 中的两个图像视图,然后在每个循环中这个:

OpenCV Error: Unspecified error (The numpy array of typenum=2, ndims=3 can not be created) in NumpyAllocator::allocate, file ..\..\..\opencv-3.1.0\modules\python\src2\cv2.cpp, line 184
OpenCV Error: Insufficient memory (Failed to allocate 921600 bytes) in cv::OutOfMemoryError, file ..\..\..\opencv-3.1.0\modules\core\src\alloc.cpp, line 52
Traceback (most recent call last):
  File "C:/myfile.py", line 140, in process_frame
    img = QtGui.QImage(cv2.cvtColor(thresh_img, cv2.COLOR_RGB2BGR), self.width, self.height,
cv2.error: ..\..\..\opencv-3.1.0\modules\core\src\alloc.cpp:52: error: (-4) Failed to allocate 921600 bytes in function cv::OutOfMemoryError

这是我的代码的一部分(没有图像处理,但它也会产生错误):

import cv2
import sys
from PySide import QtGui, QtCore
from threading import Thread


class MainWindow(QtGui.QMainWindow):
    def __init__(self, cam=0, parent=None):
        super(MainWindow, self).__init__(parent)

        self.camera = Camera(cam).start()
        self.title = "Cam %s" % cam
        self.counter = 0

        widget = QtGui.QWidget()
        self.layout = QtGui.QBoxLayout(QtGui.QBoxLayout.LeftToRight)

        self.video_frame = QtGui.QLabel()
        self.thresh_frame = QtGui.QLabel()

        self.layout.addWidget(self.video_frame)
        self.layout.addWidget(self.thresh_frame)
        self.layout.addStretch()

        self.setCentralWidget(widget)
        widget.setLayout(self.layout)

        self.setMinimumSize(640, 480)
        self._timer = QtCore.QTimer(self)
        self._timer.timeout.connect(self.process_frame)
        self._timer.start(20)

    def process_frame(self):
        self.counter += 1
        print(self.counter)
        self.frame = self.camera.read()
        self.height, self.width = self.frame.shape[:2]

        thresh_img = cv2.threshold(cv2.cvtColor(self.frame, cv2.COLOR_RGB2GRAY), 0, 255,
                                   cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
        thresh_img = cv2.erode(thresh_img, None, iterations=2)
        thresh_img = cv2.dilate(thresh_img, None, iterations=2)
        thresh_img = cv2.cvtColor(thresh_img, cv2.COLOR_GRAY2RGB)

        img = QtGui.QImage(cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR), self.width, self.height,
                           QtGui.QImage.Format_RGB888)
        img = QtGui.QPixmap.fromImage(img)
        self.video_frame.setPixmap(img)

        img = QtGui.QImage(cv2.cvtColor(thresh_img, cv2.COLOR_RGB2BGR), self.width, self.height,
                           QtGui.QImage.Format_RGB888)
        img = QtGui.QPixmap.fromImage(img)
        self.thresh_frame.setPixmap(img)

    def closeEvent(self, event):
        self.camera.stop()
        event.accept()


class Camera:
    def __init__(self, src=0):
        self.stream = cv2.VideoCapture(src)
        (self.grabbed, self.frame) = self.stream.read()

        self.stopped = False

    def start(self):
        Thread(target=self.update, args=()).start()
        return self

    def update(self):
        while True:
            if self.stopped:
                return
            (self.grabbed, self.frame) = self.stream.read()

    def read(self):
        return self.frame

    def stop(self):
        self.stopped = True

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MainWindow(0)
    window.show()
    sys.exit(app.exec_())

在 Windows 任务管理器中,我可以看到我的程序的 RAM 使用情况:

在崩溃时,该应用使用了大约 1.5 GB 的 RAM。我试过在del img之后使用gc模块和gc.collect(),没有成功。

我还能做什么?

编辑:

线程Camera 类在这里无关紧要,没有它也会出现错误。

【问题讨论】:

您能否提供一个完整的独立程序让我们重现您的问题? 我现在不在家,但我会尽快发布!任何想法如何删除 img 实例?? @tfv 我现在已经更新了我的代码。如果你能看看它会很好...... 您能提供有关 Python 和 OpenCV 版本的信息吗?我有 3.1.0 和 python 2.7.10(默认,2015 年 5 月 23 日,09:40:32)[MSC v.1500 32 位(英特尔)],并且您的程序使用罗技相机立即崩溃。 我非常怀疑(self.grabbed, self.frame) = self.stream.read() 是一个原子操作。您可能应该有一个锁来防止线程之间的并发访问。不太可能是内存泄漏的原因。您是否尝试过 PyQt 而不是 PySide 作为测试?根据我的经验,PySide 有很多内存泄漏,所以这可能不是你的错。 【参考方案1】:

显然这个错误是由于使用 python 3.x 时的垃圾收集问题

这里提供了一个使用 ctypes 的简单解决方法https://bugreports.qt.io/browse/PYSIDE-140 和 https://github.com/matplotlib/matplotlib/issues/4283#issuecomment-92773487

在最后一个链接上,请参阅帖子“mfitzp 于 2015 年 4 月 24 日发表评论”。这对我有用!

【讨论】:

感谢您的指出,我已经在大约 3 个月前发现了:D【参考方案2】:

这似乎是 PySide 特有的错误,使用 PyQt 将修复它。它甚至与 OpenCV 无关。看起来现在不会有使用 PySide 的解决方案......

【讨论】:

【参考方案3】:

看看我在 PYSIDE-140 上的评论:https://bugreports.qt.io/browse/PYSIDE-140

避免将 python 对象与 Qt 对象混合。试试下面的例子,避免使用引用计数修复。

stream = cv2.VideoCapture(0)
grabbed, frame = stream.read()

height, width = frame.shape[:2]
data = Qt.QByteArray(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR).tostring())
qimg = Qt.QImage(data, width, height, Qt.QImage.Format_RGB888)

data.clear()
del data

【讨论】:

这是很久以前的事了,不过还是谢谢你。无论如何,refcounter 方法对我来说工作得很好。 不仅留给您,也留给其他寻找相同/相似问题解决方案的人。 Refcount 解决方法不适用于使用 QTextEdit 的 Qt5。我上面的解决方案适用于这种情况;-)【参考方案4】:

就我而言,我使用QStackedWidget 在不同视图之间切换,并使用QTimer 触发视图切换(在某些条件下)。

我使用functools.partial 将参数从一个视图传递到另一个视图,其中我确实传递了实例,例如PIL.ImageQImageImageQtQPixmap

这就是它出错的地方。当这些资源从一个视图传递到另一个视图时,垃圾收集器没有很好地清理它们。

什么对我有用。

    将您计划从一个视图传递到另一个视图的所有变量声明为类属性(在 QWidget 的构造函数中)。将所有图像资源(例如PIL.ImageQImageImageQtQPixmap)存储在这些变量/属性中。

    不要将任何参数(包含图像资源)从一个视图传递到另一个视图。


此外,如果您将 PyQT 与 OpenCV 一起使用,请注意 OpenCV 2.9 包含一个错误,该错误会导致内存泄漏。在花费数小时找出内存泄漏的来源之前,请务必升级到最新版本(最新的 2.x 或 3.x)。

【讨论】:

感谢您的回答 :) 尽管目前这与我完全不相关,但我记得使用 QStackedWidget,不能 100% 确定它是否在此应用程序中。正如您在回溯中看到的,我当时使用的是 OpenCV 3.1.0!

以上是关于QImage内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

如何防止java中的内存泄漏

记录一次DialogFragment 内存泄漏

常见的内存泄漏原因及解决方法

Android ValueAnimator --内存泄漏

Android内存泄漏查找和解决

哪些操作会造成内存泄漏以及如何解决内存泄漏