🖼

画像中のRGB値から有彩色、無彩色を分類してみる

2021/12/09に公開

画像中のRGB値から有彩色、無彩色を分類してみる

これは、FORCIA Advent Calendar 2021の9日目の記事です。

こんにちは、第2旅行プラットフォーム部エンジニアの力石です。

近頃、AIが様々な分野で活躍する話を以前に増してよく聞くようになりました。
個人的にその中でも興味深いのは、AIが漫画や絵を創作するといった芸術分野やエンターテインメント分野への応用です。
こういったAIによる絵の創作では、画像をぼかしたり特定の色を抽出するような画像処理の技術や機械学習、深層学習などの技術が使われています。
今回はそれらの技術の一部を使ってみて、簡単な有彩色、無彩色の分類をしてみたいと思います。

そもそも有彩色、無彩色とは?

一般的に有彩色、無彩色の定義は以下だと考えられます。

  • 有彩色:すこしでも色味のある色
  • 無彩色:色味のない色

例を上げると、赤色や青色などは有彩色、灰色や黒色は無彩色となります。

有彩色、無彩色のパラメタとしては「色相、明度、彩度」があります。これはそれぞれ色味、明るさ、鮮やかさを表します。

なにを基準に分類するのか

定義から有彩色、無彩色の分類には「色相、明度、彩度」のパラメタを使えば良さそうです。
しかし今回は液晶ディスプレイなどで使われているRGB値で分類をしてみたいと思います。(RGB値とは、赤、緑、青の3つの原色からさまざまな色を表現する方法です)
理由としては、RGB値と有彩色、無彩色の関係がどうなっているか追求してみることにロマンを感じたからです。

RGB値で有彩色、無彩色を分類するにあたり、仮説をたててみます。
次の図では、左は有彩色と考えられる色の、右は無彩色と考えられる色のRGB値を示しています。
この図を見た感じ、左はRGB値がまばらになっており、右はRGB値がほとんど同じように思えます。

RGBの値

そこで、「RGB値それぞれの値の差の絶対値の合計が小さいほど無彩色である可能性が高く、大きいほど有彩色である可能性が高い」という仮説をたて、それに基づき分類してみたいと思います。

分類方法とその実装

ここでは以下の環境で実装しています。

Python : 3.6.3
opencv-python : 3.4.2.17
NumPy : 1.18.5
scikit-learn : 0.21.3

まずはOpenCVで画像を読み込みます。
OpenCVではデフォルトでBGRの順で画像を読み込むため、別ライブラリで画像を出力する際には注意が必要です。(matplotlibなど)
今回はOpenCVで出力するため、このままにします。

import cv2

# 画像のパス
read_path = "image_file_to_path"

# bgrで読み込み
img = cv2.imread(read_path)

次に減色処理をします。
一般にRGB値はそれぞれ0~255まであるため、組み合わせは256の乗の約1600万となります。
以降の処理を簡単にする、ある程度決まった色の数から分類してみるといった理由から、同系統の色をまとめる減色処理を行います。
何色まで減らせば良いなどの目安はないため、決め打ちで64色まで減らしてみます。
なお、以下の実装はOpenCV-Python チュートリアルを参考にしました。

# 減色処理
decrese_num = 64 # 何色まで減らすか
img = img.reshape((-1,3)) # 画像のサイズ変更
float_img = np.float32(img) # k-mmeansのためにfloatへ
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) # k-meansの終了条件
_, _, center = cv2.kmeans(float_img, decrese_num, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) # k-meansの実行

最後に有彩色、無彩色の分類をします。
先程の仮説に基づき、64色それぞれのRGB値の差の絶対値の合計(以下、スコアと呼びます)を求めます。
そしてスコアを特徴ベクトルとし、k-means法でクラスタ数2のクラスタリングを行います。
クラスタリング結果と先ほどの減色処理の結果である64色のRGB値の重心(center)から、64色のうち無彩色と分類されたRGB値、有彩色と分類されたRGB値がわかります。

# 有彩色、無彩色の分類
score_array = [] # 分類スコアの格納

## 各色のスコアを算出する
for i in range(decrese_num):
    score = abs(center[i][0] - center[i][1]) + abs(center[i][0] - center[i][2]) + abs(center[i][1] - center[i][2]) # スコアの算出
    score_array.append([score])
color_class = KMeans(n_clusters=2).fit(score_array) # クラスタ数2でクラスタリング

## 無彩色と分類されたクラスタを抽出する
min_label = 1 if color_class.cluster_centers_[0][0] > color_class.cluster_centers_[1][0] else 0 
non_colored_index = np.where(color_class.labels_ == min_label)[0]

## 有彩色と分類されたクラスタを抽出する
colored_index = [i for i in range(decrese_num) if i not in non_colored_index]

分類結果と感想

下の図が2枚の画像を分類した結果になります。
使用されている色の種類が少なそうな画像(上の段)と多そうな画像(下の段)を今回は選んでみました。
どちらも元画像と減色処理後の画像に大きな変化は見られないですね。

RGBの値

上の段の有彩色の画像を見ると、有彩色らしい色が分類されています。しかし、元画像には殆ど見られない緑色がある点が気になります。
これは減色処理の際に数ピクセルだけ緑色に変換され、それが有彩色と判断されたためだと考えられます。
下の段の有彩色の画像でも概ね有彩色らしい色が分類されていると思われます。

上の段の無彩色の画像を見ると、全体的に暗めな色や白色が分類されています。
少し明るめの肌色がある点は気になりますが、概ね無彩色だと言える色です。また、似たような色が多いため、元画像で使用されている色の種類も少ないと考えられます。
しかし、下の段の無彩色の画像では明るめの色がいくつか見られます。

以上より今回の手法では、色の種類が少ない画像の有彩色、無彩色の分類はできると考えられますが、さまざまな色を使用した画像の有彩色、無彩色の分類は難しいことがわかりました。(とはいえ、2枚の画像で試しただけですので、たまたまこうなった可能性も考えられます)
このような結果になったのは、今回の手法では特徴ベクトルが1種類でクラスタ数が2という、超シンプルなクラスタリングなためだと考えられます。どのような特徴ベクトルを追加すればよいか、そもそも仮説が間違っているのか、よければ考えてみてください。

最後に

今回は簡単な有彩色、無彩色の分類をしてみました。
このような簡単な分類でも苦戦している筆者からしたら、最初にお話ししたAIで絵の創作といったプロジェクトの研究、開発をしている方々には尊敬の念を抱かずにはいられません。

また、明日以降もFORCIA Advent Calendar 2021の記事が公開されますので、ぜひご覧ください。

FORCIA Tech Blog

Discussion