ドット絵変換時の最適なパレットを生成するAIモデルを作った話【減色AI】

　
はじめまして。[ひやめし(Hiyameshi_8bit)](https://x.com/Hiyameshi_8bit)です。
はじめての技術ブログになりますので、自己紹介含め手探りで記述していきます。
# 普段何をやっている人間か
肩書きとしては「マルチクリエイター」になるのでしょうか。
一応メインの活動としては[YouTube](https://www.youtube.com/@hiyameshi)等で初代ゲームボーイ実機を使用した音楽の8bitアレンジ等をしています。
[ゲームボーイの波形メモリに歌わせる"歌うゲームボーイ"](https://www.youtube.com/shorts/NjjEzS3L2UE)なんかが代表的かと思います。

また、人によっては**コウメ界隈の1人**と言った方が伝わるかもしれませんね。
[ゲームボーイに閉じ込められてしまったコウメ太夫](https://www.youtube.com/shorts/GMipfChiaX4)
[ポケモンの世界に閉じ込められてしまったコウメ太夫](https://www.youtube.com/watch?v=6Xgy_JGKZKc)

いずれにせよ、レトロゲームの音楽系クリエイターって感じです。

# ドット絵変換ソフト『NeSprite』
自分のクリエイター活動を効率化するために「[**NeSprite**](https://hiyameshi8bit.booth.pm/items/4317836)」というドット絵変換ソフトを開発していたりもします。
これの動画変換用Aeプラグインの「[**NeSprite for AfterEffects**](https://hiyameshi8bit.booth.pm/items/5899814)」は、アニメ制作会社やゲーム開発会社にも採用されている一番のヒット作だったりします。

**NeSprite**
![NeSprite](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/cb590f71-ebb6-4c12-9e20-6a4fffc95880.jpeg)

今回作ったのは画像のボタンにある「**Neural Palette**」というドット絵に特化した**減色AI**になります。

# 技術的に見る「ドット絵の色」
ドット絵の“色”は、通常のフルカラー画像と違い、あらかじめ定義された限られた色の中から選んで使う色のことを指します。これを「**パレット**」と呼びます。

例：

ファミコン：最大25色程度
ゲームボーイ：4階調（2bit）

技術的には「1ピクセル = インデックス番号」で、画像の中には「このピクセルはパレットのn番の色」という記述で表現されます。

ドット絵は色数が制限されたものですので、避けて通れないのが「**減色**」という訳です。

# 従来の減色手法
| 手法名            | 特徴・用途                       |
| -------------- | --------------------------- |
| **K-means**    | 高画質。AIでも使われる。クラスタリングで柔軟に減色。 |
| **Median Cut** | 定番。画像編集ソフト（Photoshop等）でも採用。 |
| **Octree**     | 高速＋そこそこ高品質。リアルタイム用途にも。      |
| **Popularity** | 超高速。シンプルな画像向け。色が偏るのが弱点。     |

ドット絵変換に良く用いられるのは「**K-means法**」になります。

いずれも、減色する画像ソースの元の色に依存しており、場合によって"平均化された薄い色"や"過剰な色"になりやすい傾向があります。

ちなみにNeSpriteではどれとも違う「あらかじめ使う色(パレット)」を定義しておき、近似色に置き換えるという手法を採用しています。

# 今回作ったAI(Neural Palette)での狙い
ドット絵制作において、「パレットを自分で作る or 選ぶ」という工程が毎回発生します。
これはある程度のセンスを要し、初心者にとってなかなか難しい工程になります。

「**この画像をドット絵にした時はこういうパレットになる**」という**減色のセンス**を学習することで、AIを使った次世代型減色手法をドット絵変換に取り入れるのが狙いです。

NeSpriteのパレットを使った変換とも相性がいいというのもありますね。

# 機械学習の流れ(Neural Palette)
今回の減色AI「Neural Palette」は、通常イラストの色分布（パレット）を入力として、
それに合うような15色のドット絵用パレットを出力するモデルです。

## 1.データセットの準備
データセットでは色の配列が記述された.txtファイルのペアを使用します。
(600ペア程)

**①trainA**
**palettes_ill/：イラスト画像の色を抽出したパレット（最大64色）**

画像をK-means法で64色に減色した際の「**色の配列**」をテキストファイルに記述したものを使用します。
なお、被写体部分の色のみを学習させるという意味で、透過色(背景)は緑(0,255,0)で塗りつぶし、計算からは除外されるように設定しました。
64色への減色程度では画像の色味が損なわれないことが、下の画像から分かると思います。

![data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/34e3b9bf-1191-4ddf-8b1c-53c860840600.png)

**②trainB**
**palettes_pix/：その画像を減色したドット絵の使用パレット（15色）**

ペアの画像がドット絵になった時のパレット(15色)をテキストファイルに記述したものを使用します。
なお、ペアになるtrainAの色分布が狭い時(色数が元々少ない)場合は**ドット絵になった時も色数が少なくなる**ということも考慮して学習してくれることを期待して、色数が少ない場合15色までの残りの色を緑(0,255,0)で埋めるようにしました。

![pixel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/e964bcb6-0ffe-4b96-bfdf-642a23e831eb.png)


pix2pix(画像➡画像)に倣い、それぞれファイルを分けてtxt2txt(配列➡配列)となるようにペアにしました。
なお、どちらの配列に対してもHSVソートを実行し、ある程度規則的な並びになるよう調整を行いました。


## 2.データの前処理
| 種類 | フォルダ            | 中身            | 処理後の形            |
| -- | --------------- | ------------- | ----------------- |
| 入力 | `palettes_ill/` | 最大64色のRGBテキスト | `[192]`ベクトル（64×3） |
| 出力 | `palettes_pix/` | 最大15色のRGBテキスト | `[45]`ベクトル（15×3）  |


色データを「数値ベクトル」として扱える形に変換し、AIにとって学習可能なデータセットにします。


## 3.モデル定義( PaletteMapper )
このAIモデルは、イラストのパレット（64色）を入力として、ドット絵用の15色パレットを出力するシンプルなニューラルネットワーク（MLP）です。

**モデルの構成**

| レイヤー | 内容                | 出力サイズ | 説明                 |
| ---- | ----------------- | ----- | ------------------ |
| 入力   | 64色 × RGB → ベクトル化 | 192   | パレットを1本のベクトルに変換    |
| 中間層① | 全結合 + ReLU        | 256   | 色の特徴を抽出            |
| 中間層② | 全結合 + ReLU        | 128   | 色の関係性を学習           |
| 出力   | 全結合 + Sigmoid     | 45    | 15色 × RGB（0〜1に正規化） |

出力は [0〜1] の値なので、RGBとして使うために最終的に ×255 して丸めます。
これで「イラストに合った減色パレット」が得られる仕組みです。

## 4.学習設定
学習に使用するパラメーターは以下の通りです。

| 項目      | 内容                      |
| ------- | ----------------------- |
| 損失関数    | MSE（Mean Squared Error） |
| 最適化手法   | Adam                    |
| 学習エポック数 | 1500                    |
| バッチサイズ  | 32                      |

## 5.学習ループとモデル保存

・各エポックごとに損失を記録

・損失が最小だった時のモデルを「ベストモデル」として保存

・最終的なモデルも別に保存

・学習の過程で得られた損失値を CSV形式でログ出力（可視化や分析用）

# 学習結果

![loss_curve.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/09401179-f412-4903-bb84-63a2bb32a2b6.png)

・学習は順調に進行
→ 序盤で急激に損失が減少し、その後安定

・発散や異常はなし
→ 損失が一貫して減少し続けている

・過学習の兆候なし
→ 後半も損失が低く安定している

・十分に収束している
→ 1000エポック以降はほぼ横ばい


# K-means減色とNeural Paletteを使った減色の比較

GPT4oで背景を透過したイラストを生成➡背景を緑(0,255,0)に塗りつぶした画像を作成

元画像１
![src02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/01e19a49-66a1-4204-af3a-393249766bd0.png)

K-means(16色) | Neural Palette(15色+緑)

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/7f3519f0-0367-45cd-9797-c1ec360ec4a5.png)

元画像2
![src03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/1c78524e-574a-4c69-bb8a-d8f8ec0ff3de.png)

K-means(16色) | Neural Palette(15色+緑)

![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/18bbc368-a940-4413-a157-54fe5bb85d05.png)

元画像3
![src01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/7e5892f2-191d-49cb-810b-0c8f918b8439.png)

K-means(16色) | Neural Palette(15色+緑)

![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/a99e4cd3-93ed-41d2-8c74-46e9d3826b12.png)

輪郭線の黒がちゃんと保持されていることや、被写体の色にちゃんとパレットの色が寄っていることから、ランダムにRGB値が生成されているだけということはなさそうなのが確認できます。

色の忠実度で言えば当然K-means法に軍配が上がりますが、このモデルは『**ドット絵にしたときの最適な色を推定する**』のが目的なので、これは望んだ結果でもあります。

比べると、Neural Paletteの方がメリハリのあるパレットになっていることが分かります。
これはK-means法では得られない効果です。

# 今後の課題とまとめ
現状のNeSpriteの仕組みでは、最適なパレットを生成出来ても**近似色への置換**という仕組みを採用していることで、配色が適切に実行できません。
ですので、配色用のAIも作れたら良いなぁと思っています。

また、このモデルでは15色への減色しかできません。
この色数をユーザーが指定してパレットを生成出来たらいいなぁと思っています。

AIを活用した次世代型の減色手法ということで、結構面白い試みだったと思います。

推論が高速なので、気に入ったパレットが生成されるまでポチポチやる使い方になりそうです。
インデックス0はアルファ用に緑(0,255,0)を後から追加しています。


![nesprite.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4108060/5ea1da21-ec3b-43cb-a539-9f9646e80b78.gif)