Python - PyQt: проблема с памятью в QThread - PullRequest
0 голосов
/ 13 ноября 2018

Я хочу создать интерфейс Qt для управления съемкой камеры.

Что я хочу: Прежде чем перейти к аппаратной связи, я тестирую графический интерфейс, который управляет «поддельной камерой», непрерывным циклом, который, если он запущен, выдает случайное изображение каждые 100 мс. Получение изображения выполняется в отдельном потоке, чтобы пользователь мог взаимодействовать с GUI. Пользователь может начать и остановить сбор данных с помощью кнопки.

Как я хочу это сделать: Моей первой попыткой было просто istanziate QThread и вызов метода run(), который затем содержал бы бесконечный цикл с получением одного изображения, чередующимся с QThread.sleep(0.1). Я заметил, что после остановки и перезапуска потока, программа начинала зависать и через некоторое время зависала. Прочитав несколько постов вокруг и главную Qt веб-страницу , я узнал, что предпочтительный способ сделать то, что я хочу, это:

подкласс QObject для создания работника. Создать экземпляр этого работника объект и QThread. Переместите рабочий в новую ветку.

Более того, следуя идее в этом посте , я добавил объект QTimer, чтобы бесконечно повторять работника внутри потока, и я реализую флаг active, который просто заставляет поток работать без выполнения что угодно, если установлено False. Это решение, казалось, сработало в начале. Я могу запускать, останавливать и перезапускать регистрацию столько раз, сколько захочу.

Проблемы:

1) Процессор всегда берет довольно много ресурсов (около 30% в моем случае, согласно диспетчеру задач Windows), когда камера не получает.

2) Иногда, после запуска сбора данных, память начинает заполняться, как если бы каждое новое изображение выделялось в новой памяти (хотя, я полагаю, предполагается, что оно перезаписывается), пока программа не перестает отвечать, а затем вылетает. Следующее изображение - это то, что я вижу в диспетчере задач, когда это происходит: enter image description here Красные стрелки соответствуют времени начала сбора.

Где я не так делаю? Это правильный путь?

Код

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

1 Ответ

0 голосов
/ 14 ноября 2018

QThread.sleep() принимает только целые аргументы, при прохождении с плавающей запятой он будет округлять его, а в вашем случае 0,1 будет округлено до 0, поэтому паузы не будет, поэтому сигнал будет подаваться непрерывно, но рисование занимает некоторое время, поэтому данные, которые будут храниться в очереди, увеличивают объем памяти. С другой стороны, если QTimer собирается вызывать задачу непрерывно, лучше жить в потоке объекта, который выполняет задачу, так что достаточно, чтобы QTimer был сыном FakeCamera. Еще одним улучшением является использование декоратора @QtCore.pyqtSlot(), поскольку соединение дано в C ++, что делает его более эффективным. И наконец, я улучшил дизайн, поскольку FakeCamera не должен напрямую взаимодействовать с графическим интерфейсом, потому что если вы хотите использовать его с другим графическим интерфейсом, вам придется модифицировать много кода, вместо этого лучше создавать слоты.

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_())
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...