アールテクニカ学生スタッフの松下です。
佐賀大学の理工学部で情報系を専攻しています。
大学でC++をやったり、趣味でPythonを書いたりしています。
今回は、画像の代表的な色(以下、「代表色」)をPythonのライブラリを使って色々なアプローチで抽出してみます。
代表色はAdobe Color CCからでも抽出することができます。
color.adobe.com
今回はこの仕組みを目指して再現してみます。
環境
- Python 3.7.3
- Pythonのライブラリ
- numpy 1.16.4
- Pillow 6.0.0
- sympy 1.4
macOS 10.13 High Sierraにて動作確認をしていますが、OSに依存する箇所はないはずのなので他のOSでも行けると思います。
また、Pythonも3以降であれば動作するかと思われます。
平均
まずは単純に平均を取るだけのアルゴリズムを作ります。
画像の読み込み、RGBの取得、作成、保存などを Pillow
を使ってやっています。
import numpy as np import PIL.ImageDraw source_file = 'XXX.jpg' source = PIL.Image.open(source_file) small_img = source.resize((100, 100)) # 時間短縮のために解像度を落とす color_arr = np.array(small_img) w_size, h_size, n_color = color_arr.shape color_arr = color_arr.reshape(w_size * h_size, n_color) color_mean = np.mean(color_arr, axis=0) color_mean = color_mean.astype(int) color_mean = tuple(color_mean) im = PIL.Image.new('RGB', (100, 100), color_mean) im.save('YYY.png')
結果は以下です。
最頻値
今度は最頻値で試してみます。
最頻値を求めるのに scipy
を用いました。
import numpy as np import PIL.ImageDraw import scipy.stats source_file = 'XXX.jpg' source = PIL.Image.open(source_file) small_img = source.resize((100, 100)) color_arr = np.array(small_img) w_size, h_size, n_color = color_arr.shape color_arr = color_arr.reshape(w_size * h_size, n_color) color_mode, _ = scipy.stats.mode(color_arr, axis=0)[0] color_mode = tuple(color_mode) im = PIL.Image.new('RGB', (100, 100), color_mode) im.save('YYY.png')
R, G, Bそれぞれで最頻値をとって、それらを組み合わせて色を再現します。
結果は以下です。
先ほどの最頻値はRGBそれぞれで最頻値をとって色を作ったので、結果の色が画像中に存在するとは限りません。
今度は画像中に存在するピクセルのうちで最頻値をとってみます。
import numpy as np import PIL.ImageDraw import scipy.stats source_file = 'XXX.jpg' source = PIL.Image.open(source_file) small_img = source.resize((100, 100)) color_arr = np.array(small_img) w_size, h_size, n_color = color_arr.shape color_arr = color_arr.reshape(w_size * h_size, n_color) color_code = ['{:02x}{:02x}{:02x}'.format(*elem) for elem in color_arr] mode, _ = scipy.stats.mode(color_code) r = int(mode[0][0:2], 16) g = int(mode[0][2:4], 16) b = int(mode[0][4:6], 16) color_mode = (r, g, b) im = PIL.Image.new('RGB', (100, 100), color_mode) im.save('YYY.png')
全ピクセルの色を一旦「RRGGBB」の形式にし、「RR」「GG」「BB」を整数に変換しています。
結果は以下です。
中央値
平均、最頻値ときたので中央値もやっておきます。
import numpy as np import PIL.ImageDraw source_file = 'XXX.jpg' source = PIL.Image.open(source_file) small_img = source.resize((100, 100)) color_arr = np.array(small_img) w_size, h_size, n_color = color_arr.shape color_arr = color_arr.reshape(w_size * h_size, n_color) r = [elem[0] for elem in color_arr] g = [elem[1] for elem in color_arr] b = [elem[2] for elem in color_arr] color_median = (int(np.median(r)), int(np.median(g)), int(np.median(b))) im = PIL.Image.new('RGB', (100, 100), color_median) im.save('YYY.png')
R, G, Bそれぞれで中央値をとっています。
結果は以下です。
k平均法
次に試すのは k平均法 と呼ばれる、無数のデータをいくつかのグループ(クラスタ)にわけるアルゴリズムです。
簡単には
1. クラスタ中心 をランダムな値で初期化する。
2. 以下を繰り返す。
2-1. 各データを、一番近い中心のクラスタに属させる。
2-2. クラスタごとに、属しているデータの重心を求め、それをクラスタ中心とする。
2-3. クラスタ中心の変化量が一定の値よりも小さくなったら終了する。
という感じで求めます。
k平均法を用いれば、得られたクラスタ中心が代表色として使えそうです。
クラスタの個数は好きに変えることができるので、前述のColor CCのように複数個抽出することもできます。
k平均法は scipy
にメソッドが用意されているので、それに任せます。
import numpy as np import PIL.ImageDraw import scipy.cluster N_CLUSTER = 1 def kmeans_process(img, n_cluster): sm_img = img.resize((100, 100)) color_arr = np.array(sm_img) w_size, h_size, n_color = color_arr.shape color_arr = color_arr.reshape(w_size * h_size, n_color) color_arr = color_arr.astype(np.float) codebook, distortion = scipy.cluster.vq.kmeans(color_arr, n_cluster) # クラスタ中心 code, _ = scipy.cluster.vq.vq(color_arr, codebook) # 各データがどのクラスタに属しているか n_data = [] # 各クラスタのデータ数 for n in range(n_cluster): n_data.append(len([x for x in code if x == n])) desc_order = np.argsort(n_data)[::-1] # データ数が多い順に「第○クラスタ、第○クラスタ、、、、」 return ['#{:02x}{:02x}{:02x}'.format(*(codebook[elem].astype(int))) for elem in desc_order] source_file = 'XXX.jpg' source = PIL.Image.open(source_file) colors = kmeans_process(source, N_CLUSTER) im_size = 100 im = PIL.Image.new('RGB', (im_size, im_size), (255, 255, 255, 255)) draw = PIL.ImageDraw.Draw(im) single_width = im_size / N_CLUSTER for i, color in enumerate(colors): # 色を描画 p1 = (single_width * i, 0) p2 = (single_width * (i + 1), im_size) pos = [p1, p2] draw.rectangle(pos, fill=color) im.save('YYY.png')
5行目の N_CLUSTER
でクラスタ数を変えることができます。
クラスタ数が2つ以上のときは、属するデータが多い順にクラスタを左から描画するようにしました。
結果は以下です。
まとめ
平均、最頻値、中央値、1クラスタのK平均法はどれも似たような感じでしたが、
それらに比べると3クラスタのk平均法は画像の特徴をしっかり捉えていて、いい感じでした。
クラスタ数をもっといじれば、より良いものになるかもしれません。
最適なクラスタ数を調べるには、 distortion
がこれ以上あまり小さくならないというところで
クラスタ数を上げるのをやめる エルボー法 などがありますが、それについてはまた機会があったときに書くことにします。