You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用OpenCV与PyQt5播放视频时速度过快,如何实现正常帧率播放?

Fixing Fast Video Playback in PyQt5 + OpenCV QThread Setup

Hey there! Let's get your video playing at normal speed instead of that hyper-fast mode you're seeing. The core issue here is that your current code is pumping out frames as quickly as your CPU can process them—no regard for the video's actual frame rate. Those cv2.CAP_PROP_FPS and cv2.CAP_PROP_BUFFERSIZE settings don't help because you aren't using the frame rate to regulate the playback timing.

Why Your Current Code Isn't Working

Right now, your VideoThread.run() method just loops non-stop, reading frames and emitting them immediately. There's no delay between frames to match the video's intended speed. Even if you read the FPS value, you need to use it to calculate how long to wait between each frame update.

The Fix: Add Timing Control & Safe Thread Termination

We'll make two key changes:

  1. Calculate the correct delay between frames using the video's actual FPS.
  2. Replace the unsafe terminate() call with a proper thread stop flag to avoid memory leaks.

Here's the modified code with explanations:

import os
import cv2
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
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
        # Add a flag to safely control thread execution
        self.is_running = True

    def run(self):
        cap = cv2.VideoCapture(self.get_file_dir)
        # Get the video's actual frame rate
        fps = cap.get(cv2.CAP_PROP_FPS)
        # Calculate delay between frames in milliseconds
        frame_delay = int(1000 / fps) if fps > 0 else 30  # Fallback to 30fps if FPS is invalid

        while self.is_running:
            ret, cv_img = cap.read()
            if ret:
                self.change_pixmap_signal.emit(cv_img)
                # Wait for the calculated delay to match real playback speed
                QThread.msleep(frame_delay)
            else:
                # End of video: reset or break the loop
                break
        cap.release()

    def stop(self):
        # Safely stop the thread by setting the flag
        self.is_running = False
        self.wait()

class Ui_MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(Ui_MainWindow, self).__init__(parent)
        self.setupUI()
        # Initialize thread variable to avoid AttributeError
        self.thread = None

    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.lower().endswith(fe):  # Use lower() to handle case-insensitive extensions
                    item = QtWidgets.QListWidgetItem(str(i))
                    self.listWidget.addItem(item)

    def itemclick(self):
        # Stop existing thread safely if it's running
        if self.thread is not None and self.thread.isRunning():
            self.thread.stop()
        
        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_())

Key Changes Explained:

  • Thread Safety: Added self.is_running flag and stop() method to safely halt the thread instead of using terminate() (which can leave resources like the video capture open).
  • Frame Timing: Calculated frame_delay based on the video's FPS, then used QThread.msleep(frame_delay) to pause between frames—this ensures each frame stays on screen for the correct amount of time.
  • Extension Check: Updated the file extension check to use endswith() and lower() to catch filenames with uppercase extensions (like .MP4).
  • Thread Initialization: Added self.thread = None to avoid AttributeErrors when stopping a thread that hasn't been created yet.

Bonus Tips:

  • If you notice slight jitter, you can adjust the delay calculation or use QThread.usleep() for microsecond precision.
  • For better performance, consider resizing frames in the thread instead of the convert_cv_qt method—this offloads work from the main UI thread.

内容的提问来源于stack exchange,提问作者gentales17

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 13:42:47