猫の手ツール

Algorithm & UI/UX

AIのマスクへ紙の切り口を加える仕組み

AIで推定した人物マスクを揺らし、白いフチと紙のノイズを重ねて切り貼り風にします。元写真の切り抜き範囲に加えて、輪郭の変形、フチ、紙質が別々の段階で合成される仕組みです。

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

現行コードの確認日:

入力
人物写真を選び、切取りの縁や背景を調整します。
処理
人物マスクから輪郭を作り、縁の図形や紙の表現を重ねてコラージュ風に合成します。
出力
長辺1500px以下へ縮小し、透明背景はPNG、紙色の背景はJPEG品質0.95で保存します。

まず確認する点:縁の見た目と人物の切取り精度は別です。足先や髪がマスクから欠けた場合、縁を変えても元の領域は戻りません。

仕上がりの例

人物を切り抜き、紙のコラージュ風にした例です。

加工前・加工後
左は青いシャツを着た人物の写真。右は人物の輪郭に紙風の縁を付け、背景を透明にした画像。

サンプルにはAIで生成した架空の人物画像を使用しています。市松模様は透明部分を示す表示用の背景です。ツールで保存する透過PNGには含まれません。

加工前・加工後

左は青いシャツを着た人物の写真。右は人物の輪郭に紙風の縁を付け、背景を透明にした画像。

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

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

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

記録の入力画像
1788×2400px (JPEG)
記録の出力画像
1117×1500px (PNG)
保存済み記録の主な操作・出力条件
画面の項目記録の値現行コードの初期値・範囲
記録で選択されていた項目透過素材(PNG)—
白フチの太さ15初期値 15 / 範囲 0〜50
アナログな紙の質感40初期値 40 / 範囲 0〜100

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

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

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

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

1. SVG Displacement Map による「ハサミ跡」の動的生成

AI(MediaPipe)が生成した滑らかなマスク境界を、ランダムなギザギザに変換するために、HTML5のSVGフィルタ機能をCanvas APIと融合させています。

具体的には、feTurbulenceで生成したフラクタルノイズを feDisplacementMap の参照画像として使用し、ピクセルの座標をランダムに「歪ませて」います。これをCanvasの filter プロパティに適用することで、ノイズに沿ってマスクの境界をずらします。CanvasからSVGフィルタを参照できるかはブラウザに依存し、対応しない環境ではギザギザが反映されないことがあります。

白いシルエットを16方向へ配置する部分です。silhouetteはマスクで白く塗り抜いたCanvas、widthはフチ幅の設定値です。

function drawBorder(ctx, silhouette, width) {
  for (let i = 0; i < 16; i++) {
    const angle = Math.PI * 2 * i / 16;
    ctx.drawImage(silhouette,
      Math.cos(angle) * width, Math.sin(angle) * width);
  }
}

2. 16方向円形オフセットによる「均一なフチ取り」

歪んだ境界線に対して「白いフチ」を付ける際、通常の strokeText や lineWidth では、複雑なノイズ境界において線の太さが不安定になる課題がありました。

そこで、本ツールでは計算幾何学的なアプローチを採用しています。歪ませた後のシルエット画像を、円状に16分割した各方向へわずかにずらして重ね書きすることで、輪郭の外側へ白い領域を広げています。16方向の近似なので、細い部分や強いギザギザで線幅が厳密に均一になるわけではありません。これにより、アナログな質感を保ちつつ、視覚的な一貫性を担保しています。

3. Procedural Noise による紙質テクスチャの合成

切り抜いた画像に「印刷された紙」のような手触り感を与えるため、実行時に256x256ピクセルのノイズテクスチャをJavaScriptで動的に生成し、CanvasPattern として利用しています。

このパターンを source-atop で切り抜き領域だけに重ね、さらに温かみのあるセピア系のカラーレイヤーを特定のアルファ値でオーバーレイすることで、単なる写真の切り抜きを「古い雑誌から切り出した素材」へと変貌させています。

出力の仕様と確認する点

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

現行のMediaPipe Tasks Vision 0.10.34とselfie_segmenter.tfliteのカテゴリマスクは、人物が0、背景が255です。人物のカテゴリ0から白フチを作ります。人物向けモデルのため、動物や商品を確実に認識するモデルではありません。

フチ幅を上げるほど人物の周囲へ白を広げます。紙質設定はノイズ模様とセピア色の透明度を変えるため、強くすると元写真の色も変わります。Canvasの外には余白を増やさないので、端のフチは欠ける場合があります。

長辺1500px以下へ縮小し、透明背景はPNG、紙色の背景はJPEG品質0.95で保存します。画像内に紙の繊維の立体形状を作る処理ではなく、模様を重ねる表現です。