首页 > 解决方案 > 如何使用 PyQt5 显示实时 OpenCV 视频源

问题描述

我正在使用 PyQt5 和 QtDesigner 开发应用程序。对于其中一页(第 2 页),我正在尝试使用 OpenCV 嵌入来自摄像机的实时视频流。该代码有一个线程正在运行,我确认它正在发送良好的帧。我面临的问题是使用 OpenCV 框架动态更新 QLabel。

当这行代码(在 MainWindow 类中)未注释时,程序当前崩溃:为什么?

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

下面是主要代码

# 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()

同样,Worker1 线程似乎正在发送良好的帧(通过 print 语句确认),但是当帧进入时我无法更新我的 QLabel(称为 FeedLabel)。

我没有将支持的 .ui 文件附加到这篇文章中。

标签: pythonopencvpyqt5

解决方案


我在您的代码中更改了一堆东西,在评论中指出。本质上,您的程序的方法是以一种奇怪的方式定义的,并且您将许多东西存储在self.ui而不是self.

我为自己制作了一个最小的 UI,以便能够测试更改并且它可以工作。下面你可以看到我放在笔记本电脑相机上的便利贴的背面:

在此处输入图像描述

这是您修改后的代码:

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`

推荐阅读