PyQt5 добавить в поток вторую функцию, но не работает - PullRequest
0 голосов
/ 25 февраля 2020

Ранее я пытался использовать Flask для одновременного выполнения следующих действий:

  • Отображение потокового видео в реальном времени
  • Отображение потоковой передачи данных в реальном времени
  • Управление робот-машина

Поскольку вышеприведенное приведено только для демонстрации, а производительность потокового видео недостаточно высока, я решил изменить все приложение на PyQt5 для дальнейшей разработки и производства. Теперь я могу создать GUI для качественного отображения потокового видео в реальном времени, в то время как потоковая передача данных в реальном времени не может быть выполнена хорошо. Ошибка:

QObject :: startTimer: Таймеры могут использоваться только с потоками, запущенными с QThread

Ниже приводится описание всей программы. Пожалуйста, помогите понять, что не так в теме добавления темы. Спасибо!

import sys
from PyQt5 import QtWidgets, QtCore, QtGui
import cv2
from vidgear.gears import CamGear
from random import random
data_list=[]

fps=60
options_cam={"CAP_PROP_FRAME_WIDTH":640,"CAP_PROP_FRAME_HEIGHT":480,"CAP_PROP_FPS":fps}
stream=CamGear(source=0,logging=False,**options_cam).start()

class MainWindow(QtWidgets.QWidget):
    def __init__(self,*args):
        super(MainWindow, self).__init__()
        self.setWindowTitle('Vehicle control')
        self.grid_layout=QtWidgets.QGridLayout()
        self.video_label = QtWidgets.QLabel('Video streaming',self)
        self.video_frame = QtWidgets.QLabel(self)
        self.grid_layout.addWidget(self.video_label,0,0)
        self.grid_layout.addWidget(self.video_frame,1,0)

        self.data_label = QtWidgets.QLabel('Data streaming',self)
        self.data_frame = QtWidgets.QListWidget(self)
        self.grid_layout.addWidget(self.data_label,0,1)
        self.grid_layout.addWidget(self.data_frame,1,1)
        self.setLayout(self.grid_layout)

        #self.thread=QtCore.QThread()
        #self.thread.started.connect(self.nextFrameSlot)
        #self.thread.start()

        self.timer=QtCore.QTimer()
        self.timer.timeout.connect(self.video_stream)
        self.timer.start(0)

        self.thread=QtCore.QThread()
        self.thread.start()

        self.timer2=QtCore.QTimer()
        self.timer2.moveToThread(self.thread)
        self.timer2.timeout.connect(self.data_stream)
        self.timer2.start(0)

    def video_stream(self):
        frame = stream.read()

        # My webcam yields frames in BGR format
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        img = QtGui.QImage(frame, frame.shape[1], frame.shape[0], QtGui.QImage.Format_RGB888)
        pix = QtGui.QPixmap.fromImage(img)
        self.video_frame.setPixmap(pix)
        QtCore.QThread.sleep(0)

    def data_stream(self):
        print("data stream")
        stream_data=round(random()*10,3)
        data_list.insert(0,str(stream_data)+'\n')
        if len(data_list)>10:
            del data_list[-1]
        for i in range(len(data_list)):
            self.data_frame.addItem(data_list[i])
        self.data_frame.show()
        QtCore.QThread.sleep(1000)

if __name__ == '__main__':

    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

Редактировать: Спасибо @ musicamante ответ. Я обновил код следующим образом, но у меня все еще есть ошибка «ошибка сегментации» для потокового видео, хотя, если я работаю только для потока данных, обновленный список может быть показан. Так что же не так с функцией setPixmap? Еще раз спасибо!

import sys
from PyQt5 import QtWidgets, QtCore, QtGui
import cv2
from vidgear.gears import CamGear
from random import random

fps=60
options_cam={"CAP_PROP_FRAME_WIDTH":480,"CAP_PROP_FRAME_HEIGHT":480,"CAP_PROP_FPS":fps}
stream=CamGear(source=0,logging=False,**options_cam).start()

class CamGrabber(QtCore.QThread):
    frame = QtCore.pyqtSignal(QtGui.QImage)

    def run(self):
        while True:
            new_frame = stream.read()
            new_frame = cv2.cvtColor(new_frame, cv2.COLOR_BGR2RGB)
            img = QtGui.QImage(new_frame, new_frame.shape[1], new_frame.shape[0], QtGui.QImage.Format_RGB888)
            self.frame.emit(img)

class DataProvider(QtCore.QThread):
    data = QtCore.pyqtSignal(object)

    def run(self):
        while True:
            newData = round(random()*10,3)
            self.data.emit(newData)
            QtCore.QThread.sleep(1)

class MainWindow(QtWidgets.QWidget):
    def __init__(self,*args):
        super(MainWindow, self).__init__()
        self.setWindowTitle('Vehicle control')
        self.grid_layout=QtWidgets.QGridLayout()
        self.video_label = QtWidgets.QLabel('Video streaming',self)
        self.video_frame = QtWidgets.QLabel(self)
        self.grid_layout.addWidget(self.video_label,0,0)
        self.grid_layout.addWidget(self.video_frame,1,0)

        self.data_label = QtWidgets.QLabel('Data streaming',self)
        self.data_frame = QtWidgets.QListWidget(self)
        self.grid_layout.addWidget(self.data_label,0,1)
        self.grid_layout.addWidget(self.data_frame,1,1)
        self.setLayout(self.grid_layout)

        self.camObject = CamGrabber()
        self.camObject.frame.connect(self.newFrame)
        self.camObject.start()

        self.dataProvider = DataProvider()
        self.dataProvider.data.connect(self.newData)
        self.dataProvider.start()

    def newFrame(self, img):
        self.video_frame.setPixmap(QtGui.QPixmap.fromImage(img))

    def newData(self, data):
        self.data_frame.insertItem(0,str(data))
        if self.data_frame.count() > 10:
            self.data_frame.takeItem(9)

if __name__ == '__main__':

    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())```

1 Ответ

0 голосов
/ 26 февраля 2020

Ошибка QTimer в основном означает, что QTimer может быть запущен только из потока, в котором он существует.

Кроме того, элемент GUI всегда должен быть напрямую доступен или изменен из основного потока, не от другого.
Чтобы выполнить sh, вам нужно будет создать отдельный «рабочий» поток и обмениваться данными с основным, используя механизм сигнала / слота.

class CamGrabber(QtCore.QThread):
    frame = QtCore.pyqtSignal(QtGui.QImage)
    def run(self):
        while True:
            frame = stream.read()
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = QtGui.QImage(frame, frame.shape[1], frame.shape[0], QtGui.QImage.Format_RGB888)
            self.frame.emit(img)


class DataProvider(QtCore.QThread):
    data = QtCore.pyqtSignal(object)

    def run(self):
        while True:
            newData = round(random()*10,3)
            self.data.emit(newData)
            # note that QThread.sleep unit is seconds, not milliseconds
            QtCore.QThread.sleep(1)


class MainWindow(QtWidgets.QWidget):
    def __init__(self,*args):
        super(MainWindow, self).__init__()
        self.setWindowTitle('Vehicle control')
        self.grid_layout=QtWidgets.QGridLayout()
        self.video_label = QtWidgets.QLabel('Video streaming',self)
        self.video_frame = QtWidgets.QLabel(self)
        self.grid_layout.addWidget(self.video_label,0,0)
        self.grid_layout.addWidget(self.video_frame,1,0)

        self.data_label = QtWidgets.QLabel('Data streaming',self)
        self.data_frame = QtWidgets.QListWidget(self)
        self.grid_layout.addWidget(self.data_label,0,1)
        self.grid_layout.addWidget(self.data_frame,1,1)
        self.setLayout(self.grid_layout)

        self.camObject = CamGrabber()
        self.camObject.frame.connect(self.newFrame)
        self.camObject.start()

        self.dataProvider = DataProvider()
        self.dataProvider.data.connect(self.newData)
        self.dataProvider.start()

    def newFrame(self, img):
        self.video_frame.setPixmap(QtGui.QPixmap.fromImage(img))

    def newData(self, data):
        self.data_frame.addItem(str(data))
        if self.data_frame.count() > 10:
            self.data_frame.takeItem(0)

Если по какой-либо причине вы хотите контролировать выборку данных из основного потока через QTimer, вы можете использовать Очередь:

from queue import Queue

class DataProvider(QtCore.QObject):
    data = QtCore.pyqtSignal(object)

    def __init__(self):
        super().__init__()
        self.queue = Queue()

    def run(self):
        while True:
            multi = self.queue.get()
            # simulate a time consuming process
            QtCore.QThread.sleep(5)
            newData = round(multi * 10, 3)
            self.data.emit(newData)

    def pushData(self, data):
        self.queue.put(data)


class MainWindow(QtWidgets.QWidget):
    def __init__(self,*args):
        # ...
        self.requestTimer = QtCore.QTimer()
        self.requestTimer.setInterval(1000)
        self.requestTimer.timeout.connect(self.requestData)
        # this will cause the timer to be executed only once after each time
        # start() is called, so no new requests will overlap
        self.requestTimer.setSingleShot(True)
        self.requestTimer.start()

    def requestData(self):
        value = random()
        print('requesting data with value {}'.format(value))
        self.dataProvider.pushData(value)
        print('waiting for result')

    def newFrame(self, img):
        self.video_frame.setPixmap(QtGui.QPixmap.fromImage(img))

    def newData(self, data):
        print('data received')
        self.data_frame.addItem(str(data))
        if self.data_frame.count() > 10:
            self.data_frame.takeItem(0)
        # restart the timer
        self.requestTimer.start()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...