OpenCVで動画を読み込み・書き出す方法

Pythonでは、OpenCVライブラリを使用して動画ファイルの操作を行うことができます。「動画を1枚ずつの画像に分けたい」「Pythonで動画を作りたい」といった場面で役立ちます。この記事では、インストールから動画の読み取り・書き出しまでを、動かせるコードで順に説明します。
この記事で分かることは次のとおりです。

  • OpenCVのインストールと、動画を扱える環境かどうかの確認方法
  • 動画をフレーム(静止画)に分けてJPEG保存する方法
  • FPS・フレームサイズ・総フレーム数の取得方法
  • 生成したフレームを動画として書き出す方法

インストール

OpenCVはpipでインストールします。Pythonで読み込むときのモジュール名はcv2です。

pip install opencv-python

動画を扱える環境かを確認する

動画の読み書きは、OpenCVの内部でFFMPEGなどのバックエンド(実際の処理を担当するライブラリ)が行います。getBuildInformation()を使うと、手元のOpenCVで利用できるバックエンドを確認できます。

import cv2

print(cv2.getBuildInformation())

ずらずらと表示されますが、Video I/O:の項目を確認します。以下は表示例で、内容は環境によって異なります。

  Video I/O:
    DC1394:                      NO
    FFMPEG:                      YES
      avcodec:                   YES (59.37.100)
      avformat:                  YES (59.27.100)
      avutil:                    YES (57.28.100)
      swscale:                   YES (6.7.100)
      avresample:                NO
    GStreamer:                   NO
    v4l/v4l2:                    YES (linux/videodev2.h)

FFMPEG: YESなら、動画の読み書きの土台は使えます。
ただし、FFMPEGがYESでもH.264やH.265(HEVCとも呼ばれます)で書き出せるとは限りません。ビルドやエンコーダ(動画を圧縮する部品)の有無によって使えないことがあり、環境によって異なります。私がOpenCV 4.13で試した環境では、FFMPEG: YESでもavc1・H264(H.264)とhev1(H.265)の書き出しは失敗し、成功したのはmp4vだけでした。本記事の書き出し例はmp4vを使います。

動画ファイルの読み取り

動画をフレーム(動画を構成する1枚1枚の静止画)に分けて、JPEG画像として保存します。
動画の読み取りの例では、入力動画(./video/input.mp4)と画像の保存先フォルダ(./images)を事前に用意してください。無いとエラー(FileNotFoundError など)になります。入力動画が無い場合はisOpened()が False になってFile open errorと表示され、保存先フォルダが無い場合は FileNotFoundError になります。

動画からフレームを取得する

import cv2
import codecs
import numpy as np

input_video_path = './video/input.mp4'
video_capture = cv2.VideoCapture(input_video_path)

if not video_capture.isOpened():
    print('File open error')
    exit()

cnt = 1
while True:
    # フレームを読み込む
    ret, frame = video_capture.read()
    if not ret or frame is None:
        break

    ret, encoded = cv2.imencode('.jpg', frame)
    if not ret:
        continue

        # フレームをjpeg形式で保存
    jpeg_bytes = encoded.tobytes()
    with codecs.open(f'./images/{cnt}.jpg', 'wb') as f_out:
        f_out.write(jpeg_bytes)
        cnt += 1
#;

# リソースを解放
video_capture.release()

コードの流れは次のとおりです。

  • VideoCaptureで動画を開き、isOpened()で開けたかを確認
  • read()で1フレームずつ読み込み、読み込めなくなったらループを終了
  • imencode()でJPEGのバイト列に変換し、1.jpg、2.jpgと連番で保存
  • 最後にrelease()で動画ファイルを解放

cv2.imwriteは、環境によって日本語を含むパスに保存できない場合があります。この例ではバイト列にしてから、Pythonのファイル書き込みで保存しています。

動画のフレームレート(FPS)を取得する

以降の3つのコードは、上の例のvideo_captureを使う前提です。isOpened()の確認後、release()の前に実行してください。
FPS(Frames Per Second)は、1秒間に表示するフレーム数です。30であれば、1秒間に30枚の画像を切り替えて動画にします。

fps = video_capture.get(cv2.CAP_PROP_FPS)

動画のフレームサイズを取得する

フレームサイズは、画像の幅と高さ(ピクセル数)です。get()は小数(float)で値を返すため、int()で整数にします。

frame_width = int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))

動画の総フレーム数を取得する

総フレーム数を FPS で割ると、動画の長さ(秒)の目安になります。処理の進捗表示にも使えます。

frame_count = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))

動画ファイルの書き出し

NumPyで作ったフレームを、1本の動画にまとめます。次の例では、黒い画像に「Frame 0」のような文字を描いた100フレームを、30FPSの動画にします。出力先は./video/output.mp4なので、./videoフォルダを用意してください。

import cv2
import numpy as np

# 動画の設定
frame_width = 640
frame_height = 480
fps = 30

fourcc = cv2.VideoWriter_fourcc(*'mp4v')
video_writer = cv2.VideoWriter('./video/output.mp4', fourcc, fps, (frame_width, frame_height))

# フレームを生成して動画に書き込む
for i in range(100):  # 100フレーム作成
    # フレームを生成(ここでは単色のフレームを生成)
    frame = np.zeros((frame_height, frame_width, 3), dtype=np.uint8)
    cv2.putText(frame, f"Frame {i}", (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)

    # フレームを書き込む
    video_writer.write(frame)
#;

# リソースを解放
video_writer.release()

ポイントは次のとおりです。

  • fourccは動画の圧縮形式(コーデック)を表す4文字のコード
  • VideoWriterの引数は、出力パス・fourcc・FPS・フレームサイズ
  • フレームサイズは(幅, 高さ)の順で、NumPy配列の(高さ, 幅, 3)とは順番が逆
  • 書き込むフレームは、指定したサイズと同じ大きさにそろえることが必要
  • write()で1フレームずつ追加し、最後にrelease()でファイルを閉じる処理

動画ファイルが作られない場合は、video_writer.isOpened()で開けているかを確認してください。False であれば、fourcc が環境で使えない可能性があります。まずはmp4vで試します。

まとめ

動画の読み書きは、VideoCaptureで開いてread()でフレームを取り出し、VideoWriterのwrite()で書き出す流れです。つまずきやすいのは、環境ごとに使えるコーデックが違う点です。

このエントリーをはてなブックマークに追加
にほんブログ村 IT技術ブログへ

コメント

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です