ヒストグラム その1: 計算して,プロットして,解析する !!!

目的

このチュートリアルでは
  • OpenCvとNumPyの両方の関数を使ってヒストグラムを計算する方法を学びます.
  • OpenCVとMatplotlibの関数を使ってヒストグラムをプロットする方法を学びます.
  • 以下の関数の使い方を学びます : cv2.calcHist(), np.histogram() etc.

理論

ヒストグラムとは何でしょうか?ヒストグラムとは画像中の画素値の全体的な分布を知るためのグラフやプロットとみなせます.横軸に画素値(常にではないけど0から255の値を持つ),縦軸に画素値の出現頻度を載せるプロットです.ヒストグラムの可視化は画像を理解するための一つの方法です.ヒストグラムを観れば画像のコントラスト,明るさ,画素値の分布などが直観的に理解できます.今日利用できる画像処理ソフトのほとんどがヒストグラムに関する機能を持っています.以下に示す画像は Cambridge in Color website の内の1枚です.詳細についてはサイトを参照してください.

Histogram Example

画像とそのヒストグラムを載せます.(このヒストグラムはグレースケール画像のヒストグラムであり,カラー画像のヒストグラムでない点を注意してください).ヒストグラムの左側の領域は画像中の暗い画素の出現頻度,右側の領域は画像中の明るい画素の出現頻度を表しています.ヒストグラムから暗い領域の方が多いことや画素値の中間領域(ここでは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] の全画素値になります.

1. OpenCVを使ったヒストグラムの計算

それでは cv2.calcHist() 関数を使ってヒストグラムを計算してみましょう.関数とそのパラメータについて慣れましょう :

cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])

  1. images : 入力画像です.画素値のデータ型はuint8かfloat32のどちらかです.記号[]を使って “[img]” のように指定します.
  2. channels : ヒストグラムを計算する画像のチャンネルのインデックスです.入力画像がグレースける画像であれば[0]を指定します.カラー画像であればヒストグラムを計算するB,G,Rの色相に対応する[0],[1],[2]のどれかの値を指定します.
  3. mask : マスク画像です.画像中の全画素のヒストグラムを計算する場合 “None” を指定します.画像中の特定領域のヒストグラムを計算する場合は特定領域を表すマスク画像を指定します(以下に例を示します).
  4. histSize : ビンの数です.この引数も記号[]を使って指定します.全画素値を対象とするのであれば [256] を指定します.
  5. ranges : ヒストグラムを計測したい画素値の範囲を表す RANGE です.通常は [0,256] を指定します.

サンプル画像のヒストグラムを計算してみましょう.サンプル画像をグレースケール画像として読み込み,全範囲のヒストグラムを計算します.

img = cv2.imread('home.jpg',0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])

histは256x1の配列で,各要素は対応する画素値を持つ画素の数を表します.

2. Numpyを使ったヒストグラムの計算

Numpyもヒストグラムの計算をするための関数 np.histogram() を用意しています. calcHist() の代わりにこの関数を使ってみましょう. :

hist,bins = np.histogram(img.ravel(),256,[0,256])

hist は先ほどのOpenCVの例と同じですが,ビンの数が257個である点が違います.なぜなら,Numpyは便を0-0.99, 1-1.99, 2-2.99 と計算するからです.つまり,最後の範囲は255-255.99になります.これを表現するためにビンの最後に256が追加されています.しかし,ビンは256もいらず,255で十分です.

See also

Numpyはnp.histogram()より10倍ほど高速な np.bincount() という関数も用意しています.1次元ヒストグラムを計算するのであれば,この関数を使ってみてください. minlength = 256 と設定するのを忘れないようにしてください.例えば hist = np.bincount(img.ravel(),minlength=256) といった具合です.

Note

OpenCVの関数はnp.histogram()と比べて,さらに高速(40倍ほど)です.なので,OpenCVを使うと良いでしょう.

次はヒストグラムを表示してみましょう.

ヒストグラムの可視化

ヒストグラムを可視化する方法は2種類あります
  1. 短いコードで: Matplotlibのプロット関数を使う
  2. 長いコードで: OpenCVの描画関数を使う

1. Matplotlibを使う

Matplotlibはヒストグラムを描画する関数を用意しています : matplotlib.pyplot.hist()

この関数はヒストグラムの計算と描画の両方を行う関数です.そのため,calcHist() や np.histogram()によってヒストグラムを計算する必要がありません.以下に使い方を示します:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('home.jpg',0)
plt.hist(img.ravel(),256,[0,256]); plt.show()

以下のようなプロットになります:

Histogram Plotting in Matplotlib

もしくはmatplotlibの通常のプロットを使います.この方法はカラー画像のヒストグラムの可視化に向いています.この方法では,まず初めにヒストグラムを計算しておく必要があります.以下のコードを試してみてください:

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()

結果:

Histogram Plotting in Matplotlib

このヒストグラムを見ると,画像中の青成分が高い値を持っていることが分かります(空の色であることは明らかです).

2. OpenCVを使う

ここではx,y座標のようにヒストグラムのビンの値を調整し,cv2.line() か cv2.polyline() を使って上記のような画像を作成します.これは既に公式のOpenCV-Python2のサンプルに含まれています. コードを見てみてください

マスクの適用

画像の全領域のヒストグラムを計算するために cv2.calcHist() 関数を使いました.画像中の特定領域のヒストグラムを計算したい時はどうすればいいでしょうか?ヒストグラムを計算したい領域を白,それ以外の領域を黒にしたマスク画像をマスクとして指定すれば計算できます.

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.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()

結果を見てください.以下のヒストグラムの内,青線が画像全体のヒストグラム,緑線がマスク画像で指定した領域のヒストグラムを表しています.

Histogram Example

課題