iPX社員によるブログ

iPX社員が"社の動向"から"自身の知見や趣味"、"セミナーなどのおすすめ情報"に至るまで幅広い話題を投下していくブログ。社の雰囲気を感じ取っていただけたら幸いです。

【OpenCV】K-meansクラスタリングを用いた色の量子化【Python】

 こんにちは。
 最近読んだ論文の要約を書こうとしましたが挫折しました、安藤です。
 そういうことってよくありますよね、はは。


 今回はOpenCVライブラリのK-means法を使って色の量子化を行いたいと思います。
 量子化というとむずかしく聞こえてしまいますので、はじめにどのような画像をつくれるのか見てみましょう。

 目標はこんな感じの画像を作ることです。

f:id:ipx-writer:20191206174514j:plain
出典:http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html
 左上が元画像、それ以外の画像はK個の色のみになるように量子化を行っています。簡単に言うと、似ている色をひとつのまとまりに分けて色付けをし直すことです。それぞれのクラスタに分類された領域には、その領域内の平均の色を割り当てています。画像のノイズ除去なんかにも使えそうですね。

  • K-meansとは

 機械学習分野における教師なし学習で、クラスタリングを行うアルゴリズムです。くわしい説明は下記のリンクなんかがわかりやすかったので、こちらを参照してみてください。
k-meansとk-means++を視覚的に理解する~Pythonにてスクラッチから~ - 医療職からデータサイエンティストへ

 入力データとして1ピクセルあたりのRGB値を1データ(3次元ベクトル)として、K-meansクラスタリングを行いました。

  • 実装

 ここの最後の項のコードを基本にして、コメントを付け加えた形です。クラスタ数を2,4,6,8で設定した際の画像を出力します。

import numpy as np
import cv2 as cv


"""
画層の読み込みから前処理
"""
# 画像の読み込み
img = cv.imread('lena.jpg')
# 画像を2次元配列にreshape(row:ピクセル数、col:3(RGB))
img_1d = img.reshape((-1, 3))

# np.float32に型変換
img_1d = np.float32(img_1d)


"""
K-meansの実行
"""
# オプションフラグ
# ここではエポックごとのクラスタ中心移動量に対する閾値と最大エポック数を指定
criteria = (cv.TERM_CRITERIA_MAX_ITER + cv.TERM_CRITERIA_EPS, 10, 1.0)

# k : 分割したいクラスタ数(2,4,6,8)
for k in range(2, 9, 2):

    # K-meansの実行
    # label  : 入力データに対するクラスタ番号配列
    # center : クラスタ中心 この場合ではK個の3次元配列が出力される
    _, label, center = cv.kmeans(img_1d, k, None, criteria, 10, cv.KMEANS_RANDOM_CENTERS)


    """
    出力の変換
    """
    # クラスタ中心になるRGB値をnp.uint8型に変換
    center = np.uint8(center)
    # 出力ラベルが2次元配列になっているので1次元にreshape
    label = label.flatten()
    # クラスタ番号にひもづくクラスタ中心RGB値を割り当て
    output_img = center[label]
    # 元画像と同じshapeになるようにreshape
    output_img = output_img.reshape((img.shape))


    """
    出力画像の表示と保存
    """
    # 表示するウィンドウの名前
    window_name = 'output_img_K{}'.format(k)
    # 表示するウィンドウの設定
    cv.namedWindow(winname = window_name, 
                   flags   = cv.WINDOW_NORMAL)
    # 画像の表示
    cv.imshow(winname = window_name, 
              mat     = output_img)
    cv.waitKey()
    cv.destroyAllWindows()

    # 出力画像の名前
    output_img_name = "result_K{}.png".format(k)
    # 画像の保存
    cv.imwrite(filename = output_img_name,
               img      = output_img)
  • 結果

無処理

f:id:ipx-writer:20191209114543j:plain
入力画像(無処理)
K = 2
f:id:ipx-writer:20191209114018p:plain
K = 2
K = 4
f:id:ipx-writer:20191209114021p:plain
K = 4
K = 6
f:id:ipx-writer:20191209114023p:plain
K = 6
K = 8
f:id:ipx-writer:20191209114027p:plain
K = 8

これをつかえば映える画像も作れるかもしれませんね!

  • 参考

labs.eecs.tottori-u.ac.jp