目的
学ぶこと
- OpenCVとNumpyの両方の関数を使ってヒストグラムを求める
- OpenCVとMatplotlibの関数を使ってヒストグラムをプロットする
- 次の関数を扱う : cv.calcHist(), np.histogram() など。
理論
では、ヒストグラムとは何だろうか。ヒストグラムは、画像の輝度分布の全体像を示すグラフまたはプロットと考えることができる。これは、X軸にピクセル値 (常にではないが0から255の範囲) を、Y軸に画像内の対応するピクセル数をとったプロットである。
これは画像を理解するための別の方法に過ぎない。画像のヒストグラムを見ることで、その画像のコントラスト、明るさ、輝度分布などについての直観が得られる。今日のほぼすべての画像処理ツールは、ヒストグラムに関する機能を備えている。以下は Cambridge in Colorのウェブサイト から引用した画像である。詳細についてはぜひそのサイトを訪れてほしい。
image
画像とそのヒストグラムが確認できる。(このヒストグラムはカラー画像ではなくグレースケール画像に対して描かれていることに注意。) ヒストグラムの左側の領域は画像内の暗いピクセルの量を、右側の領域は明るいピクセルの量を示す。ヒストグラムから、暗い領域が明るい領域より多く、中間調 (中間の範囲、たとえば127付近のピクセル値) の量が非常に少ないことが分かる。
ヒストグラムを求める
ヒストグラムとは何かが分かったので、その求め方を見ていこう。OpenCVとNumpyのどちらにも、このための組み込み関数が用意されている。これらの関数を使う前に、ヒストグラムに関連するいくつかの用語を理解しておく必要がある。
BINS :上記のヒストグラムは、0から255までの各ピクセル値ごとのピクセル数を示している。すなわち、上記のヒストグラムを表示するには256個の値が必要である。しかし、すべてのピクセル値について別々にピクセル数を求めるのではなく、ある区間内のピクセル値に対するピクセル数を求めたい場合を考えてみよう。たとえば、0から15、次に16から31、…、240から255の間にあるピクセル数を求めたいとする。その場合、ヒストグラムを表すのに必要な値は16個だけで済む。これが ヒストグラムに関するOpenCVチュートリアル の例で示されている内容である。
つまり、ヒストグラム全体を単純に16個の部分に分割し、各部分の値はその中に含まれるすべてのピクセル数の合計とする。この各部分を「BIN」と呼ぶ。最初の場合、ビン数は256個 (各ピクセルに1つ) であったが、2番目の場合は16個だけである。BINSはOpenCVのドキュメントでは histSize という用語で表される。
DIMS : データを収集する対象となる引数の数である。この場合、輝度値という1つのものに関するデータだけを収集する。したがってここでは1である。
RANGE : 測定したい輝度値の範囲である。通常は [0,256]、つまり全輝度値である。
1. OpenCVでのヒストグラム計算
ここでは cv.calcHist() 関数を使ってヒストグラムを求める。この関数とその引数に慣れておこう。
cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
- images : uint8 または float32 型の元画像である。角括弧で囲んで与える必要がある。すなわち "[img]" のように指定する。
- channels : これも角括弧で囲んで与える。ヒストグラムを計算するチャンネルのインデックスである。例えば、入力がグレースケール画像であればその値は [0] となる。カラー画像の場合は、青・緑・赤の各チャンネルのヒストグラムを計算するためにそれぞれ [0]、[1]、[2] を渡せる。
- mask : マスク画像である。画像全体のヒストグラムを求めるには "None" を与える。ただし画像の特定領域のヒストグラムを求めたい場合は、その領域のマスク画像を作成してマスクとして与える必要がある(後で例を示す)。
- histSize : これはBINの数を表す。角括弧で囲んで与える必要がある。フルスケールの場合は [256] を渡す。
- ranges : これはRANGEである。通常は [0,256] となる。
ではサンプル画像から始めよう。グレースケールモードで画像を読み込み、その全体のヒストグラムを求める。
img =
cv.imread(
'home.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
Loads an image from a file.
void calcHist(const Mat *images, int nimages, const int *channels, InputArray mask, OutputArray hist, int dims, const int *histSize, const float **ranges, bool uniform=true, bool accumulate=false)
Calculates a histogram of a set of arrays.
hist は 256x1 の配列であり、各値はその画素値に対応するピクセル数を表す。
2. Numpyでのヒストグラム計算
Numpyにも np.histogram() という関数がある。calcHist() 関数の代わりに、次の行を試すこともできる。
hist,bins = np.histogram(img.ravel(),256,[0,256])
hist は先ほど計算したものと同じである。ただし bins は257要素を持つ。なぜならNumpyはビンを 0-0.99、1-1.99、2-2.99 などとして計算するためである。したがって最後の範囲は 255-255.99 となる。これを表すために bins の末尾に 256 も追加される。しかしこの 256 は不要であり、255 までで十分である。
- 覚え書き
- Numpyにはもう一つ np.bincount() という関数があり、np.histogram() よりもはるかに(約10倍)高速である。そのため一次元ヒストグラムの場合はこちらを試すとよい。np.bincount では minlength = 256 を設定するのを忘れないこと。例えば hist = np.bincount(img.ravel(),minlength=256) のようにする。
- OpenCVの関数は np.histogram() よりも(約40倍)高速である。そのためOpenCVの関数を使うとよい。
次にヒストグラムをプロットすべきだが、どうやって行うのか?
ヒストグラムのプロット
これには2通りの方法がある。
- 簡単な方法 : Matplotlibのプロット関数を使う
- 手間のかかる方法 : OpenCVの描画関数を使う
1. Matplotlibを使う
Matplotlibにはヒストグラムをプロットする関数 matplotlib.pyplot.hist() が用意されている。
これは直接ヒストグラムを求めてプロットする。ヒストグラムを求めるために calcHist() や np.histogram() 関数を使う必要はない。以下のコードを参照のこと。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img =
cv.imread(
'home.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
plt.hist(img.ravel(),256,[0,256]); plt.show()
下図のようなプロットが得られる。
image
あるいはmatplotlibの通常のプロットを使うこともでき、これはBGRのプロットに適している。そのためにはまずヒストグラムのデータを求める必要がある。以下のコードを試してみよう。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
assert img is not None, "file could not be read, check with os.path.exists()"
color = ('b','g','r')
for i,col in enumerate(color):
plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()
結果:
image
上のグラフから、画像内で青に高い値を持つ領域があることが読み取れる(言うまでもなく、これは空によるものであろう)。
2. OpenCVを使う
ここでは、ヒストグラムの値をそのbin値とともにx,y座標のように見えるように調整し、cv.line() や cv.polyline() 関数を使って上記と同じ画像を生成できるようにしている。これはOpenCV-Pythonの公式サンプルとして既に用意されている。samples/python/hist.py のコードを参照すること。
マスクの適用
ここまで画像全体のヒストグラムを求めるために cv.calcHist() を使ってきた。では画像の一部領域のヒストグラムを求めたい場合はどうすればよいか? ヒストグラムを求めたい領域を白色、それ以外を黒色にしたマスク画像を作成すればよい。そしてこれをマスクとして渡す。
img =
cv.imread(
'home.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
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()
void bitwise_and(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray())
computes bitwise conjunction of the two arrays (dst = src1 & src2) Calculates the per-element bit-wis...
結果を見てみよう。ヒストグラムのプロットでは、青線が画像全体のヒストグラムを、緑線がマスクした領域のヒストグラムを示している。
image
追加リソース
- Cambridge in Color のウェブサイト