Claude Code に写真の顔隠しを頼むと何往復もかかる — 顔とテキストの検出器を持たせて解決した記録
ブログに載せる写真の顔隠しやモザイクを Claude Code に頼むと、加工そのものは一瞬なのに「もう少し左上」「もう少し大きく」の位置調整で何往復もかかる — こどもがきゅうりを丸かじりした記事の写真加工で、実際にスタンプ位置2回・動画ぼかし位置2回・テレビ画面のぼかし範囲3回のやり直しが発生しました。
この記事は、その原因を分析して「AI に座標を目測させるのをやめ、検出器に座標を出させる」方針でツールを作り、どこまで通用してどこで破綻するかを実測した記録です。検証とツール実装は Claude Code が行い、私は方針の相談と結果の確認をしています。
なぜ何往復もかかるのか
原因ははっきりしていて、AI は画像を縮小表示で見て座標を目測し、それを原寸に換算して描画するためです。
- 初回の位置・サイズの精度が低い (縮小画像からの目測なので)
- 修正指示が「もう少し左上」のような相対言語になり、1往復ごとに 生成 → 送付 → 確認 が必要
- 多めに覆えば外しにくくなるが、今度は見せたいところまで隠れてしまう
つまり「目測 → 相対修正」のループが構造的に往復を生んでいます。ここを断つには、座標を目測ではなく機械に出させるのが筋です。
部品は全部既製品で揃う
作ったツールは大きく3系統で、いずれも既製の検出モデルの組み合わせです。
| 系統 | 使った部品 | できること |
|---|---|---|
| 顔 | YuNet (OpenCV FaceDetectorYN) | 顔を自動検出してスタンプ / モザイク / ぼかし |
| テキスト | PP-OCRv3 検出モデル (OpenCV) | 文字の行を検出し、番号指定で黒塗り / モザイク / 消去 |
| 人物 | MediaPipe ImageSegmenter | 人物だけ残して背景をぼかす |
モデルはどれも数百 KB 〜 数 MB で、GPU なしのノート PC (メモリ 8GB の WSL) で1枚 0.1〜0.2 秒程度。導入も pip と数ファイルのダウンロードだけでした。
※以下のサンプル写真はすべてパブリックドメインの素材 (NASA の公式ポートレートなど) です。
顔: 自動検出で位置合わせの往復がゼロになった
顔検出は3種類を比較しました。手元の検証写真11枚 (家族写真。横顔・後頭部・テレビ画面に映った顔などを含む) に対して:
| 検出器 | 今回の写真11枚での結果 | 本来の持ち味 |
|---|---|---|
| YuNet | 隠すべき頭部4件をすべて検出。誤検出はスコア閾値の調整でゼロに | 軽量 (約230KB) かつ向き・距離に強い |
| MediaPipe BlazeFace (short-range) | 横顔・後頭部は検出したが、テレビ画面内の顔を見逃し。誤検出2件 | スマホの自撮り距離向けのリアルタイム検出。設計上「近くの顔」が守備範囲で、遠い顔や画面内の顔は想定外 |
| Haar カスケード | 誤検出が多く今回は不採用 | 2001年からある古典で、深層学習なしでどこでも動く軽さが魅力。正面顔のリアルタイム検出という原点の用途なら今でも通用する |
どの検出器にも設計上の得意分野があって、今回の「ブログ写真のいろんな向き・距離の顔を漏らさず拾いたい」という用途に一番合っていたのが YuNet だった、という結果です。検出した箱を基準にスタンプやモザイクを描くので、位置合わせの往復がなくなります。横顔・後頭部にわずかに見える頬・テレビ画面の中の顔まで拾いました。
面白かったのは絵画でも動いたことです。フェルメールの「真珠の耳飾りの少女」の顔をスコア 0.84 で検出しました。
集合写真 (アポロ11号のクルー3人) も全員検出です。
顔検出の「強い・弱い」
回転とサイズを変えながら実測した結果です。
| 条件 | 結果 |
|---|---|
| 回転 15° / 30° / 45° / 90° / 180° | すべて検出 (スコア 0.93 → 0.77。逆さまでも拾う) |
| 顔の大きさ | 検出解像度の中で幅 20px まで検出。12px で取りこぼし |
| 集合写真 (3人) | 3/3 検出 |
| 完全な後ろ姿 (顔の要素ゼロ) | 原理的に対象外 |
弱点ははっきりしています。遠景の群衆のような小さい顔は漏れ、顔がまったく写っていない後頭部は当然ながら検出できません。後者を隠したいときは、番号やセルでの手動指定に切り替えることになります。
副産物: モザイクの粒度バグ
公開できるサンプルを作っている途中で、モザイクのブロックサイズを固定ピクセルにしていたせいで、大きな顔だとモザイクが細かくなりすぎて顔が判別できたままになるバグが見つかりました。モザイクの粒度は領域サイズに比例させる必要があります (領域の長辺を約12分割にして解決)。サンプルを何パターンか試すのは大事です。
テキスト: 行を検出して「番号で指定」
住所やレシートの個人情報のような文字情報は、テキスト検出モデルで行単位の領域を出し、番号付きのプレビューを作って「2〜6番を隠して」と指定する方式にしました。座標のやりとりが番号のやりとりに変わります。
文字の行だけでなく、QR コードも専用検出器で同じ番号リストに載ります (上の画像の「8:QR」)。
隠し方は黒塗り・モザイクのほかに、周囲の色で塗り戻す inpaint (消去) が使えます。白背景の文書だと「最初から書いていなかったように」消えるのでいちばんきれいでした。下の1枚目は住所・氏名・電話を inpaint で消し、QR をモザイクにしたもの。品名とお問い合わせ番号は意図的に残しています — 隠すところと見せるところを行単位で選べるのがこの方式の利点です。
このサンプルは架空の宛名ラベルで、ラベル自体が 11° 傾いています。検出は傾いた四角形で返ってくるので、マスクも行の傾きに沿ってかかり、隣の行を巻き込みません。
テキスト検出の「強い・弱い」
| 条件 | 結果 |
|---|---|
| 傾き ±20° まで | 検出維持。行単位のピンポイントマスク可 |
| 傾き 30° 以上 | 検出が崩壊 (行の取りこぼし多発)。先に画像の傾きを直す必要あり |
| 行が密集した文書 (レシート等) | 検出はできるが番号ラベルが重なって読み違えやすい。実際に1回、隣の行を誤って指定した |
| QR コード・バーコード | テキスト検出では拾えない (文字ではないため)。QR・バーコード専用の検出器を併用して解決 |
QR コードは見落としやすいポイントだと思います。レシートやチケットの QR には文字で書いてある以上の情報が入っていることがあるので、文字だけ消して安心はできません。テキスト検出が QR を拾わないと分かった時点で、OpenCV の QR 検出器 (QRCodeDetector) とバーコード検出器を組み込み、文字の行と同じ番号リストに「QR」のラベル付きで並ぶようにしました。上の宛名ラベルの QR がモザイクになっているのはこの仕組みです。1つ癖があって、QR 検出は画像の解像度に敏感でした (同じレシートでフル解像度だと見つからず、幅2400px に縮小すると見つかる)。ツール側で複数の解像度を試して統合するようにしていますが、それでも漏れる可能性はあるので、QR が写っていそうな写真は最後に目視で確認しています。
背景ぼかし: 人物だけ残す
人物セグメンテーションを使うと「人物はくっきり・背景だけぼかし」ができます。顔スタンプと重ねがけすれば、以前は手作業で何往復もかけていた「背景ぼかし+顔スタンプ」が全自動の2段で終わります。
ここでは調整に2回かかりました。最初の設定はぼかしが弱く、よく見ればぼけていると分かる程度。ぼかしフィルタを強めた2回目でも、背景の月のクレーターがまだ判別できました。プライバシー目的のぼかしは「雰囲気を柔らかくする」のとは要求が違って、背景に何があったか分からないところまでやる必要があります。最終的にはフィルタを強めるのではなく、画像を数十分の一に縮小してから引き伸ばす方式にして、ディテールが原理的に残らないようにしました。
弱点はこれもはっきりしていて、手に持っている物は人物と見なされずぼけます。きゅうりを持った写真だと、きゅうりまでぼけてしまう。ここが気になる場合は、残したい領域を1回だけ手で指定する必要があります。
それでも外れたときの逃げ道
検出は万能ではないので、外れたときのフォールバックを3段用意しました。どれも「相対言語での修正」を避けて、1往復で終わることを狙ったものです。
- グリッド指定 — セル番号付きのプレビューを見て「D4-F6 を隠して」と指定
- 複数案から選ぶ — サイズ違いの加工案を1枚に並べて「2番で」と返す
- クリックで座標指定 — ローカル HTML に画像を載せてクリックすると、原寸換算の座標が出る
まとめ — 完璧さより「弱点の地図」
出来上がったワークフローはこうなりました。
- 顔隠し・背景ぼかしは自動検出で一発 (今回の検証では位置修正の往復ゼロ)
- 住所などの文字・QR コードは番号プレビューを見て番号で指定 (1往復)
- 検出が外れたらグリッド / 複数案 / クリックで指定 (1往復)
動画は今回のツールの範囲外です。きゅうり記事の動画では「顔が映らない区間の切り出し」と手作業のぼかしを組み合わせましたが、素材選択に頼るのは本意ではなく、静止画と同じ品質で動画にもぼかしを入れられるようにするのが次の課題だと思っています。
道具として完璧になったわけでもありません。小さすぎる顔は漏れる、傾き 30° を超えた文字は取れない、持ち物は背景と一緒にぼける。ただ、どこが強くてどこが弱いかの地図を人間と AI が共有できていれば、弱いところだけ人間が補えます。そしてこの地図は「ここで満足」の線引きではなく、現時点での妥協点です。弱点を課題として管理しておけば、画像をもっとたくさん使うようになったときや、さらに効率化したくなったときに、そこから再開できます。今回は無料で使える部品だけで組んだ結果なので、有料のツールや別のモデルを試す余地もまだ残っています。
実装メモ: ハマりどころと工夫
コードの詳細には立ち入りませんが、同じものを作る人がハマりそうなポイントだけ書き留めておきます (実装は Claude Code)。
EXIF 回転。スマホの縦長写真は「横長の画像+回転情報 (EXIF)」として保存されていることがあり、OpenCV の cv2.imread は回転情報を無視します。そのまま検出すると縦長写真がすべて横倒しで処理される。PIL 側で回転を適用してから渡すのが確実でした。
from PIL import Image, ImageOps
im = ImageOps.exif_transpose(Image.open(path)) # EXIFの回転を反映
検出は縮小画像・座標は原寸換算。スマホ写真は1200万画素あり、そのまま検出すると遅い。長辺1600pxに縮小して検出し、出てきた座標を倍率で原寸に戻します。これで1枚 0.1〜0.2 秒。
スコア閾値は分布を見て決める。検証写真11枚で、正解の検出はスコア 0.60〜0.86、唯一の誤検出 (おもちゃのカゴ) は 0.51 でした。間の 0.55 を閾値にすると誤検出だけが消えます。閾値は理屈で決めるより、手元のデータのスコア分布を見て決めるのが手っ取り早い。
モザイクの粒度は領域サイズに比例させる。ブロックを固定 px にすると、大きい顔ではモザイクが細かくなりすぎて顔が判別できたままになります (実際になった)。「領域の長辺を約12分割」のように相対で決めます。
プライバシーぼかしは縮小→拡大で。ぼかしフィルタを強めても「よく見ると分かる」が残りがちでした。画像を数十分の一に縮小してから引き伸ばすと、ディテールが原理的に残りません。
small = cv2.resize(img, (w // 48, h // 48)) # 情報をここで捨てる
blurred = cv2.resize(small, (w, h)) # 引き伸ばしてなめらかに
QR 検出は解像度に敏感。同じ写真でフル解像度だと見つからず、幅 2400px だと見つかることがありました。複数の解像度で試して結果を統合しています。
斜めの文字はポリゴンで塗る。テキスト検出の結果は傾いた四角形で返ってくるので、画像の軸に平行な矩形で塗ってしまうと、隣の行を巻き込みます。四角形をそのままマスクにして、マスク部分だけ加工後の画像と入れ替えると、行の傾きに沿ったピンポイントのマスクになります。
mask = np.zeros(img.shape[:2], np.uint8)
cv2.fillPoly(mask, [poly], 255) # 傾いた四角形のマスク
img[mask > 0] = processed[mask > 0] # マスク部分だけ差し替え
ぼかしの境界はなじませる。矩形のままぼかすと縁がくっきり出て加工感が強くなります。楕円のマスクを作り、マスク自体にもぼかしをかけてから合成すると、境界がなだらかに消えていきます。
文字の消去は1行。OpenCV の cv2.inpaint はマスク領域を周囲の色で補完してくれます。白背景の文書なら「最初から書いていなかったように」消えます。
img = cv2.inpaint(img, mask, 7, cv2.INPAINT_TELEA)
付録: Claude Code とのやりとり (抜粋)
このツールと記事は Claude Code との対話で作りました。どういう指示をして、どう返ってきて、それにどう返したのか — 実際のやりとりの流れを、各場面の背景と一緒に抜粋しておきます。AI に仕事を頼むときの参考になれば。
検証の初日。検討候補を書いた issue だけ用意してあって、私はこのあと作業できない日でした。丸ごと任せてみます。
翌日、顔隠しの検証結果に問題がないことを確認したあと、ふと気になって聞いたのがこれです。
記事に載せられる公開サンプル (パブリックドメイン画像) を作ってもらったところ、その過程で「モザイクが細かすぎて顔が判別できる」バグが見つかったという報告がありました。それを受けての返しです。
検証レポートの初稿では、QR コードについては「テキスト検出の対象外。情報を含む QR は手動で隠す必要あり」という注意書きで終わっていました。
記事ドラフトの初稿の比較表は、採用しなかった検出器について「見逃し」「誤検出多数」という結果だけが並んでいて、その検出器自体がダメなものに見える書き方でした。
同じく初稿には「動画は加工でがんばるより顔が映らない区間を選ぶのが最強」と書かれていました。実際には、過去記事の動画には区間選択とあわせてぼかしも入れています。
背景ぼかしは一度「弱い」と指摘して強めてもらったのですが、それでも背景の月のクレーターが判別できるレベルでした。2回目の指摘です。
振り返ると、AI が自走で出してくるのは「動くもの+弱点の正直な報告」までで、どこまでやれば十分か・何を大事にするか (批判をしない、事実に忠実に、妥協に逃げない) は、こちらから伝えて直してもらっています。ただ、これを「毎回伝え続けるしかない」と書きかけたところで、それこそ AI の記憶 (メモリや指示ファイル) に書き込めば済むのではと気づいて、実際にこの記事の執筆中に登録してもらいました。伝える → 定着させる、まで含めてが AI との分担なんだと思います。
参考リンク
同じテーマの記事・体験談:
- 動画の中の顔全てにモザイクをかける (Zenn) — 次の課題にしている「動画への顔モザイク」を実装している記事。動画に取り組むときは、これを AI に読んでもらって参考にする予定です
- 動画内の人の顔を自動検出してモザイク処理する方法 (みやしんのプログラミングスキル通信) — こちらも動画の自動モザイク。同じく動画のときの参考に
- OpenCVでリサイズ・モザイク・顔検出、ときどき象印 (Qiita) — 本文に出てくる「ヒントでピント」が懐かしくて、画像を見て確かにと思いました。分かる人には年代がばれますね
- Python, OpenCVで画像にモザイク処理 (note.nkmk.me) — AI いわく、モザイク処理の定番リファレンスだそうです
- スマホ写真を安全にブログへ!顔を隠す簡単な方法 (アメブロ) — 手作業スタンプ派の方法。2人の顔を1つのスタンプでまとめて隠すと1人に見えてしまう、というのは言われてみればで、気をつけたいところです
- SNSへの投稿に。子どもの顔を隠すスタンプ (おべんきょうばこ) — 顔隠しスタンプ素材の紹介。スタンプは AI にお任せしていますが、これを見て、うちのこども専用のスタンプを作りたくなりました
検出モデル・ライブラリ (一次情報):
- opencv_zoo — YuNet (顔検出) — 今回の主役。ONNX 1ファイル約 230KB
- opencv_zoo — PP-OCRv3 text detection — テキスト行検出。文字の中身は読まない
- MediaPipe Face Detector — 比較に使用
- MediaPipe Image Segmenter — 背景ぼかしに使用
サンプル画像の出典 (いずれもパブリックドメイン):