はじめに
今回は二値化の基本的な閾値決定法の一つであるpタイル法について考えていきます。
こちらの記事の二値化プログラムに追加する形で実装していくこととし、ここでは閾値をpタイル法で導出する関数を作ります。
pタイル法を実装するまでの前処理についてはアルゴリズムの説明は省きます。
pタイル法について
pタイル法とは、画像の二値化したい対象物の画像全体に対する割合pが予め分かっている場合に、割合pを元に閾値を指定する手法のことです。
アルゴリズムの説明
ヒストグラムの作成
二値化のプログラムを参考に、輝度値と画素数のヒストグラムを作成する。
画像全体の画素数も計算する。
pタイル法の実装
- 入力された面積比率pと画像全体の画素数ωから対象物の画素数を計算する(p*ω)。
- ヒストグラムから輝度値毎の画素数を大きい方(ここでは255)から取り出す。
- 対象物の画素数と輝度値の画素数を比較する。
- 対象物の画素数の方が大きい場合には、対象物の画素数から輝度値の画素数を引き、比較する輝度値を1小さくして比較を繰り返す。
- 上記以外の場合には、比較した輝度値を閾値とする。
要するに、ヒストグラムを使って輝度値の大きな方から順に見て、画素数が決められた割合だけ切り取れる輝度値を見つけ出す。というプログラムです。
ソースコード
//////////////////////////////////////////////////
//////////////////////////////////////////////////
//pタイル法
//////////////////////////////////////////////////
//////////////////////////////////////////////////
int p_tile(){ //pタイル法
double pct_a = 0;
std::cout << "画像全体の面積に対する対象物(白)の割合(%)を入力してください(ex:70)" << std::endl;
std::cin >> pct_a; //割合の入力
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 count = 0; //値が合計で何個あるのかを調べる,初期値に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]++;
count++;
}else if(st_value[0] == 0){ //0があった場合の処理
st_p[0]++;
count++;
}
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 tsd_v = 0;
double target_p = count * (pct_a/100); //対象物の割合から対象物の画素数を求める
for(int i = 255;i >= 0;i--){
if(target_p > st_p[i]){ //対象物の画素数と各輝度値の画素数の比較
target_p -= st_p[i]; //対象物の画素数が多ければ,各輝度値の画素数分引く
}else{
tsd_v = i; //対象物の画素数が多ければ,その時の輝度値を閾値とする
break;
}
}
return tsd_v;
}
プログラムの検証
サクッと検証します。
本記事を参考に作ったプログラムで実験を行う際には、適切な画像を複数用意してプログラムの検証をすることをお勧めします。
次のようなPGM画像を用意しました。

100 120 140 160 180 180 160 140 120 100
この画像は上記の数値の組を縦横10組ずつ繰り返したものであり、輝度値が最も高い180である画素の割合は20%であることが分かっています。
このPGM画像にpタイル法を適用し、面積比率pを20%に設定すると理論上では閾値は180となります。
do_binary(p_tile());
として二値化とpタイル法を呼び出して検証を行います。
今回作ったプログラムにこの画像を入力画像として渡すと、理論通り閾値は180が得られ、二値化を行うと次のような画像を得ることができました。

最後に
多分pタイル法はこんな感じでいいのではないでしょうか。
間違っていたら教えてください。
メモリのことなどを一切考えてないのでもしかすると重かったりコアダンプしたりするかも…
コメント