OpenCV – k 平均法で画像の代表色を取得する方法

目次

概要

OpenCV で k 平均法 (k-means) を使って、画像の代表色を取得する方法を紹介します。

cv2.kmeans

retval, bestLabels, centers = cv2.kmeans(
    data, K, bestLabels, criteria, attempts, flags[, centers])
引数
名前 デフォルト値
data ndarray
入力データ。形状が (M, N) の numpy 配列。
K int
クラスタ数
criteria list of int
アルゴリズムの終了条件
attempts int
k 平均法を何回実行するか
flags int
アルゴリズムに関するフラグ
返り値
名前 説明
retval compactness measure の値
bestLabels 各サンプルに割り当てられたクラスタ
centers クラスタの中心の一覧

criteria

criteria は最大反復回数及び移動量の閾値を 2 通りの方法で指定します。

  • 最大反復回数: 反復回数がこの値に達した場合、アルゴリズムを終了する
  • 移動量の閾値: 各反復でクラスタの中心を移動するが、この移動量が閾値未満になった場合はアルゴリズムを終了する

attempts

最初、クラスタの中心は適当な点で初期化されるので、アルゴリズムの結果は毎回異なります。attempts に 2 以上の値を指定した場合、アルゴリズムをその回数分実行し、compactness measure の値が最小となる結果、つまり、最良の結果を返します。

retval

サンプルとそのサンプルが属するクラスタの中心との 2 乗距離になります。

サンプルを $\bm{x}_1, \bm{x}_2, \cdots, \bm{x}_N$ としたとき、

$$ \text{retval} = \sum_{i = 1}^N \| \bm{x}_i – \bm{x}_i が属するクラスタの中心 \|^2 $$

画像の代表色を取得する

画像で使われているすべての色に対して、$k$ 平均法を適用し、代表色を $k$ 色取得します。

sample.jpg

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


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


# 画像を読み込む。
img = cv2.imread("sample.jpg")

# 画像で使用されている色一覧。(W * H, 3) の numpy 配列。
colors = img.reshape(-1, 3).astype(np.float32)

# クラスタ数
K = 5

# 最大反復回数: 10、移動量の閾値: 1.0
criteria = cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 10, 1.0

ret, labels, centers = cv2.kmeans(
    colors, K, None, criteria, attempts=10, flags=cv2.KMEANS_RANDOM_CENTERS
)

print(f"ret: {ret:.2f}, label: {labels.shape}, center: {centers.shape}")

labels = labels.squeeze(axis=1)  # (N, 1) -> (N,)
centers = centers.astype(np.uint8)  # float32 -> uint8
ret: 131459173.92, label: (140500, 1), center: (5, 3)
  • labels は各サンプルが割り当てられたクラスタを表す (NumSamples, 1) の numpy 配列
  • centers はクラスタの中心点を表す (K, M) の numpy 配列

例えば、$i$ 番目のサンプルが割り当てられたクラスタの中心点を取得したい場合は、centers[labels[i]] とします。

In [2]:
# 各クラスタに属するサンプル数を計算する。
_, counts = np.unique(labels, axis=0, return_counts=True)

# 可視化する。
fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(10, 3))
fig.subplots_adjust(wspace=0.5)

# matplotlib の引数の仕様上、[0, 1] にして、(R, G, B) の順番にする。
bar_color = centers[:, ::-1] / 255
bar_text = list(map(str, centers))

# 画像を表示する。
ax1.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
ax1.set_axis_off()

# ヒストグラムを表示する。
ax2.barh(np.arange(K), counts, color=bar_color, tick_label=bar_text)
plt.show()

画像の各画素をクラスタの色で置き換える

In [3]:
# 各画素を k平均法の結果に置き換える。
dst = centers[labels].reshape(img.shape)
imshow(dst)

コメント

コメントする

目次