LoginSignup
3
3

More than 3 years have passed since last update.

【機械学習】色の量子化を解説

Last updated at Posted at 2020-09-19


 こんにちは。安藤 勇輝(@holiholiday)です。

 今日はOpenCVをつかって画像の量子化を行う手法を解説します。この記事では量子化のアルゴリズムとしてK-means法とよばれる、機械学習手法を用いています。

OpenCVとは?

 OpenCVとはOSSの画像処理ライブラリです。
 C/C++,Java,Python,MATLABで使用することができます。今回はPythonを使って解説します。

量子化とは?

 さっそく本題に入りたいのですが、量子化という言葉になじみがない方も多いと思うので、量子化の説明からさせてください。

 量子化とは少数などの数値データを、整数などの離散値で近似することです。

 今回は0〜255の8ビットで表現される整数の画素値を量子化するので少数ではありませんが、整数でも量子化することは可能です。

 先に結果から見せてしまうと、OpenCVの量子化をつかうと以下のようなことができます。

  • 左:入力画像 右:量子化画像
    Parrots_with_k5.png
     今回の処理では、5色だけの画像に変換しています(処理後の色の数も決めることができます)。
     画像に対して量子化を行うと、画像内で使われている色の種類の数が減り、のっぺりした塗り絵のような画像をつくることができます。

     実際に行っていることのイメージを持ってもらうために、K-means法の説明をします。

K-means法とは?

 K-means法とは、データのグループ分けを行うアルゴリズムです。このグループ分けのことを、機械学習分野ではクラスタリングと呼びます。

 今回グループ分けを行ったのは、画像の画素値です。カラー画像は1ピクセルごとに色を表す3つの数値(画素値)を持ち、この数値1つずつが青、緑、赤の画素値をあらわしています。これら3つの値を合わせて、ピクセル1つ1つの色を表現しています。

 処理の手順としては以下のとおりです。

  1. ピクセルあたりの画素値を1つのデータとみなし、画像1枚分のデータをグループ分けします。
  2. このグループ分けによってできたグループごとに、グループ内の平均値をとります。
  3. グループに存在しているデータに、グループ内の平均値をわりあて、画像内の色の種類を減らします。

 このような処理を行うことで、画像内にたくさんあった色の種類が、グループ数と同じだけになります。


コードの解説

 処理の手順は説明し終えたので、ここからはコードの解説をしていきます。
 使用したコードとしては、以下のとおりです。

import numpy as np
import cv2


if __name__ == "__main__":
    # 画像の読み込み
    img = cv2.imread('処理したい画像のパス')
    # 画像をそのままk-meansにかけることはできないので、shapeを(ピクセル数, 3(BGR))に変換
    Z = img.reshape((-1, 3))
    # np.float32型に変換
    Z = np.float32(Z)

    # k-meansの終了条件
    # デフォルト値を使用
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
    # 分割後のグループの数
    K = 5
    # k-means処理
    _, label, center = cv2.kmeans(
        Z, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

    # np.uint8型に変換
    center = np.uint8(center)
    # グループごとにグループ内平均値を割り当て
    res = center[label.flatten()]
    # 元の画像サイズにもどす
    res2 = res.reshape((img.shape))

    # 画像の保存
    cv2.imwrite("保存先のパス", res2)

画像の読み込みからデータの変換

# 画像の読み込み
img = cv2.imread('処理したい画像のパス')
# 画像をそのままk-meansにかけることはできないので、shapeを(ピクセル数, 3(BGR))に変換
Z = img.reshape((-1, 3))
# np.float32型に変換
Z = np.float32(Z)

 OpenCVをつかって画像の読み込みを行い、データの変換を行います。

 読み込んだ画像データをそのままK-meansに入力することはできないので、画像の変換が必要になります。

 入力するデータはnp.floatの2次元配列で、(データ数, データの次元数)と変換する必要があります。データの次元数は、今回の場合で言うとBGRの3次元になります。

K-meansの設定と実行

# k-meansの終了条件
# デフォルト値を使用
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
# 分割後のグループの数
K = 5
# k-means処理
_, label, center = cv2.kmeans(
    Z, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

 K-meansを実行するためには、設定が必要なパラメータがいくつかあります。

 まずはじめにcriteriaという変数で、K-means処理の終了条件を決定します。詳しくは説明しませんが、K-means法にはイテレーション回数としきい値が存在しており、それらをcriteriaで決めることができます。

 次にKという変数で、分割後のグループ数を決めます。グループ数は処理後の色の数と一致するので、色の数を少なくしたければ、Kを小さくしてください。

 さいごに、cv2.kmeansでK-means処理を実行しています。うしろから1、2番めのパラメータは初期値の設定です。
 今回はすべて参考ページのデフォルト値をつかっています。

 cv2.kmeansが返す変数は3つあります。2、3番めの変数はNumpy配列です。

 labelという変数には、データが何番目のグループに属しているのかを示すインデックスが格納されており、このshapeは(データ数, 1(インデックスのみ))となっています。

 centerという変数には、グループごとのデータの平均値が入っています。そのため、この変数のshapeは(グループ数, データの次元数(今回は3))となります。

処理したデータの変換から保存まで

# np.uint8型に変換
center = np.uint8(center)
# グループごとにグループ内平均値を割り当て
res = center[label.flatten()]
# 元の画像サイズにもどす
res2 = res.reshape((img.shape))

# 画像の保存
cv2.imwrite("保存先のパス", res2)

 ここから処理したデータを元の画像のShapeに変換していきます。

 まずfloat型のcenterをuin8型に変換します。
 つぎの処理で、1つのグループに属するピクセルデータすべてに、そのグループ内平均値をわりあてます。つまり、グループ1に属するピクセルにはグループ1の平均値を、グループ2に属するピクセルにはグループ2の平均値を割り当てていくことになります。

 さいごに元の画像サイズに変換し、画像の保存をして終了になります。



グループ数ごとの処理結果

 分割後のグループ数によって処理結果は大きく変わってきます。

 グループ数ごとの処理結果を載せておくので、パラメータ設定の参考にしてください。

  • K=3
    Parrots_k3.png
  • K=5
    Parrots_k5.png
  • K=7
    Parrots_k7.png
  • K=9
    Parrots_k9.png


 いかがでしたか?
 こんなふうに解説記事を上げているので、興味があれば他の記事も見ていってください。

 ご閲覧どうもありがとうございました。

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3