OpenCV-PyQT プロジェクトの戦闘 (9) プロジェクト ケース 04: 動画の再生


継続的に更新している「OpenCV-PyQT Project 実戦@Youcans」シリーズへようこそ.
スロット
機構を使ったOpenCV-PyQTプロジェクト戦
(4) OpenCVとPyQtの画像変換
OpenCV-PyQTプロジェクト戦(5) Project case 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()、動画書き込みを閉じてオブジェクトを解放

注:
1. ビデオ ファイルとビデオ ストリームを読み取る場合、ビデオ ファイルとビデオ ストリームのパスを filename で渡します。カメラを使用する場合は、カメラの ID 番号を index 経由で渡します。
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 インスタンスを作成し、タイマーのタイムアウト シグナルを対応するスロット関数に接続し、start() を呼び出す必要があります。タイマーは設定された間隔でタイムアウト シグナルを送信します。

QTimer クラスの一般的なメソッド:

  • start(milliseconds): ミリ秒間隔でタイマーを開始または再開します。タイマーがすでに実行中の場合は、停止して再開します。singleShot シグナルが true の場合、タイマーは 1 回だけ起動されます。
  • Stop(): タイマーを停止します

QTimer クラスで一般的に使用されるシグナル:

  • singleShot: このシグナルは、特定の時間間隔の後にスロットが呼び出されたときに発行されます。
  • timeout: このシグナルは、タイマーが期限切れになったときに発行されます。

注: スロット関数の実行時間は設定できます。デフォルトでは、タイマーがオンになった後、定期的にスロット関数が呼び出されます。setSingleShot(True) が設定されている場合、スロットは 1 回だけ実行されます。


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 で画像の更新を制御します。

この記事で説明する方法は、ビデオのデコードと再生を同じスレッドで実装することです。マルチスレッド化により、動画のデコードと再生を2つのスレッドに分割することも可能です。


3.1 QtDesigner を使用して PyQt5 グラフィカル インターフェイスを開発する

この例の UI は uiDemo4.ui から継承され、次のように変更されています。


ここに画像の説明を挿入

このプロジェクトのグラフィカル インターフェイスの設計が完了したら、それを uiDemo10.ui ファイルとして保存します。

PyCharm で、選択した uiDemo10.ui ファイルを PyUIC を使用して .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) 「一時停止/続行」ボタンは、ビデオ ファイルの再生を一時停止/続行するために使用されます。ボタンの初期表示は「一時停止」です。「一時停止」ボタンを押して再生を一時停止すると、ボタン表示が「続行」に切り替わり、もう一度「続行」ボタンを押して再生を続けると、ボタン表示が「」に切り替わります。一時停止"。

操作結果:

ここに画像の説明を挿入


ここに画像の説明を挿入


【本節終了】


著作権表示:

著作権 2023 youcans、XUPT

作成:2023-2-20


おすすめ

転載: blog.csdn.net/youcans/article/details/129146434