GrowCut - 画像処理のおはなし
8カ月ぶりの更新。
ちょっとやるきが出たので画像処理のおはなしです。
試してみたのはGrowCutという画像のセグメンテーション手法の一つ。
画像のセグメンテーションというとGrabCutが有名ですね。OpenCVにも実装されてるし。
じゃぁ何でGrowCutなんて試したのか。
それはとても簡単だから。
実装するだけなら本当に超簡単(自分にも出来たくらい)。
また実装例は以下にあります。
(注意:この実装例にはバグがあって、ところどころおかしいです。L2ノルムの計算は平方根必要ないのにsqrtで平方根をだしてしまっています。)
あと宣伝になりますが、GitHubにプログラムをあげました。
プログラムは先に紹介した二つ目のURLを参考にし、理論部分は一つ目のURL(論文)で補強してプログラムのバグを回収しています。
kyokushin/GrowCut-gui · GitHub
GitHubにあげたプログラムはWindows上で開発しています。
ですが、Windowsに依存した実装はしたつもりはないのでLinuxなど他のOS上でも動くと思います。
利用しているライブラリはOpenCVのみ。
使い方は二通り。
一つはコマンドライン引数なしで実行。
するとGUIが立ち上がり、ソースコードと一緒に入っているネコの画像が表示されます。
ネコの部分を左クリックで大雑把にぬり、背景を右クリックで大雑把にぬります。
できたらキーボードの'g'を押すとGrowCutが開始されます。
GrowCutが始まると、セグメンテーションの進捗と各セルの強さ(論文参照)を表示するウィンドウが新たに2つ現れ、残りはひたすらまつだけです。
処理が完了すると、セグメンテーションの結果を利用してネコの部分を切り抜いた画像が表示されます。
表示されたウィンドウでキーを何か押すとプログラムは終了します。
二つ目の使い方は、コマンドライン引数に2つの画像を与えて実行します。
第一引数には、カラー画像を与えます。
第二引数には、前景(ネコの画像だったらネコ)を赤色でぬり、背景(ネコの画像ならネコ以外)を青色でぬり、さらにそれ以外は黒の画像になっている画像を与えます。
実行すると読み込んだ画像が正しいか確認の表示がでます。
間違っていなければキーボードの'n'以外を押してください。
あとはひたすらまつだけです。
これも処理が完了するとセグメンテーションの結果を利用してネコの部分を切り抜いた画像が表示されます。
以上でプログラムの使い方は終わりです。
プログラムのアルゴリズム部分だけ上げておきます。
converged = 0; for( int i=0; i<label.rows; i++ ){ unsigned char* cline = (unsigned char*)src_image.ptr(i); for( int h=0; h<label.cols; h++ ){ unsigned char* cpix = cline + 3 * h; for( int neigh_y=-1; neigh_y<=1; neigh_y++ ){ if( i+neigh_y < 0 || label.rows <= i+neigh_y) continue; unsigned char* nline = (unsigned char*)src_image.ptr(i+neigh_y); for( int neigh_x=-1; neigh_x<=1; neigh_x++ ){ if( neigh_x == 0 && neigh_y == 0 || h+neigh_x < 0 || label.cols <= h+neigh_x) continue; //パラメータの計算 unsigned char* npix = nline + 3 * (h+neigh_x); double b = (double)npix[0] - cpix[0]; double g = (double)npix[1] - cpix[1]; double r = (double)npix[2] - cpix[2]; double C = b*b + g*g + r*r; double G = 1 - C / (3 * 255 * 255); //注目しているセルが勝ちなら更新しない if( G * strength.at<double>( i+neigh_y, h+neigh_x ) <= strength.at<double>( i, h )) continue; //注目しているセルが負けたのでラベルと強さを更新 converged++;//セルの強さが何個更新されたか next_label.at<double>( i, h ) = label.at<double>( i+neigh_y, h+neigh_x ); next_strength.at<double>( i, h ) = G * strength.at<double>( i+neigh_y, h+neigh_x ); } } } }
アルゴリズム部分はほんとにこれだけ。
ほかはGUIで操作しやすいようにだったり、画像に変換して見やすいようにしたりとかの処理です。
あまり考えずにプログラム作ったのでところどころおかしいですが、一応動くはずです。
ご自由にお使いください。