Face Mosaic ツールを使う

写真をサーバに送らずに、顔にモザイクをかける仕組み

AI で写真の顔を見つける、と聞くと、写真をどこかのサーバに送って処理してもらうものだと思うかもしれません。

Face Mosaic は違います。顔を見つける AI も、モザイクをかける処理も、すべてあなたのブラウザの中で動きます。写真は一度もサーバに送られません。サーバに置いてあるのは、HTML や JavaScript、AI のモデルといった静的なファイルだけです。

このページでは、それがどうやって動いているのかを説明します。

自分で確かめられます

「送っていません」と書くだけなら誰にでもできます。Face Mosaic では、それをあなた自身で確かめられます。

  1. パソコンの Chrome で Face Mosaic を開く
  2. 開発者ツール(Mac は ⌥⌘I、Windows は F12)を開き、「ネットワーク」タブを選ぶ
  3. 写真を選ぶ

写真を選んだあとに出てくる通信は、AI のモデル(.onnx)と、それを動かすプログラム(.wasm・.mjs)を取りに行く GET だけです。どれも初回だけで、写真を送る POST はどこにもありません。blob: で始まる行は、ブラウザが自分のメモリから写真を読み出した記録で、外への通信ではありません。モデルを読み込んだあとは、機内モードにしても次の写真を処理できます。

サーバに送る方式との違い

よくある方式

  1. 写真をサーバにアップロードする
  2. サーバの AI が顔を探す
  3. 結果を受け取る

写真がネットワークを通り、相手のサーバに届きます。サーバの費用もかかります。

Face Mosaic

  1. AI のモデルをブラウザに読み込む
  2. ブラウザの中で顔を探す
  3. ブラウザの中で隠して保存する

写真は端末から出ません。届くのはモデルのほうです。

写真をサーバに送る代わりに、AI のほうを写真のあるところへ届ける。これが Face Mosaic の考え方です。

ブラウザで AI を動かす

使っている技術

役割 使っているもの
顔を見つける AI YuNet(OpenCV Zoo、MIT ライセンス、230KB の ONNX ファイル)
AI を動かす ONNX Runtime Web(Microsoft、MIT ライセンス)の WebAssembly 版
顔を隠す・保存する Canvas API
配信 静的なファイルだけ。サーバのプログラムはありません

YuNet は、画像処理のライブラリ OpenCV のプロジェクトが公開している、小さく速い顔検出のモデルです。ONNX という共通の形式で配られているので、ONNX Runtime Web を使えばブラウザでそのまま動かせます。ONNX Runtime Web の本体は WebAssembly で書かれていて、ブラウザの中でも速く計算できます。

モデルを読み込んで動かす部分は、これだけです。

import * as ort from "onnxruntime-web/wasm";

const session = await ort.InferenceSession.create("face_detection_yunet_2023mar.onnx", {
  executionProviders: ["wasm"],
});

// input は 1×3×640×640 の画像(後述)
const outputs = await session.run({ [session.inputNames[0]]: input });

速さと精度

顔検出の研究でよく使われる WIDER FACE の検証用データから、集合写真・家族写真・会議など 10 ジャンル 60 枚を選び、写っている顔のうちいくつを隠せたかを比べました。アプリと同じく枠を 20% 広げ、顔の半分以上を覆えたら「隠せた」としています。対象は、写真の長辺の 1.5% 以上の大きさで写っている顔です。

方式 隠せた顔 1 枚あたりの時間
YuNet をブラウザで動かす(いまの方式) 82% 約 25 ミリ秒
縮小した写真をサーバに送り、OpenAI の画像対応モデルで調べる 75% 約 11 秒
MediaPipe の顔検出(BlazeFace short range)をブラウザで動かす 18% 数ミリ秒

時間は、手元の Mac の Chrome で測ったものです。初めて使うときだけ、モデルとプログラムの読み込みに 1 秒ほど余分にかかります。同じページで 2 枚目からは、読み込み済みのモデルをそのまま使います。

ブラウザで動く小さなモデルが、写真をサーバに送って大きな AI に調べてもらう方式より、多くの顔を、400 倍以上速く見つけました。

前処理:写真をモデルの入力にする

YuNet は 640×640 ピクセルの画像を受け取ります。写真の長いほうの辺が 640 ピクセルに収まるように縮め、左上に寄せて置き、余ったところは黒で埋めます。縦横の比率は変えないので、細長い写真でも顔がつぶれません。

色は、OpenCV と同じ青・緑・赤の順に並べ、0〜255 の値のまま渡します。

const scale = Math.min(1, 640 / Math.max(width, height));
ctx.fillRect(0, 0, 640, 640);                       // 余白は黒
ctx.drawImage(image, 0, 0, width * scale, height * scale);

const rgba = ctx.getImageData(0, 0, 640, 640).data;
const plane = 640 * 640;
const data = new Float32Array(3 * plane);
for (let i = 0; i < plane; i++) {
  data[i] = rgba[i * 4 + 2];             // B
  data[plane + i] = rgba[i * 4 + 1];     // G
  data[2 * plane + i] = rgba[i * 4];     // R
}
const input = new ort.Tensor("float32", data, [1, 3, 640, 640]);

後処理:出力を顔の枠にする

YuNet は、画像を 8・16・32 ピクセルの 3 とおりのます目に分け、ます目ごとに「顔らしさ」と「枠の位置」を返します。ます目が細かいほど小さな顔を、粗いほど大きな顔を拾います。これを、OpenCV の FaceDetectorYN と同じ式で枠に直します。

const score = Math.sqrt(clamp01(cls[i]) * clamp01(obj[i]));
if (score < 0.5) continue;                    // 顔らしさが 50% 未満は捨てる

const cx = (col + bbox[i * 4]) * stride;      // 枠の中心
const cy = (row + bbox[i * 4 + 1]) * stride;
const w = Math.exp(bbox[i * 4 + 2]) * stride; // 枠の幅と高さ
const h = Math.exp(bbox[i * 4 + 3]) * stride;

同じ顔に枠がいくつも重なるので、顔らしさの高い順に残し、残した枠との重なり(IoU)が 0.3 を超える枠は捨てます(NMS)。最後に、縮めた分を戻して元の写真の座標にします。

Canvas で顔を隠す

元の写真をそのまま持っておき、設定を変えるたびに Canvas に描き直します。顔を探し直すことはしないので、隠し方を変えるとすぐに切り替わり、何度変えても画質は落ちません。

見つけた枠は、上下左右に少し広げてから隠します(「範囲」の設定。最初は 20%)。枠が少しずれても、顔の端が見えないようにするためです。

隠し方 やっていること
モザイク 顔の部分を数マスまで縮め、なめらかにせずに引き伸ばします
ぼかし 半分ずつ何度も縮めて数ピクセルにし、なめらかに引き伸ばします。一度に縮めると画素が飛んで、顔の細部が残ることがあるためです
絵文字 顔を強くぼかしてから絵文字を重ねます。星やハートのすき間から顔が見えないようにするためです

モザイクは、Canvas の機能だけで作れます。

// 長いほうの辺が cells マスになるまで縮める
tiny.getContext("2d").drawImage(canvas, x, y, w, h, 0, 0, sw, sh);

// なめらかにせずに引き伸ばすと、ブロック状になる
patch.getContext("2d").imageSmoothingEnabled = false;
patch.getContext("2d").drawImage(tiny, 0, 0, sw, sh, 0, 0, w, h);

手動モードの枠や角の丸は、写真の上に重ねたもう 1 枚の透明な Canvas に描いています。保存する画像には入りません。

「画像を保存」は、Canvas を toDataURL("image/png") で PNG にして保存します。画像を新しく描き直しているので、元の写真に入っていた撮影場所(GPS)などの情報は、保存した画像には残りません。

サーバがいらない、ということ

処理がすべてブラウザの中で終わるので、Face Mosaic には、写真を受け取るサーバのプログラムがありません。配っているのは静的なファイルだけです。

ファイル 大きさ
HTML・JavaScript 合わせて 200KB 以下
YuNet のモデル 230KB
ONNX Runtime Web の WebAssembly 約 11MB(通信は圧縮されて 3MB ほど)

これには、写真が安全なこと以外にもよいことがあります。

できないこと

YuNet は多くの顔を見つけますが、すべてではありません。特に、次のような顔は見落とすことがあります。

SNS などに投稿する前に、隠れていない顔がないか必ず確かめてください。見落としがあれば、手動モードで枠を足して隠せます。

ライセンス

全文は ライセンス文 にあります。

写真を選ぶだけで、ブラウザの中で顔を見つけて隠します。

Face Mosaic を使ってみる