首页 > 解决方案 > 如何使用 OpenCV 和 QThread 在 pyqt5 gui 中录制来自网络摄像头的视频?

问题描述

我正在尝试制作 pyqt5 Gui 来显示网络摄像头实时提要,同时记录提要,并在关闭时将其保存在本地。我设法在pyqt gui中使用Timer(QTimer)实现了这一点,但是当我尝试使用Qthread(我真的需要)实现它时,只有实时提要正在工作。

每当我添加录制视频所需的代码并运行程序时,它都会显示Python 已停止工作并关闭。这是我的代码:

import cv2
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QVBoxLayout
from PyQt5.QtCore import QThread, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QImage, QPixmap


class Thread(QThread):
    changePixmap = pyqtSignal(QImage)

    def run(self):
        self.cap = cv2.VideoCapture(0)

        self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        self.codec = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
        self.writer = cv2.VideoWriter('output.avi', self.codec, 30.0, (self.width, self.height))

        while self.cap.isOpened():
            ret, self.frame = self.cap.read()
            if ret:
                self.frame = cv2.flip(self.frame, 1)
                rgbimage = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                h, w, ch = rgbimage.shape
                bytesPerLine = ch * w
                convertToQtFormat = QImage(rgbimage.data, w, h, bytesPerLine, QImage.Format_RGB888)
                p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
                self.changePixmap.emit(p)


class MyApp(QWidget):
    def __init__(self):
        super(MyApp, self).__init__()
        self.title = 'Camera'
        self.initUI()

    def initUI(self):
        self.label = QLabel(self)
        lay = QVBoxLayout()
        lay.addWidget(self.label)
        self.setLayout(lay)

        self.th = Thread()
        self.th.changePixmap.connect(self.setImage)
        self.th.start()
        self.show()

    @pyqtSlot(QImage)
    def setImage(self, image):
        self.label.setPixmap(QPixmap.fromImage(image))
        self.th.writer.write(image)


def main():
    app = QApplication(sys.argv)
    ex = MyApp()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

我也尝试将其.write()放在 Thread 类的 run() 内部,这也显示了相同的错误。你们能指出我做错了什么以及如何让它发挥作用。我是 python 和 pyqt 的新手。提前致谢。

标签: python-3.xpyqtpyqt5qthreadopencv-python

解决方案


您需要分离线程。第一个线程用于信号,第二个用于记录,主线程用于 GUI。试试下面的代码。有一个按钮可以开始/停止记录。

import sys
import cv2

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import QTimer, QThread, pyqtSignal, pyqtSlot
from PyQt5 import QtWidgets, QtCore, QtGui

#https://ru.stackoverflow.com/a/1150993/396441

class Thread1(QThread):
    changePixmap = pyqtSignal(QImage)
    
    def __init__(self, *args, **kwargs):
        super().__init__()

    def run(self):
        self.cap1 = cv2.VideoCapture(0, cv2.CAP_DSHOW)
        self.cap1.set(3,480)
        self.cap1.set(4,640)
        self.cap1.set(5,30)
        while True:
            ret1, image1 = self.cap1.read()
            if ret1:
                im1 = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)
                height1, width1, channel1 = im1.shape
                step1 = channel1 * width1
                qImg1 = QImage(im1.data, width1, height1, step1, QImage.Format_RGB888)
                self.changePixmap.emit(qImg1)

class Thread2(QThread):
    
    def __init__(self, *args, **kwargs):
        super().__init__()
        self.active = True

    def run(self):
        if self.active:            
            self.fourcc = cv2.VideoWriter_fourcc(*'XVID') 
            self.out1 = cv2.VideoWriter('output.avi', self.fourcc, 30, (640,480))
            self.cap1 = cv2.VideoCapture(0, cv2.CAP_DSHOW)
            self.cap1.set(3, 480)
            self.cap1.set(4, 640)
            self.cap1.set(5, 30)
            while self.active:                      
                ret1, image1 = self.cap1.read()
                if ret1:
                    self.out1.write(image1)     
                self.msleep(10)                      

    def stop(self):
        self.out1.release()

       
class MainWindow(QWidget):
    
    def __init__(self):
        super().__init__()
        self.resize(660, 520)
        self.control_bt = QPushButton('START')
        self.control_bt.clicked.connect(self.controlTimer)
        self.image_label = QLabel()
        self.saveTimer = QTimer()
        self.th1 = Thread1(self)
        self.th1.changePixmap.connect(self.setImage)
        self.th1.start()
        
        vlayout = QVBoxLayout(self)
        vlayout.addWidget(self.image_label)
        vlayout.addWidget(self.control_bt)   

    @QtCore.pyqtSlot(QImage)
    def setImage(self, qImg1):
        self.image_label.setPixmap(QPixmap.fromImage(qImg1))

    def controlTimer(self):
        if not self.saveTimer.isActive():
            # write video
            self.saveTimer.start()
            self.th2 = Thread2(self)
            self.th2.active = True                                
            self.th2.start()
            # update control_bt text
            self.control_bt.setText("STOP")
        else:
            # stop writing
            self.saveTimer.stop()
            self.th2.active = False                   
            self.th2.stop()                         
            self.th2.terminate()                    
            # update control_bt text
            self.control_bt.setText("START")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    sys.exit(app.exec_())

推荐阅读