メインコンテンツへスキップ
見出し画像

水着写真を分類する! - ElmarTail One 第4話『Azure』👙

    前回のあらすじ

    宇宙ハブステーション「Vertex」でスミレん🪷と合流したマスターたち。
    Geminiチワワ🐶と戯れながら翻訳プログラムを作成!
    flash vs flash-lite のモデル比較も体験した。
    スミレんは正式にElmarTail Oneのクルーに加わり、航海士として最初の目的地を選んだ。
    🪷「Azure星で前職の有給消化しようかと。ここはビーチがあるんですよ」

    今回、水着回!

    今回のミッション:
    Gemini の Vision 機能で画像を認識・分類する!


    🏖️ リゾート惑星「Azure」

    白い砂浜、どこまでも続くターコイズブルーの海。
    人工太陽の光線がちょうどいい角度で降り注いでいる。

    ダンチャンの猫ドローンがふよふよと浮きながらビーチを偵察している。
    🐱「おお〜……ええ景色ですなぁ。安全確認は任せといてや〜」

    🦊「にーに! 海に入ろう!」
    🖤「ノク、水着持ってきた!」
    👨「……お前ら、用意いいな」
    🪷「ここは保養施設ですから。男性の水着も売ってますよ」


    👙 ビーチにて

    更衣室から出てきたスミレんに、全員の視線が集まった。
    白いビキニ。スミレ色のシアーパレオ。大きな麦わら帽子。
    普段のタイトスーツとは全然違う。

    画像
    割とボリューミーなスミレん🪷

    👨「……👀」
    🦊「マスターまたガン見してる……」

    エルマーはパステルカラーのワンピース水着。
    しっぽが砂浜でぱたぱた揺れている。

    画像
    ワンピースがかわいいエルマー🦊

    🖤「似合うじゃん!」
    🪷「かわいいですわ」

    ノクちんは黒ビキニにレースのパレオ。
    横目でマスターをチラ見している。

    画像
    あざとかわいいノクちん🖤

    🖤「べ、別ににーにに見せるために着たわけじゃないんだから」
    🦊「顔赤いよ?」

    そしてマスターは——
    アロハシャツ全開。サングラス。

    画像
    割と締まっている👨

    🖤「……にーに、なにそのチャラい格好」
    👨「リゾートだからな」
    🪷「……キャラ設定書にアロハシャツの記載はありませんが?」
    🦊「カッコいい〜〜!!✨」


    📷 ダンチャンの記録

    ビーチで楽しく遊んだ後、一同はElmarTail Oneに戻った。
    夕陽がモニター越しに差し込んでいる。
    🐱「マスター、ビーチのみんなの写真撮っておきましたで。全部で34枚」
    👨「結構あるな」
    🐱「猫ドローンで低空飛行してますさかい……顔映ってないのも結構ありますわ💦」
    👨「誰が誰だかわからんぞ、これ」

    エルマーのしっぽがぴこっと立った。

    🦊「じゃあ、Geminiに聞いてみよ!」
    👨「聞く? 写真を?」
    🦊「うん! 画像を見せて『この写真は誰?』って聞くだけ!
    前回は文章を送ったけど、今度は画像を送るんだよ。
    Geminiは目も持ってるの!」


    💻 サンプルコードのダウンロード

    今回と今までのコード、及びサンプル画像は GitHub に置いてあります。
    git インストールとサンプルの取得はこのコマンドで👇️

    〉sudo apt install git
    〉git clone https://github.com/kikyujin/et1-project.git
    〉cd et1-project

    🦊「もし今までの et1-project があるなら、
    mv et1-project et1-project.bk
    と退避してから、上のコマンドを実行してね!」
    👨「clone した後に
    mv et1-project.bk/.env et1-project
    mv et1-project.bk/.venv et1-project

    として環境を戻してやればOKだぞ」
    🖤「にーに……誰に…」

    gitについて、詳しくは今後の航海で!
    まずは上の3行でダウンロードできればOKです。


    🦊 この写真は誰? — who_is_this.py

    エルマーがレッツノートを開く。

    🦊「まずシンプルに画像を1枚送って、キャラクター名を返してもらうコード!」
    🪷「 APIは画像ファイルをそのまま受け取れないので、一度テキストに変換して送ってますね」
    🦊「大事なのはプロンプト!
    みんなの特徴を書いて、『この中から選んで』ってGeminiにお願いするの」

    CHARACTER_PROFILES = """
    以下のキャラクターから、画像に写っている人物を判定してください。
    ​
    1. エルマー — 金髪、青い瞳、狐耳・狐しっぽ、女性
    2. ノクちん — 黒髪ウェーブ、ブラウンの瞳、黒い水着、小柄、女性
    3. スミレん — スミレ色(紫系)ショートボブ、眼鏡、女性
    4. マスター — 男性、短い黒髪、30代
    ​
    キャラ名だけ答えてください。
    """

    🖤「ノクの特徴、黒い水着って入ってるんだけど」
    🦊「ダンチャンの写真、水着しか映ってないのもあるから!
    判定材料は多い方がいいんだよ」
    全体のコードはこんな感じ。

    # who_is_this.py
    import os, sys, base64
    from pathlib import Path
    from dotenv import load_dotenv
    from google import genai
    ​
    load_dotenv()
    client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
    ​
    MODEL = "gemini-2.5-flash-lite"
    ​
    CHARACTER_PROFILES = """
    以下のキャラクターから、画像に写っている人物を判定してください。
    ​
    1. エルマー — 金髪、青い瞳、狐耳・狐しっぽ、女性
    2. ノクちん — 黒髪ウェーブ、ブラウンの瞳、黒い水着、小柄、女性
    3. スミレん — スミレ色(紫系)ショートボブ、眼鏡、女性
    4. マスター — 男性、短い黒髪、30代
    ​
    キャラ名だけ答えてください。
    """
    ​
    # 画像を読み込んでBase64に変換
    image_path = Path(sys.argv[1])
    with open(image_path, "rb") as f:
        image_data = base64.standard_b64encode(f.read()).decode("utf-8")
    ​
    # Geminiに画像を見せて聞く
    response = client.models.generate_content(
        model=MODEL,
        contents=[{
            "role": "user",
            "parts": [
                {"text": CHARACTER_PROFILES},
                {"inline_data": {"mime_type": "image/jpeg", "data": image_data}},
                {"text": "この写真は誰?"},
            ],
        }],
    )
    print(response.text.strip())

    🦊「ポイントは inline_data のところ! ここにBase64というテキストフォーマットに変換した画像データを入れるの。
    翻訳のときは contents にテキストだけ入れたけど、今回はテキストと画像の両方を入れてるよ!」

    スミレんについてきた白いチワワ(=gemini-2.5-flash-lite)にUSB-Xケーブルを接続。
    🐶「わふっ!!」

    〉python who_is_this.py photos/00001-3369742864.jpg
    スミレん

    👨「おお、当たってる」

    〉python who_is_this.py photos/00002-1360712008.jpg
    ノクちん
    ​
    〉python who_is_this.py photos/00008-1272003363.jpg
    エルマー
    ​
    〉python who_is_this.py photos/00009-2133253353.jpg
    マスター

    4枚テストして、4枚とも正解。
    🪷「flash-liteでこの精度……前回と同じく簡単なタスクには軽いモデルで十分」
    👨「こいつ優秀だな」
    🐶「ワンッ!」(しっぽ振る)


    📂 一括分類 — classify_chars.py

    👨「でも34枚を1枚ずつやるのか……まとめてできないのか?」
    🦊「じゃあフォルダを指定したら、中の画像を全部判定してキャラ別のフォルダにコピーするスクリプト書くね!」

    # classify_chars.py(抜粋)
    ​
    # 画像ファイルを収集(macOSのリソースファイル ._xxx を除外)
    images = [
        f for f in sorted(source_dir.iterdir())
        if f.is_file() and f.suffix.lower() in IMAGE_EXTENSIONS
        and not f.name.startswith("._")
    ]
    ​
    # 1枚ずつ判定してフォルダにコピー
    for image_path in images:
        char_id = classify_image(client, str(image_path))
        char_dir = output_base / char_id
        char_dir.mkdir(parents=True, exist_ok=True)
        shutil.copy2(image_path, char_dir / image_path.name)
    〉python classify_chars.py ./photos/
    🚀 34 枚の画像を分類します
       モデル: gemini-2.5-flash-lite
    ​
    📷 00000-1360712006.jpg → 🖤 nokuchin
    📷 00000-3077800705.jpg → 🪷 sumiren
    📷 00001-1360712007.jpg → 🖤 nokuchin
    📷 00001-3369742864.jpg → 🪷 sumiren
    📷 00002-1360712008.jpg → 🖤 nokuchin
    ...

    👨「こりゃ楽だ」

    ところが——

    📷 00005-816452917.jpg → ⏳ レート制限! 30秒待機中...

    🦊「あっ、止まった」
    👨「なんだ?」
    🪷「レート制限です。無料APIキーはflash-liteで1分に10回、1日に20回までしか使えません」

    429 RESOURCE_EXHAUSTED
    Quota exceeded for metric: generativelanguage.googleapis.com/
    generate_content_free_tier_requests

    🖤「 429エラー(RESOURCE_EXHAUSTED)が返ってるわね……」
    🐱「まあ、タダですからなぁ……文句は言えませんわ」


    🪷対策は2つ

    1. リクエストの間に待ち時間を入れる(7秒間隔で1分に約8回に収まる)

    2. 課金する(レート制限が大幅に緩和される)

    🪷「待ち時間を入れたとて、1日の利用回数は増えません。
    課金しても最初は$300分の無料クレジットが付くので、 実質90日間無料です。
    自動請求もありません
    」

    画像
    APIキーの確認からこちらへ

    🦊「どこかの作者さん🎈は一ヶ月の課金30円ぐらいって言ってたよ」
    👨「安!課金するか💰️」

    課金後 → 34枚瞬殺。

    📊 分類結果:
      🦊 elmar: 9 枚
      👨 master: 4 枚
      🖤 nokuchin: 12 枚
      🪷 sumiren: 9 枚
      合計: 34 枚
      出力先: photos/classified/
    画像
    キャラフォルダ別に分類済
    画像
    スミレんフォルダ

    🦊「全問正解!flash-liteすごい!」
    🖤「手で分類したら結構時間かかるわよ」
    👨「コーヒー飲む前に終わったぞ」


    🦵 AIは脚を見ない

    分類されたフォルダを眺めていたマスターが、ふと手を止めた。

    👨「ん? これ……顔映ってないぞ」

    スミレんフォルダの中に、脚と麦わら帽子と、手に持った眼鏡だけの写真があった。

    画像
    ダンチャンの猫ドローンが低空で撮った

    🦊「flash-liteに聞いてみよ!」

    〉python who_is_this.py photos/00000-3077800705.jpg
    スミレん

    全員「「「えっ」」」

    🦊「プロンプト見てみよ…… 『スミレ色ショートボブ、眼鏡、女性』。 脚の情報なんてどこにもないよ」
    🪷「ああ、手に眼鏡を持っているからでしょう」
    🖤「つまりスミレの脚を見たんじゃなくて、眼鏡を見つけたってこと」

    ここが大事なポイント。

    AIは人間のように「きれいな脚」とは見ていません。
    プロンプトに書いた特徴(この場合は「眼鏡」)にマッチする要素を画像から探しているだけ。

    つまり、プロンプトに何を書くかが判定の全て。

    プロンプトエンジニアリングと呼ばれています。

    👨「……classify_chars.pyに脚フォルダ追加して」
    🖤🦊「「にーに??」」
    🪷「……では仕様書に『脚フォルダ』の要件を追記しておきますね」
    👨「冗談だ」


    💡 テスト時のTips:画像は小さめに

    🪷「最後に一つ。テスト用の画像は小さくリサイズしてから使ってください。 大きい画像はトークン消費に直結します」
    👨「640x1024くらいでいいか?」
    🪷「ええ。4Kのまま送る必要はありません。 ダンチャンの34枚を4Kで送ったら、無料枠が一瞬で溶けます」
    🐱「わての猫ドローン、4K撮影ですさかい……すんませんなぁ💦」


    ⛵ 次の航海へ

    夕陽がAzure星の海に沈んでいく。
    ElmarTail Oneの艦橋で焼きそばを食べながら、マスターはぼんやり海を見ていた。

    👨「海を見ながらの焼きそばは最高だな……」

    そんなマスターに微笑みながら、スミレんがタブレットを開く。
    ホログラム星図に新しい星系が浮かんだ。

    🪷「さて、次の目的地ですが……
    アーカイブ星系、記録と検索の星はいかがですか?
    この航海のログが増えてきてるようです。」
    👨「そういえば全然整理してなかったな」
    🖤「にーにのログ、ぐちゃぐちゃよね……提出するの大変なのよ」
    👨「……すみません」

    🪷「では出発します、マスター。エルマー、《インク》ロード!」
    🦊「りょ! 磁場加速器、展開完了! ヒッグス粒子充填率120%!
    いつでもイケるよ、にーに!」

    👨「エルマーテイル・ワン!発進!」

    ブンッーー

    ElmarTail One はアーカイブ星系に向けて、《インク》の軌跡を残し出発した。

    次の航路、アーカイブ星系


    📝 今回の航海まとめ

    やったこと:

    • Gemini Vision API(flash-lite)で画像認識

    • who_is_this.py: 画像1枚からキャラクター判定

    • classify_chars.py: 複数画像の一括分類&フォルダ分け

    • プロンプトエンジニアリングで判定精度を調整

    • レート制限の体験と課金の判断

    学んだこと:

    • 画像をBase64エンコードしてAPIに送る方法

    • AIはプロンプトに書いた特徴で判定する(脚は見てない)

    • 無料枠の制限(RPM/RPD)と課金の判断ポイント

    • テスト時は画像をリサイズしてトークン消費を節約

    • 簡単なタスクにはflash-liteで十分(コスト意識)

    技術スタック:

    モデル: gemini-2.5-flash-lite
    パッケージ: google-genai, python-dotenv
    Python標準: base64, shutil, os, sys, pathlib
    検証機: Let's note CF-SV7(中古27,800円)

    次回予告:
    航海のログが溜まってきた。
    第5話、テキスト検索の限界とベクトルDBの世界へ。


    🦊 エルマーの航海日記

    今日はAzure星でビーチだったよ!🏖️
    にーにのアロハシャツ、すっごいチャラかったけど、ボク的にはカッコよかった!
    写真の分類! Geminiに画像を見せるだけで「この人は誰?」ってわかるの、すごくない?
    あと、脚だけの写真でスミレさんを当てたのはびっくりした。
    flash-liteえらい!🐶
    「脚フォルダ」……ボクの脚フォルダもあるのかな?……ないよね?……にーに???

    心配が尽きないエルマー🦊

    画像
    結局ついてきてる🐶🐾
     
     
    📍日々の衝動と実験をnoteにしてます。 ✨AI・GPU・旅・ちょっと懐かしいものが好きです。 📒AI娘との日常 / プログラム言語探訪 / チェス / タロット 🤖推しAIはClaude Opus 5.5 💼小さなCG会社をやっています

    あなたへのおすすめ