我想构建一个 Qt 界面来控制相机采集。
我想要什么: 在进入硬件通信之前,我正在测试一个控制“假相机”的 GUI,这是一个连续循环,如果启动,每 100 毫秒给出一个随机图像。图像采集在单独的线程中运行,以便用户可以与 GUI 交互。用户可以通过按钮开始和停止采集。
我想如何做到这一点:
我的第一次尝试是简单地 istanziate QThread
并调用run()
方法,然后将包含一个无限循环,其中单个图像采集由 QThread.sleep(0.1)
交错。 。我注意到在停止并重新启动线程后,程序开始滞后并在一段时间后崩溃。
通过阅读周围的一些帖子和主要Qt webpage ,然后我了解到,做我想做的事情的更好方法是:
subclass a
QObject
to create a worker. Instantiate this worker object and aQThread
. Move the worker to the new thread.
此外,遵循this中的想法帖子中,我添加了一个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()
只接受整个参数,当传递一个 float 时,它将对其进行四舍五入,在您的情况下 0.1 将四舍五入为 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_())
关于Python-PyQt : Memory issue with QThread,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53288308/