ゆっくり開発

開発したい時に開発するブログ

ドット絵を少ない配色で描き直す

概要

前回の記事 の続きです.
前回作成したプログラムは以下の処理を実行できます.

  1. 入力画像のサイズを小さくすることで容量の小さいドット絵を取得
  2. ドット絵を解析し,使われているRGBの種類とそれが何ピクセル使われているかを取得
  3. 使われているRGBについて,使用頻度の多い順に並び替えた画像を作成

今回は2.ドット絵の解析で取得したデータから, 使用頻度の多いRGBをn個取得し,
そのRGBだけを使用して元のドット絵を描き直すプログラムを作成しました.

色の補完方法について

今回のプログラムでは,使用するRGBを決めた後,そのRGBが使われていないピクセルのRGBを書き換える処理が必要になり, また,どのRGBに書き換えるかを選択するアルゴリズムが必要になります.

今回は,RGBを3次元のベクトルと捉え,使用可能RGBとのノルムを計算することで,最適なRGBを選択しました.

以下はその計算の例

f:id:uttnaoki:20180902191859p:plain:h300

上記例では,補完対象のRGBは使用可能RGBの2番目のものに最も近い. このため,補完対象のrgb(204,96,90)rgb(204,105,108)に補完することとします.

この操作を全ピクセルに対して行います.

作成した画像

元画像から使用頻度の多いRGBを取得し, 上位{10, 20, 30, 40}個を使用可能RGBとし,
それらのRGBだけを使用してドット絵を描き直しました.

以下は元のドット絵(230色)

f:id:uttnaoki:20180901144422p:plain:w300

以下は使用するRGBの数を減らした画像

  • 10色
    f:id:uttnaoki:20180902095047p:plain:w300
  • 20色
    f:id:uttnaoki:20180902095057p:plain:w300
  • 30色
    f:id:uttnaoki:20180902095105p:plain:w300
  • 40色
    f:id:uttnaoki:20180902095113p:plain:w300

使用可能RGBが30種類を超えるといい感じの色合いになる.
30と40の差はあまりないので,ピカチュウに関しては多くても30種類のRGBでいい感じに描けることがわかった.

ソースコード

前回の記事 に以下のコードを追加した.

module

numpy を使ったので,以下のコードを追加した.

import numpy as np

関数

指定のRGBセットで画像を再描画する関数を追加した.

def make_less_color_img (base_img, rgb_set, filename):
    # rgbの距離(norm)を計算するためにnumpyの配列に変換
    rgb_set = np.array(rgb_set)
    rgb_set_len = len(rgb_set)

    # usable_rgb の中から最も this_rgb に近いものを返す
    def get_nearest_rgb_from_usable (this_rgb, usable_rgb):
        this_rgb = np.array(this_rgb)
        norm_set = [np.linalg.norm(this_rgb - rgb_set[i]) for i in range(rgb_set_len)]
        min_index = np.argmin(norm_set)
        return tuple(usable_rgb[min_index])

    # 指定されたピクセル(座標)の色を usable_rgb の色に修正する
    def mod_one_pixel (x, y):
        r,g,b = base_img.getpixel((x, y))
        base_img.putpixel((x, y), get_nearest_rgb_from_usable([r,g,b], rgb_set))

    # 画像の幅と高さを取得
    width, height = base_img.size
    # 画像の各ピクセルの色を usable_rgb の色に修正する
    [mod_one_pixel(x, y) for x in range(width) for y in range(height)]
    # 修正後の画像を保存
    base_img.save(filename)

main関数

上記関数を呼ぶために以下のコードをmain関数に追加した.

    def get_frequent_color (rgb_count):
        sorted_rgb_count = sorted(rgb_count.items(), key=lambda x: -x[1])
        return [list(map(int, rgb[0].split('-'))) for rgb in sorted_rgb_count]

    # リサイズ後の画像に使われている色の数を出力
    print('color_num: {0}'.format(len(img_pixel_colors)))

    # less_color.png で用いる色の数を定義
    usable_rgb_num = 30
    # 使用面積(頻度)の大きい色から usable_rgb_num の数分取ってくる
    usable_rgb_set = get_frequent_color(img_pixel_colors)[:usable_rgb_num]
    # usable_rgb_set の色だけで元画像(モザイク)を表現し,保存
    make_less_color_img(rgb_img, usable_rgb_set, 'less_color.png')

感想

  • 30色はちょっと多すぎるので,もっと少ない色でいい感じに表現したい.
  • 今回は元画像に使われているRGBをそのまま使った処理を行ったので, 次は事前に色を手動で選択して,それらの色を使っていい感じに描画したい.