はじめに
私の妻は保育士をしています。行事や日々の保育の様子を撮った写真を、あとで「誰が写っているか」で仕分けて、おたよりや卒園アルバムなどに使うという作業を、まとまった時間が取れないまま日々こなしています。
そこで、非エンジニアの自分でも、Claude Codeを使えば何とかなるのでは、と思い立ち、実際に「写真を人物ごとに自動で仕分けてくれるアプリ」を1日で作ってみました。
この記事では、
- 保育士のような「地味だけど時間を取られる作業」に困っている方向けに、アプリの使い方
- なぜ「オフラインで完結すること」にこだわったか
- 非エンジニアでも1日でアプリが作れてしまった経緯
を中心に書きます。細かい技術的な内容は記事後半にまとめていますが、そちらはコードを実際に書いてくれたClaude Code自身に執筆してもらいました。
保育士の"地味に重い"業務:写真の仕分け
保育の現場では、行事のたびに大量の写真が撮られます。それを
- クラスだよりに載せる写真を選ぶ
- 園児ごとのアルバムに仕分ける
- 定期写真販売の候補写真を選定する
といった目的で、1枚1枚目視で確認しながら「誰が写っているか」で仕分けていく作業が発生します。1枚に複数人写っていることも多く、地道な確認作業の連続です。空いた時間を見つけて少しずつ進めるしかなく、じわじわと負担になっているというのが、妻の話を聞いていて感じたことでした。
クラスだより作成にも、この園児ごとの仕分けが重要になります。
園児の写真の数をある程度均等にする必要があるためです。
つまり、特定の園児の写真のみ少ない/多いということを避けるために
園児の写真は園児ごとのフォルダに仕分けておくことで、その後の様々な業務が円滑に進むことになります。
こだわったポイント:完全「オフライン動作」
こういう写真整理の課題を解決するサービスやアプリは、探せば他にもあります。ただ、その多くは写真をクラウドにアップロードして、クラウド上のAIで解析する仕組みになっています。
保育の現場で、これはかなりハードルが高い話です。
- 園児の顔写真は、極めてセンシティブな個人情報である
- 保護者の同意なく画像を外部サービスに送信するのは、施設として避けたい
- クラウドサービス側での情報漏洩リスクをゼロにはできない
- 「子どもの写真をAIに学習させているのでは」という不安を感じる保護者・保育士も少なくない
これは決して考えすぎではなく、実際に保育現場でよく話題になる懸念点です。そこで今回作ったアプリは、
- 顔検出
- 顔の特徴量抽出
- 人物ごとのグループ分け(クラスタリング)
これらすべての処理を、自分のPCの中だけで完結させ、写真データや解析結果を一切外部に送信しない設計にしました。インターネット接続が必要なのは、最初にAIモデルをダウンロードする一度きりです。以降は完全にオフラインで使えます。
このアプリで一番こだわったのは、まさにこの「オフラインで完結する」という点です。便利さと引き換えに写真を外部に預ける必要がない、という安心感を大事にしました。
プログラミング未経験でも、1日でアプリが作れた理由
正直に言うと、自分はまともにコードを書いたことがほとんどありません。
ExcelVBAやPowershellを仕事で触ったり
趣味で一度プログラミングを学習した程度です
それでも今回、実用的に動くアプリを1日で形にできたのは、Claude CodeというAIコーディングアシスタントのおかげです。
VSCode(無料のコードエディタ)の中でClaude Codeを動かし、
- 「保育士の妻のために、写真を人物ごとに自動で仕分けるアプリを作りたい」
- 「この画面、もう少しこうしてほしい」
というように、日本語で要望や困りごとを伝えるだけで、コードの作成・修正・原因調査・GitHubへの登録まで一緒に進めてくれました。実際に開発中にはまった不具合(後述する日本語パスのエラーなど)も、症状を伝えただけで原因を特定して直してもらえました。
「アプリを作るにはプログラミングの専門知識が必須」という前提が、もはや過去のものになりつつあると実感した1日でした。同じように「身近な誰かの業務を楽にしたい」と思っている非エンジニアの方にも、可能性を感じてもらえたら嬉しいです。
実際に使ってみる:非エンジニアのための導入ガイド
ここからは、プログラミングやパソコンの専門知識がない方でも迷わないよう、実際にこのアプリを自分のPC(Windows)にセットアップして使う手順を、できるだけ丁寧に説明します。
コードは以下のGitHubリポジトリで公開しています。
用意するもの
- Windowsパソコン
- インターネット接続(セットアップ時のみ必要。以降はオフラインで使えます)
- 少しの空き時間(セットアップは最初の1回だけです)
STEP1: コードをダウンロードする
- 上記のGitHubページを開く
- 画面右上の緑色の「Code」ボタンをクリック
- 「Download ZIP」をクリックしてダウンロード
- ダウンロードしたZIPファイルを、わかりやすい場所(例:デスクトップ)で右クリックし「すべて展開」で解凍する
これで PhotoSorter というフォルダができます。
STEP2: Pythonをインストールする
このアプリはPythonというプログラミング言語で作られています。まだ入っていない場合はインストールします。
- Python公式サイト から Python 3.11 をダウンロード
- インストーラーを起動したら、画面下部の「Add python.exe to PATH」に必ずチェックを入れてから「Install Now」をクリック(ここを忘れるとあとの手順でエラーになります)
ポイント:あえて最新版ではなくPython 3.11を使う理由
このアプリが使っているAI関連のライブラリ(TensorFlow)は、最新のPythonに対応するまでにタイムラグがあります。新しすぎるPythonを使うと、この後のセットアップ手順でインストールエラーが出ることがあるため、動作確認済みのバージョンとしてPython 3.11を案内しています。
STEP3: セットアップする(コピペでOK)
- Windowsの検索欄で「PowerShell」と入力して起動
- 以下を1行ずつコピペしてEnterキーを押す(
cdの後ろは、STEP1で解凍したフォルダの場所に合わせてください)
cd Desktop\PhotoSorter
python -m venv venv
.\venv\Scripts\pip install -r requirements.txt
- 続けて、顔認識に使うAIモデルをダウンロードします(初回のみ・合計200MBほど)
$env:PYTHONUTF8 = "1"
.\venv\Scripts\python.exe download_models.py
少し時間がかかりますが、完了すればセットアップは終わりです。以降はインターネット接続なしで使えます。
STEP4: 起動する
PhotoSorter フォルダの中にある 起動.bat をダブルクリックするだけです。黒い画面(コンソール)が開いてサーバーが起動し、数秒後に自動でブラウザにアプリの画面が表示されます。
STEP5: 使い方(3ステップ)
- 解析実行:仕分けたい写真が入っているフォルダを指定して「解析開始」を押す(枚数が多いと数十分かかることもあります)
- クラスタ確認・命名:人物ごとにサムネイルがグループ表示されるので、それぞれに名前を入力して保存。間違って混ざっている写真は「外す」ボタンで簡単に取り除けます
-
振り分け実行:ボタンを押すと、
pictures/sorted/人物名/というフォルダに、人物ごとに写真がコピーされます(元の写真フォルダはそのまま残ります)
こんなときは
- 黒い画面(コンソール)がすぐ閉じてしまう:STEP2でPythonインストール時に「Add python.exe to PATH」にチェックを入れ忘れている可能性があります。Pythonを入れ直してみてください
-
セキュリティの警告が出る:
起動.batはローカルでサーバーを起動するだけのファイルです。心配な場合は中身をメモ帳で開いて確認してから実行してください - 終了したいとき:開いている黒い画面(コンソール)を閉じれば、アプリは終了します
作ってみた感想
素人でもここまでのものが作れてしまうことに衝撃を受けました
一方で、ここまで作れてしまうことの怖さというか、人知を超えたなぁとも思いました。
自分などはエンジニアではないので、正直構造を理解していません。
オフラインで動作することが必須要件でしたので
そこは念入りに確認しましたが
もう一点
素人でもソフトやサービスを簡単に作れること自体は素晴らしいのですが
かといって、エンジニアの方のスキルというのは、陳腐化しないのではと思いました。
きちんと理解し、作ることができるのは、やはりまだ重要ですね。
AI時代だからこそ、作れてしまうからこそ、逆に”本物の開発スキル”習得のハードルは高くなってしまったようにも感じます。
付き合い方はよく考えながら向き合っていく必要がありそうです。
おわりに
保育士に限らず、「地味だけど時間を取られる仕分け作業」に日々追われている方は多いと思います。今回のように、プログラミング未経験でもAIコーディングアシスタントを使えば、身近な人の業務を楽にする小さなツールを自分の手で作れる時代になってきました。同じような悩みを持つ方の参考になれば幸いです。
技術解説(ここから先はClaude Codeによる執筆です)
ここから先は、実装を担当したClaude Codeに技術的な内容をまとめてもらいました。エンジニアの方向けの補足です。
Claude Codeに執筆を全面的にお願いしたパートです。
ハルシネーションが含まれる可能性があります
技術構成
| 役割 | 採用技術 |
|---|---|
| 顔検出 | RetinaFace(confidence 0.9未満は破棄) |
| 顔特徴量抽出 | Facenet512(DeepFace経由) |
| クラスタリング | scikit-learn DBSCAN(コサイン距離ベース) |
| バックエンド | Python / Flask(127.0.0.1のみでバインド) |
| フロントエンド | HTML / CSS / Vanilla JavaScript |
処理の流れは、①指定フォルダ内の画像をスキャン →②RetinaFaceで顔検出 →③Facenet512で512次元の特徴ベクトルを抽出 →④DBSCANでクラスタリング →⑤クラスタごとに確認・命名画面を表示 →⑥命名済みクラスタ情報をもとに元写真を人物名フォルダへコピー、という流れです。
DBSCANを採用したのは、事前に「何人写っているか」が分からない状況でも使えるためです。k-meansのようにクラスタ数を指定する必要がなく、どのクラスタにも属さない「外れ値」を -1 ラベルとして自然に扱えます。
ハマったポイント①:日本語パスで解析エラーになる
写真フォルダのパスに日本語(非ASCII文字)が含まれていると、解析実行時にほぼ全ての画像がスキップされる不具合がありました。
解析エラー (Input image must not have non-english characters - C:/Users/.../旅行記録/IMG_2076.JPG)
DeepFaceの内部実装(deepface/commons/image_utils.py)を確認すると、画像パスを文字列で渡した場合に次のようなチェックが入っていました。
if not img.isascii():
raise ValueError(f"Input image must not have non-english characters - {img}")
img_obj_bgr = cv2.imread(img)
内部で cv2.imread() にパスを渡す際、OpenCVがWindows環境で非ASCIIパスを正しく扱えないケースがあるための安全策のようです。しかし裏を返せば、画像パスを渡さなければこのチェック自体を回避できます。
DeepFace.represent() の img_path 引数はファイルパスの文字列だけでなく、numpy配列(BGR形式の画像データ)も受け付ける仕様になっているため、Pillowで画像を読み込んでnumpy配列に変換してから渡すように修正しました。
# img_pathを文字列で渡すとDeepFace内部のcv2.imreadが日本語等の非ASCIIパスを
# 扱えないため、Pillowで読み込んだ画像をBGRのnumpy配列に変換して渡す
bgr_array = cv2.cvtColor(np.array(pil_img.convert("RGB")), cv2.COLOR_RGB2BGR)
results = DeepFace.represent(
img_path=bgr_array,
model_name=config.MODEL_NAME,
detector_backend=config.DETECTOR_BACKEND,
enforce_detection=False,
align=True,
)
image_utils.load_image() は先頭で isinstance(img, np.ndarray) を判定しており、numpy配列であれば即座にそのまま返すため、非ASCIIチェックには引っかかりません。日本語のフォルダ名・ファイル名を扱う国内向けアプリでは踏みやすい落とし穴なので、同様の実装をする方の参考になれば幸いです。
ハマったポイント②:解析完了後にページを開き直すと最初の画面に戻る
解析には数十分〜数時間かかることがあるため、「解析を開始したブラウザタブとは別に、あとからページを開き直して結果を確認する」という使い方をしたところ、解析が完了しているにもかかわらず常に最初の画面が表示される不具合がありました。
フロントエンドのJavaScriptが、ページ読み込み時に無条件で最初の画面を表示する作りになっていたのが原因です。
// 修正前
showScreen(1);
バックエンド側は解析の完了状態を永続化しているものの、フロントエンド側がその状態を確認せず、常にトップ画面から始まる前提のコードになっていました。ページ読み込み時に /api/clusters を呼び、analysis_done フラグを見てから表示する画面を決めるように修正しました。
// 修正後
async function init() {
try {
const data = await api("/api/clusters");
if (data.analysis_done) {
document.getElementById("source-dir").value = data.source_dir || "";
showScreen(2);
return;
}
} catch (e) {
// 状態取得に失敗した場合は解析実行画面から開始する
}
showScreen(1);
}
init();
バックエンドが持っている状態をフロントエンドが見ずに決め打ちで初期表示してしまう、というのはありがちなバグです。「バックエンドで完結する状態」と「フロントエンドの初期表示」は必ずセットで見直す必要がある、と再認識しました。
クラスタリング結果の例
DBSCANでのクラスタリング後、各クラスタ内でクラスタ重心(centroid)との類似度(コサイン類似度)を計算し、閾値を下回るメンバーには「要確認」バッジを表示するようにしています。
90枚の写真を解析した例では、顔検出数108個・クラスタ数8個(人物候補)・未分類14個という結果になりました。大きめのクラスタでは「要確認」バッジが多く付き、実際に確認すると別人が混入しているケースもありました。これは eps(DBSCANの近傍半径)が緩すぎたことが原因で、写真を再解析せずに eps だけを変えて再クラスタリングできるAPIを用意し、閾値を調整しながら試行錯誤できるようにしています。
今後の課題
- 処理時間:CPU実行のため1枚あたり数秒〜十数秒かかり、枚数が多いと数十分〜数時間かかる。GPU対応やバッチ処理の検討余地あり
- 中断機能:現状、解析を途中でキャンセルする機能がなく、止めるにはプロセスを強制終了するしかない。結果も全処理が終わってから一括保存される設計のため、途中経過を確認できない
- eps調整のUX:現状は数値を手打ちで試行錯誤する必要があり、もう少し直感的なUIにできそう