PyQt5 OpenCV 网络摄像头使用 QThread

Posted

技术标签:

【中文标题】PyQt5 OpenCV 网络摄像头使用 QThread【英文标题】:PyQt5 OpenCV WebCam Using QThread 【发布时间】:2020-06-11 01:57:36 【问题描述】:

我使用 PyQt5 和 OpenCV 编写了一个网络摄像头应用程序。它工作正常,但我想进一步改进它。我有几个问题:

1) 在 WebCam.py 的第 24 行,当我单击退出按钮时,我想正确地中断 while 循环。我的意思是我需要以某种方式从“Ui_MainWindow”类定义并传递一个“正在运行”的布尔值到 FrameGrabber 类以打破 while 循环,例如 while cap.isOpened() & running: 一旦循环被打破,我可以正确释放视频捕获。

2) Ui_MainWindow 主要由 Qt 设计器生成。我必须在其中添加代码以利用面向对象的封装规则,例如不定义全局变量。但是,如果我需要对 UI 进行更改,我是否需要一直编写代码?更新 UI 后不更改代码的正确方法是什么?

3) 如果您认为代码对其中的任何部分都更好,请随时制作 cmets,以便我也可以提高我的技能。

提前谢谢你!

这是 WebCam.py:

from PyQt5 import QtCore, QtGui, QtWidgets
import cv2


class FrameGrabber(QtCore.QThread):
    def __init__(self, parent=None):
        super(FrameGrabber, self).__init__(parent)

    signal = QtCore.pyqtSignal(QtGui.QImage)

    def run(self):
        cap = cv2.VideoCapture(0)
        cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
        cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
        while cap.isOpened():
            success, frame = cap.read()
            if success:
                image = QtGui.QImage(frame, frame.shape[1], frame.shape[0], QtGui.QImage.Format_BGR888)
                self.signal.emit(image)

class Ui_MainWindow(QtWidgets.QMainWindow):
    def __init__(self, MainWindow):
        super().__init__()
        self.MainWindow = MainWindow
        self.setupUi(self.MainWindow)
        self.grabber = FrameGrabber()
        self.grabber.signal.connect(self.updateFrame)
        self.grabber.start()

    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.setWindowModality(QtCore.Qt.NonModal)
        MainWindow.resize(1300, 799)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
        MainWindow.setSizePolicy(sizePolicy)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.label = QtWidgets.QLabel(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
        self.label.setSizePolicy(sizePolicy)
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(12)
        font.setBold(True)
        font.setUnderline(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setFrameShape(QtWidgets.QFrame.Box)
        self.label.setTextFormat(QtCore.Qt.RichText)
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")
        self.verticalLayout.addWidget(self.label)
        self.webCamDisplay = QtWidgets.QLabel(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.webCamDisplay.sizePolicy().hasHeightForWidth())
        self.webCamDisplay.setSizePolicy(sizePolicy)
        self.webCamDisplay.setFrameShape(QtWidgets.QFrame.Box)
        self.webCamDisplay.setText("")
        self.webCamDisplay.setObjectName("webCamDisplay")
        self.verticalLayout.addWidget(self.webCamDisplay)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        spacerItem = QtWidgets.QSpacerItem(500, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.quitPushButton = QtWidgets.QPushButton(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.quitPushButton.sizePolicy().hasHeightForWidth())
        self.quitPushButton.setSizePolicy(sizePolicy)
        self.quitPushButton.setObjectName("quitPushButton")
        self.horizontalLayout.addWidget(self.quitPushButton)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        MainWindow.setFixedSize(MainWindow.width(), MainWindow.height())
        self.quitPushButton.clicked.connect(self.quitApp)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    @QtCore.pyqtSlot(QtGui.QImage)
    def updateFrame(self, image):
        self.webCamDisplay.setPixmap(QtGui.QPixmap.fromImage(image))

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "WebCam"))
        self.label.setText(_translate("MainWindow", "WebCam Application"))
        self.quitPushButton.setText(_translate("MainWindow", "Quit"))

    def quitApp(self):
        QtWidgets.QApplication.quit()

main.py:

from WebCam import Ui_MainWindow
from PyQt5.QtWidgets import QApplication, QMainWindow

if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    MainWindow = QMainWindow()
    ui = Ui_MainWindow(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

【问题讨论】:

【参考方案1】:

以下是你应该知道的:

QtUI线程只能通过一个函数来控制和更新 或任何再次连接到同一线程的东西。 当您没有创建任何线程时,代码中的所有内容都会连接到 ui 线程。 尤其是在处理相机并对其进行一些处理时,界面中可能会出现一些抖动问题,因为ui线程正在等待更新自身,直到处理完成背景。我认为您还使用另一个线程来解决此问题。 它不可能在一个新线程上emit ui 的东西,因为它不是 ui 线程。要从另一个线程发出某些东西,您可以使用全局值。您可以在线程中更改全局值,并且可以在连接到 ui 线程 的另一个函数中进行更新。在这种情况下,您应该使用互斥体。 当您检查documentation 关于QThread 类时,您控制线程及其正常的功能有限。基本上你可以停止线程或启动线程,没有暂停。当您想关闭在线程中的 while 循环中运行的相机时,您可以在线程内部关闭,但线程仍会运行,或者您可以关闭线程。 因此,如果您要进行一些视频处理,则需要使用线程。简单的流程不需要。

【讨论】:

感谢您的回复。我看到您提到我没有创建除 UI 线程之外的单独线程。这些行不是创建单独的线程吗? self.grabber = FrameGrabber() self.grabber.signal.connect(self.updateFrame) self.grabber.start() 我不是说你没有创建一个单独的线程。您创建的任何线程都是独立的。不幸的是,我不熟悉 pyqt,这些只是概念性的东西。使用qt论坛可以更好地获得更好的回复

以上是关于PyQt5 OpenCV 网络摄像头使用 QThread的主要内容,如果未能解决你的问题,请参考以下文章

Python QT5 - 多进程 OpenCV 网络摄像头和 Requests.Get

Pyqt5 Qtimer理解

多个摄像头馈送不适用于 PyQt5 线程:

在opencv中使用网络摄像头未显示图像

OpenCV 网络摄像头帧率

如何使用 opencv 获取网络摄像头设备列表?