GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-05-18中級

Gemini Vision で壁紙アプリの自動カテゴリ分類を実装した話

個人開発の壁紙アプリで Gemini Vision API を使い、画像の自動カテゴリ分類を実装した実体験です。精度改善のプロセスと、公式ドキュメントには載っていない落とし穴、GPT-4o Vision とのコスト比較までまとめました。

gemini102gemini-api279vision4multimodal24python103個人開発91壁紙アプリ7

Beautiful HD Wallpapersの Android 版(v2.0.0)を Google Play に出す準備をしていた頃、壁紙のカテゴリ分類に頭を抱えていました。アプリには数千枚の画像があり、「自然」「都市」「抽象」「動物」「宇宙」といった30以上のカテゴリに正確に分類するのが、長年の課題でした。手作業で行うと数日かかる作業を、Gemini Vision API でどこまで自動化できるか試した記録です。

結論から言うと、精度は想像以上でしたが、画像の曖昧さに対するモデルの振る舞いが公式ドキュメントだけでは読み切れず、実装面でいくつかの工夫が必要でした。

個人開発を長く続けるなかで、画像の整理・分類作業はいつも頭の痛い問題でした。特に Beautiful HD Wallpapers は画像点数が多く、新しい壁紙を追加するたびにカテゴリ判断へ時間を取られていました。「いつかAIで自動化できないか」と思いながら年月が過ぎ、ようやく実用に足る解が見つかったという感覚があります。

なぜ Gemini Vision を選んだか

壁紙の自動分類には、いくつかの選択肢がありました。

ローカルの画像分類モデル(MobileNet, EfficientNet)は高速で安価ですが、カテゴリ定義が固定されており、「幻想的な森」「水彩画風の夜景」のような曖昧なカテゴリへの対応が難しいです。これらのモデルはImageNetのクラス分類に最適化されており、独自カテゴリへの転移学習には相応のデータとトレーニング工数が必要になります。

GPT-4o Vision は精度が高い一方で、Gemini 2.0 Flash と比較して1リクエストあたり約2〜3倍のコストがかかります。個人開発で数千枚の画像を一括処理することを考えると、この差は無視できません。

最終的に Gemini 2.0 Flash(マルチモーダル)を選んだ理由は、コストと精度のバランスでした。2026年5月時点で 1,000トークンあたり約$0.0001 程度。GPT-4o Vision と比べると大幅に安価です。加えて、Function Calling による構造化出力との親和性が高く、JSON形式での回答をプロンプト側でコントロールしやすい点も魅力でした。

基本的な実装

まず、Gemini APIで壁紙を分類する最小構成のコードです。

import google.generativeai as genai
from PIL import Image
import json
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
 
# カテゴリ定義(アプリのカテゴリ一覧)
CATEGORIES = [
    "nature", "city", "abstract", "animals", "space",
    "ocean", "mountain", "forest", "flower", "architecture",
    "art", "minimal", "dark", "colorful", "japan",
]
 
def classify_wallpaper(image_path: str) -> dict:
    """
    壁紙画像をGemini Visionで分類する
    戻り値: {"category": "nature", "confidence": "high", "reason": "..."}
    """
    model = genai.GenerativeModel("gemini-2.0-flash")
    image = Image.open(image_path)
 
    prompt = f"""
あなたは壁紙アプリのカテゴリ分類システムです。
この画像を以下のカテゴリから1つだけ選んでください:
{', '.join(CATEGORIES)}
 
以下のJSON形式で回答してください:
{{
  "category": "選んだカテゴリ(英語)",
  "confidence": "high / medium / low",
  "reason": "分類の根拠(日本語で1文)"
}}
 
必ずJSON形式のみで回答し、他の説明は不要です。
"""
 
    response = model.generate_content([prompt, image])
 
    # JSON抽出(モデルが```jsonブロックで囲んで返すことがある)
    text = response.text.strip()
    if "```json" in text:
        text = text.split("```json")[1].split("```")[0].strip()
    elif "```" in text:
        text = text.split("```")[1].split("```")[0].strip()
 
    return json.loads(text)
 
# 使用例
result = classify_wallpaper("wallpaper_001.jpg")
print(result)
# 出力例: {"category": "nature", "confidence": "high", "reason": "緑豊かな森の朝霧を捉えた写真"}

このコードは動きますが、本番投入するとすぐに問題が出てきます。

実際に起きた問題と解決策

JSONパースの失敗

Gemini 2.0 Flash は、指示してもJSON以外のテキストを含めて返すことがあります。特に「confidence: low」の場合に「この画像は分類が難しいです。最も近いカテゴリとしては...」のような前置きを付けることがありました。

最初は単純な json.loads() で対応していましたが、本番では10〜15%のレスポンスでパースに失敗しました。正規表現でJSONブロックを抽出する関数を作ることで安定しました。

import re
 
def extract_json_safely(text: str) -> dict | None:
    """レスポンステキストからJSONを安全に抽出する"""
    # コードブロックを除去
    text = re.sub(r'```(?:json)?\n?', '', text).strip()
 
    # JSON部分だけを正規表現で抽出
    json_match = re.search(r'\{[^{}]*\}', text, re.DOTALL)
    if not json_match:
        return None
 
    try:
        return json.loads(json_match.group())
    except json.JSONDecodeError:
        return None

公式ドキュメントにはJSONモードの設定として response_mime_type="application/json" を使う方法も記載されています。ただし、この設定を有効にすると reason フィールドに日本語が含まれる場合に文字化けするケースが稀に発生しました。プロンプト側でのJSON指示 + 正規表現抽出の組み合わせの方が、私の環境では安定していました。

曖昧な画像のカテゴリ定義

「抽象的な水彩画の森」「宇宙のような暗い海中」など、複数のカテゴリにまたがる画像で分類が不安定になることがありました。試行錯誤の末、プロンプトに**各カテゴリの「これに含まれない例」**を追加することで精度が大幅に改善しました。

CATEGORY_DESCRIPTIONS = {
    "nature": "森・草原・田舎の風景。都市の建物が入っていたら architecture か city を選ぶ",
    "ocean": "海・海岸・波。水族館の水中写真も含む。川や湖は nature",
    "abstract": "現実の物体が認識できない抽象的なパターン・グラデーション・デジタルアート",
    "art": "絵画・イラスト・アニメ風・水彩画。写真でも強く加工されているものも含む",
    "japan": "日本の神社仏閣・桜・浮世絵・着物。都市の場合でも日本的な要素が主題であれば japan",
}
 
def build_prompt_with_descriptions(categories: list, descriptions: dict) -> str:
    cat_lines = []
    for cat in categories:
        desc = descriptions.get(cat, "")
        if desc:
            cat_lines.append(f"- {cat}: {desc}")
        else:
            cat_lines.append(f"- {cat}")
    return "\n".join(cat_lines)

カテゴリの除外例をプロンプトに埋め込んだ後、中信頼度(medium)の精度が78.6%から87.3%に向上しました。

バッチ処理でのレート制限

数千枚をバッチ処理すると、Gemini API のレート制限(無料枠: 15 RPM / 1,000 RPD)に引っかかりました。有料プランでも急激な並列リクエストは制限を受けます。エラーメッセージは 429 Resource Exhausted として返ってきます。

import time
 
def classify_batch(image_paths: list, rpm_limit: int = 30) -> list:
    """
    レート制限を守りながらバッチ処理する
    rpm_limit: 1分あたりの最大リクエスト数
    """
    results = []
    delay = 60 / rpm_limit  # リクエスト間隔(秒)
 
    for i, path in enumerate(image_paths):
        try:
            result = classify_wallpaper(path)
            results.append({"path": path, "status": "ok", **result})
        except Exception as e:
            # 429エラーは少し待ってリトライ
            if "429" in str(e):
                time.sleep(30)
                result = classify_wallpaper(path)
                results.append({"path": path, "status": "retry", **result})
            else:
                results.append({"path": path, "status": "error", "error": str(e)})
 
        if (i + 1) % 10 == 0:
            print(f"進行状況: {i + 1}/{len(image_paths)}")
 
        if i < len(image_paths) - 1:
            time.sleep(delay)
 
    return results

精度の実測値

Beautiful HD Wallpapers の実データ(2,400枚)で検証した結果です。

  • 高信頼度(high)の分類精度: 94.2%(2,400枚中 約1,400枚がhigh判定)
  • 中信頼度(medium)の分類精度: 78.6%→87.3%(カテゴリ説明追加後)
  • 低信頼度(low)の分類精度: 51.3%(約150枚がlow判定)

confidence: low の画像は手動確認キューに入れる運用にすることで、全体の自動化率は約92%を達成しました。Gemini Vision を使う前は、手作業で1日200〜300枚が限界でしたが、バッチ処理で1時間あたり1,000枚以上を処理できるようになりました。

精度の誤分類パターンで特に多かったのは、「japan」カテゴリへの誤判定でした。桜の木が入った「nature」画像を「japan」と判定するケースが頻発したため、japan の定義に「自然風景の中に桜だけがある場合は nature を選ぶ」という一文を追加しました。

Gemini 2.0 Flash と GPT-4o Vision のコスト比較

同じ2,400枚の壁紙を両モデルで分類したときのコストです。

  • Gemini 2.0 Flash: 約$0.18(画像+テキスト合計)
  • GPT-4o Vision: 約$0.54(同条件)

個人開発の規模では3倍の差は無視できません。精度差は高信頼度の分類で約1〜2%程度でした。コストを許容できるなら GPT-4o の方がやや安定していますが、壁紙分類のような用途では Gemini 2.0 Flash で十分だと感じています。

ただし、美術品・アート作品の分類に使う場合は話が変わります。浮世絵壁紙(Ukiyo-e Wallpapers)の分類では、「これは浮世絵の特定の時代様式か」「作者の特徴があるか」といった細かい判定が必要になることがあり、そこでは GPT-4o の方が明らかに安定していました。用途とコストを天秤にかけて選ぶのが現実的だと思います。

構造化出力で JSON パースを根本から減らす

この記事の実装は、正規表現で JSON を救出する方針でした。ただ、その後 SDK 側の構造化出力が実用域に入り、パース失敗そのものをほぼ起こさずに済むようになりました。新しい google-genai SDK では、response_schema に Pydantic モデルを渡すと、モデルがそのスキーマに沿った JSON だけを返してくれます。

from google import genai
from pydantic import BaseModel
from enum import Enum
 
class Confidence(str, Enum):
    high = "high"
    medium = "medium"
    low = "low"
 
class Classification(BaseModel):
    category: str
    confidence: Confidence
    reason: str
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
def classify_structured(image_path: str) -> Classification:
    img = client.files.upload(file=image_path)
    resp = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=["この壁紙を1カテゴリに分類してください。", img],
        config={
            "response_mime_type": "application/json",
            "response_schema": Classification,
        },
    )
    return Classification.model_validate_json(resp.text)

スキーマを渡す方式へ切り替えてから、前置きテキストの混入による json.loads() の失敗は、私の手元ではほぼゼロになりました。confidenceEnum で縛っておくと、"とても高い" のような表記揺れも防げます。記事公開当時に困っていた日本語 reason の文字化けも、最近のモデルでは再現しなくなりました。冒頭で紹介した正規表現フォールバックは、response_schema 非対応の古いモデルを併用する場合の保険として残しておけば十分です。

モデル名は手元の利用環境に合わせて選んでください。軽量ティアの Flash 系で一括分類し、判定が割れた画像だけ上位ティアで再分類する二段構えにすると、コストを抑えつつ精度を底上げできます。

自動化と人間の目をどう分けるか

壁紙アプリを長く運営してきて感じるのは、「美しさ」には数値化しきれない何かが残るということです。AI が mediumlow と返してくる壁紙のなかにも、人の目が引き寄せられる一枚は確かにあります。逆に high でも、並べてみると単調に見えるものもあります。分類の正しさと、選ばれる魅力は別物だと痛感します。

だからこそ私自身は、完全自動化ではなく「92%自動 + 8%人間」という運用に落ち着いています。明確に判定できる画像は思い切って AI に任せ、confidence: low の手動確認キューに人の目を集中させる。この線引きが、個人で運営を続けるうえでは最も無理がないと感じています。

運用を回すうえで効いたのは、reason フィールドをログに残しておくことでした。誤分類が起きたとき、モデルが「なぜそのカテゴリを選んだか」を後から読み返せるので、プロンプトのどの記述を直せばよいかがすぐ分かります。精度を一度に上げようとせず、reason を手がかりにカテゴリ定義を一文ずつ調整していく。結局これが、いちばん遠回りに見えていちばん早い改善ルートでした。

次のステップ

まず Google AI Studio でAPIキーを取得し、上記の最小構成コードをそのまま試してみてください。gemini-2.0-flash であれば、無料枠の範囲内でも数百枚の分類を試せます。

カテゴリ定義を自分のアプリに合わせて書き換えるだけで、すぐに実用的な分類器として機能します。まずは100枚程度の小さなバッチで試して、精度感を掴んでから本番規模に拡張していくのがおすすめです。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-05-16
Gemini Vision で壁紙アプリのカテゴリ自動分類を試した — 精度と落とし穴の実記録
壁紙アプリ(累計5,000万DL超)の運営者が Gemini Vision を使って画像カテゴリ自動分類を実装。精度67%→87%への改善プロセスと、個人開発者が実運用で感じた限界を具体的に紹介します。
API / SDK2026-03-30
Gemini API × Python で音声ファイルを文字起こし・要約するアプリを作る方法
Gemini APIのマルチモーダル機能を使い、Pythonで音声ファイルの文字起こしと自動要約を行うアプリの作り方をステップバイステップで解説します。
API / SDK2026-07-14
Gemini Code Execution の計算結果とモデルの文章がずれる — 実行結果だけを信頼する検証ゲートの実装
Gemini の Code Execution は実際に走らせた計算結果と、それを説明する自然文を別々に返します。文章側の数値を鵜呑みにすると幻覚を掴みます。実行結果だけを真実として抽出し照合する検証ゲートの実装を、動くコードで解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →