OpenCV – cv2.compareHist で画像のヒストグラムを比較する方法

目次

概要

OpenCV の cv2.compareHist() を使用してヒストグラムの類似度を計算する方法について解説します。

cv2.compareHist

retval = cv2.compareHist(H1, H2, method)
引数
名前 デフォルト値
H1 ndarray
ヒストグラム
H2 ndarray
ヒストグラム
method HistCompMethods
ヒストグラムの比較手法
返り値
名前 説明
retval 計算結果

サンプルコード

以下の 2 枚のグレースケール画像のヒストグラム計算し、cv2.compareHist() で比較します。

sample1.jpg

sample2.jpg

ヒストグラムを計算する

In [1]:
import cv2
import numpy as np
from IPython.display import Image, display


def imshow(img):
    """ndarray 配列をインラインで Notebook 上に表示する。"""
    ret, encoded = cv2.imencode(".jpg", img)
    display(Image(encoded))

2 枚の画像のヒストグラムをそれぞれ計算します。

In [2]:
import matplotlib.pyplot as plt


# 画像をグレースケール形式で読み込む。
img1 = cv2.imread("sample1.jpg", cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread("sample2.jpg", cv2.IMREAD_GRAYSCALE)


def calc_hist(img):
    # ヒストグラムを計算する。
    hist = cv2.calcHist([img], channels=[0], mask=None, histSize=[256], ranges=[0, 256])
    # ヒストグラムを正規化する。
    hist = cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX)
    # (bins_num, 1) -> (bins_num,)
    hist = hist.squeeze(axis=-1)

    return hist


hist1 = calc_hist(img1)
hist2 = calc_hist(img2)

ヒストグラムを描画する

In [3]:
def plot_hist(hist, title=None):
    fig, ax = plt.subplots()

    ax.set_xticks([0, 255])
    ax.set_xlim([0, 255])
    ax.set_xlabel("Pixel Value")
    if title:
        ax.set_title(title)

    bins = np.linspace(0, 255, 256)
    ax.plot(bins, hist, color="k")

    plt.show()


# 描画する。
plot_hist(hist1, "sample1.jpg")
plot_hist(hist2, "sample2.jpg")

ヒストグラムを比較する

cv2.HISTCMP_CORREL を指定した場合、計算される値はヒストグラムが近いほど $1$、遠いほど $-1$ に近くなります。

In [4]:
score = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)
print(score)
-0.2177525030677163

cv2.compareHist を利用して類似画像を検索する

ディレクトリ内の大量の画像から cv2.compareHist() を使って類似画像を探します。ヒストグラムの計算は、画像の色空間を BGR から HSV 色空間に変換し、Hue (色相) のヒストグラムの比較によって行います。これにより、色相が似た画像を探します。

  1. ディレクトリ内の画像を読み込む
  2. BGR から HSV 色空間に変換する
  3. cv2.calcHist() でヒストグラムを計算する
  4. cv2.compareHist() でヒストグラムを比較する
In [5]:
from pathlib import Path

img_paths = sorted(str(x) for x in Path("/data/dataset/samples").glob("*.jpg"))


def calc_hue_hist(img):
    # HSV 形式に変換する。
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    # 成分ごとに分離する。
    h, s, v = cv2.split(hsv)
    # Hue 成分のヒストグラムを計算する。
    hist = calc_hist(h)

    return hist


# 各画像の Hue 成分のヒストグラムを計算する。
hists = []
for path in img_paths:
    # 画像を読み込む。
    img = cv2.imread(str(path))
    # 画像の Hue 成分のヒストグラムを取得する
    hist = calc_hue_hist(img)

    hists.append(hist)

hists = np.array(hists)

検索対象のヒストグラムを人工的に作成します。Hue が $[50, 70]$ あたりは緑色を表すので、そこが盛り上がったヒストグラムにします。

Hue

In [6]:
# 検索対象のヒストグラムを作成する。
target_hist = np.zeros(256, dtype=np.float32)
target_hist[50:70] = 1
target_hist = cv2.normalize(target_hist, target_hist, 0, 1, cv2.NORM_MINMAX)
plot_hist(target_hist)

検索対象のヒストグラムと各画像のヒストグラムの類似度を計算します。cv2.HISTCMP_CORREL を使用する場合、類似度が高いヒストグラムほど 1 に近い値になります。そのため、値が大きい順にソートし、類似度が高い上位 5 枚を抽出します。

In [7]:
# 各画像のヒストグラムとの類似度を計算する。
dists = []
for hist in hists:
    dist = cv2.compareHist(hist, target_hist, cv2.HISTCMP_CORREL)
    dists.append(dist)
dists = np.array(dists)

sorted_indices = dists.argsort()[::-1]

# 上位5枚を取り出す。
for i in sorted_indices[:5]:
    img = cv2.imread(img_paths[i])
    imshow(img)
    plot_hist(hists[i])

ヒストグラムの類似度が高い画像を抽出した結果、緑色の画像が抽出されました。

コメント

コメントする

目次