PyQt5のQGraphicsViewに描画した内容をOpenCVで動画として保存する

2020/10/24 categories:PyQt5| tags:Python|OpenCV|PyQt5|

PythonでGUIアプリを作ろうと思ったときに、私が良く使うライブラリはPyQt5です。PyQt5に含まれるQGraphicsViewというクラスは画像を表示したり、図形や文字を表示できたり、それらを重ね合わせて表示できたりしてとても便利です。このQGraphicsViewに表示した内容を動画として保存できれば動画編集ソフトとして活用できるのではないかと思って、まずはQGraphicsViewに表示した内容を動画として保存するプログラムを作成してみました。PyQt5には動画を保存するような機能は無いようなので、OpenCVで動画を保存してみることにします。

処理の流れ

PyQt5のQGraphicsViewへの描画をOpenCVで動画として保存できるか確認するために、以下のような流れのプログラムを作成してみました。

ViewとSceneの作成

下記コードで、ViewとSceneを作成しました。ViewはsetStyleSheetで背景を透過する設定にしています。また、setFrameShapeでフレームが無いという設定にしないと、出力した動画が枠が付いた状態になってしまいますので、枠が無いほうがいい場合はsetFrameShapeでフレーム無しとする必要があります。動画の解像度はQGraphicsSceneのwidthとheightの値になり、今回は640×480の解像度の動画としました。

view = QtWidgets.QGraphicsView()
view.setStyleSheet("background-color: transparent;")
view.setFrameShape(QtWidgets.QFrame.NoFrame)
scene = QtWidgets.QGraphicsScene(0, 0, 640, 480, view)
view.setScene(scene)

OpenCVの動画書き込み用のオブジェクト

下記コードで動画書き込み用のオブジェクトを作成し、今回はmp4vのコーデックでmp4のコンテナの動画を作成する設定にしました。ここで指定した動画のコーデックと出力した動画の解像度の組み合わせによっては、出力した動画が再生できないことがあったので注意が必要です。ちなみにmp4vのコーデックで動画の解像度を320×240で出力した場合は再生できませんでしたが、mp4vで640×480なら再生が出来ました。

codec = cv2.VideoWriter_fourcc(*'mp4v')
video = cv2.VideoWriter('temp.mp4', codec, 30.0, (view.size().width(), view.size().height()))

フレーム番号をQGraphicsSceneに描画

下記コードのように、QGraphicsTextItemを使ってQGraphicsSceneに文字列を追加します。文字列はフレーム番号として、フォントサイズや色を指定して追加しています。

text = QtWidgets.QGraphicsTextItem(str(i))
text.setPos(0, 0)
font = QtGui.QFont()
font.setPixelSize(80)
text.setFont(font)
text.setDefaultTextColor(QtGui.QColor(255, 0, 0))
scene.addItem(text)

QGraphicsViewから画像を取得

QGraphicsView.grab()でQPixmapを取得できます。このQPixmapを作成した関数pixmap_to_cvに渡すことで、QPixmapからOpenCV用のNumpy arrayに変換します。その後、Numpy arrayを動画書き込み用のオブジェクトvideoに渡すことで、QGraphicsViewの描画内容を動画のフレームとしてセットできます。

pixmap = view.grab() frame = pixmap_to_cv(pixmap) video.write(frame)

def pixmap_to_cv(pixmap): qimage = pixmap.toImage() w, h, d = qimage.size().width(), qimage.size().height(), qimage.depth() bytes_ = qimage.bits().asstring(w * h * d // 8) arr = np.frombuffer(bytes_, dtype=np.uint8).reshape((h, w, d // 8)) im_bgr = cv2.cvtColor(arr, cv2.COLOR_BGRA2BGR) return im_bgr

動画のリリース

video.release()で動画を開放することで動画の保存が完了します。

出力された動画

所感

今回のプログラムはQGraphicsViewを動画上に色々描画するキャンバスとして使用しているだけで、PyQt5を使ってはいますがGUIとしては何も表示されません。しかしGUIライブラリの特定の機能を使用することで、Pythonでも動画編集が出来そうだなと感じました。特に、パソコンの画面上に何も描画させることなく動画を作成できたので結構便利かなと思います。PyQt5の他にもPILやその他のライブラリで動画が作成できないか調べてみたいなと思います。

ソースコード

import cv2
import numpy as np
import sys
from PyQt5 import QtWidgets, QtCore, QtGui

def main():
    view = QtWidgets.QGraphicsView()
    view.setStyleSheet("background-color: transparent;")
    view.setFrameShape(QtWidgets.QFrame.NoFrame)
    scene = QtWidgets.QGraphicsScene(0, 0, 640, 480, view)
    view.setScene(scene)

    codec = cv2.VideoWriter_fourcc(*'mp4v')
    video = cv2.VideoWriter('temp.mp4', codec, 30.0, (view.size().width(), view.size().height()))

    for i in range(30*5):
        for item in scene.items():
            scene.removeItem(item)

        text = QtWidgets.QGraphicsTextItem(str(i))
        text.setPos(0, 0)
        font = QtGui.QFont()
        font.setPixelSize(80)
        text.setFont(font)
        text.setDefaultTextColor(QtGui.QColor(255, 0, 0))
        scene.addItem(text)

        pixmap = view.grab()
        frame = pixmap_to_cv(pixmap)
        video.write(frame)
    
    video.release()

def pixmap_to_cv(pixmap):
    qimage = pixmap.toImage()
    w, h, d = qimage.size().width(), qimage.size().height(), qimage.depth()
    bytes_ = qimage.bits().asstring(w * h * d // 8)
    arr = np.frombuffer(bytes_, dtype=np.uint8).reshape((h, w, d // 8))
    im_bgr = cv2.cvtColor(arr, cv2.COLOR_BGRA2BGR)
    return im_bgr

if __name__ == "__main__":
    app   = QtWidgets.QApplication(sys.argv)
    main()

Share post

Related Posts

コメント