このチュートリアルでは
ヒストグラムとは、画像中の画素値の全体的な分布を知るためのグラフやプロットと考えることができる.横軸に画素値(大抵の場合0から255の値を持つ),縦軸に画素値の出現頻度をプロットしたものである.ヒストグラムをこのように可視化することは、画像を理解する一つの方法である.ヒストグラムを見れば画像のコントラスト,明るさ,画素値の分布などが直観的に理解できるからである.今日利用できる画像処理ソフトのほとんどがヒストグラムに関する機能を持っている.次に示す画像は Cambridge in Color website から取り出したものの1枚である.詳細についてはサイトを参照すること.
画像とそのヒストグラムを比べてみよう(このヒストグラムはグレースケール画像のヒストグラムであり,カラー画像のヒストグラムではないことに注意).ヒストグラムの左側の領域は画像の中の暗い画素の出現頻度,右側の領域は画像の中の明るい画素の出現頻度を表している.このヒストグラムから、この画像は暗い領域の方が多いことや、画素値の中間領域(ここでは127付近)の画素はほとんどないことなどが分かる.
ヒストグラムがどんなものであるか分かったこととして,次は計算方法について学ぼう.このための関数を,OpenCvとNumpyはそれぞれ用意している.ただし、これらの関数を使う前にヒストグラムに関する専門用語を理解する必要がある.
BINS(ビン) :上に示したヒストグラムは全画素値(0から255)の画素数を表示しており,合計256個の数値が必要である.しかし,画素値それぞれの頻度(画素数)ではなく、画素値のある範囲ごとの頻度を知りたいとしよう.例えば0から15,16から31, ..., 240から255の範囲での画素値を持つ画素の出現頻度を知りたいとする。このヒストグラムを表すために必要なのはたったの16個の数だけである.これがまさに OpenCVのヒストグラムのチュートリアル(英語版) の例である(下図はそこから拝借).
このためにすべき作業は、ヒストグラム全体を16個の小領域に分割し,それぞれの小領域の画素数の合計値を計算することである.この小領域のことを “ビン(BIN)” と呼ぶ.最初の例ではビンの数は256(各ビンが各画素値に対応),二番目の例ではビンの数は16である.OpenCVのドキュメント中ではビンの数は histSize
と呼ばれている.
DIMS(次元数) : 扱うデータがもつパラメタの個数である.今回の例では1つの値(画素値)だけを持つデータを扱っている.それゆえ、この例では1である.
RANGE(ビン境界) : 計測対象の値の範囲を表す.通常は画素値の全範囲、すなわち [0,256]
とする.
それでは cv2.calcHist() 関数を使ってヒストグラムを計算してみよう.まずは関数とそのパラメータについて慣れよう :
cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
[]
を使って [img]
のように指定する (imgが画像データの変数とした).[0]
を指定する.カラー画像であればヒストグラムを計算するB,G,Rの色相に対応する[0],[1],[2]
のどれかの値を指定する.[]
を使って指定する.全画素値を対象とするのであれば [256]
を指定する.[0,256]
を指定する.サンプル画像のヒストグラムを計算してみよう.サンプル画像をグレースケール画像として読み込み,全範囲のヒストグラムを計算する.
import cv2
img = cv2.imread('home.jpg',0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])
print(hist.shape)
histは256x1の配列で,その要素は、インデックスで表される画素値を持つ画素の数を表す.
Numpyもヒストグラムの計算をするための関数 np.histogram() を用意している. calcHist() の代わりにこの関数を使ってみよう. :
import numpy as np
hist,bins = np.histogram(img.ravel(),256,[0,256])
print(hist.shape, bins.shape)
hist は先ほどのOpenCVの例と同じであるが,ビンの数が257個である点が違う.なぜなら,Numpyはビンを0-0.99, 1-1.99, 2-2.99 などと計算するからである.つまり,最後の範囲は255.99-256になる.これを表現するためにビンの最後に256が追加されている.しかし,実際にはこの256は不要で255までで十分である.
参考: Numpyはnp.histogram()より10倍ほど高速な np.bincount() という関数も用意している.1次元ヒストグラムを計算するのであれば,この関数を使うのがよいだろう.minlength = 256
と設定するのを忘れないようにしよう.例えば hist = np.bincount(img.ravel(),minlength=256)
といった具合である.
Note: OpenCVの関数はnp.histogram()
と比べて,さらに高速(40倍ほど)である.よってOpenCVを使うと良い.
%timeit hist = cv2.calcHist([img],[0],None,[256],[0,256])
%timeit hist,bins = np.histogram(img.ravel(),256,[0,256])
%timeit hist = np.bincount(img.ravel(),minlength=256)
次はヒストグラムを表示してみよう.
%matplotlib inline
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('home.jpg',0)
plt.figure(figsize=(10,5))
plt.subplot(1,2,1)
plt.imshow(img,'gray')
plt.subplot(1,2,2)
plt.hist(img.ravel(),256,[0,256])
plt.show()
別な方法として、matplotlibの通常のプロットを使いてもよい.この方法はカラー画像のヒストグラムの可視化に向いている.まず初めにヒストグラムを計算しておく必要がある.以下のコードを試してみよ:
%matplotlib inline
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('home.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
histr = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()
このヒストグラムを見ると,画像中の青成分が高い値を持っていることが分かる(空の色であることは明らかだろう).
%matplotlib inline
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('home.jpg',0)
# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv2.bitwise_and(img,img,mask = mask)
# Calculate histogram with mask and without mask
# Check third argument for mask
hist_full = cv2.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv2.calcHist([img],[0],mask,[256],[0,256])
plt.figure(figsize=(15,10))
plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask,'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])
plt.show()
結果を見てみよう.上のヒストグラムのうち,青線が画像全体のヒストグラム,橙線がマスク画像で指定した領域のヒストグラムを表している.