結論
最終的に、1枚のSDキャラクターを面330パーツ+線800ストローク、合計1,130パーツへ分解しました。公開ページが読み込むのは、座標・色・グラデーション・部位ラベルを収めたJavaScriptデータです。描画時に元PNGは読みません。
この方式の価値は「画像をJavaScriptに置き換えた」ことより、目、口、髪などを別々に動かせる単位へ分けたことにあります。現在は、まばたき、呼吸に連動する口、二つの正弦波で揺れるアホ毛を実装しています。
元画像を貼るのではなく、輪郭・色・線幅をデータへ変換し、ブラウザで組み直す。
動かせるイラストを作るための解析パイプラインです。
最終的に、1枚のSDキャラクターを面330パーツ+線800ストローク、合計1,130パーツへ分解しました。公開ページが読み込むのは、座標・色・グラデーション・部位ラベルを収めたJavaScriptデータです。描画時に元PNGは読みません。
この方式の価値は「画像をJavaScriptに置き換えた」ことより、目、口、髪などを別々に動かせる単位へ分けたことにあります。現在は、まばたき、呼吸に連動する口、二つの正弦波で揺れるアホ毛を実装しています。
PNG全体を上下に動かすだけなら、画像を1枚表示すれば十分です。しかし、まばたきだけを作りたい場合は、少なくとも白目、瞳、まつ毛、前髪を区別する必要があります。髪を揺らすなら、根元と毛先も分けたくなります。
そこで、開発時にPythonで画像を解析し、公開時は次の役割へ分離しました。
tools/analyze.py:背景除去、減色、領域分割、輪郭生成tools/strokes.py:線画を中心線と線幅へ変換shapes.js:SVGパス、色、深度、部位ラベルの生成結果main.js:SVGを組み立て、部位ごとに動かす実行コード解析には元画像を使いますが、生成後のページは shapes.js からSVGを構築します。開発用の入力と、配布用の描画データを切り離した形です。
最初は「白に近い画素を透明にする」方法を試しました。しかし、髪やパーカーにも白があり、一緒に消えてしまいます。そこで画像外周から探索し、外側と連結している白だけを背景と判定しました。
メディアンで細かな階調をならした後、Lab色空間でk-meansを行います。面積の小さい紫の瞳が薄紫の髪へ吸収されやすかったため、明るさより色度 a*b* を強めてクラスタリングしました。
同じ色でも、右目と左目は別パーツでなければ動かせません。色クラスタを連結成分へ分け、面積だけでなく彩度と暗さを含む重要度で採用します。これにより、小さくても表情を決めるまつ毛や瞳を残せます。
marching squaresで輪郭を取り、RDPで点を減らし、Catmull-Romから3次ベジェへ変換します。点を減らしすぎると顔が角張り、残しすぎるとパスが重くなるため、部位と残差を見ながら調整しました。
小領域を単に捨てると、パーツの継ぎ目から背景が見えます。未採用画素を同色クラスタの最近傍パーツへ割り当て、前景に穴が残らないようにしました。
単色だけで塗ると、髪や服の緩やかな明暗が段差になります。パーツ内の色を位置の線形関数として最小二乗近似し、SVGの linearGradient へ変換します。
いったんSVGで塗り直し、原画とのLab距離を計算します。差が大きい領域だけを細かい設定で再解析することで、顔や口の座標を手で決め打ちせず、「再現できていない場所」を画像自身から探しました。
残差を調べると、面はかなり近いのに、輪郭線や服のロゴが消えていました。black-hat処理で細い暗線を抽出し、塗りより前面へ重ねます。ただし、この段階でも線を面として扱うと別の問題が起きました。
細い線のマスクから外周を取ると、輪郭は線の両側を往復します。小さな凹凸が棘のように増え、平滑化を強くすると線そのものが消えます。実際、ぼかしを sigma 1.1 まで上げた時点で、顎の輪郭と服の「J」が消えました。
そこで tools/strokes.py では、線を次のように変換しています。
分岐点を最初に通った線の所有物にすると、交差する別の髪線がそこで切れます。そのため分岐点は占有せず、線ごとに通過できるようにしました。これで「平滑化の強さ」と「細い線を残すこと」を別々に調整できます。
main.js は、同じラベルのSVGパーツを <g> へまとめ、グループ単位で変形します。
色だけで部位を推定すると、白目が髪へ分類されるなどの誤りが残ります。目については瞳とまつ毛の外形を求め、その内側にある明るいパーツも取り込んでいます。グループ化で重なり順が変わらないよう、解析時の depth で並べ直します。
髪は684パーツありますが、「髪色に近い白目」や「肌に近い前髪」まで完全自動では分けられていません。ここは現在も、アニメーションの自由度を上げるために分類方法を比較している部分です。
解析ツールは各段階で平均Lab距離を出しますが、この値は面積の大きい「塗り」の再現度を強く反映します。線画は1pxずれるだけで両側に誤差が生まれるため、線を重ねると数値が悪化しても、人の目には原画へ近づく場合があります。
現在は、面の残差、重要部位の残存、輪郭の視認性、動かしたときの破綻を別々に確認しています。一つのスコアだけで完成度を決めないことが、この実験で最も大きかった学びです。