【RTX 3060活用】Pythonと自作AIで釣具(ロッド)写真を自動判別してエクスプローラー検索に対応させる方法

当ページのリンクには広告が含まれています。
  • URLをコピーしました!

増え続ける釣具の写真、整理に困っていませんか?
今回は、中古で買ったグラフィックボード「NVIDIA GeForce RTX 3060」のパワーをフルに解放し、自分で育てたAI(機械学習)を使って「ロッド」と「それ以外」の写真を自動で判別、さらにWindowsのエクスプローラーで一発検索できるように写真のメタデータ(Exif)へ自動仕分けタグを埋め込むシステムを構築します。

難しいことはありません、Google Geminiに聞きながら進めると、なんとかなります!

【ご利用にあたっての注意(免責事項)】

本記事で紹介しているコードや手法は、動作環境によって挙動が異なる場合があります。本プログラムの実行によるデータの破損やPCのトラブル等について、当サイトは一切の責任を負いません。大切な写真データを扱う際は、必ずバックアップを取得してから実行してください。また、画像判別AIの精度は100%ではありません。すべて自己責任のもとでのご利用をお願いいたします。

目次

写真整理プログラム作成に至った背景

釣りブログを始めて10年以上、関連する写真画像は3万枚を超えています。

何か記事を書くときに、あの写真どこに保存してたっけ?と探すのに時間を要してしまいます。数分で見つかることもあれば、10分くらいかかったりすることもあり、以前から写真の「タグ」に検索できるように情報を書き込もうと思っていました。

画像情報のタグデータ
画像情報のタグデータ

🛠️ 1. 開発環境の準備(ここが一番重要!)

機械学習を手元のPCで安定して、かつ高速に動かすためには、Pythonとライブラリのバージョン選びが非常に重要です。

動作確認済み環境

  • OS: Windows 11
  • GPU: NVIDIA GeForce RTX 3060
  • Python: 3.12.x (AI開発で最も安定しているバージョンです。最新の3.14等はライブラリが未対応のため避けます)
ポイント

私はずっとPython3.14.xでいろんなものを作って試していましたが、今回はダウングレードする必要があり、仮想環境に3.12.xをインストールしました。もちろんGeminiに聞きながら。

必要ライブラリのインストール

専用の仮想環境(.venv)を作成・有効化した上で、コマンドプロンプトやターミナルで以下のコマンドを実行します。 特に、RTX 3060(VRAM 12GB)のパワーを使うために「CUDA(GPU)対応版のPyTorch」を明示的に指定してインストールするのが最大のポイントです。

# 既存のCPU版を一度クリア
pip uninstall -y torch torchvision

# RTX 3060用のCUDA対応版PyTorchと、画像・メタデータ処理ライブラリをインストール
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
pip install Pillow piexif

📂 2. AIを育てるための「データセット」の準備

AIに「これがロッドだよ」「これは違うよ」と教えるための写真フォルダを作ります。

プロジェクトのフォルダ内に dataset というフォルダを作成し、以下のように構成します。各フォルダの中に、直接画像ファイル(.jpg)を配置してください。

D:\AI\photo-organization\dataset\
  ├── train/            # AIの学習用(枚数が多いほど賢くなります)
  │     ├── rod/        # ロッド(釣竿)の写真(50〜100枚目安)
  │     └── other/      # リール、バッグ、背景、タモの柄などの写真(50〜100枚目安)
  └── val/              # AIの実力テスト用
        ├── rod/        # テスト用のロッド写真(10〜20枚目安)
        └── other/      # テスト用のその他写真(10〜20枚目安)

💡 精度爆上げのコツ AIは背景をモノそのものと誤解しやすいため、家の中、車の中、釣り場、釣具店など、背景や明るさ、角度がバラバラな写真をたくさん集めるのが完璧に見分けるための極意です。これがなかなか大変で、プログラムを作るよりも時間がかかりました。

学習用に使ったロッドの写真
学習用に使ったロッドの写真

🤖 3. 【プログラム①】GPU対応AI学習スクリプト(train.py

準備した写真を使って、あなた専用の「釣具識別AI」を訓練するプログラムです。RTX 3060をフル稼働させて爆速で処理します。

CPUだけ使って学習させるよりは時間が短くなるという意味で、実際私のPC環境(Intel Core i5 14400 , メモリ16GB , NVIDIA GeForce RTX 3060 VRAM 16GB)では15分くらいかかりました。

import os
import time
import copy
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, models, transforms

BATCH_SIZE = 8
NUM_EPOCHS = 15  # 写真を多くした場合は 20〜30 に増やすのがおすすめ
NUM_CLASSES = 2  # 「other」と「rod」の2クラス

# 画像の変形・水増し(データ拡張)設定
data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

def train_model():
    train_dir = r'D:\AI\photo-organization\dataset\train' #相対パスでもOK
    val_dir = r'D:\AI\photo-organization\dataset\val' #相対パスでもOK
    
    image_datasets = {
        'train': datasets.ImageFolder(train_dir, data_transforms['train']),
        'val': datasets.ImageFolder(val_dir, data_transforms['val'])
    }
    dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=BATCH_SIZE, shuffle=True) for x in ['train', 'val']}
    dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
    
    # 使用デバイスを自動判定(Trueなら自動でRTX 3060になります)
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print(f"使用デバイス: {device} ({torch.cuda.get_device_name(0) if device.type == 'cuda' else 'CPU'})")

    # ベースとなる学習済みモデル(ResNet18)の読み込みと出力層の改造
    model = models.resnet18(pretrained=True)
    num_ftrs = model.fc.in_features
    model.fc = nn.Linear(num_ftrs, NUM_CLASSES)
    model = model.to(device) # モデルをGPUメモリへ転送

    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

    print("\nRTX 3060 による高速学習を開始します...")
    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0.0

    for epoch in range(NUM_EPOCHS):
        print(f'Epoch {epoch+1}/{NUM_EPOCHS}\n' + '-'*10)
        for phase in ['train', 'val']:
            if phase == 'train': model.train()
            else: model.eval()

            running_loss, running_corrects = 0.0, 0
            for inputs, labels in dataloaders[phase]:
                inputs, labels = inputs.to(device), labels.to(device) # データをGPUへ
                optimizer.zero_grad()
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)

            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects.double() / dataset_sizes[phase]
            print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')

            if phase == 'val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())
        print()

    model.load_state_dict(best_model_wts)
    torch.save(model.state_dict(), 'fishing_gear_model.pth')
    print("学習完了! 'fishing_gear_model.pth' を保存しました。")

if __name__ == '__main__':
    train_model()

これを実行すると、同じ場所にAIの脳みそとなる fishing_gear_model.pth が生成されます。

📂 4. 【プログラム②】写真自動整理・タグ埋め込みスクリプト(organize.py

いよいよ本番です。ダイアログで整理したい写真フォルダを選択すると、サブフォルダまで全て自動で探索。「所属しているフォルダ名」「自作AIによる画像判別(ロッドなど)」のダブルのキーワードを、写真のExifデータ(タグ領域)に自動で書き込みます。

スマホ写真によくある「壊れたExifデータ」を検知して自動でスキップ・修正する安全機構付きです。

import os
import tkinter as tk
from tkinter import filedialog
from PIL import Image
import piexif
import torch
import torch.nn as nn
from torchvision import models, transforms

# 1. 自作AIモデルの読み込み(2クラス版)
model = models.resnet18()
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 2)

MODEL_PATH = 'fishing_gear_model.pth'
if os.path.exists(MODEL_PATH):
    model.load_state_dict(torch.load(MODEL_PATH, map_location=torch.device('cpu')))
    model.eval()
else:
    raise FileNotFoundError("先に学習を行って 'fishing_gear_model.pth' を作成してください。")

# アルファベット順(other -> rod)に対応する日本語タグ
CLASS_NAMES = ['その他', 'ロッド']

data_transform = transforms.Compose([
    transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

def analyze_image_custom(image_path):
    """自作AIで画像がロッドかどうかを判定する"""
    keywords = []
    try:
        img = Image.open(image_path).convert("RGB")
        img_t = data_transform(img).unsqueeze(0)
        with torch.no_grad():
            outputs = model(img_t)
            probabilities = torch.nn.functional.softmax(outputs[0], dim=0)
            confidence, preds = torch.max(probabilities, 0)
            # 確信度が70%以上の場合のみ採用
            if confidence.item() > 0.70:
                detected_class = CLASS_NAMES[preds.item()]
                if detected_class != 'その他': # 「その他」タグはエクスプローラーに不要なので除外
                    keywords.append(detected_class)
    except Exception as e:
        print(f"AI解析エラー ({os.path.basename(image_path)}): {e}")
    return keywords

def write_exif_keywords(image_path, keywords):
    """Exif(XPKeywords)にタグを安全に書き込む"""
    try:
        keyword_str = ";".join(keywords)
        xp_keywords = keyword_str.encode('utf-16le')
        
        try:
            exif_dict = piexif.load(image_path)
        except Exception:
            exif_dict = {"0th": {}, "Exif": {}, "GPS": {}, "1st": {}, "thumbnail": None}
            
        # 不正なExifデータ(SceneType等の型違いバグ)対策
        for tag in [41728, 41729]:
            if "Exif" in exif_dict and tag in exif_dict["Exif"]:
                if not isinstance(exif_dict["Exif"][tag], bytes):
                    del exif_dict["Exif"][tag]
                    
        exif_dict["0th"][40094] = xp_keywords
        exif_bytes = piexif.dump(exif_dict)
        
        img = Image.open(image_path)
        img.save(image_path, exif=exif_bytes)
        print(f"成功: {os.path.basename(image_path)} -> タグ: {keyword_str}")
    except Exception:
        # 強制突破:Exifが深刻に壊れている場合は、新規でタグ領域を作って書き込む
        try:
            clean_exif = {"0th": {40094: xp_keywords}, "Exif": {}, "GPS": {}, "1st": {}, "thumbnail": None}
            img = Image.open(image_path)
            img.save(image_path, exif=piexif.dump(clean_exif))
            print(f"強制成功: {os.path.basename(image_path)} -> タグ: {keyword_str}")
        except Exception as ce:
            print(f"書き込み不可 ({os.path.basename(image_path)}): {ce}")

def main():
    root = tk.Tk()
    root.withdraw()
    target_dir = filedialog.askdirectory(title="写真整理を行うフォルダを選択してください")
    if not target_dir: return
        
    print(f"処理開始: {target_dir}\n" + "-"*40)
    for root_path, dirs, files in os.walk(target_dir):
        folder_name = os.path.basename(root_path)
        for file in files:
            if file.lower().endswith(('.jpg', '.jpeg')):
                image_path = os.path.join(root_path, file)
                keywords = [folder_name] # フォルダ名を追加
                keywords.extend(analyze_image_custom(image_path)) # AI判定を追加
                write_exif_keywords(image_path, list(set(keywords)))
    print("すべての写真整理が完了しました!")

if __name__ == "__main__":
    main()
写真タグに「ロッド」が書き込まれている
写真タグに「ロッド」が書き込まれている

🎯 5. 実行結果とWindowsでの検索方法

プログラムが完了した写真のプロパティ(詳細タブ)を開くと、「タグ」の項目に「所属していたフォルダ名」やAIが判別した「ロッド」という文字が自動で入っています!

これにより、Windowsのエクスプローラーの右上にある検索窓に、

  • ロッド と打ち込めば、PC内のあらゆるフォルダからロッドの写った写真だけが爆速で一覧表示されます。
  • 2023_淡路島遠征 ロッド のように「フォルダ名 + AIタグ」で組み合わせ検索をすれば、目的の写真へ一瞬でアクセス可能になります。
Windowsエクスプローラーで一瞬で検索できるようになりました
Windowsエクスプローラーで一瞬で検索できるようになりました

PythonとRTX 3060のパワーを使えば、これまで丸一日かかっていた写真の仕分け作業が数分で自動化できます。釣行写真の山に埋もれている方は、ぜひ試してみてください!

PVアクセスランキング にほんブログ村

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次