OpenCV

やりたいことはゲーム画面中での動画再生。
できればテクスチャとしても使いたい

OpenCV3.10を使用。
Windows OS / VC2015
C++環境・描画はOpenGL、サウンドはDirectSound
動画は、MP4 / m4Vでテスト。

音はここでは関与しない。OpenCV的に非対応なので別に同時再生させることにする。
なので動画ファイルに内包されている音声データはむだになっちゃうので、m4vが適切かもしれない。

必要なもの

64bit環境用のLibファイルしかないので64bit版でコンパイルが必要。
x86でもコンパイルは通るが、リンク時にエラーが出る。

■ダウンロード
 opencv-3.1.0.exeをダウンロードして展開。その中から必要なファイルを抜き出す。

■includeファイルをコピーしてパスの通ったところに置く。
 ルートは「dir」のところにする。

+dir
  +-opencv
   opencv2

■ライブラリのパスが通ったところにおいておく
・opencv_world310.lib
※これとリンクすればDLLはいらないと思っていたけどDLLは別途必要だった

■実行ファイルと同じ階層においておく
・opencv_ffmpeg310_64.dll
・opencv_world310.dll

簡単なコード

ココを参考にメインループに組み込んでみる
http://nonbiri-tereka.hatenablog.com/entry/2014/04/15/094559

・付属の「box.mp4」を読んで見る。
 自分で作ったメインウインドウとは別にウインドウが開いて正しく再生された

・自分で用意した「test.mv4」を読んで見る。
 こちらも正しく動画再生された

cure01.PNG

独自にWindowを生成して表示することができるあたりOpenGLと似たような考え方なのかもしれない。データから現在のフレーム情報を取得して画面幅分のRGB値を取得することができるだろうか?

自分のメインウインドウに表示するにはどうすればいいか。

最短距離でコレができることを調査してみる。

・現在のフレームデータをARGBで取得する関数を作れるか?
 Uint32* GetFrameData(int frmIndex);
・ReadTextureしてVRAMバッファにコピーする
・UploadTextureしてVRAMバッファを更新(毎フレーム)
・PutSpriteして画面に表示する

現在のフレームデータをARGBで取得する関数を作れるか?

cure02.PNG

キュアップラパパ!
簡単にできた。OpenCVすごい。テクスチャとして描画しているので、ムービーを半透明にしたり回転させたり自由にできる。これは背景としてPutSpriteしている。cvtColor(frame, edges, CV_RGB2BGRA);でRGBに変換しているけれども、ここで好きなフォーマットに変換できる。処理速度がきになるところ。
テクスチャとして毎フレームバックバッファを更新している。OpenGLだとテクスチャのバインドが早くてこの手のバックバッファ書き換えがとても実用的なんだけれども、DirectXだとこのテクスチャのバインドが遅い。仕組み的にOpenGLがなんでここまで速くできるのかがよくわからないけれども、DirectXは読み込みが遅い分、描画が速い印象がある。

int OpenCVTest()
{
	static int frm = 0;

	static cv::VideoCapture video;

	if( frm == 0 )
	{
		//m4vファイル読み込み
		video.open("030_dokkin(256x170).m4v");
	}

	if( !video.isOpened() ) return 0;

	if( video.grab())	//grabするごとにフレームが更新される
	{
		Mat frame;
		Mat edges;

		video.retrieve(frame, 0);

		//画像のサイズを得る
		Sint32 w, h;

		w = frame.cols;
		h = frame.rows;

		//送られてきた画像をOpenGLのRGBAに変換する
		cvtColor(frame, edges, CV_RGB2BGRA);

		Uint8 *pData;
		pData = edges.data;

		//メモリ内にTGA画像を作ってテクスチャを読みこませる

		CFileTarga tga;

		tga.Create(w, h, 24);

		//w x hのARGB画像をテクスチャに打ち込む

		Sint32 n = 0;

		for (Sint32 y = 0; y < h; y++)
		{
			for (Sint32 x = 0; x < w; x++)
			{
				tga.setARGB(x, y, ARGB(0xff, pData[n * 4 + 0], pData[n * 4 + 1], pData[n * 4 + 2]));
				n++;
			}
		}

		//出来あがったテクスチャを読み込む

		dqLib::ReadTexture(16, tga.getImage(), tga.getFileSize(), 0xff00ff00);
		dqLib::UploadTexture();

		//で、表示する

		dqLib::PutSprite(0, 0, 200, 16, 0, 0, 256, 256, 0, 0);

		//ファイルに吐く場合にこっち

		//tga.SaveFile("test.tga");
	}

	frm ++;

	return 0;
}

再生してみた

速度が倍速で歌と絵があっていない。そういえば動画のスペック的に29.9FPSとかってどっかでみたな。と思って、2回に1回grub()するようにしたら「だいたい」合った。しかしぴったりではない。う~ん。

アルファチャンネルムービー!!

今度は動画スプライトを描画優先順位(プライオリティ)を最前面に持ってきた。

cure03.PNG

毎回全ピクセルを更新してメモリ内にテクスチャを作っては更新しているのでその時のピクセル値を加工してあげれば、ムービーデータそのものを加工できる。写真はRGBがそれぞれ0x80を下回っているピクセルのアルファ値をゼロにしたもの。右側の帽子のところが透けているのがわかると思う。

こうすると、ムービーの特定の部分(今回は暗いところ)が透けてみえるようになった。
単純に生成するTGAファイルのアルファ値をゼロにしただけなので「だから何?」となるが。

CRIのSofdecでの使われ方を参考にしてみよう。
http://www.cri-mw.co.jp/qn39gm000000cv09.html

例えばレースゲームのゴールで紙吹雪が舞いながら画面の奥から3Dで「GOAL」ってでてくるような演出を想像した時に、ムービーで作ったゴール演出をスプライトとして貼ってあげれば、プログラマがその演出を作ることなしに、デザイナーにお任せ出来ちゃうのがいいところ。

既存の動画から無理やり透明ピクセルを算出すると思った通りにするのが難しい(例えば黒いところを抜いたら瞳の中まで抜けちゃうとか)ので、そもそも動画作成時に00ff00とかの背景にしておいてその色付近を抜いてしまうのが現実的かもしれない。

複数のムービーは?

OpenCVは同時に2つ以上の動画を読み込めるのか?

→できた。普通にVideoCapture変数を2つ用意して別々に再生してそれぞれのRGB値を取得できる。