Pythonで画像内の物体をクラスタリングして、カッコいい画像を作成する方法

2020年3月29日

今日はクラスタリング手法を利用したカッコいい画像の作成方法を紹介します。

クラスタリングとは

クラスタリング(clustering)とは、簡単に言うと与えられたデータを何種類かの似たようなデータの集まりに分けることです。

画像に適用すると必要な色数に減色できます。

最低限の色数でフラットなデザインを作りたいと思っている人には良い方法だと思います。

今回はk-means 法というクラスタリングとしては最もシンプルな手法を用います。

k-means法

k-means法は、教師なし学習の一つです。与えられたデータを k個のクラスタに分類します。ただし、最初にk個に分解することを決めてあげる必要があるため、厳密にはデータ以外の基準が必要です。その意味では純粋な教師なし学習とは言えません。k-means法は他のクラスタリング手法(最短距離法とか)に比べ計算負荷が小さい(計算時間が短い)ことがメリットです。

プログラムとしては、Python のライブラリの中で主に科学技術計算用に作られた SciPy (サイパイ)を使います。

SciPy をインストールしていない場合はインストールしてください。

Anaconda がインストールされていれば Anaconda Prompt から以下を実行してください。

> pip install scipy

プログラム紹介

以下のソースコードを見てください。

import os
import cv2
import glob
import numpy as np
from PIL import Image
from natsort import natsorted
import matplotlib.pyplot as plt
from scipy import cluster
from scipy import ndimage
from scipy.spatial.distance import euclidean


# パラメータ
km = 4

# 画像の読み込み。pltで読み込むので色順はRGB
img_rgb = plt.imread('./test_photo/hana/001_800px_light.jpg')

# 画像の形状と色チャンネル数を調べる
h, w, c = img_rgb.shape
print('img_bgr_shape =', img_rgb.shape)

# 画像を全ピクセル数と色チャンネルに分ける。
# この後のクラスタ分類計算のことを考えて、
# 各ピクセルは浮動小数点型のデータとしておく。
img_2d = img_rgb.reshape(h*w, c).astype(float)
print('img_2d =', img_2d.shape)

# 空の入れ物(リスト)を準備
d = []

### クラスタ分類の計算 ###
for i in range(km):
    cluster_centers, distortion = cluster.vq.kmeans(img_2d, k_or_guess = i+2)

    print('cluster_centers =', cluster_centers, 'distortion =', distortion)

    img_2d_labeled = img_2d.copy()

    labels = []

    for i in range(img_2d.shape[0]):
        distances = [euclidean(img_2d[i], center) for center in cluster_centers]
        min_d = np.argmin(distances)
        labels.append(min_d)

    img_c = cluster_centers[labels].reshape(h,w,c) / 255.0
    img_c_ndarray = np.array(img_c)

    d.append(img_c_ndarray)


### 画像保存 ###
# クラスタ分類数に応じて画像を保存
for i in range(km - 1):
    plt.figure(figsize=(6, 6))
    plt.imshow(d[i])
    plt.savefig('./test_photo/hana/temp/' + '{}.png'.format(i))


### 画像結合 ###
# 所定のフォルダ内にあるjpgファイルを連続で読み込んでリスト化する
files = glob.glob("./test_photo/hana/temp" + "/*.png")

# 空の入れ物(リスト)を準備
e = []

# natsortedで自然順(ファイル番号の小さい順)1個づつ読み込む
for i in natsorted(files):
    img = Image.open(i)
    img = np.asarray(img)
    e.append(img)

# 画像結合
img_x = np.hstack(e[0:km-1])

# 色順をBGRからRGBに変換
img_x = cv2.cvtColor(img_x, cv2.COLOR_BGR2RGB)

cv2.imshow('img_after', img_x)
cv2.imwrite('./test_photo/hana/temp/result.png', img_x)

cv2.waitKey(0)
cv2.destroyAllWindows()

最初にプログラム実行に必要なライブラリを import してください。

SciPy のパッケージ(cluster と ndimage)の部分を説明します。

clusterクラスタ分析のアルゴリズムを含むサブパッケージです。
今回はvqというサブモジュールを使います。
vqモジュールにはk-means 法の関数が用意されています。
ndimage多次元画像処理の関数やアルゴリズムを含むサブパッケージです。

ソースコードの km=4 の部分はクラスタの分類数です。

今の場合は最大で4つのクラスタ(4色)に分けることになります。

画像を plt.imread() で読み込んでRGBの色順の ndarray 型データとして img_rgb に代入しておきます。

その後、画像データを全ピクセル数と色に分けます。

また、この後のクラスタ計算に備えてデータ型を浮動小数点タイプにしておきます。

その後にクラスタ計算してクラスタ分類数に応じて画像を保存します。

なお、クラスタ分類数毎の画像が比較しやすいように画像を一枚に結合して保存できるようにしています。

画像結合の部分が不要なら、””” でソースコードを挟むか、# をソースコードの先頭に付けてコメントアウトしてください。

実行結果

プログラムを実行すると以下の画像が得られます。

今回は前回記事でも使った花の画像を使っています。

hana_image
オリジナル画像

比較画像は以下です。

一番左の画像からクラスタ分類数が2の場合(2色)、3の場合(3色)、4の場合(4色)です。

クラスタ分類数(ソースコードのパラメータ km の数字)を変えれば色々な減色画像が作れます。

まとめ

  • SciPy を使うとクラスタリングが簡単にできる。
  • 画像にクラスタリング手法を使うとフラットな減色画像が作れる。
  • プログラムのクラスタ分類数(km)を変えると色々な減色画像が作れる。

参考にした書籍