OpenCVのK-Meansクラスタリング¶
目的¶
- データクラスタリングのためのOpenCVの関数 cv2.kmeans() の使い方を学ぶ.
パラメータの理解¶
関数cv2.kmeans(samples, K, labels, criteria, attempts, flags[, centers]) は6個の引数data, K, bestLabels, criteria, attempts, flagsと、ひとつのオプションcentersを取る。そして、3つの値のタプル(comapctness, labels, centers)を返す(入力と出力のlabelsは同じもの)。
入力パラメータ¶
samples : np.float32 型のデータとして与えられ,各特徴ベクトルは一列に保存されていなければならない.
nclusters(K) : 最終的に必要とされるクラスタの数.
- criteria : 繰り返し処理の終了条件である.この条件が満たされた時,アルゴリズムの繰り返し計算が終了する.実際は3個のパラメータのタプル
( type, max_iter, epsilon )
として与えられる:
- type : 以下に示す3つのフラグを持っている:
cv2.TERM_CRITERIA_EPS : 指定された精度(epsilon)に到達したら繰り返し計算を終了する.
- cv2.TERM_CRITERIA_MAX_ITER : 指定された繰り返し回数(max_iter)に到達したら繰り返し計算を終了する.
- cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER : 上記のどちらかの条件が満たされた時に繰り返し計算を終了する.
max_iter : 繰り返し計算の最大値を指定するための整数値.
epsilon : 要求される精度.
attempts : 異なる初期ラベリングを使ってアルゴリズムを実行する試行回数を表すフラグ.アルゴリズムは最良のコンパクトさをもたらすラベルを返す.このコンパクトさが出力として返される.
flags : このフラグは重心の初期値を決める方法を指定する.普通は二つのフラグ cv2.KMEANS_PP_CENTERS と cv2.KMEANS_RANDOM_CENTERS が使われる.
出力パラメータ¶
- compactness : 各点と対応する重心の距離の二乗和.
- labels : 各要素に与えられたラベル(‘0’, ‘1’ ...)の配列 (前チュートリアルにおける ‘code’ ).
- centers : クラスタの重心の配列.
これから3個の例を使ってK-Meansアルゴリズムを適用する方法を示す.
1. 一つの特徴しか持たないデータ¶
一つの特徴しか持たないデータの集合を考える.例えば先のTシャツ問題で、身長データだけからTシャツのサイズを決定する問題を考えてみよう.
データの作成とMatplotlibによるプロットから始めよう(コード).
import numpy as np
import cv2
from matplotlib import pyplot as plt
x = np.random.randint(25,100,25)
y = np.random.randint(175,255,25)
z = np.hstack((x,y))
z = z.reshape((50,1))
z = np.float32(z)
plt.hist(z,256,[0,256]),plt.show()
このコードを実行して生成される ‘z’ は、サイズが50,0から255の値をとる.’z’ を列ベクトルに変形したので,一つ以上の特徴がある時に便利になる.またデータ型をnp.float32に変えた.
最終的に以下の画像が得られる:
K-Means関数を適用する前に criteria を指定する必要がある.ここでは繰り返し回数の上限を10回とし,精度が epsilon = 1.0
に達した時に終了するように終了条件を設定する.
# criteria = ( type, max_iter = 10 , epsilon = 1.0 ) と定義
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
# フラグの設定 (コードで改行しないようにするための措置)
flags = cv2.KMEANS_RANDOM_CENTERS
# KMeansを適用
compactness,labels,centers = cv2.kmeans(z,2,None,criteria,10,flags)
このコードを実行するとコンパクトさ,ラベル,そして重心が得られる.今回は重心が60と207となった.ラベルはテストデータと同じサイズとなり,各データには対応する重心データに依存して ‘0’,‘1’,‘2’, ...とラベルが与えられる.それではラベルに応じて異なるクラスタに分割する.
A = z[labels==0]
B = z[labels==1]
Aを赤,Bを青,各重心を黄色でプロットする.
# 'A'を赤色で, 'B' を青色で、'centers' を黄色でプロット
plt.hist(A,256,[0,256],color = 'r')
plt.hist(B,256,[0,256],color = 'b')
plt.hist(centers,32,[0,256],color = 'y')
plt.show()
以下の図が出力として得られるプロットである: (コード)
2. 複数の特徴を持つデータ¶
前の例ではTシャツ問題で身長しか使わなかった.ここでは身長と体重という二つの特徴を使う.
前の例ではデータを一つの列ベクトルとしていたことを覚えておこう.各特徴は一つの列に配置される一方で,各行は一つの入力サンプルに対応している.
例えば,今回の例では50人の身長・体重のデータを50x2のサイズのテストデータとして用意する.第一列は50人分の伸長,第二列は50人分の体重のデータに対応している.第一行は二つの要素を持っており,最初の要素が一人目の身長,二番目の要素が一人目の体重を表す.同様に,残りの行はその他の人の身長と体重のデータを表す.以下の図を見てみよう:
コードは以下のようになる:
import numpy as np
import cv2
from matplotlib import pyplot as plt
X = np.random.randint(25,50,(25,2))
Y = np.random.randint(60,85,(25,2))
Z = np.vstack((X,Y))
# np.float32に変換
Z = np.float32(Z)
# criteriaを定義し kmeans()関数を適用
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret,label,center=cv2.kmeans(Z,2,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
# データを分割する。flatten()関数(ravel)適用に注意
A = Z[label.ravel()==0]
B = Z[label.ravel()==1]
# データを描画する
plt.scatter(A[:,0],A[:,1])
plt.scatter(B[:,0],B[:,1],c = 'r')
plt.scatter(center[:,0],center[:,1],s = 80,c = 'y', marker = 's')
plt.xlabel('Height'),plt.ylabel('Weight')
plt.show()
コードを実行した結果は以下である:(コード)
3. 色の量子化¶
色の量子化とは、画像中で使用される色の数を削減する処理を指す.このような処理をする理由として、記憶容量の削減が必要だったり、限られた表示色しか使えない装置があるかもしれない.このような状況で色の量子化が行われる.ここでは色の量子化にK-Meansクラスタリングを使う.
ここでは新しい説明はない.3つの特徴(色のRGB成分)があり、画像をMx3のサイズの配列に変形する.ここでMは画像中の画素数を表す.クラスタリング後に重心の値(RGB値)を全画素に適用し,適用後の画像の色数が指定した数になるようにする.色変換後に原画像の形状に変形し直すことで出力画像を得る.以下にコードを示す:(コード, 画像)
import numpy as np
import cv2
img = cv2.imread('home.jpg')
Z = img.reshape((-1,3))
# np.float32に変換
Z = np.float32(Z)
# criteriaとクラスタ数(K)を定義し kmeans()関数を適用
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 8
ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
# uint8型に戻し, 元の画像を復元する
center = np.uint8(center)
res = center[label.flatten()]
res2 = res.reshape((img.shape))
cv2.imshow('res2',res2)
cv2.waitKey(0)
cv2.destroyAllWindows()
K=8 としたときの結果を以下に示す: