LoginSignup
26
15

More than 1 year has passed since last update.

OpenCV でリアルタイム生成した画像を可変フレームレート動画として保存する

Last updated at Posted at 2021-12-16

本記事は OpenCV Advent Calendar 2021 第17日の記事です


OpenCV で動画像処理をする典型的なパターンとして、(1) VideoCapture でウェブカメラなどから画像を取得 → (2)なんやかんやする → (3) VideoWriter で動画として保存、というやり方を取ることがよくありますが、この設計でよくぶつかる問題があります。
(1) のウェブカメラでは一定のフレームレートで取得することが出来たとしても、大抵の用途では (2) なんやかんやするの所で時間がかかる処理をするため、フレーム落ちすることがよくあります。また、かかる時間も一定ではないため (3) VideoWriter で設定する出力フレームレートを合わせないと、早送りしたようなおかしな動画になってしまいます。
結局何が問題かというと、VideoWriter はあまり機能が豊富でなく、固定フレームレートしかサポートしていないため、フレームレートを成り行きに合わせてくれないということです。

このような場合は、もっと機能豊富な ffmpeg (を Python から使う ffmpeg-python)を使って動画保存するのが良い方法です。

ffmpeg-python で動画を保存するには

ffmpeg-python は ffmpeg プログラムを外部プロセスとしてうまいこと簡単に実行してくれるライブラリです。
自分のプログラムとの画像データのやり取りは、標準入出力をパイプとして繋げることで行います。
リアルタイム処理ではなく、単に画像を動画ファイルとして保存するには以下のようにします。

process = (
    ffmpeg
    .input('pipe:', format='rawvideo', pix_fmt='bgr24',
           s='{}x{}'.format(width, height))
    .output('output.avi')
    .overwrite_output()
    .run_async(pipe_stdin=True)
)
process.stdin.write(image.astype(np.uint8).tobytes()) # これを必要なだけ繰り返す
# 最後にこれを入れないと動画ファイルの終端処理がされないので、
# 動画プレイヤーなどで再生できない壊れたファイルになってしまう
process.stdin.close()
process.wait()

この場合、標準入力のデータには画像の輝度値しか渡すことが出来ませんので、ffmpegはこのフレームがいつのものか(タイムスタンプ)がわかりません。そのため実時間ではなく固定のフレームレートとみなして保存してしまい、早送りしたような動画になってしまうのです。
リアルタイム生成でないときはなぜ問題にならないかというと、ffmpeg は通常はリアルタイム画像ではなく、既に保存された動画ファイルなどをバッチ処理することを目的としているからです(その場合は入力動画ファイルのフレームレートを知ることができるので、それに合わせることができる)。OpenCV の VideoWriter がうまく行かないのと同じ理由です。

ffmpeg-python でリアルタイムの画像を可変フレームレート動画として保存するには

そこで、入力フィルターの -use_wallclock_as_timestamps オプションと、出力フィルターの -vsync オプションを使います。

process = (
    ffmpeg
    .input('pipe:', format='rawvideo', pix_fmt='bgr24',
           s='{}x{}'.format(width, height), use_wallclock_as_timestamps=1)
    .output('output.avi', vsync='vfr', r=60.0)
    .overwrite_output()
    .run_async(pipe_stdin=True)
)

入力フィルターの -use_wallclock_as_timestamps オプションは、フレームのタイムスタンプにマシンの時計を使う設定です。これによって、プログラムから入力パイプを通して渡された画像データのタイムスタンプをリアルタイム時刻に決定することが出来ます。
出力フィルターの -vsync=vfr オプションは、出力フレームレートを可変にするオプションです。-r を共に使うと最大フレームレートを指定することが出来ます。

パッケージのインストール

$ pip install ffmpeg-python opencv-python numpy

ソース

「なんやかんやする」の部分は人それぞれなので、例では適当にリサイズ処理を入れておきます。

import cv2
import ffmpeg
import numpy as np

width = 480
height = 320

cap = cv2.VideoCapture(0)
process = (
    ffmpeg
    .input('pipe:', format='rawvideo', pix_fmt='bgr24',
           s='{}x{}'.format(width, height), use_wallclock_as_timestamps=1)
    .output('D:/output.avi', vsync='vfr', r=60.0)
    .overwrite_output()
    .run_async(pipe_stdin=True)
)

while (True):
    ret, frame = cap.read()
    if not ret:
        break
    resized = cv2.resize(frame, (width, height))
    cv2.imshow('image', resized)
    if cv2.waitKey(1) != -1:
        break
    process.stdin.write(resized.astype(np.uint8).tobytes())

process.stdin.close()
process.wait()

実行時のログ

Input #0, rawvideo, from 'pipe:':
  Duration: N/A, start: 1639054782.520000, bitrate: 92160 kb/s
    Stream #0:0: Video: rawvideo (BGR[24] / 0x18524742), bgr24, 480x320, 92160 kb/s, 25 tbr, 25 tbn, 25 tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> mpeg4 (native))
Output #0, avi, to 'D:/output.avi':
  Metadata:
    ISFT            : Lavf58.29.100
    Stream #0:0: Video: mpeg4 (FMP4 / 0x34504D46), yuv420p, 480x320, q=2-31, 200 kb/s, 60 fps, 60 tbn, 60 tbc
    Metadata:
      encoder         : Lavc58.54.100 mpeg4
    Side data:
      cpb: bitrate max/min/avg: 0/0/200000 buffer size: 0 vbv_delay: -1
frame=  482 fps= 25 q=5.0 Lsize=     461kB time=00:00:19.18 bitrate= 196.8kbits/s speed=0.999x
video:428kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 7.653102%
[ WARN:0] global D:\a\opencv-python\opencv-python\opencv\modules\videoio\src\cap_msmf.cpp (438) `anonymous-namespace'::SourceReaderCB::~SourceReaderCB terminating async callback

speed=0.999x と、ほぼ1.0でリアルタイムな時間になっているらしいことがわかります。

参考文献

26
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
15