Pynote

Python、機械学習、画像処理について

OpenCV - 大津の手法/判別分析法

概要

大津の手法 (Otsu method) は2値化を行う際の閾値を画像に応じて自動的に決めるアルゴリズムである。
2値化を輝度値のヒストグラムを2クラス分類する問題と考えて解くので、判別分析法とも呼ばれる。

アルゴリズム

h_i (i = 0, 1, \cdots, 255) を輝度値のヒストグラムを作成した際の輝度値 i の度数とする。
つまり、画像中で輝度値が i である画素数を表す。

輝度値のヒストグラム閾値 t によって2クラスに分ける。

{\displaystyle
\left\{ 
\begin{array}{l}
h_i \in C_1 & i \le t\\
h_i \in C_2 & otherwise
\end{array}
\right.
}

クラス C_1 の平均と分散

輝度値が i \le t である画素がクラス C_1 であるから、

クラス C_1 の画素数
{\displaystyle
\omega_1 = \sum_{i=0}^{t}{h_i}
}

クラス C_1 の輝度値の平均
{\displaystyle
\mu_1 = \frac{1}{\omega_1} \sum_{i=0}^{t}{h_i i}
}

クラス C_1 の輝度値の分散
{\displaystyle
\sigma_1^2 = \frac{1}{\omega_1} \sum_{i=0}^{t}{(h_i - \mu_1)^2}
}

クラス C_2 の平均と分散

輝度値が i > t である画素がクラス C_2 であるから、

クラス C_2 の画素数
{\displaystyle
\omega_2 = \sum_{i=t+1}^{255}{h_i}
}

クラス C_2 の輝度値の平均
{\displaystyle
\mu_2 = \frac{1}{\omega_2} \sum_{i=t+1}^{255}{h_i i}
}

クラス C_2 の輝度値の分散
{\displaystyle
\sigma_2^2 = \frac{1}{\omega_2} \sum_{i=t+1}^{255}{(h_i - \mu_2)^2}
}

画像全体の平均と分散

画像全体の画素数
{\displaystyle
\omega = \omega_1 + \omega_2
}

画像全体の輝度値の平均
{\displaystyle
\mu = \frac{1}{\omega} \sum_{i=0}^{255}{h_i i} = \frac{1}{\omega} (\mu_1 + \mu_2)
}

画像全体の輝度値の分散
{\displaystyle
\sigma^2 = \frac{1}{\omega} \sum_{i=0}^{255}{(h_i - \mu)^2} = \frac{1}{\omega} (\sigma_1^2 + \sigma_2^2)
}

クラス内分散

クラス内分散とは、各クラスの分散の平均値である。

{\displaystyle
\sigma_w^2 = \frac{1}{\omega} (\omega_1 \sigma_1^2 + \omega_2 \sigma_2^2)
}

クラス間分散

クラス間分散とは、各クラスの平均値の分散である。

{\displaystyle
\sigma_b^2 = \frac{1}{\omega} (\omega_1 (\mu_1 - \mu)^2 + \omega_2 (\mu_2 - \mu)^2)
}

ここで、

{\displaystyle
\sigma^2 = \sigma_w^2 + \sigma_b^2
}

という関係が成り立つことに注意する。

分離度

ここで、分離度を以下のように定義する。

{\displaystyle
\frac{\sigma_b^2}{\sigma_w^2} = \frac{\sigma_b^2}{\sigma^2 - \sigma_b^2}
}

各クラス内の分散を小さくしつつ、各クラスの平均が離れているようなクラス分類が望ましい。
つまり、分離度が最大になるように閾値を決めればよい。

\sigma^2閾値によらず画像に応じて決まる値なので、\sigma_b^2 が最大となるとき、\frac{\sigma_b^2}{\sigma^2 - \sigma_b^2} も最大となる。
したがって、\sigma_b^2 が最大となる閾値 t を決めればよい。

サンプルコード

OpneCV では、cv2.threshold() で thresholdType=cv2.THRESH_BINARY + cv2.THRESH_OTSU とすることで行える。

ret, dst = cv2.threshold(src, dst, threshold, maxValue, thresholdType)
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 画像をグレースケールとして読み込む。
img = cv2.imread('sign.jpg', 0)

# Thresholding を実行する。
# 第2引数 threshold の値は使われないのでなんでもよい。
# 返り値の thresh に大津の手法によって決まった閾値が入る。
thresh, bin_img = cv2.threshold(img, 0, 255,
                                cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# 結果をグラフに描写する。
fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(10, 6))

ax1.imshow(img, cmap='gray')
ax1.set_title('Original Image')
ax1.set_xticks([]), ax1.set_yticks([])

ax2.imshow(bin_img, cmap='gray')
ax2.set_title('Otsu')
ax2.set_xticks([]), ax2.set_yticks([])

print('threshold = {}'.format(thresh))
plt.savefig("otsu_01.png", bbox_inches="tight")

f:id:nekobean:20170731231744p:plain