今回はクラスタリング手法で、画像の重要な色を検出するタスクをやった。ニューラルネットワークならより正確な検出が可能だけど、データセット作成もろもろコストがでかい。なので、昔からあるクラスタリング手法で手軽に、かつ精度よく色を検出してみた。そのロジックと工程を書いてく。
目次
・rgb値から色を特定する方法
・用意したもの&ライブラリ
・クラスタリングで色検出
・色同士のノルム計算で色の特定
・実験
・追記-なぜ思いついたからのルーツ
rgb値から色を特定する方法
色はrgb値の3つの値で表される。赤なら255:0:0だし、紫なら128:0:128。各3つの値のとる範囲は0〜255。rgb値はよく数学であるxyz空間と同じで、rgb空間上の点で表せる。
rgb空間は図で表すと下のような、6角形の形。
数学では三平方の定理とかでよく出てくるけど、「ベクトル間の距離(ノルム)が近いもの同士は似てる」という性質がある。
色でも同じ性質が使えて、rgb値のノルムが近いほど、互いの色が似ていることを表す。
この性質を使って、
・rgb値のノルムが近いもの同士=似た色
として色を検出する。
ただし、rgb空間は六角形なので、xyz空間のように3次元に変換してやる必要がある(座標変換)。rgb空間の3次元空間はlab空間というらしい。
lab値の各値のとりうる範囲は
L: [0, 100]
a: [-127, 127]
b: [-127, 127]
これでノルムを計算する。
色を特定する手順はこんな感じ
②rgbで画像の読み込み
③rgb値のままクラスタリング
④skimage.color.rgb2lab()でrgbをlabに変換
⑤リストのlab値と、読み込んだ画像のlabのノルムを計算。リスト内の色と類似した色のtop4を特定
lab値の変換について
ライブラリのopencvでrgbをlabに変換すると、値は0〜255の範囲のまま。それだと困るので、ここではskimageというライブラリを使う。skimageならlabの値の範囲内で正確に変換してくれる。
用意したもの&ライブラリ
ライブラリ
・opencv=> rgb値での画像読み込み用
・skimage => rgb値をlab値に変換用
・k-means => クラスタリング用
用意したもの
・色のlab値のリスト
=> lab値の取得は下記のサイトを使った
(https://syncer.jp/color-converter)
・試す画像複数枚 =>上手く検出できるか試す画像
クラスタリングで色検出
まず、下の画像をopencvでrgbで読み込む。
def load_image(image_file): # cv2 load images as BGR image_bgr=[cv2.imread(image_file+'/'+i) for i in os.listdir(image_file)] image_rgb = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in image_bgr] image_rgb = [cv2.resize(i, (150, 150)) for i in image_rgb] return image_rgb img = load_image("/Users/d/clust_img") img=np.reshape(img, (len(img),150,150,3)) hstack=np.hstack(img) plt.imshow(hstack) plt.show()
そのままこの画像をクラスタリング、して色の抽出。最後は、5色のrgb値を算出してる。
メインの関数はコード参照
img = load_image("/Users/tatsuyahagiwara/d/clust_img") img=np.reshape(img, (len(img),150,150,3)) b,w,h,c=img.shape N=7 sample_img=[i[int(w/10):int(N*w/10), int(h/10):int(N*h/10)] for i in img] sample_img=[i.reshape(-1,3) for i in sample_img] color=[] for i in sample_img: clt = KMeans(n_clusters = 5) clt.fit(i) hist = centroid_histogram(clt) bar = plot_colors(hist, clt.cluster_centers_) plt.figure() plt.axis("off") plt.imshow(bar) plt.show()
k-meansはお手頃だし、割と正確に検出できたので使った。クラスタリングした結果の一部はこんな感じ。
色同士のノルム計算で色の特定
算出したrgb値をskimage.color.rgb2lab()でlab値に変換。
あとは関数でリストの色と読み込んだ色のlab値のノルムを計算して、一番近い(似ている)色を出した。
for i in sample_img: clt = KMeans(n_clusters = 5) clt.fit(i) hist = centroid_histogram(clt) bar = plot_colors(hist, clt.cluster_centers_) plt.figure() plt.axis("off") plt.imshow(bar) plt.show() bar_lab=rgb2lab(bar) # LAB値に変換 c1, c2, c3, c4, c5=get_lab_from_list(bar_lab[0].tolist()) dic1, dic2, dic3, dic4, dic5=lab_distance_dic(color_name, lab_list, c1, c2, c3, c4, c5) t1,t2,t3,t4,t5=get_topN_color(dic1,dic2,dic3,dic4,dic5) color.append([t1,t2,t3,t4,t5]) main_color=[] for i in color: for n in i: main_color.append(n[0][0]) N=sorted(list(collections.Counter(main_color).values()),reverse=True) for key, value in collections.Counter(main_color).items(): if value in N[:4]: print('top_color:{}'.format(key))
色のtop4算出結果はこんな感じ。割と画像のメインの色を特定できてる感じなので、十分使えると思う。
ブラック系
ピンク系
ブルー系
グレー系
精度を上げるためにしたこと
メインのカラーが全部で13 。なので、色ごとに一つのlab値(青なら青一色のlab値)しか用意してない場合、単一のlab値でしか色を判断できない。
しかし、色一つとっても、その色に近い色はたくさんある。(例えば青でも、青っぽい色はたくさんある)
→http://irononamae.web.fc2.com/colorlist/wa.html
なので、精度を上げる方法としては、
②代表色に似ている色とそのlab値を、列の2番目以降に入れる。
こうすれば、代表色をいろんなlab値で表せるので、精度は上がる。今回は青、イエロー、赤を増やしてみた結果、精度が上がった。
(ex:青っぽい色を全部「青」と区分すれば、「青っぽい色=全て青」となるので、いろんなlab値でも青と特定できる)。
実験
一応実験として、下の画像群でも色の特定をしてみた
やることは上の作業を別の画像でやるだけ
クラスタリング結果と検出した色名は以下の通り
ブラウン系
ピンク系
ブラック系
グレー系
イエロー系
赤とブラウンの区別が難しいので、主観でlab値を選んだので、検出結果はよくわからない。
総括では、割といい感じにできて、実用できるレベルだと思う
ガチのところは、ニューラルネットワークを使ってかなり精度高く色検出やってる(メルカリとか)。ただ、管理、データセット作成、計算もろもろコストがでかすぎるのが難点。その分、k-meansみたいな昔からある手法ても、工程さえしっかりやれば、簡単かつ高い精度でできるケースのが多い。アルゴリズムは手段にすぎないので使えさえすれば、k-meansでも十分精度の良いものができた。
追記-なぜ思いついたからのルーツ
・まず調査しまくる・書籍、ネットの情報、自分の経験それらを総動員して考えまくるとアイデアがわく。like アインシュタイン、アイデアが思いつく法則
・リクルートの事例参考になんで抽出できるか考えてたら、ML分野の中でクラスリングならできそうだということで、調べ、考えまくってたら、ベクトルノルムの方法思いついた