猫の手ツール

Algorithm & UI/UX

ブラウザ完結型AI背景合成の技術解剖

本ツールは、GoogleのMediaPipe Image SegmenterをWebAssembly (Wasm) 上で動作させることで、ユーザーの画像を処理サーバーへ送らずに人物の領域を推定しています。さらに、Canvas 2D APIの特性を活かした独自の「スパイラル・スムージング」アルゴリズムにより、マスクの境界を広げて写真と背景を重ねています。

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

現行コードの確認日:

入力
人物写真と置き換える背景画像をそれぞれ選び、人物の位置・大きさ・角度を調整します。
処理
人物のマスクを一度作って人物レイヤーを保持し、背景と合成するたびに移動・拡大・回転を反映します。
出力
人物画像は長辺1500px以下へ縮小し、完成品をJPEG品質0.95で保存します。

まず確認する点:背景の取得やモデル初期化には通信が必要です。人物境界に元の背景が残る箇所と、新しい背景との明るさの差を見ます。

仕上がりの例

人物の背景を、愛猫2匹の写真に差し替えた例です。

加工前・加工後
左は人物写真と背景に使う愛猫2匹の写真。右は猫の写真を背景にして人物を重ねた画像。

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

加工前・加工後

左は人物写真と背景に使う愛猫2匹の写真。右は猫の写真を背景にして人物を重ねた画像。

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

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

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

記録の入力画像
1788×2400px (JPEG)、800×667px (JPEG)
記録の出力画像
1117×1500px (JPEG)
保存済み記録の主な操作・出力条件
画面の項目記録の値現行コードの初期値・範囲
記録の境界ぼかし2—
記録の人物サイズ100%—
記録の人物角度0°—

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

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

仕組みを読み進めるときは、MediaPipeの画像セグメンテーション(人物マスクの寸法、カテゴリと資源解放)、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)が参考になります。

1. Wasmによる「完全ローカル」AI推論

本実装では、モデルを取得してブラウザ上で人物のマスクを推定します。この構成には次の特徴があります。

  • 画像の処理場所: 選んだ写真の切取りと合成はブラウザ内で行います。ページ・SDK・モデルなどの取得には通信があり、端末やブラウザ自体の安全性を保証する仕組みではありません。
  • 低コスト運用: 画像ごとの推論をユーザーの端末で実行するため、画像処理用サーバーを経由しません。
  • 即時性: 通信待ち(アップロード・ダウンロード)がないため、一度Wasmモジュールが読み込まれれば端末内で推論と合成を進められます。

境界を滑らかにするため、マスクを16か所へずらす計算です。smoothは0〜10の設定値です。描画時は同じマスクを透明度0.25で重ねます。

function maskOffsets(smooth) {
  return Array.from({length: 16}, (_, i) => {
    const angle = i * 2.39996;
    const radius = Math.sqrt(i / 16) * smooth * 1.5;
    return [Math.cos(angle) * radius, Math.sin(angle) * radius];
  });
}

2. Canvasスパイラル・ブラーによる境界線の最適化

AIが生成するセグメンテーションマスクは、解像度によっては境界がカクついたり(ジャギー)、髪の毛の周辺が不自然になったりすることがあります。 本ツールでは、マスク画像をキャンバス上に描画する際、独自の計算式に基づいた「多重オフセット描画」を行うことで、軽量かつ高品質なスムージング処理を実現しています。

通常のGaussian Blurを適用すると処理が重くなりますが、この手法では黄金角に近い定数を用いた回転オフセットでマスクを重ね書きすることで、16回の重ね描きでエッジの透明度を分散させています。ぼかし方はガウスぼかしと同じではなく、効果を強くすると人物の縁まで薄くなる場合があります。

3. Multi-Layer Composition による自由な位置調整

「背景透過して終わり」ではなく、その場で合成まで完結させるため、3層の仮想キャンバス構造を維持しています。「背景レイヤー」「マスク適用済みの人物レイヤー」「UI/操作レイヤー」をリアルタイムに結合(Composite)することで、拡大・縮小・回転を行っても画質劣化を最小限に抑えています。

Canvasの globalCompositeOperation = 'source-in' を活用することで、AIが生成した抜き型の中に元の画像を流し込む処理をCanvasの合成操作で行っています。GPUの利用や速度はブラウザ環境に依存します。

出力の仕様と確認する点

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

人物用のモデルからマスクを作り、別に読み込んだ背景写真へ合成します。背景はCanvasを埋めるよう中央で切り取り、人物は位置・10〜300%の拡大率・-180〜180度の回転を調整できます。

現行のMediaPipe Tasks Vision 0.10.34とselfie_segmenter.tfliteのカテゴリマスクは、人物が0、背景が255です。人物のカテゴリ0を残し、背景のカテゴリ255を差し替えます。髪の縁や半透明の物を正しく分離できるとは限りません。スムージングは誤認識の領域を直す機能ではありません。

人物画像は長辺1500px以下へ縮小し、完成品をJPEG品質0.95で保存します。拡大しても細部は増えず、回転してCanvasの外に出た部分は書き出されません。処理用の人物レイヤーは描画時に新しく作るため、全Canvasを固定で再利用しているわけではありません。