見出し画像

ローカルAIで写真を自動整理する環境を作った

前回はダッシュボードの話だった。

今回は、NASに溜まり続ける写真をローカルAIで自動整理する話。

以前「NASを組んだら写真管理が楽になった」と書いたが、
あれは「保存」が楽になっただけだった。

「検索」と「整理」はまだ手つかずだった。

写真は増え続ける

スマホで撮った写真をNASに自動アップロードする仕組みは
以前の記事に書いた通り作っていた。

便利だが、問題が1つある。

写真が増え続けるのに、
整理は追いつかない。

「あの旅行の写真どこだっけ?」
「水族館に行ったときの写真だけ見たい」
「重複している写真を消したい」

こういう要望に、
日付フォルダだけでは対応できない。

3つの自動処理

作ったのは3つの自動処理パイプライン。

1. 自動タグ付け

Vision対応のローカルLLM(Qwen2.5-VL 7B)を使って、
写真の内容を自動で分析する。
「猫」「食事」「風景」「書類」といった34種類の固定タグから、
該当するものを1〜5個自動付与。

EVO-X2で1枚あたり約5秒。
速くはないが、
夜間にバッチ処理すれば数百枚を翌朝までに処理できる。

精度は体感で85%程度。

「猫」を「犬」と誤認識することはほぼないが、
「自撮り」の判定が緩い。
(人物が写っていると自撮りと判定しがち)

でも手動でタグ付けするよりは圧倒的に速い。

最初はLLaVAを試したが、
日本語のタグを選ばせる指示にまったく従えなかった。
(30枚中タグが付いたのは2枚だけ)

Qwen2.5-VLに替えたら100%タグが付くようになった。
Vision LLMの世代差は大きい。

2. 重複検出

同じ写真を複数回アップロードしてしまうことがある。

ファイル名が違っていても、
画像のハッシュ(pHash)で類似度を計算して重複を検出する。

完全一致だけでなく、
リサイズや軽い編集をした「ほぼ同じ写真」も検出できる。
閾値を調整して、誤検出を減らしている。

3. タグ別ビューで写真を探す

タグ付け結果はSQLiteデータベースに保存される。
CLIで

photo-organizer view 魚

と叩けば魚の写真パスが一覧で出てくる。

さらにWeb UIも作った。
ブラウザでサムネイル付きのグリッド表示ができるので、
タグの誤検出チェックや重複候補の目視確認が圧倒的に楽である。

魚のタグで表示した場合
(お刺身の写真まで、、、)


ファイルの実体は1つだけ。

写真を一切コピー・移動しない。
NAS上のオリジナルはそのまま、
データベースでタグ関係だけ管理する。

Web UIの中身

CLIだけだとパスの羅列しか見えないので、
FastAPI + Jinja2で軽量なWeb UIを作った。

photo-organizer serveで起動すると、
ブラウザが開いてダッシュボードが表示される。

  • ダッシュボード: 総枚数・タグ済み・未タグ・重複グループ数と、タグ別件数

  • タグ別グリッド: タグをクリックするとサムネイル付きで写真が並ぶ(60枚ずつページネーション)

  • キャプション検索: 「登山」「カフェ」などキーワードで写真を探せる

  • 写真詳細: 写真をクリックすると拡大表示+全タグ+AI生成キャプション

  • 重複候補: pHashで検出した類似写真をグループ単位で並べて目視比較

サムネイルはオンデマンドで生成してローカルにキャッシュする。
初回だけ少し待つが、2回目以降は一瞬。

技術的にはPython完結でNode.jsは不要。
Tailwind CSSはCDNから読むだけとした。
個人ツールとしてはこれで十分である。

環境構成

[EVO-X2]
  ├─ Qwen2.5-VL 7B (Ollama) ── 画像認識・タグ付け
  ├─ Pythonスクリプト ── パイプライン制御
  ├─ SQLite ── タグ・メタデータ管理
  └─ FastAPI ── Web UI(localhost:8000)
[NAS]
  └─ \\NAS\photos\originals\ ── 写真の実体(UNCパスで直接アクセス)


元の記事ではシンボリックリンクでタグ別フォルダを生成する構想だったが、
Windows + SMB環境ではsymlinkに制約が多い。

データベースでインデックスを管理し、
CLI/Web UIで検索する方式に変更した。

結果的にこちらの方がシンプルで壊れにくい。

セットアップのポイント

NASのマウントは不要。

EVO-X2からNASにUNCパスで直接アクセスする(\\NAS\photos\originals\)。

ギガビットLAN環境なら画像の読み込みはボトルネックにならない。
Vision LLMはOllama経由で動かす。

ollama pull qwen2.5vl:7b

でモデルを取得し、
PythonスクリプトからOpenAI互換APIで呼び出す。

Strix Halo(EVO-X2)固有の設定

EVO-X2のAMD Radeon 8060S(Strix Haloアーキテクチャ)で
Ollamaを動かすには、ROCmの環境変数が必須。

これを設定しないとGPUが認識されず、
CPUフォールバックで極端に遅くなる。
(気づくまでは、やけに時間がかかるなと思っていた)

powershell
[Environment]::SetEnvironmentVariable("HSA_OVERRIDE_GFX_VERSION", "11.5.1", "Machine")
[Environment]::SetEnvironmentVariable("OLLAMA_FLASH_ATTENTION", "1", "Machine")
Restart-Service ollama

これだけで推論速度が劇的に変わる。

自分の環境では設定なしだとタイムアウト(120秒超え)、
設定後は1枚5秒となった。

注意点

  • Vision LLMは通常のテキストLLMよりメモリを食う。EVO-X2は96GBユニファイドメモリのうち48GBをVRAMに割り当てているので余裕がある

  • 夜間バッチ推奨。推論中はEVO-X2の他の作業が重くなる

  • タグの体系は最初に決めておく。後から変えると再処理が必要

  • 個人の写真をクラウドに送りたくない人にはローカル処理の安心感は大きい

  • LLaVA系の古いVision LLMは日本語指示への追従が弱い。Qwen2.5-VL以降の世代を推奨


パイプラインの流れ

# 1. 写真フォルダをスキャン(pHash計算 + DB登録)
photo-organizer scan "\\NAS\photos\originals\2026-03"
# 2. 重複検出
photo-organizer dedupe
# 3. AIタグ付け(中断しても再開可能)
photo-organizer tag
# 4. 統計確認
photo-organizer stats
# 5. Web UIで閲覧
photo-organizer serve

`tag` コマンドは中断しても次回は続きから処理される。
Ollamaが落ちていたら5回連続失敗で自動停止するので、翌朝まで空回りし続けることもない。


今回使った機材

※本記事はAmazonアソシエイト・プログラムを利用しています。
リンクから購入されると、報酬を得る場合があります。

GMKtec EVO-X2

Ryzen AI Max+ 395搭載・96GBユニファイドメモリ。
Vision LLMのような大型モデルもVRAM側に48GB割り当ててGPU推論できる。


エレコム スイッチングハブ 5ポート ギガビット 金属筐体
他のミニPCやデスクトップと接続している。
かなり小型で省エネでかつ、速度もちゃんと出る。


CAT6A LANケーブル
ギガビット以上の速度を安定して出すなら6A以上を。
写真データの転送速度に直結する。
特にこの商品はすごく細いので取り回ししやすいのでオススメ。




次回は、
毎日noteを書き続けるために使っているツールと仕組みを全部公開します。フォローしてもらえると!

いいなと思ったら応援しよう!