Algorithm & UI/UX
多重レイヤー合成による横方向ブラーの実装
同じ写真を横方向へ少しずつずらして重ね、その上へAIで抜いた人物を戻すことで流し撮り風にします。実際の移動速度や露光時間を求める物理シミュレーションではありません。
このツールの入力から出力まで
現行コードの確認日:
- 入力
- 人物写真を選び、背景を横へ流す効果の強さを指定します。
- 処理
- 人物マスクを残し、背景画像を水平方向へずらして重ねることで流れを表現します。
- 出力
- 入力は長辺1500px以下、出力はJPEG品質0.95です。
まず確認する点:人物の撮影速度を測る処理ではありません。人物が残る範囲と、背景の文字が伸びる方向を別々に確認します。
仕上がりの例
人物を残し、背景にモーションブラーを加えた例です。
サンプルにはAIで生成した架空の人物画像を使用しています。背景の猫はユーザーの愛猫の写真です。
掲載画像はサイトにある既存の公開作例です。保存済みの資料ではAIで作成した人物画像と運営者の愛猫の写真を使用しています。今回の確認日が、この作例を実行した日を表すわけではありません。
保存済み検証記録の条件を見る
資料の種類は「ツールの出力画像を保存した記録」です。公開作例と保存済み記録で素材や色などが異なる場合があるため、下の数値は保存済み検証記録の条件として読んでください。
- 記録の入力画像
- 1117×1500px (JPEG)
- 記録の出力画像
- 1117×1500px (JPEG)
| 画面の項目 | 記録の値 | 現行コードの初期値・範囲 |
|---|---|---|
| 記録の効果の強さ | 30 | — |
資料ファイルの作成日は実行日として扱っていません。個別端末の処理速度や、ほかの写真に対する検出・加工の精度を測定した資料でもありません。
実装の確認箇所:src/pages/ai/ima-seg/motion-blur/index.astro。以下のコード抜粋と出力条件は、この本番実装に基づいて説明します。
仕組みを読み進めるときは、MediaPipeの画像セグメンテーション(人物マスクの寸法、カテゴリと資源解放)、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)が参考になります。
1. 反復合成によるモーションブラー・アルゴリズム
通常の「ぼかし(Blur)」フィルタは全方向に平均化されますが、流し撮りの質感を出すには「水平方向のみ」に光を流す必要があります。
本ツールでは、背景レイヤーに対して画像を水平方向に少しずつずらしながら複数回重ね描きする「多重レイヤー合成」を採用しています。まず元画像を不透明な下地として描き、各反復の不透明度(globalAlpha)を1 / (i + 1)にして重ねます。不透明な画像が全反復で重なる内部は移動サンプルの平均になり、画像外へずれた端は元画像の下地を残します。
横方向へ12回重ねる部分です。画像幅800pxを基準に移動量を拡大縮小し、strengthには画面の0〜100の値を渡します。
function horizontalBlur(ctx, image, w, h, strength) {
ctx.clearRect(0, 0, w, h);
ctx.save();
const totalShift = strength * w / 800;
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1;
ctx.drawImage(image, 0, 0, w, h);
for (let i = 0; i < 12; i++) {
ctx.globalAlpha = 1 / (i + 1);
ctx.drawImage(image, (i - 6) * totalShift / 12, 0, w, h);
}
ctx.restore();
} 2. Edge AIを活用したピクセル単位の領域分離
背景だけにブラーをかけ、被写体をクッキリ残すためには人物に当たる範囲のマスクが必要です。本ツールでは Googleの MediaPipe を採用し、WebAssembly (Wasm) 上で推論を実行しています。
サーバーに画像を送らず、ユーザーの手元のデバイス(Edge)でピクセルごとに「人間か背景か」のマスクを生成します。このマスク情報を Canvas の source-in 合成モードと組み合わせることで、複雑な境界線を持つ人物も高速かつ非破壊的に切り出すことが可能になっています。
3. requestAnimationFrame による描画予約の整理
スライダーを動かした際にリアルタイムでブラーを更新すると、計算負荷によって画面がフリーズするリスクがあります。
これを防ぐため、描画要求をキューイングし、ブラウザの描画更新タイミングに合わせて実行する requestAnimationFrame (RAF) 制御を導入しました。連続したイベントが発生しても「次のフレーム」で一度だけ描画を行うため、連続入力による描画の重複を抑えます。描画自体はメインスレッドで行い、端末によって待ち時間が発生することがあります。
出力の仕様と確認する点
マスクの大きさは返却されたwidth・heightを参照します。readMaskDataで処理Canvasの寸法へ合わせ、カテゴリは最近傍補間で所有配列へコピーします。コピー後にImageSegmenterResult.close()で元のマスク資源を解放し、編集と描画にはコピーを使います。
効果は水平のみです。車の移動方向やカメラの動きを認識して角度を変える機能はありません。強さを上げると横へずらす距離が増えますが、画像の外側の内容を補完しないため、端の濃さが変わる場合があります。
現行のMediaPipe Tasks Vision 0.10.34とselfie_segmenter.tfliteのカテゴリマスクは、人物が0、背景が255です。人物のカテゴリ0を残し、背景のカテゴリ255へブラーを掛けます。スポーツ用品や乗り物などが人物と同じ領域として残る保証はなく、背景と人物の境界に元画像がにじむ場合があります。
入力は長辺1500px以下、出力はJPEG品質0.95です。強さ0では背景の位置をずらさず元画像を描きます。入力縮小とJPEG再圧縮による差は残ります。
