【C++】判別分析法による閾値決定プログラム【画像処理の基本】

スポンサーリンク
判別分析法出力結果C++
スポンサーリンク

はじめに

今回は二値化の基本的な閾値決定法の一つである判別分析法について考えていきます。

こちらの記事の二値化プログラムに追加する形で実装していくこととし、ここでは閾値を判別分析法で導出する関数を作ります。

判別分析法を実装するまでの前処理についてはアルゴリズムの説明は省きます。

判別分析法について

判別分析法とは、ある閾値を基準に輝度ヒストグラムを二つのクラスに分けた時、この二つの分離が最も良い閾値を求める手法。

具体的には、クラス間分散(二つのクラスの平均値の分散)をクラス内分散(各クラスの分散)で割った値が最大になるように閾値を求めます。

アルゴリズムの説明

ヒストグラムの作成

二値化のプログラムを参考に、輝度値と画素数のヒストグラムを作成する。

画像全体の画素数も計算する。

判別分析法の実装

  1. 閾値を仮置きして式に代入する
  2. 式から得た値がそれまでの閾値の時より大きければ、値とその時の閾値を保存
  3. 全ての閾値で1,2を繰り返して、最大値を取る閾値を求める

ソースコード

//////////////////////////////////////////////////
//////////////////////////////////////////////////
//判別分析法
//////////////////////////////////////////////////
//////////////////////////////////////////////////

int d_analysis(){

	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 ay_p = 1;
	int tsd_v = 0;
	double ay_m = 0;
	
	while(ay_p < 255){
		
		double pix_1 = 0;
		double pix_2 = 0;
		
		double pix_t1 = 0;
		double pix_t2 = 0;
		
		for(int i = 0;i < ay_p;i++){ //クラス1の画素数と輝度値の量を算出
			
			pix_1 += st_p[i];
			pix_t1 += i*st_p[i];
		}
		
		for(int i = ay_p;i < 256;i++){ //クラス2の画素数と輝度値の量を算出
			
			pix_2 += st_p[i];
			pix_t2 += i*st_p[i];
		}
		
		if(ay_m < pix_1*pix_2*(((pix_t1/pix_1)-(pix_t2/pix_2))*((pix_t1/pix_1)-(pix_t2/pix_2)))){ //求める式の値が今までのものより大きければ値と閾値を保存
			
			ay_m = pix_1*pix_2*(((pix_t1/pix_1)-(pix_t2/pix_2))*((pix_t1/pix_1)-(pix_t2/pix_2)));
			tsd_v = ay_p;
		}
		
		ay_p++;
	}
	
	return tsd_v;
}

プログラムを実行する

閾値を計算して実行するには、次のようにして関数を呼び出します。

do_binary(d_analysis());

例えば次のような画像に対して実行すると

モード法検証画像

閾値は63に選定され、二値化すると次のようになります。

判別分析法出力結果

最後に

アルゴリズムが単純で簡単に書ける手法ですが、精度が良い手法だと思います。

デバッグ等を全くしてない状態なので、エラーなどが起こってしまった場合にはコメント欄でお知らせください。

コメント

タイトルとURLをコピーしました