Search code examples
python-3.xpyqt5opencv3.0

opencv pyqt video normal frame rate


I’m creating a special purpose video player in Python 3.6 using OpenCV3 and ffmepg for handling the images and using PyQt5 for the Windows environment. I chose this combination of packages because ffmpeg handles a wider variety of codecs than QtMultimedia.

I’ve run into one snag. My player does not play at regular speed – it plays at roughly ¾ of normal speed. I use QTimer.timer to loop my display engine (nextFrameSlot) at a speed of 1/framerate.

Any suggestions on how to get the video to play at regular speed? Here is an abbreviated set of code that demonstrates my problem.

import sys
from PyQt5.QtWidgets import QWidget, QLabel, QFormLayout, QPushButton, QMainWindow
from PyQt5.QtWidgets import QAction, QMessageBox, QApplication, QFileDialog
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import QTimer
import cv2


class VideoCapture(QWidget):
    def __init__(self, filename, parent):
        super(QWidget, self).__init__()
        self.cap = cv2.VideoCapture(str(filename[0]))
        self.length = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
        self.frame_rate = self.cap.get(cv2.CAP_PROP_FPS)
        #self.codec = self.cap.get(cv2.CAP_PROP_FOURCC)
        self.video_frame = QLabel()
        parent.layout.addWidget(self.video_frame)

    def nextFrameSlot(self):
        ret, frame = self.cap.read()
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        img = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888)
        pix = QPixmap.fromImage(img)
        self.video_frame.setPixmap(pix)

    def start(self):
        self.timer = QTimer()
        self.timer.timeout.connect(self.nextFrameSlot)
        self.timer.start(1000.0/self.frame_rate)

    def pause(self):
        self.timer.stop()

    def deleteLater(self):
        self.cap.release()
        super(QWidget, self).deleteLater()

class VideoDisplayWidget(QWidget):
    def __init__(self,parent):
        super(VideoDisplayWidget, self).__init__(parent)
        self.layout = QFormLayout(self)
        self.startButton = QPushButton('Start', parent)
        self.startButton.clicked.connect(parent.startCapture)
        self.startButton.setFixedWidth(50)
        self.pauseButton = QPushButton('Pause', parent)
        self.pauseButton.setFixedWidth(50)
        self.layout.addRow(self.startButton, self.pauseButton)
        self.setLayout(self.layout)

class ControlWindow(QMainWindow):
    def __init__(self):
        super(ControlWindow, self).__init__()
        self.setGeometry(50, 50, 800, 600)
        self.setWindowTitle("PyTrack")

        self.capture = None

        self.isVideoFileLoaded = False

        self.quitAction = QAction("&Exit", self)
        self.quitAction.setShortcut("Ctrl+Q")
        self.quitAction.triggered.connect(self.closeApplication)

        self.openVideoFile = QAction("&Open Video File", self)
        self.openVideoFile.setShortcut("Ctrl+Shift+V")
        self.openVideoFile.triggered.connect(self.loadVideoFile)

        self.mainMenu = self.menuBar()
        self.fileMenu = self.mainMenu.addMenu('&File')
        self.fileMenu.addAction(self.openVideoFile)
        self.fileMenu.addAction(self.quitAction)

        self.videoDisplayWidget = VideoDisplayWidget(self)
        self.setCentralWidget(self.videoDisplayWidget)

    def startCapture(self):
        if not self.capture and self.isVideoFileLoaded:
            self.capture = VideoCapture(self.videoFileName, self.videoDisplayWidget)
            self.videoDisplayWidget.pauseButton.clicked.connect(self.capture.pause)
        self.capture.start()

    def endCapture(self):
        self.capture.deleteLater()
        self.capture = None

    def loadVideoFile(self):
        try:
            self.videoFileName = QFileDialog.getOpenFileName(self, 'Select a Video File')
            self.isVideoFileLoaded = True
        except:
            print ("Please Select a Video File")

    def closeApplication(self):
        choice = QMessageBox.question(self, 'Message','Do you really want to exit?',QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            print("Closing....")
            sys.exit()
        else:
            pass

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

Solution

  • Solved - I needed to specify self.timer.setTimerType(Qt.PreciseTimer) after the statement self.timer = QTimer() in the function start(self). By default, QTimer() uses a coarse timer. For Windows, the coarse time is 15.6 msec intervals.