简体   繁体   English

Python-PyQt:QThread的内存问题

[英]Python - PyQt: Memory issue with QThread

I want to build a Qt interface to control a camera acquisition. 我想建立一个Qt界面来控制摄像机的采集。

What I want: Before going into the hardware communication, I am testing a GUI which control a "fake camera", a continuous loop which, if started, gives a random image every 100 ms. 我想要的是:在进行硬件通信之前,我正在测试一个控制“伪相机”的GUI,该GUI是一个连续的循环,如果启动该循环,则每100毫秒给出一个随机图像。 The image acquisition is running in a separate thread so that the user could interact with the GUI. 图像采集在单独的线程中运行,以便用户可以与GUI进行交互。 The user can start and stop the acquisition via a button. 用户可以通过按钮开始和停止采集。

How I want to do it: My first attempt was to simply istanziate a QThread and call the run() method which would then contain an infinite loop with single image acquisitions interleaved by a QThread.sleep(0.1) . 我要如何做:我的第一次尝试是简单地对QThread进行tanzi,然后调用run()方法,该方法将包含一个无限循环,其中单个图像采集由QThread.sleep(0.1)交错。 I noticed that after stopping and restarting the thread, the program was starting to lag and crashed after some time. 我注意到在停止并重新启动线程后,该程序开始滞后并在一段时间后崩溃。 By reading some posts around and the main Qt webpage , I then learned that the preferable way to do what I want is to: 通过阅读一些Qt主页和周围的文章,然后我了解到,做我想做的最好的方法是:

subclass a QObject to create a worker. 子类化QObject以创建工作器。 Instantiate this worker object and a QThread . 实例化此worker对象和QThread Move the worker to the new thread. 将工作程序移动到新线程。

Moreover, following the idea in this post, I added a QTimer object to iterate indefinitely the worker inside the thread, and I implement an active flag that just makes the thread run without doing anything if it's set to False . 此外,在之后的想法这个帖子,我添加了一个QTimer对象无限期循环的线程内的工作人员,我实现一个active标志,只是让线程运行,如果没有它设置为做任何False This solution seemed to work at the beginning. 这种解决方案似乎在一开始就起作用。 I can start, stop and restart the acquisition as many times as I want. 我可以根据需要多次启动,停止和重新开始采集。

Problems: 问题:

1) The CPU is always taking quite some resources (about 30% in my case, according to windows task menager) when the camera is not acquiring. 1)当摄像头无法获取时,CPU总是占用相当多的资源(根据Windows任务管理器,在我看来,这是大约30%)。

2) Sometimes, after acquisition is started, the memory start to be filled, like if every new image is allocated in new memory (while it is suppose to be overwritten I guess), till the program becomes irresponsive and then crashes. 2)有时,开始获取后,内存开始被填充,就像每个新映像都被分配到新内存中一样(我想应该是将其覆盖),直到程序变得无响应然后崩溃。 The following image is what I see in task menager when this happens: 下图是发生这种情况时在任务管理器中看到的图像: 在此处输入图片说明 Red arrows correspond to the time the acquisition start. 红色箭头对应于采集开始的时间。

Where am I doing wrong? 我在哪里做错了? Is it the right way to procede? 这是正确的前进方法吗?

The code 编码

from PyQt5 import QtCore, QtWidgets
import sys
import numpy as np
import matplotlib
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg


class MyWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('MyWindow')
        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main) 

        # generate matplotlib canvas
        self.fig = matplotlib.figure.Figure(figsize=(4,4))
        self.canvas = FigureCanvasQTAgg(self.fig)
        self.ax = self.fig.add_subplot(1,1,1)
        self.im = self.ax.imshow(np.zeros((1000, 1000)), cmap='viridis')
        self.im.set_clim(vmin=0,vmax=1) 
        self.canvas.draw()

        # Add widgets and build layout
        self.startcambutton = QtWidgets.QPushButton('Start', checkable=True)
        self.startcambutton.released.connect(self.acquire)
        self.contincheck = QtWidgets.QCheckBox("Continuous")
        self.contincheck.clicked.connect(self.continuous_acquisition)
        self.contincheck.setChecked(True)
        layout = QtWidgets.QGridLayout(self._main)
        layout.addWidget(self.canvas, 0, 0)
        layout.addWidget(self.startcambutton, 1, 0)
        layout.addWidget(self.contincheck, 2, 0)

        # Initialize worker and timer and moveToThread
        self.fake_camera_thread = QtCore.QThread()
        self.fake_camera_timer = QtCore.QTimer()
        self.fake_camera_timer.setInterval(0)
        self.fake_camera_worker = FakeCamera(self)
        self.fake_camera_worker.moveToThread(self.fake_camera_thread)
        self.fake_camera_timer.timeout.connect(self.fake_camera_worker.acquire)
        self.fake_camera_thread.started.connect(self.fake_camera_timer.start)
        self.fake_camera_thread.finished.connect(self.fake_camera_worker.deleteLater)
        self.fake_camera_thread.finished.connect(self.fake_camera_timer.deleteLater)
        self.fake_camera_thread.finished.connect(self.fake_camera_thread.deleteLater)
        self.fake_camera_thread.start()

        self.camera_thread = self.fake_camera_thread
        self.camera = self.fake_camera_worker
        self.camera.image.connect(self.image_acquired)

    def continuous_acquisition(self):
        if self.contincheck.isChecked(): self.startcambutton.setCheckable(True)
        else: self.startcambutton.setCheckable(False)

    def acquire(self):
        if self.startcambutton.isCheckable() and not self.startcambutton.isChecked():
            self.startcambutton.setText('Start')
            self.contincheck.setEnabled(True)
        elif self.startcambutton.isCheckable() and self.startcambutton.isChecked():
            self.startcambutton.setText('Stop')
            self.contincheck.setDisabled(True)
        self.camera.toggle()

    @QtCore.pyqtSlot(object)
    def image_acquired(self, image):
        self.im.set_data(image)
        self.canvas.draw()


    def closeEvent(self, event):
        """ If window is closed """
        self.closeApp()
        event.accept() # let the window close

    def closeApp(self):
        """ close program """
        self.camera_thread.quit()
        self.camera_thread.wait()
        self.close()
        return



class FakeCamera(QtCore.QObject):
    image = QtCore.pyqtSignal(object)

    def __init__(self, parent):
        QtCore.QObject.__init__(self)
        self.parent = parent
        self.active = False

    def toggle(self):
        self.active = not self.active

    def acquire(self):
        """ this is the method running indefinitly in the associated thread """
        if self.active:
            self.new_acquisition()

    def new_acquisition(self):
        noise = np.random.normal(0, 1, (1000, 1000))
        self.image.emit(noise)
        if not self.parent.startcambutton.isChecked():
            self.active = False
        QtCore.QThread.sleep(0.1)



if __name__ == '__main__':
    app = QtCore.QCoreApplication.instance()
    if app is None:
        app = QtWidgets.QApplication(sys.argv)
    mainGui = MyWindow()
    mainGui.show()
    app.aboutToQuit.connect(app.deleteLater)
    app.exec_()

QThread.sleep() only accepts whole arguments, when passing a floating it will round it and in your case 0.1 that will be rounded to 0 so there is no pause, so the signal will be continuously issued but the painting takes a while so the data it will be stored in a queue for it increases the memory. QThread.sleep()仅接受整个参数,当传递一个浮点数时,它将对其进行四舍五入,在您的情况下,该值将被四舍五入为0,因此没有暂停,因此将连续发出信号,但是绘画需要一段时间,所以数据将存储在队列中,因为它增加了内存。 On the other hand if a QTimer is going to call a task continuously it is better to live in the thread of the object that handles the task so that it is enough that the QTimer is the son of FakeCamera. 另一方面,如果QTimer要连续调用任务,则最好将其驻留在处理任务的对象的线程中,这样QTimer就是FakeCamera的儿子就足够了。 Another improvement is the use of the decorator @QtCore.pyqtSlot() since the connection is given in C++ making it more efficient. 另一个改进是装饰器@QtCore.pyqtSlot()的使用,因为连接是用C ++给出的,因此效率更高。 And finally I have improved the design since FakeCamera should not interact directly with the GUI because if you want to use it with another GUI you will have to modify a lot of code, instead it is better to create slots. 最后,我改进了设计,因为FakeCamera不应直接与GUI交互,因为如果要与另一个GUI一起使用,则必须修改很多代码,而创建插槽更好。

from PyQt5 import QtCore, QtWidgets
import numpy as np
import matplotlib
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg


class MyWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('MyWindow')
        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main) 

        # generate matplotlib canvas
        self.fig = matplotlib.figure.Figure(figsize=(4,4))
        self.canvas = FigureCanvasQTAgg(self.fig)
        self.ax = self.fig.add_subplot(1,1,1)
        self.im = self.ax.imshow(np.zeros((1000, 1000)), cmap='viridis')
        self.im.set_clim(vmin=0,vmax=1) 
        self.canvas.draw()

        # Add widgets and build layout
        self.startcambutton = QtWidgets.QPushButton('Start', checkable=True)
        self.startcambutton.released.connect(self.acquire)
        self.contincheck = QtWidgets.QCheckBox("Continuous")
        self.contincheck.toggled.connect(self.startcambutton.setCheckable)
        self.contincheck.setChecked(True)
        layout = QtWidgets.QGridLayout(self._main)
        layout.addWidget(self.canvas, 0, 0)
        layout.addWidget(self.startcambutton, 1, 0)
        layout.addWidget(self.contincheck, 2, 0)

        # Initialize worker and timer and moveToThread
        fake_camera_thread = QtCore.QThread(self)
        self.fake_camera_worker = FakeCamera()
        self.fake_camera_worker.moveToThread(fake_camera_thread)
        self.startcambutton.toggled.connect(self.fake_camera_worker.setState)
        self.fake_camera_worker.image.connect(self.image_acquired)
        fake_camera_thread.started.connect(self.fake_camera_worker.start)
        fake_camera_thread.finished.connect(self.fake_camera_worker.deleteLater)
        fake_camera_thread.finished.connect(fake_camera_thread.deleteLater)
        fake_camera_thread.start()

    @QtCore.pyqtSlot()
    def acquire(self):
        if self.startcambutton.isCheckable():
            text = "Stop" if self.startcambutton.isChecked() else "Start"
            self.startcambutton.setText(text)
            self.contincheck.setEnabled(not self.startcambutton.isChecked())

    @QtCore.pyqtSlot(object)
    def image_acquired(self, image):
        self.im.set_data(image)
        self.canvas.draw()


class FakeCamera(QtCore.QObject):
    image = QtCore.pyqtSignal(object)

    def __init__(self, parent=None):
        super(FakeCamera, self).__init__(parent)
        self.active = False
        self.fake_camera_timer = QtCore.QTimer(self, interval=0)
        self.fake_camera_timer.timeout.connect(self.acquire)

    @QtCore.pyqtSlot()
    def start(self):
        self.fake_camera_timer.start()

    @QtCore.pyqtSlot(bool)
    def setState(self, state):
        self.active = state

    @QtCore.pyqtSlot()
    def toggle(self):
        self.active = not self.active

    @QtCore.pyqtSlot()
    def acquire(self):
        """ this is the method running indefinitly in the associated thread """
        if self.active:
            self.new_acquisition()
        QtCore.QThread.msleep(100)

    def new_acquisition(self):
        noise = np.random.normal(0, 1, (1000, 1000))
        self.image.emit(noise)


if __name__ == '__main__':
    import sys
    app = QtCore.QCoreApplication.instance()
    if app is None:
        app = QtWidgets.QApplication(sys.argv)
    mainGui = MyWindow()
    mainGui.show()
    app.aboutToQuit.connect(app.deleteLater)
    sys.exit(app.exec_())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM