[英]Python - PyQt: Memory issue with QThread
我想建立一個Qt界面來控制攝像機的采集。
我想要的是:在進行硬件通信之前,我正在測試一個控制“偽相機”的GUI,該GUI是一個連續的循環,如果啟動該循環,則每100毫秒給出一個隨機圖像。 圖像采集在單獨的線程中運行,以便用戶可以與GUI進行交互。 用戶可以通過按鈕開始和停止采集。
我要如何做:我的第一次嘗試是簡單地對QThread
進行tanzi,然后調用run()
方法,該方法將包含一個無限循環,其中單個圖像采集由QThread.sleep(0.1)
交錯。 我注意到在停止並重新啟動線程后,該程序開始滯后並在一段時間后崩潰。 通過閱讀一些Qt主頁和周圍的文章,然后我了解到,做我想做的最好的方法是:
子類化
QObject
以創建工作器。 實例化此worker對象和QThread
。 將工作程序移動到新線程。
此外,在之后的想法這個帖子,我添加了一個QTimer
對象無限期循環的線程內的工作人員,我實現一個active
標志,只是讓線程運行,如果沒有它設置為做任何False
。 這種解決方案似乎在一開始就起作用。 我可以根據需要多次啟動,停止和重新開始采集。
問題:
1)當攝像頭無法獲取時,CPU總是占用相當多的資源(根據Windows任務管理器,在我看來,這是大約30%)。
2)有時,開始獲取后,內存開始被填充,就像每個新映像都被分配到新內存中一樣(我想應該是將其覆蓋),直到程序變得無響應然后崩潰。 下圖是發生這種情況時在任務管理器中看到的圖像: 紅色箭頭對應於采集開始的時間。
我在哪里做錯了? 這是正確的前進方法嗎?
編碼
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()
僅接受整個參數,當傳遞一個浮點數時,它將對其進行四舍五入,在您的情況下,該值將被四舍五入為0,因此沒有暫停,因此將連續發出信號,但是繪畫需要一段時間,所以數據將存儲在隊列中,因為它增加了內存。 另一方面,如果QTimer
要連續調用任務,則最好將其駐留在處理任務的對象的線程中,這樣QTimer
就是FakeCamera的兒子就足夠了。 另一個改進是裝飾器@QtCore.pyqtSlot()
的使用,因為連接是用C ++給出的,因此效率更高。 最后,我改進了設計,因為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.