JavaでOpenCVを使用して背景を除去しようとやってきたのですが、色々と欠点が見えてきて、軽くKOされていました。
しかし、OpenCVの本を読んでみると「背景除去の弱点」という形で記載されていました。
。。。では、どうしたら良いのか?
シーンのモデル化
背景と前景を定義するところから記載されていました。
まとめると、高度な「シーンのモデル」が必要ということです。
本の中では、動画を撮影している時の話をしているのですが、静止画ではなく動画なので、『物を動かしたら「前景」と「背景」が変わってしまう。』ということを話していました。
内容がまとまりきらなかったので、文章を書きぬきます。
一般にシーンモデルは、「新しい前景」オブジェクトレベルに入れられ、ポジティブな物体、または穴として印がつけられます。前景オブジェクトのない領域は、背景モデルを更新し続けることができます。前景オブジェクトが所定の時間動かなかったら、それは「より古い前景」に降格されます。
そこで、ピクセルの統計値が一時的に学習され、最後に、その学習されたモデルが学習済みの背景モデルに組み入れられます。
部屋の赤ライをつけるようなグローバルな変化の検出については、グローバルなフレーム差分を使います、たとえ、多くのピクセルが一度に変化したら、それは局所的な変化ではなくグローバルな変化として分類でき、あたらいい状況用のモデルを使うように切り替えることができます。
書いてみると、ちょっとまとまりました。
シーンモデルを作成して、そのモデルが「前景」と「背景」にわけられ、それぞれに、学習し続けることでそれぞれのモデルに対して、「前景」なのか「背景」なのか?のレベルを持たせることで「前景」と「背景」を区別する
というところに落ち着きました。
そのために。。。
ピクセルの断面(要約です)
ピクセルの変化のモデル化に入る前に、ちょっと考えましょう。考える(イメージする)シーンは下のようなイメージです。
風に吹かれる気のシーンを窓から見張っている(撮影している)カメラを考える
そして、所定の線の上にあるピクセルが60フレームに渡りどう見えるか?つまり、この種類の変動をモデル化したいということです。
平均背景法
平均背景法は、背景モデルとして画素ごとにしきい値
を設定し、画素ごとに前景を判定する。
各ピクセルの平均値と標準偏差を背景のモデルとして学習する。
この平均背景法は、OpenCVの以下の4つのルーチンを使用します。
C言語での名前です。(Java版doc)
- cvAbsDiff():フレーム艦の画像差分を累積する
- cvInRage(): 前景領域と背景領域に分割
- cvOr(): 異なるからチャンネルの分割結果えお単一のマスク画像にまとめる
色々な処理を行い、結局のところは。。。
平均、分散、共分散を累積する
というところに至ります。このページから失敬しました文言だとわかりやすいです。
平均値でデータ全体の平均的な大きさを把握出来て、
標準偏差で、そのデータ列のバラつき度合を見る
平均
これを求めるには、cvAcc()
を使用します。
画像の各値、画素値の平均は「移動平均」を使うと良いと本に記載してあるのですが、意味がわからなかったので調べました。
移動平均:株価や気温など時間で細かく変化するデータを眺めるとき、変動が細かすぎて全体の傾向を掴みにくい場合、変化をより滑らかにしてデータを見やすくできます。
具体的には、こちらのページを参考にしましたが、細かいデータだと1日の変化しかわからないが、移動平均にすると全体の変化が観れるということ
なので、動画や、複数の画像のcvAccで計算してやると移動平均が取れるというわけです。
分散
これを求めるには、cvSquareAcc()
を使用します。
分散は、「ばらつき」を示し
ここのページから失敬した文言ですが、
低コントラスト画像は、バラつきが小さい、
高コントラスト画像は、バラつきが大きい、
なので、コントラストを求めることができるであろうというわけです。
共分散
これを求めるには、cvMultiplyAcc()
を使用します。
この関数を使って、様々な統計ベースの背景モデルを作成することができます。
※ここら辺のjava版ドキュメントは見つけることができませんでした。。。
これらは、統計で使用する計算と同じです。
以前学習した「数理モデル」でも平均、分散、共分散(ちょっと怪しい)について記載しています。
<コードで見る>
これはC言語ですが、メイン処理を含めた、1フレーム分の背景の統計値を学習するコード
// グローバル変数(Javaの場合はフィールド変数)
CvCapture* capture = cvCreateFileCapture(argv[1]);
int max_buffer;
IplImage* rawImage;
// 配列の要素数を指定して宣言する
int r[1000], g[1000], b[1000];
CvLineIterator iterator;
FILE * fptrb = fopen("blueLines.csv", "w");
FILE * fptrg = fopen("GreenLines.csv", "w");
FILE * fptrr = fopen("RedLines.csv", "w");
CvSize sz;
IplImage *Iscratch;
IplImage *Iscratch2;
// 上の書き方をまとめると下のようになる
IplImage *IIavgF, *IdiffF, *IprevF;
float Icount;
// メイン処理ループ
for(;;) {
if (!cvGrabFrame(capture)) break;
rawImage = cvRetrieve(capture);
max_buffer = cvInitLineIterator(rawImage, pt1, pt2, &iterator, 8.0);
for(int j = 0; j < max_buffer; j++) {
fprintf(fptrb, "%d,", iterator.ptr[0]); // 青の値を書き込む
fprintf(fptrb, "%d,", iterator.ptr[1]); // 緑の値を書き込む
fprintf(fptrb, "%d,", iterator.ptr[2]); // 赤の値を書き込む
iterator.ptr[2] = 255; // このサンプルに赤で印をつける
CV_NEXT_LINE_POINT(iterator);// 次のピクセル
}
// データを行ごと出力する
fprintf(fptrb, "\n");
fprintf(fptrg, "\n");
fprintf(fptrr, "\n");
}
// メモリの解放
fclose(fptrb);
fclose(fptrg);
fclose(fptrr);
cvReleaseCapture(&capture);
// こちらが先に動く
void AllocateImage(IplImage *I) {
CvSize sz = CVGetSize(I);
*Iscratch = cvCreateImage(sz, IPL_IMAGE_DEPTH_32_F, 3);
*Iscratch2 = cvCreateImage(sz, IPL_IMAGE_DEPTH_32_F, 3);
Icount = 0.00001;
}
// 1フレーム文の背景の統計値を追加学習する
// 引数は3チャンネル富豪なし8ビット背景色サンプル、
void accmulateBackground( IplImage *I) {
static int first = 1;
cvCvtScale( I, Iscratch, 1, 0);
if (!first) {// 1 = true : 0 = false
cvAcc(Iscratch, IavgF);
cvAbsDiff(Iscratch, IprevF, Iscratch2);
cvAcc(Iscratch2, IdiffF);
Icount += 1.0;
}
cvCopy(Iscratch, IprevF);
}
このような手法で背景を割り出すのも1つの方法です。
まとめ
背景を取得する(判別する)ためには、findContuorのようなメソッドで〜という方法もあるが、これではいろんな背景に対応ができない。
解決方法としては、上記の「平均背景法」という手法で解決ができそうだということがわかった。
ここで使用するのは「統計値」、これらの値により以下のことができる
- 画像のコントラストの算出
- 変化量を算出
- 統計ベースの背景モデルを作成する
次に学習する「コードブック法」で「モデル」という部分をより詳細に学習していく予定です。
※各画素に背景モデルを複数作成して、
常に変動する画素値に対応する手法
でわでわ。。。
数理モデル関連
第二弾
第三弾