OpenCV 4.13.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 4.13.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
OpenCVによる動画入力と類似度測定

前のチュートリアル: GDALを用いた地理空間ラスタファイルの読み込み
次のチュートリアル: OpenCVで動画を作成する

原著者Bernát Gábor
互換性OpenCV >= 3.0

目標

今日では、デジタル映像記録システムを手元に持つことは一般的だ。そのため、いずれは一連の画像のバッチを処理するのではなく、ビデオストリームを処理する状況に出くわすことになる。これらは2種類ある: リアルタイムの画像入力(ウェブカメラの場合)、あるいは事前に録画されハードディスクドライブに保存されたファイルだ。幸いOpenCVはこれら2つを、同じC++クラスで同じように扱う。そこで、このチュートリアルで学ぶ内容は次のとおり:

  • ビデオストリームを開いて読み込む方法
  • 画像の類似度を確認する2つの方法: PSNRとSSIM

ソースコード

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で、それ以降のものは増加していく。これらに渡すパラメータが文字列の場合、それはビデオファイルを指し、その文字列はファイルの場所と名前を指し示す。例えば、上のソースコードに対して有効なコマンドラインは次のとおり:

video/Megamind.avi video/Megamind_bug.avi 35 10

類似度チェックを行う。これには参照用とテスト用のビデオファイルが必要だ。最初の2つの引数がこれらを指す。ここでは相対アドレスを使う。これは、アプリケーションが現在の作業ディレクトリ内を見てvideoフォルダを開き、その中から Megamind.aviMegamind_bug.avi を見つけようとすることを意味する。

const string sourceReference = argv[1],sourceCompareWith = argv[2];
VideoCapture captRefrnc(sourceReference);
// or
VideoCapture captUndTst;
captUndTst.open(sourceCompareWith);
virtual bool open(const String &filename, int apiPreference=CAP_ANY)
Opens a video file or a capturing device or an IP video stream for video capturing.

クラスのビデオソースへのバインドが成功したかどうかを確認するには、cv::VideoCapture::isOpened 関数を使う:

if ( !captRefrnc.isOpened())
{
cout << "Could not open reference " << sourceReference << endl;
return -1;
}

オブジェクトのデストラクタが呼ばれると、ビデオは自動的に閉じられる。しかし、それより前に閉じたい場合は cv::VideoCapture::release 関数を呼ぶ必要がある。動画のフレームは単なる画像にすぎない。したがって、cv::VideoCapture オブジェクトからそれらを取り出して Mat に入れるだけでよい。ビデオストリームは逐次的だ。cv::VideoCapture::read またはオーバーロードされた >> 演算子によって、フレームを次々に取得できる:

Mat frameReference, frameUnderTest;
captRefrnc >> frameReference;
captUndTst.read(frameUnderTest);
virtual bool read(OutputArray image)
Grabs, decodes and returns the next video frame.

上のread操作は、フレームを取得できなかった場合(ビデオストリームが閉じられたか、ビデオファイルの終わりに達したかのいずれか)、Mat オブジェクトを空のままにする。これは簡単なifで確認できる:

if( frameReference.empty() || frameUnderTest.empty())
{
// exit the program
}
bool empty() const
Returns true if the array has no elements.

readメソッドは、フレームのグラブと、それに適用されるデコードから成る。cv::VideoCapture::grab とそれに続く cv::VideoCapture::retrieve 関数を使って、この2つを明示的に呼ぶこともできる。

動画には、フレームの内容以外にも非常に多くの情報が付随している。これらは通常は数値だが、場合によっては短い文字列(4バイト以下)であることもある。このため、これらの情報を取得するには cv::VideoCapture::get という汎用関数があり、これらの特性を含むdouble値を返す。double型から文字をデコードするにはビット演算を、有効な値が整数のみの場合は型変換を使う。その唯一の引数は、問い合わせる特性のIDだ。例えばここでは、参照用とテスト用のビデオファイルにおけるフレームのサイズと、参照用のフレーム数を取得する。

Size refS = Size((int) captRefrnc.get(CAP_PROP_FRAME_WIDTH),
(int) captRefrnc.get(CAP_PROP_FRAME_HEIGHT)),
cout << "Reference frame resolution: Width=" << refS.width << " Height=" << refS.height
<< " of nr#: " << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl;

動画を扱う際には、これらの値を自分で制御したいことがよくあるだろう。これを行うには cv::VideoCapture::set 関数がある。その第1引数は変更したい特性の名前のままで、設定する値を含むdouble型の第2引数がある。成功すればtrueを、そうでなければfalseを返す。この良い例は、ビデオファイル内で指定した時刻やフレームへシークすることだ:

captRefrnc.set(CAP_PROP_POS_MSEC, 1.2); // go to the 1.2 second in the video
captRefrnc.set(CAP_PROP_POS_FRAMES, 10); // go to the 10th frame of the video
// now a read operation would read the frame at the set position

読み取りや変更が可能な特性については、cv::VideoCapture::get および cv::VideoCapture::set 関数のドキュメントを参照すること。

画像の類似度 - PSNRとSSIM

動画変換操作がどれだけ知覚されないかを確認したいので、フレームごとに類似度や差異をチェックする仕組みが必要だ。これに最もよく使われるアルゴリズムは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実装を見れば、その良いイメージがつかめるだろう。

覚え書き
SSIMは次の論文でより詳しく説明されている: "Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli, "Image quality assessment: From error visibility to structural similarity," IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004."

これは画像の各チャンネルについての類似度指標を返す。この値はゼロから1の間で、1が完全な一致に対応する。残念ながら、多数のガウシアン平滑化はかなりコストが高いため、PSNRがリアルタイムのような環境(毎秒24フレーム)で動作する一方、こちらは同程度の性能を達成するのにかなり多くの時間がかかる。

そのため、このチュートリアルの冒頭で示したソースコードは、各フレームについてPSNR測定を行い、SSIMはPSNRが入力値を下回るフレームについてのみ行う。可視化のために両方の画像をOpenCVウィンドウに表示し、PSNRとMSSIMの値をコンソールに出力する。次のようなものが表示されると思ってよい:

これの実行時の様子はこちらのYouTubeで見られる。