0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

200KBのAIモデルで顔自動モザイク — face-api.js + Canvasでブラウザ完結

0
Posted at

集合写真をSNSに上げる前、通行人や同意のない他人の顔を一括でモザイクにしたい。これを 画像をクラウドに送らず、ブラウザ内のAIだけで完結 させようとすると、ライブラリ選定とモデル配布で工夫が要る。

ぱんだツールズに「顔自動モザイク」を作ったので、@vladmandic/face-api の TinyFaceDetector(約200KB)でブラウザ完結の顔検出を回し、Canvas でモザイク・ぼかし・黒塗りを適用するまでの実装と、ハマったところをまとめる。

なぜ「ブラウザ完結」にこだわるか

顔検出系のクラウドAPI(Google Cloud Vision、AWS Rekognition等)は精度が高い。一方で 画像をアップロードする時点で「サードパーティに人の顔写真を送っている」 ことになる。プライバシー保護目的のツールが、そのために他社のサーバーに画像を送らせる設計はそもそも本末転倒。

ブラウザ内 AI なら以下が成立する:

  • 画像が端末から外に出ない
  • モデルファイルすら自サイトから配信すれば、Google/AWS への問い合わせも発生しない
  • ユーザーが「サーバーにアップロードしてない」をネットワークタブで自分で確認できる

精度はクラウド型に劣る。ただ「絶対送らない保証」が立つなら、その差を埋めて余りある価値がある。

ライブラリ選定:vladmandic 版 face-api.js

face-api.js 系で選択肢が2つある:

パッケージ 状態
face-api.js(justadudewhohacks 版) 元祖。メンテ停止状態(最終更新2020年)
@vladmandic/face-api(vladmandic 版) fork して活発にメンテ。TF.js v4対応済み、TypeScript 型完備

face-api.js は今でもサンプルが多いが、TensorFlow.js 新バージョンとの相性問題が出るので、新規で組むなら @vladmandic/face-api 一択

モデルファイルは public/ に置いて self-host する

face-api.js は内部で TensorFlow.js を使い、訓練済みモデルファイル(重みファイル + manifest)をネットワーク経由でロードする。デフォルト挙動だと どこかのCDNから取りにいくので、これを自サイトから配信するように差し替える。

public/
  models/
    face-api/
      tiny_face_detector_model-weights_manifest.json
      tiny_face_detector_model.bin

このファイルは vladmandic/face-api リポジトリの model/ ディレクトリからコピーしてくる。tiny_face_detector_model.bin の実体が約189KB、manifest 込みで約192KB。本記事ではざっくり「約200KB」と表記している。

ロード時は URI を指定:

const mod = await import('@vladmandic/face-api')
await mod.nets.tinyFaceDetector.loadFromUri('/models/face-api')

これで 外部CDNに一切問い合わせず、自サイト内で完結する。Cloudflare Pages 配信なので CDN キャッシュも勝手に効く。初回のみ約200KBダウンロード、以降はブラウザキャッシュから即起動。

dynamic import で SSR を避ける

face-api.js は内部で windownavigator を参照するため、Next.js の SSR でトップレベル import するとビルドが通らない。必ず dynamic import で 'use client' 内から呼ぶ:

'use client'

const faceApiRef = useRef<typeof import('@vladmandic/face-api') | null>(null)

const loadModel = useCallback(async () => {
  if (faceApiRef.current) return faceApiRef.current
  const mod = await import('@vladmandic/face-api')
  await mod.nets.tinyFaceDetector.loadFromUri('/models/face-api')
  faceApiRef.current = mod
  return mod
}, [])

useRef でモジュール参照をキャッシュしておくと、2回目以降の検出時にロードをスキップできる。

ただし型情報の import は別。type だけは静的にOK:

// 型のみ import(バンドルに含まれない)
interface FaceDetectionLike {
  box: { x: number; y: number; width: number; height: number }
}

face-api.js 自体の型をモジュールから引っ張ろうとすると SSR 時に評価が走るので、最低限の型定義を手動で書くのが無難だった。

検出処理:detectAllFaces で複数顔を一気に取る

検出は detectAllFaces 一発:

const options = new mod.TinyFaceDetectorOptions({
  inputSize: 416,
  scoreThreshold: 0.5,
})
const detections = await mod.detectAllFaces(img, options) as unknown as FaceDetectionLike[]

オプションの意味:

  • inputSize: 推論時の入力解像度。224 / 320 / 416 / 512 / 608 から選ぶ(32の倍数)。大きいほど精度↑、速度↓。416 がデフォルトでバランス良い
  • scoreThreshold: 顔として認識する確信度の閾値。0.5 で「半信半疑以上」。下げると検出漏れが減る代わりに誤検出(背景の模様を顔と判断する)が増える

返ってくる detections[i].box画像座標系の矩形 { x, y, width, height }。これがそのまま Canvas での描画座標に使える。

検出した矩形をそのまま使うと耳・髪が覆えない

ここが地味にハマったポイント。TinyFaceDetector が返す矩形は「目・鼻・口」を中心とした顔のコア部分で、耳や髪、あご下まではカバーしてくれない。そのまま矩形にモザイクを掛けると、髪や耳から本人特定情報がはみ出す。

解決策として、矩形を一定割合だけ拡張する関数を入れている:

export function expandRegion(
  region: ImageRegion,
  ratio: number,
  maxWidth: number,
  maxHeight: number,
): ImageRegion {
  const padX = region.w * ratio
  const padY = region.h * ratio

  const x = Math.max(0, Math.floor(region.x - padX))
  const y = Math.max(0, Math.floor(region.y - padY))
  const right = Math.min(maxWidth, Math.ceil(region.x + region.w + padX))
  const bottom = Math.min(maxHeight, Math.ceil(region.y + region.h + padY))

  return { x, y, w: right - x, h: bottom - y }
}

ratio 0.2(20%)をデフォルトにしている。耳・髪・あごまで確実に隠したいときは 30〜50% に増やせるよう UI でスライダー化した。Math.max(0, ...)Math.min(maxWidth, ...)画像境界からはみ出さないクランプ を入れるのが地味に重要。

検出した矩形にどうエフェクトを適用するか

検出結果の矩形に対して、モザイク・ぼかし・黒塗りの3種類を選択できるようにしている。

それぞれの実装パターン(縮小→拡大の擬似ピクセレート、ImageDataベースの自前 gaussianBlur、fillRect 黒塗り)は別記事「モザイクとぼかしの違いを実装で理解する」で詳しく書いているので、本記事では face-api.js の検出矩形と組み合わせる部分だけ要点をまとめる。

// 検出ループ → エフェクト適用
for (const det of detections) {
  const region = boxToRegion(det, canvas.width, canvas.height)  // 矩形拡張済み
  if (effect === 'mosaic')   applyMosaicToRegion(ctx, img, region, strength)
  else if (effect === 'blur') applyBlurToRegion(ctx, region, strength)
  else                        applyBlackoutToRegion(ctx, region)
}

face-api.js 固有のポイントは以下:

  • ctx.filter = 'blur(...)' は使わない: Safari 18 未満が未対応のため、ImageData ベースの自前 gaussianBlur(box blur 3回近似)を src/lib/image/gaussianBlur.ts に切り出して使っている
  • 黒塗りは「絶対に復元させたくない」用: モザイクは強度が低いと AI で復元される可能性がゼロではないので、法的・倫理的に強めの隠蔽が必要なら黒塗りを選ばせる
  • 強度パラメータは検出した矩形サイズに対してスケール: 顔の大きさが画像内でバラついても、strength * 5(モザイクのブロック)や strength * 2(ぼかしの半径)が矩形相対で効くようにしている

検出漏れへの逃げ道を必ず用意する

TinyFaceDetector はあくまで約200KBの軽量モデル。以下のケースは そもそも検出できない:

  • 真横のプロフィール
  • マスク・サングラスで大部分隠れた顔
  • 低解像度・ぼけ・暗い画像
  • 集合写真の奥に小さく写った顔

「自動」を売りにしておきながら検出漏れがあると、ユーザーは詰む。なので 検出0件のときに別ツール(手動範囲指定)への導線を必ず出す ようにしている(以下は実装からの抜粋。実際は detectedCountuseState<number | null>(null) で扱っており、初期状態と処理中を弾くため detectedCount !== null && detectedCount === 0 && !isProcessing の条件を付けている):

{detectedCount !== null && detectedCount === 0 && !isProcessing && (
  <div className="...">
    顔が検出されませんでした。
    手動で範囲指定したい場合は <Link href="/tools/image-partial-blur">部分モザイク・ぼかし</Link></div>
)}

「自動検出 → 漏れたら手動」という二段構えで、ユーザーが詰まないように設計するのが体験として大事だった。

モバイル端末のパフォーマンス

実機(iPhone 13)で 4000px 級の写真を流すと、推論に5〜10秒、Canvas処理に2〜3秒かかる。「処理中…」表示なしだとフリーズしたように見えるので、ボタンの状態と進捗ラベルは欠かさない。

事前リサイズを案内するのも有効:

高解像度画像(4000px超)は推論に時間がかかります。画像リサイズ で 2000px 程度に縮小してからのご利用を推奨。

ちなみに inputSize: 416 を 320 に下げれば推論は速くなるが、奥に映る小さい顔の検出率が落ちるので痛し痒し。デフォルト 416 のままにして、ユーザーには事前リサイズで対応してもらう設計にしている。

まとめ

face-api.js(vladmandic 版)+ Canvas でブラウザ完結の顔自動モザイクは、約200KBのモデル1個ロードするだけで動く。クラウドAPI 並みの精度は出ないが、「画像を絶対サーバーに送らない」「外部CDNにすら問い合わせない」設計が成立する。

押さえたポイント:

  • @vladmandic/face-api を使う(元祖はメンテ停止)
  • モデルは public/ に self-host、loadFromUri で読み込む
  • dynamic import + useRef キャッシュ で SSR 回避と再ロード防止
  • 検出矩形の拡張(耳・髪・あごをカバー)
  • ぼかしは ImageData 自前実装(Safari 18 未満互換)
  • 検出漏れは手動指定ツールへの導線で逃がす

「ブラウザ完結 AI」のユースケースとしては、顔検出は分かりやすく価値が出る部類。テキスト系(OCR・要約)はクラウドの方が精度が圧倒的だが、画像系は軽量モデルでも実用に届く。

ぱんだツールズ ではこの他にも 部分モザイク・画像ぼかし・PDF処理・CSV処理などブラウザ完結ツールを80本以上公開している。全部無料・登録不要・サーバー送信なし。
https://sakutto-panda.com


この記事は Zenn にも同じ内容を投稿しています。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?