ヒストグラム その2: ヒストグラム平坦化¶
理論¶
画素値が特定の範囲に集中している画像を考えてみてください.例えば明るい画像は画素値が高い範囲に集中しています.しかし,良い画像は画素値が全範囲に万遍なく分布しているものです.良い画像を得るにはこのヒストグラムを両側に向けて伸ばす必要があり,これがまさにヒストグラム平坦化の処理になります.この処理によって画像のコントラストが改善されます.
詳細な説明に関しては,Wikipediaの ヒストグラム平坦化 に関する記事を読むことをお勧めします.例を示しながら詳しく説明しているため,十分な理解が得られるでしょう.代わりに,Numpyを使った実装を紹介し,次にOpenCVの関数を紹介します.
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('wiki.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])
cdf = hist.cumsum()
cdf_normalized = cdf * hist.max()/ cdf.max()
plt.plot(cdf_normalized, color = 'b')
plt.hist(img.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')
plt.show()
ヒストグラムが明るい領域に集中している事が分かります.全スペクトルが必要なため,原画像の明るい領域に集中した画素値を全範囲に分布させるための変換関数が必要です.これがヒストグラム平坦化が行う処理です.
ヒストグラムの最小値(0ではない)を見つけ,wikipediaのページに書いてあるようにヒストグラム平坦化の式に適用します.ここでNumpyのマスクされた配列の概念を使います.マスクされた配列を使うと,全処理がマスクされていない要素にのみ作用します.詳しくはNumpyのドキュメントのマスクされた配列(masked arrays)の部分を参照してください.
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
cdf = np.ma.filled(cdf_m,0).astype('uint8')
出力画像を得るための参照テーブルが求まりました.参照テーブルを使った画素値の返還は以下の王になります.
img2 = cdf[img]
結果画像,結果画像のヒストグラム,結果画像の画素値の累積分布関数は以下のようになります :
もう一つの重要な特徴として,もしも入力画像が例にしようした明るい画像ではなく全体的に暗い画像だったとしても,ヒストグラム平坦化によって得られる結果はほとんど同じような画像になります.結果として全ての画像を同じような光源環境で撮影した画像へ変換する “参照ツール” とも言えます.これは様々なケースで役に立ちます.例えば,顔認識を行う際に,顔画像データベースを使った学習を行う前に全データに対してヒストグラム平坦化を行い光源環境を揃えることができます.
OpenCVを使ったヒストグラム平坦化¶
OpenCVはヒストグラム平坦化を使うための cv2.equalizeHist() 関数を用意しています.グレースケール画像を入力とし,ヒストグラム平坦化された画像を出力します.
同じ画像に対して cv2.equalizeHist() を適用した結果を示します :
img = cv2.imread('wiki.jpg',0)
equ = cv2.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side
cv2.imwrite('res.png',res)
これで光源環境が異なる色々な画像を撮影し,コントラストを向上させ,結果を確認できるようになりました.
ヒストグラム平坦化はヒストグラムが特定の範囲に集中している時に効果を発揮します.一方で,ヒストグラムが広範囲に分布しているような画像に対してはうまく機能しません.補足資料に載せたStackOverflowのページを参照してください.
CLAHE (Contrast Limited Adaptive Histogram Equalization)¶
最初に紹介したヒストグラム平坦化は画像全体のコントラストを考慮した処理です.大半のケースではこのアイディアはあまり上手くいきません.例えば以下に示す画像は入力画像とヒストグラム平坦化の結果画像です.
背景領域のコントラストは向上したものの,胸像の顔を見ると過度に明るくなってしまったために詳細を失っています.これは,入力画像のヒストグラムが特定の範囲に偏っていないことが原因です(入力画像のヒストグラムをプロットしてみてください).
この問題を解決するためには 適用的ヒストグラム平坦化 をしなければなりません.この処理は,画像を “タイル(tiles)” (OpenCVのデフォルトでは8x8)と呼ばれる小領域に分割し,領域毎にヒストグラム平坦化を適用します.(ノイズが無ければ)小領域中ではヒストグラムが小さい範囲に集中すると仮定しています.ノイズがある場合,強調されてしまいます.これを防ぐために コントラストの制限 を適用します.もしもビンの出現頻度が特定の上限値(OpenCVのデフォルトでは40)を超えた場合,上限値を超える画素はその他のビンに均等に分配され,その後にヒストグラム平坦化を適用します.平坦化の適用後にタイルの境界に生じる疑似輪郭を消すためにbilinearの内挿をします.
以下のコードがOpenCVでCLAHEを適用するためのコードです:
import numpy as np
import cv2
img = cv2.imread('tsukuba_l.png',0)
# create a CLAHE object (Arguments are optional).
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv2.imwrite('clahe_2.jpg',cl1)
以下の結果を見て上記の結果(特に胸像の領域)と比較してみてください: