![]() |
OpenCV 4.13.0
Open Source Computer Vision
|
前のチュートリアル: GDALを用いた地理空間ラスタファイルの読み込み
次のチュートリアル: OpenCVで動画を作成する
| 原著者 | Bernát Gábor |
| 互換性 | OpenCV >= 3.0 |
今日では、デジタル映像記録システムを手元に持つことは一般的だ。そのため、いずれは一連の画像のバッチを処理するのではなく、ビデオストリームを処理する状況に出くわすことになる。これらは2種類ある: リアルタイムの画像入力(ウェブカメラの場合)、あるいは事前に録画されハードディスクドライブに保存されたファイルだ。幸いOpenCVはこれら2つを、同じC++クラスで同じように扱う。そこで、このチュートリアルで学ぶ内容は次のとおり:
OpenCVを使ってこれらを披露するテストケースとして、2つのビデオファイルを読み込んで両者の類似度チェックを行う小さなプログラムを作成した。これは、新しいビデオ圧縮アルゴリズムがどれだけうまく動作するかを確認するのに使えるものだ。この小さなMegamindクリップのような参照(オリジナル)動画と、その圧縮版があるとしよう。ソースコードとこれらのビデオファイルは、OpenCVソースライブラリの samples/data フォルダにもある。
本質的に、ビデオ操作に必要なすべての機能は cv::VideoCapture C++クラスに統合されている。これ自体はFFmpegオープンソースライブラリの上に構築されている。これはOpenCVの基本的な依存ライブラリなので、気にする必要はないだろう。動画は連続した画像で構成されており、文献ではこれらをフレームと呼ぶ。ビデオファイルの場合、2つのフレームの間隔を指定する フレームレート がある。ビデオカメラの場合は通常、1秒間にデジタル化できるフレーム数に上限があるが、カメラはどの時点でも世界の現在のスナップショットを捉えているため、この特性はそれほど重要ではない。
最初に行うべきタスクは、cv::VideoCapture クラスにそのソースを割り当てることだ。これは cv::VideoCapture::VideoCapture または cv::VideoCapture::open 関数を通じて行える。この引数が整数であれば、クラスをカメラ、つまりデバイスにバインドする。ここで渡す番号は、オペレーティングシステムによって割り当てられたデバイスのIDだ。システムにカメラが1台だけ接続されている場合、そのIDはおそらく0で、それ以降のものは増加していく。これらに渡すパラメータが文字列の場合、それはビデオファイルを指し、その文字列はファイルの場所と名前を指し示す。例えば、上のソースコードに対して有効なコマンドラインは次のとおり:
類似度チェックを行う。これには参照用とテスト用のビデオファイルが必要だ。最初の2つの引数がこれらを指す。ここでは相対アドレスを使う。これは、アプリケーションが現在の作業ディレクトリ内を見てvideoフォルダを開き、その中から Megamind.avi と Megamind_bug.avi を見つけようとすることを意味する。
クラスのビデオソースへのバインドが成功したかどうかを確認するには、cv::VideoCapture::isOpened 関数を使う:
オブジェクトのデストラクタが呼ばれると、ビデオは自動的に閉じられる。しかし、それより前に閉じたい場合は cv::VideoCapture::release 関数を呼ぶ必要がある。動画のフレームは単なる画像にすぎない。したがって、cv::VideoCapture オブジェクトからそれらを取り出して Mat に入れるだけでよい。ビデオストリームは逐次的だ。cv::VideoCapture::read またはオーバーロードされた >> 演算子によって、フレームを次々に取得できる:
上のread操作は、フレームを取得できなかった場合(ビデオストリームが閉じられたか、ビデオファイルの終わりに達したかのいずれか)、Mat オブジェクトを空のままにする。これは簡単なifで確認できる:
readメソッドは、フレームのグラブと、それに適用されるデコードから成る。cv::VideoCapture::grab とそれに続く cv::VideoCapture::retrieve 関数を使って、この2つを明示的に呼ぶこともできる。
動画には、フレームの内容以外にも非常に多くの情報が付随している。これらは通常は数値だが、場合によっては短い文字列(4バイト以下)であることもある。このため、これらの情報を取得するには cv::VideoCapture::get という汎用関数があり、これらの特性を含むdouble値を返す。double型から文字をデコードするにはビット演算を、有効な値が整数のみの場合は型変換を使う。その唯一の引数は、問い合わせる特性のIDだ。例えばここでは、参照用とテスト用のビデオファイルにおけるフレームのサイズと、参照用のフレーム数を取得する。
動画を扱う際には、これらの値を自分で制御したいことがよくあるだろう。これを行うには cv::VideoCapture::set 関数がある。その第1引数は変更したい特性の名前のままで、設定する値を含むdouble型の第2引数がある。成功すればtrueを、そうでなければfalseを返す。この良い例は、ビデオファイル内で指定した時刻やフレームへシークすることだ:
読み取りや変更が可能な特性については、cv::VideoCapture::get および cv::VideoCapture::set 関数のドキュメントを参照すること。
動画変換操作がどれだけ知覚されないかを確認したいので、フレームごとに類似度や差異をチェックする仕組みが必要だ。これに最もよく使われるアルゴリズムはPSNR(別名 ピーク信号対雑音比 (Peak signal-to-noise ratio))だ。最も単純な定義は 平均二乗誤差 (mean squared error) から始まる。2枚の画像 I1 と I2 があるとし、2次元のサイズ i と j を持ち、c 個のチャンネルで構成されているとする。
\[MSE = \frac{1}{c*i*j} \sum{(I_1-I_2)^2}\]
するとPSNRは次のように表される:
\[PSNR = 10 \cdot \log_{10} \left( \frac{MAX_I^2}{MSE} \right)\]
ここで \(MAX_I\) はピクセルが取りうる最大の有効値だ。ピクセルあたりチャンネルあたり1バイトの単純な画像の場合、これは255だ。2枚の画像が同一の場合、MSEはゼロになり、PSNRの式で無効なゼロ除算が発生する。この場合PSNRは未定義となり、このケースを個別に扱う必要がある。ピクセル値が非常に広いダイナミックレンジを持つため、対数スケールへの変換が行われる。これらをすべてOpenCVに置き換えて関数にすると次のようになる:
ビデオ圧縮では通常、結果の値は30から50の範囲のどこかになり、高いほど良い。画像が大きく異なる場合は、15程度といったずっと低い値になる。この類似度チェックは計算が簡単で高速だが、実際には人間の目の知覚といくらか一致しないことが判明する場合がある。構造的類似性 (structural similarity) アルゴリズムはこれを補正することを目的としている。
手法の詳細を説明することは、このチュートリアルの目的を大きく超える。それについては、それを紹介した論文を読むことをお勧めする。とはいえ、以下のOpenCV実装を見れば、その良いイメージがつかめるだろう。
これは画像の各チャンネルについての類似度指標を返す。この値はゼロから1の間で、1が完全な一致に対応する。残念ながら、多数のガウシアン平滑化はかなりコストが高いため、PSNRがリアルタイムのような環境(毎秒24フレーム)で動作する一方、こちらは同程度の性能を達成するのにかなり多くの時間がかかる。
そのため、このチュートリアルの冒頭で示したソースコードは、各フレームについてPSNR測定を行い、SSIMはPSNRが入力値を下回るフレームについてのみ行う。可視化のために両方の画像をOpenCVウィンドウに表示し、PSNRとMSSIMの値をコンソールに出力する。次のようなものが表示されると思ってよい:
これの実行時の様子はこちらのYouTubeで見られる。