動画を描画し、各フレームで各座標ごとの色を確認したりしたい

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
風きつね

動画を描画し、各フレームで各座標ごとの色を確認したりしたい

#1

投稿記事 by 風きつね » 15年前

はじめまして、風きつねと申します。
Windows Vista Visual C++ DXライブラリを用いています。

「BadApple!! 影絵」で検索をすると一つの動画が出てくるのですが、それを使った弾幕を作ろうと考えています。
黒と白に分かれている境界の部分に弾を配置し、動画のように動かしてみたいと思っています。

具体的に考えているルーチンは
1、動画の各フレーム毎に、影絵の黒と白の境となる部分の座標を全て読み取り、テキストにX座標(LX)、Y座標(LY)、現在のフレーム(LF)のデータを出力する。(これで一つのプログラムを作る)

2、龍神録プログラミングを利用し、①で出力したデータを読み取り、LFと、現在のboss_shot.cntが一致していた場合、(LX,LY)の位置に弾を出現させる。

3、弾が出現していて、LFとboss_shot.cntが異なっていた場合、弾のフラグをオフにする。
といった感じです。


問題は1の部分でして、
①動画を1フレーム毎に検証していく方法が分からない。

②影絵といっても完全に0xffffffと0x000000に分かれているわけでもなく、二色だけでできているわけでもないのでその座標が黒と白の境目なのかを判断することができない。

③色の識別に重いと噂のGetPixel関数を用いているため、X座標、Y座標総当り(480x640)でやるとCPUが耐えられない。
と問題点が多数あり、非常に困っている状況です。

何かこれらを解決できるよい方法、別のルーチン等はないものでしょうか。
皆様のお力添えを何とぞよろしくお願いします。

ルファー

GetPixelからRGBへ逆変換

#2

投稿記事 by ルファー » 15年前

その方法だと、まず GetPixel で得た色が何色なのかを判別する方法がないとどうにもなりませんね。
僕は知りませんが…。
DxLib の作者さんなら何か知っているかも知れません。

アトラス

Re:GetPixelからRGBへ逆変換

#3

投稿記事 by アトラス » 15年前

②については色の差が一定以上であれば境界であると判定すればいいのではないでしょうか
2色ではないといっても、ほとんど(0,0,0)か(255,255,255)のどちらかに近い色ですから
col = GetPixel( i , j ) ;
R = (col & 0xFF0000) >> 16 ;
G = (col & 0x00FF00) >> 8 ;
B = col & 0x0000FF;
[pre]

という具合に色の成分を分けて、RGB値を比較すればよいと思います

Dixq (管理人)

Re:動画を描画し、各フレームで各座標ごとの色を確認したりしたい

#4

投稿記事 by Dixq (管理人) » 15年前

いいですね~ニコニコ技術部的なネタ大好きです^^

ところで、処理はリアルタイムにしなくてはならないのですか?
リアルタイムである必要がないなら予めデータを作っておき、それに合わせて弾を配置すればよいだけだと思います。

また、画像処理は2値化してから行う方がよいでしょう。
境界線は自力でも計算出来るでしょうが、OpenCVを使えばスマートになるかもしれません。
動画データを画像処理する時は予め何かのソフトでBmpなどにわけておくと良いでしょう。
良いソフトが見つからなければffmpegがあればなんとでもなります。

現在携帯からなのでリンクとか貼れないですが…

ルファー

ビット数配分

#5

投稿記事 by ルファー » 15年前

しかしビットシフトを使う方法はカラーモードによって使えないのではないですか?
各成分に使うビット数 α:R:G:B は
8:8:8:8 のときもあれば 0:5:6:5 や 4:4:4:4 、0:11:11:10 かもしれませんし、 インデックスカラーかも知れません。

風きつね

Re:ビット数配分

#6

投稿記事 by 風きつね » 15年前

皆様色々ご意見ありがとうございます。
申し遅れましたが、当方初心者なものであまり知識もないものですので、
色々迷惑かけると思いますがよろしくお願いします。

>>皆様
GetPixel関数は
Rの値+Gの値*256+Bの値*65536を返すようです。
影絵ですので青に偏る事もないでしょうし、10000000を越えたら黒…と曖昧な判定で白と黒に分けられそうです。
ひとまず問題②に関しては無事に解決できそうです。


>>Dixq様
上に書いてなかったのですが、
プログラムはテキストデータを読み取り弾幕を展開する龍神録プログラミングと、
動画から白と黒の境目の座標をテキストデータに出力するものと二つあるのです。
で、動画のフレームレートが30、プログラムの方がおそらく60だと思われますので、2cnt毎に、
1、テキストファイルから配置する弾の座標のデータを格納
2、①で格納した座標に弾を配置
3、描画
4、弾を消す
というのを龍神録プログラミングの方でやろうとしているので、
実際にやっているのは予めデータを製作してからそれに合わせて弾を配置しているのと変わらないんですよね。
ただ、そのデータ製作のほうがうまくいかないのでご助力をお願いした次第でございます。


問題①が解決の糸口も掴めずどうしようもない状態です。
PauseMovieToGraph関数を使えばどうにかなるかと思ったんですが…どうにもなりませんでした。

Dixq (管理人)

Re:ビット数配分

#7

投稿記事 by Dixq (管理人) » 15年前

そうですね~、抽象的な説明では解りにくいと思いましたので、ソースコードを作ってみました。
以下のソースコードを使えば輪郭を抽出した座標データが生成出来、動画に従って弾を境界線に置くことが出来ます。

ソースコードとプロジェクト一式はこちら
http://dixq.net/zip/bbs/BadApple.zip

imgフォルダの画像やdatフォルダの座標は
試しに実行できるように最初の300枚分だけ入っていますので、完全なデータは後から説明する手順で用意して下さい。

詳しい実行の仕方やプログラムの説明は次の投稿で書きます。
とりあえず仕上がりはこんな感じになりました。
弾の隙間の大きさなどは自由に変更可能です。

画像

動画はこちら
[nico]http://www.nicovideo.jp/watch/sm12290600[/nico]

Dixq (管理人)

Re:ビット数配分

#8

投稿記事 by Dixq (管理人) » 15年前

OpenCVでも輪郭抽出出来ますが、今回の場合はCVを使うまでも無さそうです。
手順としては
2値化して、全ピクセルの周辺4ピクセルを見て、4ピクセル中一つでも異なる色があればそこが境界線だと判断しています。
では詳しい手順を書きます。


【プログラムを書くまでの下準備】

・craving explorerなどでBadApple!!をダウンロードします。AVIがいいでしょう。

・ソフトは何でもいいですが、AVIからBMPに変換します。
 → AVI2JPGとか良いかもしれません。http://novolization.hp.infoseek.co.jp/download.html
 → 変換する時、動画のファイル名を「BadApple.avi」にしておいてください
 → 数GBのすごい大きさになるので、容量には注意して。BadAppleのソースコードのプロジェクトがあるフォルダの「img」フォルダをビットマップの展開先に設定してからエンコードして下さい。


【プログラムにおける手順】(こんなに長くなると思わなかったので超いい加減なコードですが・・)
(一つのソースコードで、連続画像を読み込んで、座標データファイルを作る機能と、座標データファイルを読み込んで結果を表示してみる機能が2つ備わっています。「MAKEMODE_ON」の定義を有効にすると前者モード、注釈にすると後者モードになります)

【連続画像を読み込んで→座標データファイル作成】

・ビットマップの大きさはいくつでも自動的に対応し、ウィンドウサイズやファイルサイズが変わります。領域を確保する為に、最初は「MakeBmpFrame」を呼びます。

・処理1ループの最初にbmpファイルを展開・読み込みます(6000枚以上あります)
 GetBmpでビットマップを読み込みます。ビットマップのファイルフォーマットについてはURL1を参考に

・BmpThresholdで二値化します。閾値は64にしました。低すぎると、ノイズを拾ってしまうし、高すぎると星や花弁が拾えませんでした。

・RegBulletで弾を登録します。先ほど言った通り、2x2の4ピクセルを全てのピクセルにおいて調べます。4ピクセル中一つでも異なるピクセルがあれば、そこは境界です。で、条件にマッチする場合全てに弾を置くとものすごい密集してしまうので、近くに弾が無ければ置くという処理をしています。近くに弾があるかどうかはFindBullet関数で計算しています。RANGEの定義が最低限の幅です。幅を広げたい時はここを広くして下さい。

・WriteToFileで登録した弾の座標をファイルに書き込んでいます。画像ファイル名とdatファイル名は対応しています。最初の4バイトに弾の数を記録し、登録した弾の数だけ保存するので、読み込むときは、最初の4バイトを先に読みましょう。

・DrawBmp、DrawBulletは描画しています。

・FPS制御は龍神録の館のコードを使っています。館はご覧になっているようなので、説明は省略します。


【座標データファイルを読み込んで、どんな感じになったか確認】
(MAKEMODE_ONの定義を外せばこちらのモードになります。)
(特に説明するほどのコードは書いていないので、省略します。)

URL1
http://www.google.co.jp/search?hl=ja&lr ... =&gs_rfai=


もう少しシンプルなサンプルを作るつもりだったのに長くなってしまいました。
こんなことならファイル分けして後から汎用的に使いまわせるように作ればよかったですね・・。
あんまり綺麗なコードじゃないので、もし使うときは適度に書き換えて下さい^^;

風きつね

Re:ビット数配分

#9

投稿記事 by 風きつね » 15年前

ありがとうございます。
おかげ様でルーチンについては大体理解しました。
解像度が変わっているのが少々気になりますが、BMPの画像に依存するとこればっかりは仕方の無いことなんでしょうかね…。


一方で、BMPの処理についてはさっぱり分かりません。
どこか分かりやすい説明サイト等ご存じないでしょうか?

Dixq (管理人)

Re:ビット数配分

#10

投稿記事 by Dixq (管理人) » 15年前

解像度については、640x480の方が良いという事でしょうか?
それであれば動画をダウンロードした際動画の解像度を640x480に変更すればよいと思います。
ffmpegでも、windowsムービーメーカーでも、aviutlでも何ででも出来ます。
補間してやればプログラム側でも可能でしょう。

> 一方で、BMPの処理についてはさっぱり分かりません。

どの辺が解りませんか?
BMPについての参考サイトはURL1に記載しましたが、こちらよりわかりやすいのは無いのではないでしょうか・・。
バイト単位で全て書いてあると思います。
どの辺が解らないか教えて頂ければまたお答えします。

風きつね

Re:ビット数配分

#11

投稿記事 by 風きつね » 15年前

BmpThreshold関数の中で、
if(Bmp->Pixel[ y*w + x ].r > THRESHOLD)
という記述があります。
これを見る限りPixel[/url].rの中には(x,y)座標におけるr成分がすでに格納されているようですが、
プログラムのどこを見ても自分にはどのタイミングで格納しているのか分からないものでして…。

Dixq (管理人)

Re:ビット数配分

#12

投稿記事 by Dixq (管理人) » 15年前

GetBmp関数に「Bmp画像の取得」と書いてある通り、ここで、画像データを読み込んでいます。
ヘッダ情報の読み込みはそれより前にあるMakeBmpFrameでのみ行っています。
必要なヘッダ情報はどのビットマップでも変わらないであろうことを前提にしているので、最初に1枚しか読み込んでいません。
詳しい事はURL1で読んで頂いたと思いますが、
ビットマップはBITMAPFILEHEADERにファイルヘッダ情報があった後、BITMAPINFOHEADERに情報ヘッダが付いています。
ここに書いてある情報を元に画像の縦横サイズを調べています。

で、ファイルからバイナリデータを読み込む時は、fread関数で行います。
GetBmp関数の中にもこの関数を呼んでいる所がありますよね。

ビットマップのファイルフォーマットは、54バイトのヘッダ情報の後に実際の画像データが並んでいます。
画素の並びは左下から右上の方向に並んでいます。
ですから、下から読むために、for文のyがMAXから減るようになっています。
ピクセルの数だけfreadすれば読み込み完了です。
個別に何度もfreadを呼ぶのが面倒なら、全部まとめて読み込んで、左下が原点であることを意識して画像を操作するようにしても構いません。

で、今回は画像がグレースケールであることを前提としていたのでよかったのですが、カラーでも対応できるようにするには

if( Bmp->Pixels[ y*w + x ].r > THRESHOLD ){
Bmp->Pixels[ y*w + x ].r = 255;
} else {
Bmp->Pixels[ y*w + x ].r = 0;
}
if( Bmp->Pixels[ y*w + x ].g > THRESHOLD ){
Bmp->Pixels[ y*w + x ].g = 255;
} else {
Bmp->Pixels[ y*w + x ].g = 0;
}
if( Bmp->Pixels[ y*w + x ].b > THRESHOLD ){
Bmp->Pixels[ y*w + x ].b = 255;
} else {
Bmp->Pixels[ y*w + x ].b = 0;
}

こちらの部分は

if( (int)Bmp->Pixels[ y*w + x ].r + Bmp->Pixels[ y*w + x ].g + Bmp->Pixels[ y*w + x ].b + > THRESHOLD*3 ){
Bmp->Pixels[ y*w + x ].r = 255;
Bmp->Pixels[ y*w + x ].g = 255;
Bmp->Pixels[ y*w + x ].b = 255;
} else {
Bmp->Pixels[ y*w + x ].r = 0;
Bmp->Pixels[ y*w + x ].g = 0;
Bmp->Pixels[ y*w + x ].b = 0;
}

こちらの方がよさそうですね。
上のままだと、カラーでは完全に二値化されない場合がありそうです。

そして、逆に書き込みはfwriteで行います。
計算で算出した弾の座標はバイナリデータとしてファイルにfwriteで書き込んでいます。

ファイルの入出力についてのプログラムは作ったことがありますか?

風きつね

Re:ビット数配分

#13

投稿記事 by 風きつね » 15年前

丁寧な解説ありがとうございました。
無事に解決できそうです。

txtファイルに出力するだけの簡単なプログラムでしたら作った事があります。

閉鎖

“C言語何でも質問掲示板” へ戻る