Search code examples
pythonopencvpyqtpyqt5qthread

Display CV2 Live in QLabel Element


I'm currently coding a GUI for a surveillance System I made with OpenCV. I'd like to see the live video input in a QLabel next to the settings box but I'm quite new on the emit/signal/slot topic so I'm a bit overwhelmed by the code order. The GUI is converted from QtCreator, just to get the Layout right. The buttons don't have any functions yet. Here's my code so far:

import sys
import cv2
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *


#getting the live vid
class Thread(QThread):
    changePixmap = pyqtSignal(QImage)

    def run(self):
        cap1 = cv2.VideoCapture('single.mp4')
        while True:
            ret, frame = cap1.read()
            rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            cvt2qt = QImage(rgb_image.data, rgb_image.shape[1], rgb_image.shape[0], QImage.Format_RGB888)
            self.changePixmap.emit(cvt2qt)                         # I don't really understand this yet


class Ui_MainWindow(object):
    def setImage(self, image):
        self.label.setPixmap(QPixmap.fromImage(image))

    @pyqtSlot(QImage)                                              # I'm not sure about this function
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1280, 779)
        MainWindow.setMinimumSize(QtCore.QSize(920, 405))
        self.centralWidget = QtWidgets.QWidget(MainWindow)
        self.centralWidget.setMinimumSize(QtCore.QSize(400, 400))
        self.centralWidget.setBaseSize(QtCore.QSize(800, 600))
        self.centralWidget.setObjectName("centralWidget")

        # here is where I want to put the image.
        self.label = QtWidgets.QLabel(self.centralWidget)
        self.label.setGeometry(QtCore.QRect(10, 10, 881, 671))
        self.label.setScaledContents(True)
        self.label.setObjectName("label")
        th = Thread(self)                                           # Here is, where I struggle
        th.changePixmap.connect(self.setImage)
        th.start()
        MainWindow.setCentralWidget(self.centralWidget)

        self.dockWidget = QtWidgets.QDockWidget(MainWindow)
        self.dockWidget.setMinimumSize(QtCore.QSize(200, 0))
        self.dockWidget.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.dockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable)
        self.dockWidget.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea|QtCore.Qt.RightDockWidgetArea)
        self.dockWidget.setObjectName("dockWidget")

        self.dockWidgetContents = QtWidgets.QWidget()
        self.dockWidgetContents.setObjectName("dockWidgetContents")

        self.groupBox = QtWidgets.QGroupBox(self.dockWidgetContents)
        self.groupBox.setGeometry(QtCore.QRect(0, 30, 141, 281))
        self.groupBox.setMinimumSize(QtCore.QSize(90, 0))
        self.groupBox.setObjectName("groupBox")

        self.pushButton = QtWidgets.QPushButton(self.groupBox)
        self.pushButton.setGeometry(QtCore.QRect(10, 20, 121, 32))
        self.pushButton.setObjectName("pushButton")

        self.pushButton_2 = QtWidgets.QPushButton(self.groupBox)
        self.pushButton_2.setGeometry(QtCore.QRect(10, 50, 121, 32))
        self.pushButton_2.setObjectName("pushButton_2")

        self.pushButton_3 = QtWidgets.QPushButton(self.groupBox)
        self.pushButton_3.setGeometry(QtCore.QRect(10, 80, 121, 32))
        self.pushButton_3.setObjectName("pushButton_3")

        self.radioButton = QtWidgets.QRadioButton(self.groupBox)
        self.radioButton.setGeometry(QtCore.QRect(20, 120, 100, 20))
        self.radioButton.setObjectName("radioButton")

        self.radioButton_2 = QtWidgets.QRadioButton(self.groupBox)
        self.radioButton_2.setGeometry(QtCore.QRect(20, 150, 100, 20))
        self.radioButton_2.setObjectName("radioButton_2")

        self.radioButton_3 = QtWidgets.QRadioButton(self.groupBox)
        self.radioButton_3.setGeometry(QtCore.QRect(20, 180, 100, 20))
        self.radioButton_3.setObjectName("radioButton_3")
        self.dockWidget.setWidget(self.dockWidgetContents)
        MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.dockWidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)


    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "ARCV"))
        self.groupBox.setTitle(_translate("MainWindow", "Settings"))
        self.pushButton.setText(_translate("MainWindow", "Start Recording"))
        self.pushButton_2.setText(_translate("MainWindow", "Stop Recording"))
        self.pushButton_3.setText(_translate("MainWindow", "Quit GUI"))
        self.radioButton.setText(_translate("MainWindow", "Camera 1"))
        self.radioButton_2.setText(_translate("MainWindow", "Camera 2"))
        self.radioButton_3.setText(_translate("MainWindow", "Camera 3"))


class Prog(QMainWindow):
    def __init__(self):
        super().__init__();
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

if __name__=='__main__':
    Program =  QApplication(sys.argv)
    MyProg = Prog()
    MyProg.show()
    sys.exit(Program.exec_())

I hope, you can help me. Thanks. Aaroknight


Solution

  • QThread waits as a parameter for a parent that must be a QObject, in your case self is an object of the Ui_MainWindow class that does not inherit from QObject causing the problem.

    I see that you are implementing a class called Prog, there you must do the logic and not modify the code generated by Qt Designer.

    So if we move the logic to that class there should be no problems.

    On the other hand the decorator @pyqtSlot must be used in the function that is connected to the signal, you place it in the constructor which does not make sense, it must be on top of setImage().

    import sys
    
    import cv2
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            MainWindow.setObjectName("MainWindow")
            MainWindow.resize(1280, 779)
            MainWindow.setMinimumSize(QtCore.QSize(920, 405))
            self.centralWidget = QtWidgets.QWidget(MainWindow)
            self.centralWidget.setMinimumSize(QtCore.QSize(400, 400))
            self.centralWidget.setBaseSize(QtCore.QSize(800, 600))
            self.centralWidget.setObjectName("centralWidget")
    
            # here is where I want to put the image.
            self.label = QtWidgets.QLabel(self.centralWidget)
            self.label.setGeometry(QtCore.QRect(10, 10, 881, 671))
            self.label.setScaledContents(True)
            self.label.setObjectName("label")
            MainWindow.setCentralWidget(self.centralWidget)
    
            self.dockWidget = QtWidgets.QDockWidget(MainWindow)
            self.dockWidget.setMinimumSize(QtCore.QSize(200, 0))
            self.dockWidget.setLayoutDirection(QtCore.Qt.LeftToRight)
            self.dockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable)
            self.dockWidget.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea|QtCore.Qt.RightDockWidgetArea)
            self.dockWidget.setObjectName("dockWidget")
    
            self.dockWidgetContents = QtWidgets.QWidget()
            self.dockWidgetContents.setObjectName("dockWidgetContents")
    
            self.groupBox = QtWidgets.QGroupBox(self.dockWidgetContents)
            self.groupBox.setGeometry(QtCore.QRect(0, 30, 141, 281))
            self.groupBox.setMinimumSize(QtCore.QSize(90, 0))
            self.groupBox.setObjectName("groupBox")
    
            self.pushButton = QtWidgets.QPushButton(self.groupBox)
            self.pushButton.setGeometry(QtCore.QRect(10, 20, 121, 32))
            self.pushButton.setObjectName("pushButton")
    
            self.pushButton_2 = QtWidgets.QPushButton(self.groupBox)
            self.pushButton_2.setGeometry(QtCore.QRect(10, 50, 121, 32))
            self.pushButton_2.setObjectName("pushButton_2")
    
            self.pushButton_3 = QtWidgets.QPushButton(self.groupBox)
            self.pushButton_3.setGeometry(QtCore.QRect(10, 80, 121, 32))
            self.pushButton_3.setObjectName("pushButton_3")
    
            self.radioButton = QtWidgets.QRadioButton(self.groupBox)
            self.radioButton.setGeometry(QtCore.QRect(20, 120, 100, 20))
            self.radioButton.setObjectName("radioButton")
    
            self.radioButton_2 = QtWidgets.QRadioButton(self.groupBox)
            self.radioButton_2.setGeometry(QtCore.QRect(20, 150, 100, 20))
            self.radioButton_2.setObjectName("radioButton_2")
    
            self.radioButton_3 = QtWidgets.QRadioButton(self.groupBox)
            self.radioButton_3.setGeometry(QtCore.QRect(20, 180, 100, 20))
            self.radioButton_3.setObjectName("radioButton_3")
            self.dockWidget.setWidget(self.dockWidgetContents)
            MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.dockWidget)
    
            self.retranslateUi(MainWindow)
            QtCore.QMetaObject.connectSlotsByName(MainWindow)
    
    
        def retranslateUi(self, MainWindow):
            _translate = QtCore.QCoreApplication.translate
            MainWindow.setWindowTitle(_translate("MainWindow", "ARCV"))
            self.groupBox.setTitle(_translate("MainWindow", "Settings"))
            self.pushButton.setText(_translate("MainWindow", "Start Recording"))
            self.pushButton_2.setText(_translate("MainWindow", "Stop Recording"))
            self.pushButton_3.setText(_translate("MainWindow", "Quit GUI"))
            self.radioButton.setText(_translate("MainWindow", "Camera 1"))
            self.radioButton_2.setText(_translate("MainWindow", "Camera 2"))
            self.radioButton_3.setText(_translate("MainWindow", "Camera 3"))
    
    
    #getting the live vid
    class Thread(QtCore.QThread):
        changePixmap = QtCore.pyqtSignal(QtGui.QImage)
    
        def __init__(self, *args, **kwargs):
            QtCore.QThread.__init__(self, *args, **kwargs)
            self.flag = False
    
        def run(self):
            cap1 = cv2.VideoCapture('single.mp4')
            self.flag = True
            while self.flag:
                ret, frame = cap1.read()
                if ret:
                    rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    cvt2qt = QtGui.QImage(rgb_image.data, rgb_image.shape[1], rgb_image.shape[0], QtGui.QImage.Format_RGB888)
                    self.changePixmap.emit(cvt2qt)                         # I don't really understand this yet
    
        def stop(self):
            self.flag = False
    
    
    class Prog(QtWidgets.QMainWindow, Ui_MainWindow):
        def __init__(self):
            super().__init__()
            self.setupUi(self)
            self.th = Thread(self)
            self.th.changePixmap.connect(self.setImage)
            self.th.start()
    
        @QtCore.pyqtSlot(QtGui.QImage) 
        def setImage(self, image):
            self.label.setPixmap(QtGui.QPixmap.fromImage(image))
    
        def closeEvent(self, event):
            self.th.stop()
            self.th.wait()
            super().closeEvent(event)
    
    if __name__=='__main__':
        Program =  QtWidgets.QApplication(sys.argv)
        MyProg = Prog()
        MyProg.show()
        sys.exit(Program.exec_())