OpenCV で画像のヒストグラムを作成する方法

目次
概要
画像の画素値のヒストグラムを作成することで、その画像の特性を理解し、2値化などの処理に役立てることができます。
この記事では、OpenCV の calcHist() で画像のヒストグラムを作成する方法を紹介します。
Advertisement
画像のヒストグラム
画像のヒストグラムとは、横軸に画素値、縦軸に頻度をとったヒストグラムです。 カラー画像の場合はチャンネルごとにヒストグラムを作成します。
cv2.calcHist()
OpenCV の calcHist() を使用すると、画像から指定したチャンネルのヒストグラムを計算できます。
hist = cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
- 引数
- images: 画像の一覧。
- channels: ヒストグラムを計算するチャンネルの一覧。
- mask: マスクの一覧。
- histSize: 各チャンネルのビン数
- ranges: 各チャンネルのヒストグラムの範囲。この範囲外の画素値は集計対象外となる。
- 返り値
- hist: ヒストグラム
channels
、 histSize
、 ranges
の指定方法は少々複雑なので、詳しく見ていきます。
1次元ヒストグラム
1次元ヒストグラムの場合、以下のように指定します。
hist = cv2.calcHist([img], [ch], None, histSize=[bins], ranges=[l, u])
ch
は1次元ヒストグラムを計算するチャンネルを指定します。bins
はビンの数を指定します。l, u
はヒストグラムを作成する範囲 $[l, u)$ を指定します。
例えば、bins=[15], ranges=[0, 256]
とした場合、$[0, 256]$ を15等分したビンが作成されます。
Advertisement
グレースケール画像のヒストグラム
グレースケール画像のヒストグラムする場合、channels=[0], histSize=[ビンの数], ranges=[下限, 上限]
と指定します。
In [1]:
import cv2
import matplotlib.pyplot as plt
import numpy as np
# 画像をグレースケール形式で読み込む。
img = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE)
# 1次元ヒストグラムを作成する。
n_bins = 100 # ビンの数
hist_range = [0, 256] # 集計範囲
hist = cv2.calcHist(
[img], channels=[0], mask=None, histSize=[n_bins], ranges=hist_range
)
hist = hist.squeeze(axis=-1) # (n_bins, 1) -> (n_bins,)
# 描画する。
def plot_hist(bins, hist, color):
centers = (bins[:-1] + bins[1:]) / 2
widths = np.diff(bins)
ax.bar(centers, hist, width=widths, color=color)
bins = np.linspace(*hist_range, n_bins + 1)
fig, ax = plt.subplots()
ax.set_xticks([0, 256])
ax.set_xlim([0, 256])
ax.set_xlabel("Pixel Value")
plot_hist(bins, hist, color="k")
plt.show()

カラー画像のヒストグラム
カラー画像のヒストグラムする場合、channels=[チャンネル], histSize=[ビンの数], ranges=[下限, 上限]
と指定します。
例えば、BGR 画像の場合、0が blue、1が greeen、2が red になります。
In [2]:
import cv2
import matplotlib.pyplot as plt
import numpy as np
# 画像を読み込む。
img = cv2.imread("sample.jpg")
# ヒストグラムを作成する。
n_bins = 256 # ビンの数
hist_range = [0, 256] # 集計範囲
hists = []
channels = {0: "blue", 1: "green", 2: "red"}
for ch in channels:
hist = cv2.calcHist(
[img], channels=[ch], mask=None, histSize=[n_bins], ranges=hist_range
)
hist = hist.squeeze(axis=-1)
hists.append(hist)
# 描画する。
def plot_hist(bins, hist, color):
centers = (bins[:-1] + bins[1:]) / 2
widths = np.diff(bins)
ax.bar(centers, hist, width=widths, color=color)
bins = np.linspace(*hist_range, n_bins + 1)
fig, ax = plt.subplots()
ax.set_xticks([0, 256])
ax.set_xlim([0, 256])
ax.set_xlabel("Pixel Value")
for hist, color in zip(hists, channels.values()):
plot_hist(bins, hist, color=color)
plt.show()

2次元ヒストグラム
2次元ヒストグラムの場合、以下のように指定します。
hist = cv2.calcHist(
[img], [ch1, ch2], None, histSize=[bins1, bins2], ranges=[l1, u1, l2, u2])
ch1, ch2
は1次元ヒストグラムを計算するチャンネルを指定します。bins1
はチャンネルch1
のビンの数、bins2
はチャンネルch2
のビンの数を指定します。l1, u1
はチャンネルch1
のヒストグラムを作成する範囲 $l1, u1$、l2, u2
はチャンネルch2
のヒストグラムを作成する範囲 $[l2, u2)$ を指定します。
例えば、bins=[15, 17], ranges=[0, 256, 0, 256]
とした場合、$x$ 軸は [0, 256]
を15等分、$y$ 軸は [0, 256]
を17等分したビンが作成されます。
Advertisement
HSV 画像の2次元ヒストグラムを作成する
In [3]:
import cv2
import matplotlib.pyplot as plt
import numpy as np
# 画像を読み込む。
img = cv2.imread("sample.jpg")
# HSV に変換する。
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 2次元ヒストグラムを作成する。
hist_range1 = [0, 256]
hist_range2 = [0, 256]
n_bins1, n_bins2 = 20, 20
hists = []
channel_pairs = [[0, 1], [1, 2], [0, 2]]
for pair in channel_pairs:
hist = cv2.calcHist(
[hsv], channels=pair, mask=None, histSize=[20, 20], ranges=[0, 256, 0, 256]
)
hists.append(hist)
# 描画する。
ch_names = {0: "Hue", 1: "Saturation", 2: "Brightness"}
fig = plt.figure(figsize=(10, 10 / 3))
for i, (hist, ch) in enumerate(zip(hists, channels), 1):
xlabel, ylabel = ch_names[pair[0]], ch_names[pair[1]]
ax = fig.add_subplot(1, 3, i)
fig.subplots_adjust(wspace=0.3)
ax.imshow(hist, cmap="jet")
# 2Dヒストグラムを描画する。
ax.set_title(f"{xlabel} and {ylabel}")
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
plt.show()

参考文献
- ディジタル画像処理 P58
- OpenCV: Histograms
-
前の記事
OpenCV – 画像をグリッド上に分割する、複数の画像をグリッド上に結合する方法 2020.03.11
-
次の記事
OpenCV – 特徴点マッチングを行う方法について 2020.04.07