OpenCV-PyQT项目实战项目案例04:视频播放

Posted YouCans

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV-PyQT项目实战项目案例04:视频播放相关的知识,希望对你有一定的参考价值。

欢迎关注『OpenCV-PyQT项目实战 @ Youcans』系列,持续更新中
OpenCV-PyQT项目实战(1)安装与环境配置
OpenCV-PyQT项目实战(2)QtDesigner 和 PyUIC 快速入门
OpenCV-PyQT项目实战(3)信号与槽机制
OpenCV-PyQT项目实战(4)OpenCV 与PyQt的图像转换
OpenCV-PyQT项目实战(5)项目案例01:图像模糊
OpenCV-PyQT项目实战(6)项目案例02:滚动条应用
OpenCV-PyQT项目实战(7)项目案例03:鼠标框选
OpenCV-PyQT项目实战(8)项目案例04:鼠标定位
OpenCV-PyQT项目实战(9)项目案例04:视频播放

文章目录

OpenCV-PyQT项目实战(9)项目案例05:视频播放

本节介绍OpenCV和PyQt 实现视频播放。

PyQt 不支持视频解码。本例使用 OpenCV对视频文件进行解码获得图像帧,然后用 QTime 定时器和 QThread 的方式来控制 QLabel 中的图像更新。


1. 通过OpenCV 读取视频文件,在 PyQt 控件中播放

1.1 OpenCV 读取视频文件

视频文件是由一系列图像组成的,视频的每一帧都是一幅图像。
OpenCV提供了VideoCapture类和VideoWriter类处理视频流,既可以处理视频文件,也可以处理摄像头设备。

函数原型:

cv.VideoCapture( index[, apiPreference] ) → <VideoCapture object>
cv.VideoCapture(filename[, apiPreference]) → <VideoCapture object>
cv.VideoWriter([filename, fourcc, fps, frameSize[, isColor]]) → <VideoWriter object>

VideoCapture类用于读取视频文件、视频流或从摄像机捕获视频,VideoWriter类用于视频文件的写入和保存。
构造函数cv.VideoCapture和cv.VideoWrite用于实现类的初始化。

参数说明:
● index:摄像头的 ID 编号,0 表示默认后端打开默认摄像机
● filename:读取或保存的视频文件的路径,包括扩展名
● apiPreference:读取视频流的属性设置
● fourcc:用于压缩帧的编码器/解码器的字符代码,
● fps:视频流的帧速率
● frameSize:元组 (w, h),视频帧的宽度和高度
● isColor:是否彩色图像

成员函数:

  • cv.VideoCapture.isOpened(),检查视频捕获是否初始化成功
  • cv.VideoCapture.read(),捕获视频文件、视频流或捕获的视频设备
  • cv.VideoCapture.release(),关闭视频文件或设备,释放对象
  • cv.VideoWriter.release(),关闭视频写入,释放对象

注意问题:
⒈读取视频文件、视频流中读取时,通过 filename 传递视频文件、视频流的路径。使用摄像头时,通过 index 传递摄像头的 ID 号。
2视频处理过程较为复杂,一些程序设置与具体系统环境有关,本文只介绍基本的成员函数,通用的处理方法。更多内容详见:[https://docs.opencv.org/]。


1.2 例程:OpenCV 播放视频文件

视频读取的基本步骤为:
(1)创建视频读取/捕获对象;
(2)获取视频的一帧图像;
(3)检查视频获取是否成功;
(4)释放视频读取/捕获对象。

import cv2 as cv

if __name__ == '__main__':
    # 创建视频读取/捕获对象
    vedioRead = "../images/nasa_m420p.mov"  # 读取视频文件的路径
    capRead = cv.VideoCapture(vedioRead)  # 实例化 VideoCapture 类

    # 读取视频文件
    frameNum = 0  # 视频帧数初值
    while capRead.isOpened():  # 检查视频捕获是否成功
        ret, frame = capRead.read()  # 读取下一帧视频图像
        if ret is True:
            cv.imshow(vedioRead, frame)  # 播放视频图像
            if cv.waitKey(1) & 0xFF == ord('q'):  # 按 'q' 退出
                break
        else:
            print("Can't receive frame at frameNum ".format(frameNum))
            break

    capRead.release()  # 关闭读取视频文件
    capWrite.release()  # 关闭视频写入对象
    cv.destroyAllWindows()  # 关闭显示窗口

2. PyQt 中的 QTimer定时器

使用 OpenCV 对视频文件进行解码获得图像帧以后,可以使用 QTime 定时器来控制 QLabel 控件中的图像更新,实现视频播放。

2.1 PyQt 中的QTimer类

PyQt5 中的 QTimer类提供了重复的和单次的定时器,为计时器提供了高级编程接口。
要使用定时器,需要先创建一个QTimer实例,将定时器的timeout信号连接到相应的槽函数,并调用start(),定时器就会以设定的间隔发出timeout信号。

QTimer类中的常用方法:

  • start(milliseconds):启动或重新启动定时器,时间间隔为毫秒。如果定时器已经运行,它将被停止并重新启动。如果singleShot信号为真,定时器将仅被激活一次。
  • Stop():停止定时器

QTimer类中的常用信号:

  • singleShot:在给定的时间间隔后调用一个槽函数时发射此信号。
  • timeout:当定时器超时时发射此信号。

注意:可以设置槽函数的执行次数,默认为定时器开启后周期性调用槽函数。如果设置了setSingleShot(True),则槽函数仅执行一次。


2.2 例程:QTimer定时器的使用

from PyQt5.QtCore import QTimer

self.timer = QTimer(self) #初始化一个定时器
self.timer.timeout.connect(self.operate) #计时结束则调用槽函数operate()
self.timer.start(1000) #设置计时间隔 1000ms

def operate(self):  # 自定义的槽函数
    # 具体操作,以下示例
    time = QDateTime.currentDateTime()
    # 时间显示格式
    str = time.toString("yyyy-mm-dd hh:mm:ss");
    self.lcdNumber.display(str);


2.3 例程:使用 QTimer 在PyQt 控件中播放解码的图像帧

    self.timerCam = QtCore.QTimer()  # 定时器,毫秒
    self.timerCam.timeout.connect(self.refreshFrame)  # 计时器结束时调用槽函数刷新当前帧

    def refreshFrame(self):  # 刷新视频图像
        ret, self.frame = self.cap.read()  # 读取下一帧视频图像
        qImg = self.cvToQImage(self.frame)  # OpenCV 转为 PyQt 图像格式
        self.label_1.setPixmap((QPixmap.fromImage(qImg)))  # 加载 PyQt 图像
        return


3. 项目实战:PyQt 视频播放器

本项目基于 PyQt5 GUI 实现一个视频播放器。具体方法使用 OpenCV对视频文件进行解码获得图像帧,然后用 QTime 定时器和 QThread 的方式来控制 QLabel 中的图像更新。

本文介绍的方法是在同一个线程中实现视频的解码和播放。也可以通过多线程操作,将视频的解码和播放分为两个线程。


3.1 使用 QtDesigner 开发 PyQt5 图形界面

本例的 UI 继承自 uiDemo4.ui ,并进行修改如下:


完成了本项目的图形界面设计,将其保存为 uiDemo10.ui文件。

在 PyCharm中,使用 PyUIC 将选中的 uiDemo10.ui 文件转换为 .py 文件,就得到了 uiDemo10.py 文件。



3.2. 完整例程:视频播放程序 OpenCVPyqt10.py

# OpenCVPyqt10.py
# Demo05 of GUI by PyQt5
# Copyright 2023 Youcans, XUPT
# Crated:2023-02-20

import sys
import cv2 as cv
import numpy as np
from PyQt5 import QtCore
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from uiDemo10 import Ui_MainWindow  # 导入 uiDemo10.py 中的 Ui_MainWindow 界面类

class MyMainWindow(QMainWindow, Ui_MainWindow):  # 继承 QMainWindow 类和 Ui_MainWindow 界面类
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)  # 初始化父类
        self.setupUi(self)  # 继承 Ui_MainWindow 界面类
        self.timerCam = QtCore.QTimer()  # 定时器,毫秒
        self.cap = None  #
        self.frameNum = 1  # 视频帧数初值

        # 菜单栏
        self.actionOpen.triggered.connect(self.openVideo)  # 连接并执行 openSlot 子程序
        self.actionHelp.triggered.connect(self.trigger_actHelp)  # 连接并执行 trigger_actHelp 子程序
        self.actionQuit.triggered.connect(self.close)  # 连接并执行 trigger_actHelp 子程序

        # 通过 connect 建立信号/槽连接,点击按钮事件发射 triggered 信号,执行相应的子程序 click_pushButton
        self.pushButton_1.clicked.connect(self.openVideo)  # 打开视频文件
        self.pushButton_2.clicked.connect(self.playVideo)  # 播放视频文件
        self.pushButton_3.clicked.connect(self.pauseVideo)  # 停止视频播放
        self.pushButton_4.clicked.connect(self.trigger_actHelp)  # 按钮触发
        self.pushButton_5.clicked.connect(self.close)  # 点击 # 按钮触发:关闭
        self.timerCam.timeout.connect(self.refreshFrame)  # 计时器结束时调用槽函数刷新当前帧

        # 初始化
        return

    def openVideo(self):  # 读取视频文件,点击 pushButton_1 触发
        self.videoPath, _ = QFileDialog.getOpenFileName(self, "Open Video", "../images/", "*.mp4 *.avi *.flv")
        print("Open Video: ", self.videoPath)
        return

    def playVideo(self):  # 播放视频文件,点击 pushButton_2 触发
        if self.timerCam.isActive()==False:
            self.cap = cv.VideoCapture(self.videoPath)  # 实例化 VideoCapture 类
            if self.cap.isOpened():  # 检查视频捕获是否成功
                self.timerCam.start(20)  # 设置计时间隔并启动,定时结束将触发刷新当前帧
        else:  # 
            self.timerCam.stop()  # 停止定时器
            self.cap.release()  # 关闭读取视频文件
            self.label_1.clear()  # 清除显示内容

    def pauseVideo(self):
        self.timerCam.blockSignals(False)  # 取消信号阻塞,恢复定时器
        if self.timerCam.isActive() and self.frameNum%2==1:
            self.timerCam.blockSignals(True)  # 信号阻塞,暂停定时器
            self.pushButton_3.setText("3 继续")  # 点击"继续",恢复播放
            print("信号阻塞,暂停播放。", self.frameNum)
        else:
            self.pushButton_3.setText("3 暂停")  # 点击"暂停",暂停播放
            print("取消阻塞,恢复播放。", self.frameNum)
        self.frameNum = self.frameNum + 1

    def closeEvent(self, event):
        if self.timerCam.isActive():
            self.timerCam.stop()

    def refreshFrame(self):  # 刷新视频图像
        ret, self.frame = self.cap.read()  # 读取下一帧视频图像
        qImg = self.cvToQImage(self.frame)  # OpenCV 转为 PyQt 图像格式
        self.label_1.setPixmap((QPixmap.fromImage(qImg)))  # 加载 PyQt 图像
        return

    def cvToQImage(self, image):
        # 8-bits unsigned, NO. OF CHANNELS=1
        if image.dtype == np.uint8:
            channels = 1 if len(image.shape) == 2 else image.shape[2]
        if channels == 3:  # CV_8UC3
            # Create QImage with same dimensions as input Mat
            qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_RGB888)
            return qImg.rgbSwapped()
        elif channels == 1:
            # Create QImage with same dimensions as input Mat
            qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_Indexed8)
            return qImg
        else:
            QtCore.qDebug("ERROR: numpy.ndarray could not be converted to QImage. Channels = %d" % image.shape[2])
            return QImage()


    def trigger_actHelp(self):  # 动作 actHelp 触发
        QMessageBox.about(self, "About",
                          """数字图像处理工具箱 v1.0\\nCopyright YouCans, XUPT 2023""")
        return

if __name__ == '__main__':
    app = QApplication(sys.argv)  # 在 QApplication 方法中使用,创建应用程序对象
    myWin = MyMainWindow()  # 实例化 MyMainWindow 类,创建主窗口
    myWin.show()  # 在桌面显示控件 myWin
    sys.exit(app.exec_())  # 结束进程,退出程序


3.3 程序说明

(1)“打开”按钮用于从文件夹选择播放的视频文件。

(2)“播放”按钮用于播放打开的视频文件,播放结束后自动关闭。

(3)“暂停/继续”按钮用于暂停/继续播放视频文件。按钮初始显示为“暂停”,按下“暂停”按钮后暂停播放,按钮显示切换为“继续”;再次按下“继续”按钮后继续播放,按钮显示切换为“暂停”。

运行结果:



【本节完】


版权声明:

Copyright 2023 youcans, XUPT

Crated:2023-2-20


以上是关于OpenCV-PyQT项目实战项目案例04:视频播放的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV-PyQT项目实战(12)项目案例08:多线程视频播放

OpenCV-PyQT项目实战(12)项目案例08:多线程视频播放

OpenCV-PyQT项目实战(10)项目案例06:键盘事件与视频抓拍

OpenCV-PyQT项目实战(10)项目案例06:键盘事件与视频抓拍

OpenCV-PyQT项目实战(11)项目案例07:摄像头操作与拍摄视频

OpenCV-PyQT项目实战(11)项目案例07:摄像头操作与拍摄视频