はじめに
今回は二値化の基本的な閾値決定法の一つであるモード法について考えていきます。
こちらの記事の二値化プログラムに追加する形で実装していくこととし、ここでは閾値をモード法で導出する関数を作ります。
モード法を実装するまでの前処理についてはアルゴリズムの説明は省きます。
モード法について
モード法とは対象と背景の輝度にある程度明瞭な差があるとき。
要するに画像の輝度ヒストグラムにおいて対象および背景の平均輝度近傍にそれぞれピークを持つ双峰系の分布を示す場合に、輝度ヒストグラムにおいて分布の極小値を示す輝度を閾値とする方法です。
アルゴリズムの説明
ヒストグラムの作成
二値化のプログラムを参考に、輝度ヒストグラムを作成する。
画像全体の画素数も計算する。
モード法の実装
- 輝度をある程度の範囲で区切って、改めて輝度の範囲によるヒストグラムを作成
- 作成したヒストグラムにおいて、極小値をとる輝度のうち最も小さい値の範囲を選択
- 選択した値の範囲を拡大したヒストグラムを作成
- 値の範囲が1になるまで2,3を繰り返す
ソースコード
////////////////////////////////////////////////// ////////////////////////////////////////////////// //モード法 ////////////////////////////////////////////////// ////////////////////////////////////////////////// int d_mode(){ std::ifstream ifs(input); //入力ファイルストリーム std::string str_i; //文字列型 if(ifs.fail()){ //ファイルを開けない時のエラー処理 std::cerr << "Failed to open file." << std::endl; return -1; } getline(ifs, str_i); //一行目の読み込み if(str_i != "P2"){ //P2画像でない時のエラー処理 std::cerr << "Can not handle." << std::endl; return -1; } while(1){ //コメント行から最大値行までを読み込み getline(ifs, str_i); //一行を読み取り if(str_i[0] != '#'){ //コメント行でなければ最大値行を読み込んでループ処理を抜ける getline(ifs, str_i); //最大値の読み込み int max = atoi(str_i.c_str()); if(max > 255){ //最大値が255より大きい時のエラー処理 std::cerr << "Can not handle." << std::endl; return -1; } break; } } int st_p[256] = {}; //ヒストグラム用の配列に0 int st_value[3] = {-1,-1,-1}; //一時記憶用の配列 while(getline(ifs, str_i)){ //最終行まで繰り返し,一行ずつ読み込み for(int i = 0;i < str_i.length();i++){ //文字列の長さだけループ if(str_i[i] == '#'){ //コメントがついた時点でループを抜け出す. break; } else if(str_i[i] == ' '){ //空白だった時の処理 int digits = 0; int value = 0; for(int j = 2;j >= 0;j--){ //配列にスタックされたデータを全て取り出す. if(st_value[j] > -1){ value += st_value[j]*std::pow(10,digits); //各桁の数字から実際の数値に変換(ab) > (10*a + b) digits++; } } if(value > 0){ //数値が何かあった場合の処理 st_p[value]++; }else if(st_value[0] == 0){ //0があった場合の処理 st_p[0]++; } for(int l = 0;l < 3;l++){ //配列の初期化 st_value[l] = -1; } }else{ //空白でなかった時の処理(データを格納する場所を探す) for(int k = 0;k < 3;k++){ //0から2までで格納(最大値が255)[0] = 一の位 [2] = 100の位 if(st_value[k] == -1){ //-1であれば値が入っていない=まだ決まっていない桁 int data_v = 0; data_v = (int)(str_i[i] - '0'); //char型の数字をint型に変換 st_value[k] = data_v; //各桁の数字のみを配列に格納 break; } } } } } //開いたファイルを閉じる ifs.close(); int ptc_st = 32; //分割数 int pm = 0; int pm_f = 0; int pm_l = 256; int tsd_v = 0; //閾値 do{ int st_m[256] = {}; int p_m = pm; for(int i = pm_f;i < pm_l;i++){ //拡大ヒストグラム作成 if(i < (256/ptc_st)*(p_m+1)){ st_m[p_m] += st_p[i]; }else{ p_m++; st_m[p_m] += st_p[i]; } } int ch_c = 0; for(int i = 1;i < ptc_st-1;i++){ if(st_m[i-1] > st_m[i] && st_m[i] < st_m[i+1]){ //極小値の検索 pm = (i-1)*2; //次の範囲指定 pm_f = (256/ptc_st)*(i-1); pm_l = (256/ptc_st)*(i+2); tsd_v = i; //極小値の輝度を閾値とする ch_c = 1; //極小値があったか確認 break; } } if(ch_c == 0){ //極小値がなければ谷の中心を閾値にする tsd_v *= 2; } ptc_st *= 2; //範囲を拡大する }while(ptc_st < 257); return tsd_v; }
プログラムを実行する
閾値を計算して実行するには、次のようにして関数を呼び出します。
do_binary(d_mode());
例えば次のような画像に対して実行すると
閾値は110に選定され、二値化すると次のようになります。
最後に
画像処理が得意なわけではないのですが、参考になれば幸いです。
ノイズの除去についてはかなり大雑把な処理になっているので、注意してください。
コメント