OpenCV 5.0.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 5.0.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
ヒストグラム - 2: ヒストグラム平坦化

目的

本節では、

  • ヒストグラム平坦化の概念を学び、それを用いて画像のコントラストを改善する。

理論

ピクセル値がある特定の値の範囲のみに限られている画像を考える。例えば、明るい画像はすべてのピクセルが高い値に集中する。しかし良い画像は、画像のあらゆる領域にわたるピクセルを持つ。そのため、このヒストグラムを両端へ引き伸ばす必要があり(下の画像にあるとおり、wikipedia より)、それがヒストグラム平坦化の行うこと(簡単に言えば)である。これにより通常、画像のコントラストが改善される。

image

詳細については、Wikipediaの Histogram Equalization のページを読むことを勧める。具体例を交えた非常に分かりやすい説明があり、それを読めばほぼすべてを理解できるだろう。ここではその代わりに、Numpyによる実装を見ていく。その後、OpenCVの関数を見る。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('wiki.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
hist,bins = np.histogram(img.flatten(),256,[0,256])
cdf = hist.cumsum()
cdf_normalized = cdf * float(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()
Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
Loads an image from a file.

ヒストグラムが明るい領域に偏っているのが分かる。スペクトル全体が必要である。そのためには、明るい領域の入力ピクセルを全域の出力ピクセルにマッピングする変換関数が必要となる。これがヒストグラム平坦化の役割である。

次に、ヒストグラムの最小値(0を除く)を求め、wikiのページに示されたヒストグラム平坦化の式を適用する。ただし、ここではNumpyのマスク配列(masked array)の概念を用いている。マスク配列では、すべての演算がマスクされていない要素に対して行われる。これについてはNumpyのマスク配列に関するドキュメントで詳しく読むことができる。

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]

次に、先ほどと同様にヒストグラムとcdfを計算すると(自分でやってみよう)、結果は以下のようになる:

image

もう一つの重要な特徴は、(ここで使った明るい画像ではなく)暗い画像であっても、平坦化後にはほぼ同じ画像が得られるという点である。その結果、これはすべての画像を同じ照明条件にそろえる「基準ツール」として利用される。これは多くの場面で役立つ。例えば顔認識では、顔データを学習させる前に、顔画像をヒストグラム平坦化してすべて同じ照明条件にそろえる。

OpenCV におけるヒストグラム平坦化

OpenCVにはこれを行う関数 cv.equalizeHist() がある。その入力はグレースケール画像だけで、出力はヒストグラム平坦化された画像である。

以下は、これまで使ってきたのと同じ画像に対する使い方を示す簡単なコード片である:

img = cv.imread('wiki.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
equ = cv.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side
cv.imwrite('res.png',res)
bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
Saves an image to a specified file.
void equalizeHist(InputArray src, OutputArray dst)
Equalizes the histogram of a grayscale image.

これで、さまざまな照明条件で撮影した画像を平坦化し、結果を確認することができる。

ヒストグラム平坦化は、画像のヒストグラムが特定の領域に偏っている場合に有効である。ヒストグラムが広い領域に分布し、明暗のピクセルがともに存在するような強度変化の大きい場所ではうまく機能しない。Additional Resourcesにある SOF のリンクを確認してほしい。

CLAHE (Contrast Limited Adaptive Histogram Equalization)

今見たばかりの最初のヒストグラム平坦化は、画像全体のコントラストを考慮するものである。多くの場合、これは良い方法ではない。例えば、下の画像は入力画像と、それに全体的なヒストグラム平坦化を適用した結果を示している。

image

ヒストグラム平坦化後に背景のコントラストが改善されたのは事実である。しかし、両方の画像で像(彫像)の顔を比べてみてほしい。明るくなりすぎたために、そこではほとんどの情報が失われている。これは、前の例で見たようにヒストグラムが特定の領域に偏っていないためである(入力画像のヒストグラムをプロットしてみると、より直感的に理解できるだろう)。

この問題を解決するために、適応ヒストグラム平坦化 (adaptive histogram equalization) が用いられる。これでは、画像は「タイル(tiles)」と呼ばれる小さなブロックに分割される(OpenCVではtileSizeはデフォルトで8x8である)。次に、これらの各ブロックを通常どおりヒストグラム平坦化する。したがって小さな領域では、ヒストグラムは(ノイズがない限り)狭い領域に収まる。ノイズがあると、それが増幅されてしまう。これを避けるために、コントラスト制限 (contrast limiting) が適用される。いずれかのヒストグラムビンが指定されたコントラスト上限(OpenCVではデフォルトで40)を超える場合、それらのピクセルはクリップされ、ヒストグラム平坦化を適用する前に他のビンへ均等に分配される。平坦化後、タイル境界のアーティファクトを除去するために、バイリニア補間が適用される。

以下のコード片は、OpenCVでCLAHEを適用する方法を示す:

import numpy as np
import cv2 as cv
img = cv.imread('tsukuba_l.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
# create a CLAHE object (Arguments are optional).
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv.imwrite('clahe_2.jpg',cl1)
Ptr< CLAHE > createCLAHE(double clipLimit=40.0, Size tileGridSize=Size(8, 8))
Creates a smart pointer to a cv::CLAHE class and initializes it.

下の結果を見て、上の結果、特に彫像の領域と比べてみてほしい:

image

その他のリソース

  1. Wikipediaの Histogram Equalization のページ
  2. Masked Arrays in Numpy

コントラスト調整に関する次の SOF の質問も確認してほしい:

  1. How can I adjust contrast in OpenCV in C?
  2. How do I equalize contrast & brightness of images using opencv?