Reevve
Reevve

Reputation: 11

How to display a live OpenCV video feed using a PyQt5

I'm developing an application with PyQt5 and QtDesigner. For one of the pages (page 2), I'm trying to embed a live video stream from a camera with OpenCV. The code has a thread running and I confirmed that it is sending good frames. The problem I'm facing is dynamically updating a QLabel with the OpenCV frame.

The program currently crashes when this line of code (in the MainWindow class) is uncommented: Why?

self.ui.Worker1.ImageUpdate.connect(self.ui.ImageUpdateSlot)

Below is the main code

# by: reevve

# Import Modules
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import cv2

# Import UI files
from ui_main import Ui_MainWindow
from ui_splashscreen import Ui_SplashScreen


# Global Variables
counter = 0


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)


        # add page btn click functionality
        self.ui.btn_page_1.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.page_1))
        self.ui.btn_page_2.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.page_2))
        self.ui.btn_page_3.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.page_3))

        
        # set up the video feed
        self.ui.CancelBTN.clicked.connect(lambda: self.ui.CancelFeed)
        self.ui.Worker1 = Worker1()
        self.ui.Worker1.start()

        # the line below is causing the program to crash
        #self.ui.Worker1.ImageUpdate.connect(self.ui.ImageUpdateSlot)

        def ImageUpdateSlot(self, Image):
            print('recieve frames')
            self.ui.FeedLabel.setPixmap(QPixmap.fromImage(Image))

        def CancelFeed(self):
            print('cancel feed')
            self.ui.Worker1.stop()
        


class SplashScreen(QMainWindow):
    def __init__(self):
        super(SplashScreen,self).__init__()
        self.ui = Ui_SplashScreen()
        self.ui.setupUi(self)

        # remove title bar
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)

        # drop shadow effect
        self.shadow = QGraphicsDropShadowEffect(self)
        self.shadow.setBlurRadius(20)
        self.shadow.setXOffset(0)
        self.shadow.setYOffset(0)
        self.shadow.setColor(QColor(0, 0, 0, 60))
        self.ui.dropShadowFrame.setGraphicsEffect(self.shadow)

        # start timer
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.progress)
        
        # specify duration of launcher
        self.timer.start(15)

        # initial text 
        self.ui.label_description.setText("<strong>UD ASAE</strong> Ground Station GUI")

        # change texts during loading process
        QtCore.QTimer.singleShot(1500, lambda: self.ui.label_description.setText("<strong>LOADING</strong> the good stuff"))
        QtCore.QTimer.singleShot(3000, lambda: self.ui.label_description.setText("<strong>GATHERING</strong> remaining braincells"))

        # show main window
        self.show()

    def progress(self):
        global counter
        self.ui.progressBar.setValue(counter)

        # close splash screen and open main gui
        if counter > 100:
            self.timer.stop()
            self.main = MainWindow()
            self.main.show()
            self.close()

        counter += 1


# FPV thread
class Worker1(QThread):
    ImageUpdate = pyqtSignal(QImage)
    
    def run(self):
        print('\nrun feed')
        self.ThreadActive = True       
        Capture = cv2.VideoCapture(0) 

        while self.ThreadActive:
            ret, frame = Capture.read()
            if ret:
                Image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                ConvertToQtFormat = QImage(Image.data, Image.shape[1], Image.shape[0], QImage.Format_RGB888)
                Pic = ConvertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
                self.ImageUpdate.emit(Pic)
                print('send good frames')
            
    def stop(self):
        print('stop feed')
        self.ThreadActive = False
        self.quit()


        

def window():
    app = QApplication(sys.argv)
    win = SplashScreen()
    sys.exit(app.exec_())

window()

Again, the Worker1 thread seems to be sending good frames (confirmed with print statement), but I'm having trouble updating my QLabel (called FeedLabel) as the frames come in.

I did not attach the supporting .ui files to this post.

Upvotes: 0

Views: 2772

Answers (1)

Guimoute
Guimoute

Reputation: 4629

I changed a bunch of things in your code, indicated in the comments. Essentially the methods of your program were defined in a strange way and you stored many things in self.ui instead of self.

I made myself a minimal UI to be able to test the changes and it works. Below you can see the back of the sticky note I put on my laptop's camera:

enter image description here

Here is your modified code:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__() # (optional) removed the args of `super`
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # add page btn click functionality
        ...
        
        # set up the video feed
        self.ui.CancelBTN.clicked.connect(self.CancelFeed) # removed `lambda` and `.ui`
        self.Worker1 = Worker1() # (optional) removed `.ui` because your thread should be an attr of the program, not of the ui. This is a matter of preference though.
        self.Worker1.start() # (optional) removed `.ui`
        self.Worker1.ImageUpdate.connect(self.ImageUpdateSlot) # removed `.ui`

    @pyqtSlot(QImage) # (optional) decorator to indicate what object the signal will provide.
    def ImageUpdateSlot(self, Image): # Unindented by 4 spaces.
        print('recieve frames')
        self.ui.FeedLabel.setPixmap(QPixmap.fromImage(Image))

    def CancelFeed(self): # Unindented by 4 spaces.
        print('cancel feed')
        self.Worker1.stop() # (optional) removed `.ui`

Upvotes: 1

Related Questions