Pythonによる画像処理100本ノック#4 大津の二値化(判別分析法)


はじめに

どうも、らむです。
二値化に用いる閾値を自動で決定する手法である大津の二値化(判別分析法)を実装します。

4本目:大津の二値化

二値化は画像を白黒の2色のみのモノクロ画像に変換する処理です。なお、閾値を決めておき、閾値未満の画素値は白、閾値以上の画素値を持つ画素は黒に置き換えます。ここまでは、前回の二値化にて説明しました。
今回はこの閾値を自動で決定する手法を取り扱います。

大津の二値化では閾値によってクラスを2つに分割します。
この2つのクラスにおいて分離度が最大となるときの閾値を二値化のときの閾値とします。
分離度の計算に必要なパラメータは以下の式によって算出できます。

分離度:$X = \dfrac{\sigma _{b}^{2}}{\sigma _{w}^{2}}$

クラス内分散:$\sigma _{b}^{2} = \dfrac{\omega _{0}\omega _{1}}{(\omega _{0}+\omega _{1}) ^2} (M _{0}+M _{1}) ^2$

クラス間分散:$\sigma _{b}^{2} = \omega _{0}\sigma _{0}^{2}+\omega _{1}\sigma _{1}^{2}$

クラス0,1に属する画素数:$\omega _0,\omega _1$

クラス0,1に属する画素値の分散:$\sigma _0,\sigma _1$

クラス0,1に属する画素値の平均:$M_0,M_1$

画像全体の画素値の平均値:$M$

クラス0,1に属する画素値の合計:$P_0,P_1$

まとめると、閾値が0から255の場合で分離度を256回計算し、分離度が最大となるような閾値を求めればいいです。

otsuBinarization.py
import numpy as np
import cv2
import matplotlib.pyplot as plt
# from statistics import variance
import statistics as st
plt.gray()


def otsuBinarization(img):
  # 画像コピー
  dst = img.copy()
  # グレースケール化
  gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)

  w,h = gray.shape
  Max = 0

  # 画像全体の画素値平均
  M = np.mean(gray)

  # 閾値256値全てに適用
  for th in range(256):
    # クラス分け
    g0,g1 = gray[gray<th],gray[gray>=th]

    # 画素数
    w0,w1 = len(g0),len(g1)
    # 画素値分散
    s0_2,s1_2 = g0.var(),g1.var()
    # 画素値平均
    m0,m1 = g0.mean(),g1.mean()
    # 画素値合計
    p0,p1 = g0.sum(),g1.sum()

    # クラス内分散
    sw_2 = w0*s0_2 + w1*s1_2
    # クラス間分散
    sb_2 = ((w0*w1) / ((w0+w1)*(w0+w1))) * ((m0-m1)*(m0-m1))
    # 分離度
    if (sb_2 != 0):
      X = sb_2 / sw_2
    else:
      X = 0

    if (Max < X):
      Max = X
      t = th

  # 二値化
  idx = np.where(gray < t)
  gray[idx] = 0
  idx = np.where(gray >= t)
  gray[idx] = 255

  return gray


# 画像読込
img = cv2.imread('image.jpg')

# 大津の二値化
mono = otsuBinarization(img)

# 画像保存
cv2.imwrite('result.jpg', mono)
# 画像表示
plt.imshow(mono)
plt.show()

  

画像左は入力画像、画像真ん中は手動で閾値を128に設定したときの出力画像、画像右は今回の出力画像です。
自動で閾値を決めて二値化してもさほど違和感の無い画像が出力されていますね。
余談ですが、私の実装では画像全体の画素値平均Mは使ってないですね。

おわりに

もし、質問がある方がいらっしゃれば気軽にどうぞ。
imori_imoriさんのGithubに公式の解答が載っているので是非そちらも確認してみてください。
それから、pythonは初心者なので間違っているところがあっても優しく見守ってコメントしてあげてください。