OpenCV

やりたいことはゲーム画面中での動画再生。できればテクスチャとしても使いたい
マルチ環境でMac / iOS / Android / Linux / Windowsに対応している。

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.m4v」を読んで見る。
 こちらも正しく動画再生された

cure01.PNG

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

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

最短距離でコレができるか?調査してみる。

・指定した動画の画面をARGBで取得する関数を作れるか?例えばこんなの「Uint32* GetFrameData(int frmIndex);」
・取得したARGBデータを仮想TGAファイルにまとめReadTextureする
・その後、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++;
			}
		}

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

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

		//で、表示する

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

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

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

	frm ++;

	return 0;
}

再生してみた

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

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

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

cure03.PNG

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

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

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

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

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

複数のムービーは?

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

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

動画の縦横サイズを自動判定したい

縦横のサイズが取れる。エンコーダーによって自動でアスペクト比を整えるために余白を入れられることもあるので想定した画像サイズと異なる場合もあるので注意。

cv::VideoCapture *m_pVideo;
m_pVideo->get(CAP_PROP_FRAME_WIDTH);			
m_pVideo->get(CAP_PROP_FRAME_HEIGHT);			

動画に応じたFPSを取得する方法はあるのか?

あった。

m_pVideo->get(CAP_PROP_FPS);

動画の絵の総枚数を得る

これで取れる。

m_pVideo->get(CAP_PROP_FRAME_COUNT);

現在再生中の動画のフレーム数を得る

簡単に取れる

now = m_pVideo->get(CAP_PROP_POS_FRAMES);

動画に音声をくっつける

OpenCVで動画から取得することはできない。OpenCVは音声に対応していないのでmp4よりはm4vが適切。音は別途用意して再生させる必要がある。効果音とかBGMの再生と同じくoggとかmp3、wavで用意してビデオの再生と同時にDirectSoundとかOpenALとかで再生させることになる。

動画の頭出しをして特定の絵を出したい

再生位置を設定できる

m_pVideo->set( CAP_PROP_POS_FRAMES , 再生したいフレーム番号 );

ループ再生させたい

動画の総枚数を取得して

max = m_pVideo->get(CAP_PROP_FRAME_COUNT);

現在の再生フレームを取得して、再生フレームが総枚数を上回っていたら再生位置を最初に戻せばいい

now = m_pVideo->get(CAP_PROP_POS_FRAMES);
if( now >= max )
{
 m_pVideo->set( CAP_PROP_POS_FRAMES , 0 );
}

添付ファイル: filecure03.PNG 274件 [詳細] filecure02.PNG 267件 [詳細] filecure01.PNG 300件 [詳細]