【Python】OpenCVで画像のヒストグラムをプロットする

画像処理ヒストグラムを使ってその画像の特徴量を分析するケースがあります。今回は画像におけるヒストグラムの意味と、OpenCVで画像からヒストグラムの情報を取得する方法を紹介します。

画像におけるヒストグラム

画像におけるヒストグラムとは、画像中の画素値の度数分布を表したものです。

ヒストグラムを理解するにあたり、「ピクセル」を知っておく必要があります。きれいな画像も、拡大していくと単色の正方形の集合体です。この単色の正方形がピクセルです。

よく画像や動画のサイズで1428×955みたいな表記がされますが、これは横に1428個、縦に955個のピクセルが敷き詰められてできた画像であることを意味しています。このピクセル1つ1つが色を持ち、組み合わせることで綺麗な画像が出来上がります。

ピクセルは「画素」とも呼ばれます。

画素値とは

画素値とは、ピクセルの明るさのことを指します。グレースケールとカラー画像では画素値の定義が異なるので、順に見ていきます。

グレースケールの画素値

グレースケールの画像では、ピクセルの色は白か黒か、そしてその間の灰色のいずれかです。ところが画素値は3パターンではなく、266パターンあります。

この時画素値0が黒、255が白色になります。純粋な黒や白でない色を加えると、256パターンの色を表すことができます。

画素値0が黒、255が白色

この256の会長は8bitで表現することができます。

カラー画像(RGB)の画素値

カラー画像では、1つのピクセルにおいてR(赤)、G(緑)、B(青)の3つの色を混ぜ合わせて表現します。カラー画像ではR、G、Bそれぞれで256の諧調を持ちます。

R、G、Bそれぞれで256の諧調を持つ

R、G、Bそれぞれが8bitで表現されるため、カラー画像では1つのピクセルで24bitの情報を持つことになります。そして、ピクセルの色はそれぞれの組み合わせで256×256×256=16,777,216通りの色を表現することができます。

ヒストグラムとは

ここまできたところで画像におけるヒストグラムを考えます。冒頭で述べたように、画像のヒストグラムは画像中の画素値の度数分布を表したものです。横軸に画素値、縦軸にその画素値のピクセル数を表します。

例えば以下の画像を例を見てみます。

rabit
rabit.jpg

この画像のヒストグラムは以下のようになります。

RGBヒストグラム

ヒストグラムから、RGBの色要素はあまりないことがわかります。続いてグレースケールの画像のヒストグラムも見てみます。

rabit.jpg

ヒストグラムを見ると暗めのピクセルが多いことがわかりますが、画像をみると確かにそんな感じがします。

Pythonで画像のヒストグラムを描画する

ヒストグラムについて理解したところで、Pythonで画像のヒストグラムを描画してみます。例によってOpenCVを使います。

以下は、読み込んだ画像のヒストグラムを取得し、描画するコードです。ヒストグラムはcv2.calcHist()を使って取得します。以下パラメータは必須なので注意してください。

cv2.calcHist([img], [channel], mask, [histSize], [range])

第一引数には入力画像を指定します。グレースケール、カラーいずれの画像も可です。引数を渡す際は角括弧で囲い[img]のようにします。

第二引数にはヒストグラムを取得するチャネルを指定します。グレースケールの場合は[0]で固定です。カラーの場合は青:[0]、緑:[1]、赤:[2]を指定します。この時も角括弧が必要です。

maskは画像の一部分のヒストグラムを取得したい場合に使うパラメータです。画像全体のヒストグラムを取得したい場合は特に指定せずNoneを渡します。画像の一部のヒストグラムを取得したい場合には、マスキングを実施した画像を渡します。

histSizeはヒストグラムの階級数、rangeはヒストグラムの値の範囲を指定します。いずれも角括弧が必要です。

上記のウサギの画像のヒストグラムは、以下のコードで出力したものです。

import cv2
import matplotlib.pyplot as plt 

#ヒストグラムを取得
def get_hist(img):
    if len(img.shape) == 3:
        # カラーのときは3チャネル(B, G, R)
        channels = 3
    else:
        # モノクロのとき
        channels = 1

    histogram = []
    for ch in range(channels):
        # チャンネル(B, G, R)ごとのヒストグラム
        hist_ch = cv2.calcHist([img],[ch],None,[256],[0,256])
        histogram.append(hist_ch[:,0])

    # チャンネルごとのヒストグラムを返す
    return histogram

#ヒストグラムを描画
def draw_hist(hist):
    # チャンネル数
    ch = len(hist)

    # グラフの表示色
    if (ch == 1):
        colors = ["black"]
        label = ["Gray"]
    else:
        colors = ["blue", "green", "red"]
        label = ["B", "G", "R"]

    # ヒストグラムをmatplotlibで表示
    x = range(256)
    for col in range(ch):
        y = hist[col]
        plt.plot(x, y, color = colors[col], label = label[col])

    # 凡例の表示
    plt.legend()

    plt.show()

if __name__ == "__main__":
    # 画像の読込
    img = cv2.imread("rabit.jpg")
    #モノクロ画像は第二引数に0を渡す
    # img = cv2.imread("rabit.jpg", 0)
    
    # 画像の表示
    cv2.imshow("Image", img)
    
    # ヒストグラムの取得
    hist = get_hist(img)
    
    # ヒストグラムを描画
    draw_hist(hist)

OpenCVの学習におすすめ書籍

PythonでOpenCV始めてみようという方におすすめなのが以下書籍です。実装例も豊富なので、1からコードを書かずとも学習を進めることができます。

オライリーの1冊は読み物というより辞書としての利用におすすめです。お値段結構しますが、細かい情報までしっかりと詰め込まれています。

まとめ

PythonのOpenCVで画像のヒストグラムを取得する方法を紹介しました。calcHist()は必須のパラメータが多いのが少し厄介ですが、1行でヒストグラムの値を取得できるので便利です。

是非ご活用ください。ではでは👋