前のチュートリアル: OpenCVによる動画入力と類似度の計測 次のチュートリアル: Kinectおよびその他のOpenNI互換深度センサーの利用
原著者 Bernát Gábor
互換性 OpenCV >= 3.0
目標
動画フィードを扱う際には、画像処理の結果を新しい動画ファイルとして保存したくなることがある。単純な動画出力には、このために設計されたOpenCV組み込みの cv::VideoWriter クラスを使用できる。
OpenCVで動画ファイルを作成する方法
OpenCVで作成できる動画ファイルの種類
動画から指定した色チャンネルを抽出する方法
簡単なデモとして、入力動画ファイルのBGRカラーチャンネルのうちの1つを抽出し、新しい動画にする。アプリケーションの動作はコマンドライン引数から制御できる:
第1引数は処理対象の動画ファイルを指す
第2引数は R G B のいずれかの文字である。これにより、どのチャンネルを抽出するかを指定する。
最後の引数は文字 Y(Yes)または N(No)である。これがnoの場合、出力に使用するコーデックは入力動画ファイルと同じものになる。そうでなければ、ウィンドウがポップアップし、使用するコーデックを自分で選択できる。
たとえば、有効なコマンドラインは次のようになる:
video-write.exe video/Megamind.avi R Y
ソースコード
ソースコードとこれらの動画ファイルは、OpenCVソースライブラリの samples/cpp/tutorial_code/videoio/video-write/ フォルダ内でも見つけられるほか、ここからダウンロード することもできる。
#include <iostream>
#include <string>
static void help()
{
cout
<< "------------------------------------------------------------------------------" << endl
<< "This program shows how to write video files." << endl
<< "You can extract the R or G or B color channel of the input video." << endl
<< "Usage:" << endl
<< "./video-write <input_video_name> [ R | G | B] [Y | N]" << endl
<< "------------------------------------------------------------------------------" << endl
<< endl;
}
int main (
int argc,
char *argv[])
{
help();
if (argc != 4)
{
cout << "Not enough parameters" << endl;
return -1;
}
const string source = argv[1];
const bool askOutputType = argv[3][0] =='Y' ;
if (!inputVideo.isOpened())
{
cout << "Could not open the input video: " << source << endl;
return -1;
}
string::size_type pAt = source.find_last_of('.' );
const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi" ;
int ex = static_cast< int > (inputVideo.get(CAP_PROP_FOURCC));
char EXT[] = {(char)(ex & 0XFF) , (char)((ex & 0XFF00) >> 8),(char)((ex & 0XFF0000) >> 16),(char)((ex & 0XFF000000) >> 24), 0};
Size S =
Size ((
int ) inputVideo.get(CAP_PROP_FRAME_WIDTH),
(int ) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
if (askOutputType)
outputVideo.
open (NAME, ex=-1, inputVideo.get(CAP_PROP_FPS), S,
true );
else
outputVideo.
open (NAME, ex, inputVideo.get(CAP_PROP_FPS), S,
true );
{
cout << "Could not open the output video for write: " << source << endl;
return -1;
}
cout <<
"Input frame resolution: Width=" << S.
width <<
" Height=" << S.
height
<< " of nr#: " << inputVideo.get(CAP_PROP_FRAME_COUNT) << endl;
cout << "Input codec type: " << EXT << endl;
int channel = 2;
switch (argv[2][0])
{
case 'R' : channel = 2; break ;
case 'G' : channel = 1; break ;
case 'B' : channel = 0; break ;
}
vector<Mat> spl;
for (;;)
{
inputVideo >> src;
if (src.empty()) break ;
for (int i =0; i < 3; ++i)
if (i != channel)
spl[i] = Mat::zeros(S, spl[0].type());
outputVideo << res;
}
cout << "Finished writing" << endl;
return 0;
}
n-dimensional dense array class
Definition mat.hpp:840
Template class for specifying the size of an image or rectangle.
Definition types.hpp:335
_Tp height
the height
Definition types.hpp:363
_Tp width
the width
Definition types.hpp:362
Class for video capturing from video files, image sequences or cameras.
Definition videoio.hpp:786
Video writer class.
Definition videoio.hpp:1085
virtual bool open(const String &filename, int fourcc, double fps, Size frameSize, bool isColor=true)
Initializes or reinitializes video writer.
virtual bool isOpened() const
Returns true if video writer has been successfully initialized.
void split(const Mat &src, Mat *mvbegin)
Divides a multi-channel array into several single-channel arrays.
void merge(const Mat *mv, size_t count, OutputArray dst)
Creates one multi-channel array out of several single-channel ones.
int main(int argc, char *argv[])
Definition highgui_qt.cpp:3
動画の構造
まず、動画ファイルがどのようなものかを把握しておくべきである。すべての動画ファイルは、それ自体がコンテナである。コンテナの種類はファイルの拡張子で表される(たとえば avi 、mov 、mkv )。これは、動画フィード、音声フィード、その他のトラック(たとえば字幕など)といった複数の要素を含む。これらのフィードがどのように格納されるかは、それぞれに使用されるコーデックによって決まる。音声トラックの場合、一般的に使用されるコーデックは mp3 や aac である。動画ファイルの場合、その一覧はやや長く、XVID 、DIVX 、H264 、LAGS (Lagarith Lossless Codec )などの名前を含む。システム上で使用できるコーデックの全一覧は、何がインストールされているかに依存する。
見ての通り、動画に関しては事態が非常に複雑になり得る。しかし、OpenCVは主にコンピュータビジョンのライブラリであり、動画ストリーム・コーデック・書き込みのためのライブラリではない。そのため、開発者はこの部分を可能な限り単純に保とうとしてきた。このため、OpenCVは動画コンテナとして avi 拡張子(その最初のバージョン)のみをサポートする。これによる直接的な制限として、2 GBを超える動画ファイルは保存できない。さらに、コンテナ内に作成・拡張できる動画トラックは1つだけである。音声やその他のトラック編集のサポートはここにはない。それでも、システム上にある任意の動画コーデックは動作する可能性がある。これらの制限に遭遇した場合は、FFmpeg のようなより専門的な動画書き込みライブラリや、HuffYUV 、CorePNG 、LCL のようなコーデックを検討する必要がある。代替として、OpenCVで動画トラックを作成し、VirtualDub や AviSynth のような動画操作プログラムを使ってそれに音声トラックを追加したり、他の形式に変換したりするとよい。
VideoWriterクラス
ここで述べる内容は、すでに OpenCVによる動画入力と類似度の計測 チュートリアルを読み、動画ファイルの読み込み方を知っていることを前提としている。動画ファイルを作成するには、cv::VideoWriter クラスのインスタンスを作成するだけでよい。そのプロパティはコンストラクタの引数で指定するか、後から cv::VideoWriter::open 関数で指定できる。いずれの方法でも引数は同じである: 1. コンテナの種類を拡張子に含む出力の名前。現時点では avi のみがサポートされている。これは入力ファイルから構築し、使用するチャンネルの名前を加え、最後にコンテナの拡張子で仕上げる。
const string source = argv[1];
string::size_type pAt = source.find_last_of('.' );
const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi" ;
動画トラックに使用するコーデック。すべての動画コーデックは、最大4文字の固有の短い名前を持つ。したがって XVID 、DIVX 、H264 という名前になる。これはfour character code(4文字コード)と呼ばれる。これは入力動画から get 関数を使って取得することもできる。get 関数は汎用的な関数であるため、常にdouble値を返す。double値は64ビットで格納される。4文字は4バイト、つまり32ビットを意味する。これらの4文字は double の下位32ビットにコード化されている。上位32ビットを捨てる簡単な方法は、この値を int に変換することである: int ex = static_cast< int > (inputVideo.get(CAP_PROP_FOURCC));
OpenCVは内部でこの整数型を扱い、2番目の引数としてこれを期待する。整数形式から文字列に変換するには、2つの方法を使える。ビット演算子による方法とunionによる方法である。最初の方法、つまりintから文字を取り出す方法は次のようになる(「and」演算、いくつかのシフト、そして文字列を閉じるための末尾への0の追加): char EXT[] = {ex & 0XFF , (ex & 0XFF00) >> 8,(ex & 0XFF0000) >> 16,(ex & 0XFF000000) >> 24, 0};
同じことを union で次のように行える: union { int v; char c[5];} uEx ;
uEx.v = ex;
uEx.c[4]='\0' ;
この利点は、代入後に変換が自動的に行われることである。一方、ビット演算子の場合は、コーデックの種類を変更するたびに演算を行う必要がある。コーデックの4文字コードを事前に知っている場合は、CV_FOURCC マクロを使って整数を構築できる: int CV_FOURCC(char c1, char c2, char c3, char c4)
Constructs the 'fourcc' code, used in video codecs and many other places. Simply call it with 4 chars...
Definition cvdef.h:936
この引数にマイナス1を渡すと、実行時にウィンドウがポップアップし、システムにインストールされているすべてのコーデックが表示され、使用するものを選択するよう求められる:
出力動画のフレームレート(fps)。ここでも、get 関数を使って入力動画のフレームレートをそのまま保持している。
出力動画のフレームサイズ。ここでも、get 関数を使って入力動画のフレームサイズをそのまま保持している。
最後の引数は省略可能である。デフォルトはtrueで、出力がカラーになることを意味する(したがって書き込みには3チャンネル画像を渡す)。グレースケール動画を作成するには、ここにfalseの引数を渡す。
サンプルでの使い方は次の通りである:
Size S =
Size ((
int ) inputVideo.get(CAP_PROP_FRAME_WIDTH),
(int ) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
outputVideo.
open (NAME , ex, inputVideo.get(CAP_PROP_FPS),S,
true );
その後、cv::VideoWriter::isOpened() 関数を使って、open操作が成功したかどうかを調べる。動画ファイルは VideoWriter オブジェクトが破棄されると自動的に閉じられる。オブジェクトのオープンに成功したら、クラスの cv::VideoWriter::write 関数を使って、動画のフレームを順番に送ることができる。代わりに、オーバーロードされた演算子 << を使うこともできる:
outputVideo << res;
virtual void write(InputArray image)
Writes the next video frame.
BGR画像から色チャンネルを抽出するとは、他のチャンネルのBGR値をゼロに設定することを意味する。これは画像走査操作で行うか、splitとmerge操作を使って行える。まずチャンネルを別々の画像に分割し、他のチャンネルを同じサイズ・型のゼロ画像に設定し、最後にそれらを再びマージする:
split(src, spl);
for ( int i =0; i < 3; ++i)
if (i != channel)
spl[i] = Mat::zeros(S, spl[0].type());
merge(spl, res);
これらをすべてまとめると、上のソースコードが得られる。その実行時の結果は、おおよそ次のような内容を示す:
この実行時の様子は こちらのYouTube で確認できる。
VIDEO