猫の手ツール

Algorithm & UI/UX

ブラウザ完結型AIによる髪色認識と
ピクセルブレンドの実装

本ツールは、Googleの機械学習モデルをWebAssemblyでブラウザ上で動作させ、さらにCanvas APIを用いた高度なピクセル操作を組み合わせることで、インストール不要かつプライベートなヘアカラー体験を実現しています。

このツールの入力から出力まで

現行コードの確認日:

入力
髪が写った人物写真と色・濃さを選びます。
処理
URL名にint-segがありますが、本番はImageSegmenterと髪用モデルです。髪のマスクの画素へ色を重ね、元写真の明暗を残します。
出力
長辺1500px以下の処理画像からJPEG品質0.95で保存します。

まず確認する点:人物全体のマスクと髪のマスクを混同しないことが要点です。細い毛、帽子、背景と似た髪色で境界がずれることがあります。

仕上がりの例

人物の髪を、暗い色から赤紫色に変えた例です。

加工前・加工後
左は暗い色の髪の人物の顔写真。右は髪の色を赤紫色に変えた顔写真。

サンプルにはAIで生成した架空の人物画像を使用しています。

加工前・加工後

左は暗い色の髪の人物の顔写真。右は髪の色を赤紫色に変えた顔写真。

掲載画像はサイトにある既存の公開作例です。保存済みの資料ではAIで作成した架空の人物・手の画像を使用しています。今回の確認日が、この作例を実行した日を表すわけではありません。

保存済み検証記録の条件を見る

資料の種類は「ツールの出力画像を保存した記録」です。公開作例と保存済み記録で素材や色などが異なる場合があるため、下の数値は保存済み検証記録の条件として読んでください。

記録の入力画像
1788×2400px (JPEG)
記録の出力画像
1117×1500px (JPEG)
保存済み記録の主な操作・出力条件
画面の項目記録の値現行コードの初期値・範囲
自由に作る#ff3366初期値 #ff3366
染色の濃さ70初期値 70 / 範囲 0〜100
地毛の濃さ(透過)100初期値 100 / 範囲 0〜100

記録では「この画像で保存へ進む」から完成表示へ進んでいます。初期値と記録値を比べてから、効果を1項目ずつ変えると作用を確認しやすくなります。

資料ファイルの作成日は実行日として扱っていません。個別端末の処理速度や、ほかの写真に対する検出・加工の精度を測定した資料でもありません。

実装の確認箇所:src/pages/ai/int-seg/hair-dye/index.astro。以下のコード抜粋と出力条件は、この本番実装に基づいて説明します。

仕組みを読み進めるときは、髪と服のセグメンテーション(目的に合うモデルとカテゴリの選び方)、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)が参考になります。

1. WebAssemblyによる「サーバーレスAI」の実現

従来の画像解析ツールは、画像を一度サーバーにアップロードして処理を待つ必要がありました。本ツールでは「Image Segmenter」モデルをWebAssembly (WASM) 技術を用いてブラウザ内で直接実行しています。

このアプローチの最大のメリットは、ユーザーの画像が一切ネットワークを介さないため、写真を処理サーバーへ送る工程を省ける点にあります。モデルの取得や初期化には待ち時間があり、推論速度は端末に依存します。

髪の確信度を使って1画素を着色する例です。densityとstrengthは0〜100、confidenceは0〜1、rgbとtargetはRGB配列です。実装ではコピーしたマスクの透明度を255で割ってconfidenceに相当する値を使います。

function tintHair(rgb, target, density, strength, confidence) {
  const gray = rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114;
  const bleach = (1 - density / 100) * 0.85;
  const light = gray + (255 - gray) * bleach;
  const mix = strength / 100 * confidence;
  return rgb.map((v, i) => v * (1 - mix) + light * target[i] / 255 * mix);
}

2. 「仮想ブリーチ」アルゴリズムによる発色補正

単純に髪の毛領域に色を乗せるだけでは、日本人に多い黒髪の場合、色が沈んでしまい綺麗に発色しません。そこで本ツールでは、着色前にピクセル単位で「仮想的なブリーチ(脱色)」処理を施しています。

具体的には、髪の毛領域の各ピクセルの輝度を算出し、独自の係数を用いてベースの明るさを持ち上げてからターゲットカラーを乗算しています。これにより、黒髪からでも透明感のあるパステルカラーやハイトーンカラーへの明るめの色を重ねやすくしています。実際の脱色や染色の結果を予測する計算ではありません。

3. requestAnimationFrameによる滑らかなプレビュー

スライダーを動かした際に、都度Canvasの重いピクセル演算を行うと、描画がカクつく原因になります。本ツールでは、スライダーの input イベント内で直接描画を呼ぶのではなく、requestAnimationFrame (RAF) を介してブラウザの更新タイミングに同期させています。

これにより、連続的なパラメータ変更時も描画の競合を抑え、ユーザーの操作に対して吸い付くようなUI体験を提供しています。

出力の仕様と確認する点

マスクの大きさは返却されたwidth・heightを参照します。readMaskDataで処理Canvasの寸法へ合わせ、カテゴリは最近傍、確信度は双線形補間で所有配列へコピーします。コピー後にImageSegmenterResult.close()で元のマスク資源を解放し、編集と描画にはコピーを使います。

使用するHair Segmenterのラベル順は背景0・髪1です。髪の確信度マスク(インデックス1)を選び、「染まり具合」と掛け合わせて着色の混合率にします。確信度が中間の境界は薄く着色します。髪と背景の境界や、帽子・影・細い毛で誤分類が起きる場合があります。手動で範囲を塗り直すブラシはありません。

「地毛の濃さ」を下げるほど、元の明るさを白へ近づけます。「染まり具合」は元色と着色後の色の混合率の上限で、実際の混合率には髪の確信度も掛けます。地毛の濃さを0にしても補正係数は0.85までで、肌や髪の材質を解析しているわけではありません。

長辺1500px以下の処理画像からJPEG品質0.95で保存します。照明、元の髪色、画面の色表示によって結果は変わるため、色の雰囲気を比較する画像加工として使用します。