Pythonで画像からメインカラーを抽出する

# TL;DR

![output_42_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/b5ae4a13-1dd5-e751-014f-1c93d143dee3.png)

↑などの画像から、

![output_48_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/a2815cca-5bb5-4e9f-a601-a6ef8a6e5dbf.png)

↑みたいに、メインカラーをPythonで抽出します。

# 使うもの

- scikit-learn（k-means法）
- PIL、OpenCV（画像周り）

# サンプルデータ

元々はDCGANモデル検証でのイラストの色変換などのためのデータセットでカラーヒントを用意するためだったのですが、ここではフリー素材の写真を使ってみます。（イラスト側でも問題なく対応できるのは確認済みです）

![KAZ7842_DSCF3133_TP_V.jpg](https://qiita-image-store.s3.amazonaws.com/0/228778/1d036a60-c528-8ee2-2204-bf6c8a3452ae.jpeg)

https://www.pakutaso.com/20160102025post-6676.html

![nuko-8_TP_V4.jpg](https://qiita-image-store.s3.amazonaws.com/0/228778/7e48d876-1ae5-30d6-d556-5d50f57d8eaa.jpeg)

https://www.pakutaso.com/20180234044post-15166.html

![kazuhiro17810000_TP_V.jpg](https://qiita-image-store.s3.amazonaws.com/0/228778/a5f24942-d751-f183-ab9d-b89e330697aa.jpeg)

https://www.pakutaso.com/20170835230post-12885.html

![rtethjdyrsetgshf_TP_V.jpg](https://qiita-image-store.s3.amazonaws.com/0/228778/ae37142c-333b-665c-1e7b-e569b96b4ab8.jpeg)

https://www.pakutaso.com/20170333074post-10666.html

# スクリプト

## 画像の読み込みと変換

花の画像を対象に進めていきます。


```python
import PIL
```


```python
from PIL import Image
```


```python
import cv2
```


```python
import sklearn
```


```python
from sklearn.cluster import KMeans
```


```python
cv2_img = cv2.imread('./flower.jpg')
```

OpenCV、読み込んだままだとカラーチャンネルの順番がRGBになっていないようなので変換をしておきます。


```python
cv2_img = cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB)
```

処理の都合、(縦, 横, カラーチャンネル)の形式の画像のテンソルを、(縦と横をフラット化させた値, カラーチャンネル)の2次元の形式に変換しておきます。


```python
cv2_img.shape
```




    (900, 1600, 3)




```python
cv2_img = cv2_img.reshape(
    (cv2_img.shape[0] * cv2_img.shape[1], 3))
```


```python
cv2_img.shape
```




    (1440000, 3)



## クラスタリング

k-means法でクラスタリングを行います。各ピクセルの色で、指定したクラスタ数でクラスタリングされ、その各クラスタの中央の座標の色をメインカラーとして扱います。

引数のn_clusterはクラスター数となります。今回はメインカラーを5色算出したいので、5を指定していますが、適宜調整してください。


```python
cluster = KMeans(n_clusters=5)
```

対象画像に対してクラスタリングを行います。画像サイズによっては少し時間がかかります。


```python
cluster.fit(X=cv2_img)
```




    KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
        n_clusters=5, n_init=10, n_jobs=1, precompute_distances='auto',
        random_state=None, tol=0.0001, verbose=0)



cluster\_centers\_ という属性に、各クラスタの中央の値（RGBの値）が格納されます。


```python
cluster.cluster_centers_
```




    array([[211.60346671, 238.98895507, 231.41817764],
           [104.07336583, 143.33552179, 167.95419295],
           [183.88814356,  93.68673428,  49.43004867],
           [167.54667869, 212.4601815 , 211.04911242],
           [ 63.75784365,  60.18503292,  86.27467466]])




```python
cluster.cluster_centers_.shape
```




    (5, 3)



クラスタ数を5にし、RGB3チャンネルの画像なので、(5, 3)の配列となりました。

## 色を表示してみる

RGBの配列だとイメージがつきづらいので、PILを使って単色画像でそれぞれ表示してみます。

Pythonでは、02xと指定することで、0～255までの値を16進数の00～ffのフォーマットにしてくれるので、それを使って#ffffffといった文字列を用意してPILに渡します。


```python
format(255, '02x')
```




    'ff'




```python
'#%02x%02x%02x' % (255, 255, 255)
```




    '#ffffff'



浮動小数点数は不要なので、キャストしておきます。（16進数変換の都合）


```python
cluster_centers_arr = cluster.cluster_centers_.astype(
    int, copy=False)
```

ループで回す都合、Jupyter上で全ての画像を表示するためにdisplay関数を使用します。（そのままだと最後の画像しか、アウトプットとして表示されないため）


```python
from IPython.display import display
```


```python
for rgb_arr in cluster_centers_arr:
    color_hex_str = '#%02x%02x%02x' % tuple(rgb_arr)
    color_img = Image.new(
        mode='RGB', size=(32, 32), color=color_hex_str)
    display(color_img)
```


![output_38_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/d9eb5ed0-c0a4-3f6b-e144-e5a9f89be0ab.png)

![output_38_1.png](https://qiita-image-store.s3.amazonaws.com/0/228778/7b9b4670-55f5-db9d-b79b-a866727dae1a.png)

![output_38_2.png](https://qiita-image-store.s3.amazonaws.com/0/228778/cb7decd4-977d-dd29-f753-1ecad8a29a96.png)

![output_38_3.png](https://qiita-image-store.s3.amazonaws.com/0/228778/3ba297a9-f95a-aa17-9759-bcc270d24c48.png)

![output_38_4.png](https://qiita-image-store.s3.amazonaws.com/0/228778/f60f6eca-3702-5b28-9384-803077d9828a.png)


元画像を表示してみて、比較してみます。


```python
original_img = Image.open('./flower.jpg')
```


```python
original_img.size
```


    (1600, 900)



```python
original_img.resize((320, 180))
```


![output_42_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/63453ca0-00ad-4d87-1658-7868758a60c7.png)


いい感じです。

おまけで、PILを使って背景をグレーにし、横並びの画像を作ってみます。


```python
# 幅と高さ64px × 横並び5画像 + 上下左右余白15pxずつの画像を作ります。
IMG_SIZE = 64
MARGIN = 15
width = IMG_SIZE * 5 + MARGIN * 2
height = IMG_SIZE + MARGIN * 2
print(width, height)
```

    350 94



```python
tiled_color_img = Image.new(
    mode='RGB', size=(width, height), color='#333333')
```

この段階では上記の用意した画像は、グレーの横長画像です。

この画像に、各クラスタの色の画像をペーストしていきます。PILのpaste関数を使います。引数のboxで、左の余白値、上の余白値といった具合でペーストする位置を設定できるので、ループのインデックスを加味して位置を指定します。

Pythonでループのインデックスを取りたい場合には、enumerate関数を使うとindex, valueといった形で扱えるのでシンプルです。


```python
for i, rgb_arr in enumerate(cluster_centers_arr):
    color_hex_str = '#%02x%02x%02x' % tuple(rgb_arr)
    color_img = Image.new(
        mode='RGB', size=(IMG_SIZE, IMG_SIZE),
        color=color_hex_str)
    tiled_color_img.paste(
        im=color_img,
        box=(MARGIN + IMG_SIZE * i, MARGIN))
```


```python
tiled_color_img
```


![output_48_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/55fd55ad-9d0c-5d01-6958-95b0c9054621.png)


Adobe Color CCの探索画面みたいなカラーセット画像を用意することができました。

# 他の画像で試して見る

関数化して、他の写真でも試してみましょう。


```python
def get_main_color_list_img(img_path):
    """
    対象の画像のメインカラーを算出し、色を横並びにしたPILの画像を取得する。
    
    Parameters
    ----------
    img_path : str
        対象の画像のパス。
    
    Returns
    -------
    tiled_color_img : Image
        色を横並びにしたPILの画像。
    """
    cv2_img = cv2.imread(img_path)
    cv2_img = cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB)
    cv2_img = cv2_img.reshape(
        (cv2_img.shape[0] * cv2_img.shape[1], 3))
    
    cluster = KMeans(n_clusters=5)
    cluster.fit(X=cv2_img)
    cluster_centers_arr = cluster.cluster_centers_.astype(
        int, copy=False)
    
    IMG_SIZE = 64
    MARGIN = 15
    width = IMG_SIZE * 5 + MARGIN * 2
    height = IMG_SIZE + MARGIN * 2
    
    tiled_color_img = Image.new(
        mode='RGB', size=(width, height), color='#333333')
    
    for i, rgb_arr in enumerate(cluster_centers_arr):
        color_hex_str = '#%02x%02x%02x' % tuple(rgb_arr)
        color_img = Image.new(
            mode='RGB', size=(IMG_SIZE, IMG_SIZE),
            color=color_hex_str)
        tiled_color_img.paste(
            im=color_img,
            box=(MARGIN + IMG_SIZE * i, MARGIN))
    return tiled_color_img
```


```python
def get_original_small_img(img_path):
    """
    元画像の小さくリサイズしたPILの画像を取得する。
    
    Parameters
    ----------
    img_path : str
        対象の画像のパス。
    
    Returns
    -------
    img : Image
        リサイズ後の画像。
    """
    img = Image.open(fp=img_path)
    width = int(img.size[0] / 5)
    height = int(img.size[1] / 5)
    img = img.resize(size=(width, height))
    return img
```


```python
get_original_small_img(img_path='./cat.jpg')
```


![output_53_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/852ac8c4-6310-23d7-2d8d-7e2e23b62a35.png)


```python
get_main_color_list_img(img_path='./cat.jpg')
```


![output_54_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/1164d380-c2da-70db-b1c4-d3db3d93c361.png)


```python
get_original_small_img(img_path='./sky.jpg')
```

![output_55_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/3b5f0b30-60bf-eeb0-fa6c-e618467b25b1.png)


```python
get_main_color_list_img(img_path='./sky.jpg')
```


![output_56_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/2ff387f8-17c6-f0c1-3280-378e8bdf4ebb.png)


```python
get_original_small_img(img_path='./buildings.jpg')
```

![output_57_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/c4577336-2c08-3334-84ba-1e39e17beebe.png)


```python
get_main_color_list_img(img_path='./buildings.jpg')
```

![output_58_0.png](https://qiita-image-store.s3.amazonaws.com/0/228778/c56b30fc-8ada-4271-e9c3-b365452a30c3.png)



# 実行環境

- Azure Notebooks


```python
!python -V
```

    Python 3.5.4 :: Anaconda custom (64-bit)



```python
PIL.__version__
```




    '4.3.0'




```python
cv2.__version__
```




    '3.3.1'




```python
sklearn.__version__
```




    '0.18.1'



# 参考サイト

[OpenCV and Python K-Means Color Clustering](https://www.pyimagesearch.com/2014/05/26/opencv-python-k-means-color-clustering/)

# おまけ

ノートをgithubにアップしておきました。

[20180503_画像からメインカラーを抽出する.ipynb](https://github.com/simon-ritchie/note/blob/master/20180503_img_dominant_color/20180503_%E7%94%BB%E5%83%8F%E3%81%8B%E3%82%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%AB%E3%83%A9%E3%83%BC%E3%82%92%E6%8A%BD%E5%87%BA%E3%81%99%E3%82%8B.ipynb)