Search code examples
python-3.xopencvpyqt5

Videos are playing too fast using OpenCV and PyQT5


My GUI is able to play videos automatically when selected in the QListWidget. However, instead of normal speed, the videos play very fast. I use 720p Mp4 videos as examples and are placed in a certain folder. I tried using cv2.CAP_PROP_FPS and cv2.CAP_PROP_BUFFERSIZE, but they are both not working. I am using pyqtSignal in the QThread and the convert_cv_qt function which I saw in other guides. How do I play the videos in normal speed / frame rate?

import os
import cv2
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtMultimedia import QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QThread


class VideoThread(QThread):
    change_pixmap_signal = pyqtSignal(np.ndarray)

    def __init__(self, parent=None):
        super(VideoThread, self).__init__(parent)
        self.get_file_dir = self.parent().file_dir #Get the file_dir from UI_MainWindow

    def run(self):
        cap = cv2.VideoCapture(self.get_file_dir)
        
        while True:
            ret, cv_img = cap.read()
            if ret:
                self.change_pixmap_signal.emit(cv_img)
        cap.release()


class Ui_MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(Ui_MainWindow, self).__init__(parent)
        self.setupUI()
    
    def setupUI(self):
        MainWindow.resize(1720, 863)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
        self.tabWidget.setTabPosition(QtWidgets.QTabWidget.North)
        self.tabWidget.setTabShape(QtWidgets.QTabWidget.Triangular)
        self.tabWidget.setElideMode(QtCore.Qt.ElideNone)
        self.tabWidget.setTabsClosable(False)
        self.tabWidget.setMovable(True)
        self.tab = QtWidgets.QWidget()
        self.videoimage = QtWidgets.QLabel(self.tab)
        self.videoimage.setGeometry(QtCore.QRect(0, 10, 1280, 720))
        self.listWidget = QtWidgets.QListWidget(self.tab)
        self.listWidget.setGeometry(QtCore.QRect(1290, 60, 391, 241))
        self.listWidget.setObjectName("listWidget")
        self.listWidget.itemClicked.connect(self.itemclick)
        self.tabWidget.addTab(self.tab, "")
        self.tabWidget.setTabText(0, "Testing")
        self.verticalLayout_2.addWidget(self.tabWidget)
        MainWindow.setCentralWidget(self.centralwidget)

        self.listvideos()
        self.tabWidget.setCurrentIndex(0)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    @pyqtSlot(np.ndarray)
    def listvideos(self):
        file_extension = ['avi','mp4','mov']
        arr = os.listdir('D:\\Files\\VSCODE\\APPS\\HDTS\\videos')
        for i in arr:
            for fe in file_extension:
                    if i[-3:] == fe:
                        item = QtWidgets.QListWidgetItem(str(i))
                        self.listWidget.addItem(item)

    def itemclick(self):
        try:
          self.thread.terminate()
        except:
          print("Nothing to terminate!")

        item = self.listWidget.currentItem()
        self.file_dir = 'D:\\Files\\VSCODE\\APPS\\HDTS\\videos\\' + item.text()
        print(self.file_dir)        
        self.thread = VideoThread(self)
        self.thread.change_pixmap_signal.connect(self.update_image)
        self.thread.start()
    
    
    def update_image(self, cv_img):
        qt_img = self.convert_cv_qt(cv_img)
        self.videoimage.setPixmap(qt_img)
    
    def convert_cv_qt(self, cv_img):
        rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb_image.shape
        bytes_per_line = ch * w
        convert_to_Qt_format = QtGui.QImage(rgb_image.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)
        p = convert_to_Qt_format.scaled(1280, 720, Qt.KeepAspectRatio)
        return QtGui.QPixmap.fromImage(p)
    
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    MainWindow.show()
    sys.exit(app.exec_())

Solution

  • When you use VideoCapture with a file if you call "cap.read()" you will obtain the next frame on the video, regardless of its actual framerate. Hence, you should use a "msleep" every time you capture a frame:

    def run(self):
        cap = cv2.VideoCapture(self.get_file_dir)
        
        while True:
            ret, cv_img = cap.read()
            if ret:
                self.change_pixmap_signal.emit(cv_img)
            self.msleep(1000//30) # You can change 30 with 60 if you need 60 fps.
        cap.release()