はじめに
画像処理を簡単に解説していきます。
下部にはJavaでのソースコードも載せているのでぜひご参照ください。
不定期にはなりますが、随時更新していく予定です。
判別分析法とは?
二値化のしきい値を自動で決める手法の一つです。
別名として大津の二値化という名称もよく知られています。
どういう方法かというと、二値化した時の画像の白黒のバランスが良くなるように計算します。
具体的には白画素領域と黒画素領域のクラス内分散を小さく、かつ、クラス間分散を大きくするようにしてくれます。
画像ヒストグラムを想像するとわかりやすいと思います。
画像ヒストグラムとは?
画像ヒストグラムとは画像内の画素数を、画素値ごとにカウントしたグラフです。
グレースケール画像の場合、横軸が0から255になり、縦軸がそれぞれの画素数です。
つまり、山が右寄りなら明るい画像、左寄りならば暗い画像となります。
これを知っていると、イメージがしやすくなるので覚えておくことをお勧めします。
二値化というのは、画像ヒストグラム状に一本の縦線を引くという処理です。
引いたうえで、線の左は黒、右は白にしてしまうというわけです。
判別分析法では、この縦線を引く個所を最適になるように計算してくれるのです。
判別式はどんな式なの?
判別式は、判別分析法で用いられる評価式です。
判別式が最大となるときのしきい値が最適なしきい値として採用されます。
実際の式としては以下の通りです。
導出過程を載せると長くなりますので、最終的な式だけ載せます。
途中式を見たい方はちょっと調べればすぐに出てくると思います。
$\sigma = \frac{黒画素数}{全画素数} × \frac{白画素数}{全画素数} × \left(\frac{黒領域の画素値合計}{黒画素数}-\frac{白領域の画素値合計}{白画素数}\right)^2$
アルゴリズム
- カラー画像はグレースケール変換する。
- しきい値と最大判別式を保存する変数を用意する。
- しきい値を1から255まで移動しながら、4から9の処理を行う。
- 判別式計算用の変数(画素数合計、画素値合計)を初期化する。
- グレースケール画像をラスタスキャンし、各画素に対して6と7の処理を行う。
- 画素のグレー値を取得し、3のしきい値より大きければ白を、そうでなければ黒とする。
- 6で求めた色に応じて、4で用意した合計変数をカウントアップする。
- ラスタスキャン後、前述の判別式を4の変数を用いて計算する。
- 8で求めた判別式が、2で作成した最大判別式より大きければ、しきい値と最大判別式を更新する。
- 3のループが終了後に得られた最適なしきい値で二値化する。
判別分析法による二値化の例
ソースコード
画像がグレースケールであることの判定なども入れるべきなのですが、複雑になると考えたので省略しています。
また、高速化などは一切入れていないので、処理は早くないのであらかじめご了承ください。
public static SImage discriminantAnalysisMethod(SImage grayImg) {
// グレースケール画像でない場合、まずグレースケール変換する
if (grayImg.channel != 1)
return discriminantAnalysisMethod(toGray(grayImg));
// しきい値
int threshold = 0;
// 判別式の最大値
double sigmaMax = Double.MIN_VALUE;
// 暫定しきい値tを1から255まで移動
for (int t = 1; t <= 255; t++) {
// 計算用の変数初期化
int[] count = {0, 0};
int[] sum = {0, 0};
// 画像をラスタスキャン
for (int x = 0; x < grayImg.width(); x++)
for (int y = 0; y < grayImg.height(); y++) {
// グレー値を取得
int gray = grayImg.getGray(x, y);
// 暫定しきい値を超えた場合1を、そうでない場合0をインデックスにセット
int index = gray >= t ? 1 : 0;
// 計算用の変数を更新
count[index]++;
sum[index] += gray;
}
// 判別式を用いた更新
if (count[0] > 0 && count[1] > 0) {
// 判別式計算
double sigma = ((double) count[0] / (count[0] + count[1])) * ((double) count[1] / (count[0] + count[1])) * Math.pow((double) sum[0] / count[0] - (double) sum[1] / count[1], 2);
// 判別式が更新された場合、しきい値を更新
if (sigma >= sigmaMax) {
sigmaMax = sigma;
threshold = t;
}
}
}
// 計算結果のしきい値で二値化
return binarize(grayImg, threshold);
}