pyqtgraph ImageView 在多线程时冻结

pyqtgraph ImageView Freezes when multithreaded

我有多个通过 wifi 无线连接的摄像头,我正在尝试将数据流式传输到客户端,该客户端在 GUI 上显示流。

我的问题是 pyqtgraph ImageItems 似乎在大约 30 秒后停止重新绘制,或者如果我点击 window,或者如果我调整其中一张图像上的控件。之后,我可以设法通过调整 window 的大小来重新绘制图像,但这有点乏味。

我想也许 pyqtgraph 不是线程安全的,但我什至不知道我是否在使用真正的线程,因为我必须通过 Qt (QThread) 拥有所有 运行 才能让事情正常进行。

我发现一些论坛帖子显示了与此类似的问题。 The first one which redirected to here explains that you can use something called NSAppSleepDisabled but that appears to be only on OSX and I'm running Windows 10. The second one 解释说他们的整个 GUI 都冻结了,我没有遇到这个问题,只有 ImageItem 冻结,整个 GUI 的其余部分都是响应式的。

这些是我的进口商品

import time
from multiprocessing import Queue
from threading import Lock
from queue import Empty

import pyqtgraph as pg
from PyQt5.QtCore import QThread

为了保持我的软件的可扩展性,我使用工作线程来管理传入的图像数据,为了保持 ImageView 的线程安全,我添加了一个锁:

graph_lock = Lock()

class WindowUpdater(QThread):

    stop_q = Queue()

    def __init__(self, cam_q: Queue, img_control: pg.ImageView, **kwargs):
        super().__init__(**kwargs)
        self.q = cam_q
        self.c = img_control

    def run(self) -> None:
        while self.stop_q.empty():
            try:
                timg = self.q.get_nowait()
                graph_lock.acquire(True)
                self.c.setImage(timg.frame)
                self.c.invalidate()
                graph_lock.release()
            except Empty:
                time.sleep(0.1)

然后主应用程序处理将这些线程链接到传入数据

app = QApplication(sys.argv)
window = QMainWindow()

grid = QGridLayout()
grid.setSpacing(10)
widg = QWidget()
widg.setLayout(grid)
window.setCentralWidget(widg)
window.show()

window.setGeometry(300, 300, 500, 400)
window.show()

threads = []

for i, h in enumerate(hosts):
    img = pg.ImageView(window)
    grid.addWidget(img, i, 0)

    img_item = pg.ImageItem()
    img.addItem(img_item)

    threads.append(WindowUpdater(img_queue, img)

for t in threads:
    t.start()

sys.exit(app.exec_())

其中 hosts 是用于连接的主机名列表,img_queue 是特定于该主机的相机流的多处理队列。

有人知道为什么同时拥有多个 pyqtgraph ImageView 或 ImageItem 实例 运行ning 会导致问题吗?

您不应该从另一个线程更新 GUI,因为 Qt 禁止它 (1)。所以必须用信号来传递信息。

class WindowUpdater(QThread):
    imageChanged = pyqtSignal(np.ndarray)
    stop_q = Queue()

    def __init__(self, cam_q: Queue, **kwargs):
        super().__init__(**kwargs)
        self.q = cam_q

    def run(self) -> None:
        while self.stop_q.empty():
            try:
                timg = self.q.get_nowait()
                graph_lock.acquire(True)
                self.imageChanged.emit(timg.frame)
                graph_lock.release()
            except Empty:
                time.sleep(0.1)
# ...
for i, h in enumerate(hosts):
    img = pg.ImageView(window)
    grid.addWidget(img, i, 0)

    img_item = pg.ImageItem()
    img.addItem(img_item)
    thread = WindowUpdater(img_queue)
    thread.imageChanged.connect(img.setImage)
    threads.append(thread)
# ...

(1) GUI Thread and Worker Thread