GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-05-03Intermediate

Auto-Categorizing 3,000 Wallpaper Images With Gemini Vision API — A Real Production Account

Manually categorizing thousands of wallpaper images doesn't scale. This is a hands-on account of building an auto-classification pipeline with Gemini Vision API — covering design, implementation, actual cost, and the failure patterns I hit running 3,000 images through it.

Gemini API192Vision APIImage Classification3Wallpaper App2Batch Processing5Indie Dev7

If you run a wallpaper app long enough, you hit the same wall: images accumulate faster than you can classify them.

My wallpaper app has over 3,000 images. Early on I categorized everything by hand. At a few hundred additions per month, that turned into several hours of classification work every week — just labeling, not building.

So I built an auto-classification pipeline using Gemini Vision API. The short result: 93% accuracy on custom categories, at roughly a few cents per hundred images. Here's the full account of how I built it, what broke, and what I'd do differently.

Why Manual Classification Breaks Down

Wallpaper categorization looks trivial from the outside. "This is nature scenery. This is minimalist. This is dark theme." Straightforward.

Three problems emerge in practice:

Inconsistency. My judgment varies depending on focus level. Several App Store reviews mentioned categories that felt mismatched — they were right. I wasn't consistent across 3,000 decisions.

Scale. 100 images by hand is fine. 1,000 takes a day. 3,000 breaks the release cycle.

Multilingual overhead. My app supports multiple languages. Every manual category decision also meant translating category names and descriptions. The cognitive overhead compounds.

Why Gemini Vision API

Other options exist — Google Cloud Vision API, AWS Rekognition. I chose Gemini for three reasons:

Custom category support. Standard vision APIs return generic labels: "mountain," "sky," "person." My app uses custom categories: "Lo-fi chill," "Gradient," "Cyberpunk neon." Gemini accepts category lists in natural language, so I can define the exact taxonomy my app uses.

Simultaneous description generation. The same API call that classifies an image can also generate a short description — useful for App Store metadata and language-specific tags.

Predictable cost. With Gemini Flash, processing 1,000 images runs to a few cents. For an indie app, that's a manageable and foreseeable cost.

Implementation: The Classification Pipeline

The flow is straightforward:

List image files → Send image + category list to Gemini
→ Parse response → Save to DB → Low-confidence → manual review queue
import google.generativeai as genai
import json
from pathlib import Path
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-2.0-flash")
 
CATEGORIES = [
    "Nature Scenery", "Minimalist", "Dark Theme", "Gradient",
    "Abstract Art", "Cityscape", "Sunset", "Ocean",
    "Forest", "Space", "Flowers & Plants", "Animals",
    "Texture", "Pattern", "Typography", "Architecture",
    "Food", "Sports", "Technology", "Gaming",
    "Anime & Illustration", "Watercolor & Art", "Monochrome", "Vintage",
    "Neon & Cyberpunk", "Japanese Style", "Nordic Style", "Lo-fi",
    "Seasonal", "Other"
]
 
def classify_wallpaper(image_path: str) -> dict:
    image_file = genai.upload_file(image_path)
 
    prompt = f"""Analyze this wallpaper image and select the most appropriate category.
 
Available categories:
{json.dumps(CATEGORIES)}
 
Respond in this exact JSON format:
{{
  "primary_category": "category name from the list",
  "secondary_category": "optional secondary category",
  "confidence": 0.0 to 1.0,
  "description_en": "atmosphere description in 30 words or fewer",
  "tags": ["tag1", "tag2", "tag3"]
}}
 
Return JSON only."""
 
    response = model.generate_content([image_file, prompt])
 
    try:
        return json.loads(response.text.strip())
    except json.JSONDecodeError:
        return {"primary_category": "Other", "confidence": 0.0, "error": response.text}

Batch Processing 3,000 Images Without Hitting Rate Limits

Processing one image at a time runs into rate limits quickly. Add a brief delay between calls and log progress to recover gracefully from interruptions.

import time
from pathlib import Path
 
def classify_batch(image_dir: str, output_file: str):
    images = list(Path(image_dir).glob("*.jpg")) + list(Path(image_dir).glob("*.png"))
    results = []
    manual_review_queue = []
 
    print(f"Processing {len(images)} images")
 
    for i, img_path in enumerate(images):
        result = classify_wallpaper(str(img_path))
        result["filename"] = img_path.name
        results.append(result)
 
        if result.get("confidence", 0) < 0.7:
            manual_review_queue.append(img_path.name)
 
        if (i + 1) % 50 == 0:
            print(f"Progress: {i+1}/{len(images)} | Manual review queue: {len(manual_review_queue)}")
 
        time.sleep(0.5)  # Rate limit buffer
 
    with open(output_file, "w", encoding="utf-8") as f:
        json.dump({
            "total": len(images),
            "manual_review_count": len(manual_review_queue),
            "manual_review_queue": manual_review_queue,
            "results": results
        }, f, indent=2)
 
    print(f"Done. Manual review needed: {len(manual_review_queue)} images")

What Actually Happened When I Ran This

Accuracy. 93% of images were classified at confidence ≥ 0.7 and routed to automatic assignment. The remaining 7% (~210 images) landed in the manual queue. Almost all were images that genuinely span two categories — "nature scenery" that's also "minimalist," for example. That ambiguity isn't a model failure; it's a real classification problem that humans find equally difficult.

Cost. Processing 3,000 images with Gemini 2.0 Flash, including image input tokens, cost approximately ¥450 (roughly $3 USD). Compare that to the time cost of manual classification and it's not a close comparison.

Failure patterns I hit:

  • Near-black or near-white images: the model wavers between "Monochrome" and "Minimalist"
  • Complex art pieces: classification stability drops when an image has competing visual elements
  • Low-resolution thumbnails: detail is lost, accuracy falls

Prompt refinements improved these cases significantly. Adding "If uncertain between categories, prefer 'Other'" reduced the number of overconfident wrong answers.

What's Next: Closing the Feedback Loop

I'm currently experimenting with feeding user engagement data — save rate, favorites count, time spent viewing — back into the classification prompts. The hypothesis is that images users strongly prefer share classifiable visual characteristics, and Gemini can learn to weight those.

This is still early, but it's the direction: not just "classify what the image looks like" but "classify how users respond to it." The gap between those two things is where interesting product decisions live.

For an indie app, replacing 5–10 hours per month of manual classification with a $3 API call is the kind of leverage that makes the difference between sustainable and not. Small automations compound.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API / SDK2026-07-16
I Asked Gemini to Grade My App Store Screenshots. Everything Scored 78–85.
Ask Gemini Vision to grade App Store screenshots out of 100 and good candidates and deliberately broken ones both land at 78–85. Here is how I measured the judge's discrimination power, dropped absolute scoring, and rebuilt it as debiased pairwise comparison.
API / SDK2026-07-11
When Gemini's Context Cache Quietly Expires Mid-Run: A TTL Guard for Pipelines That Pause
When a nightly batch or a retry backoff pauses your pipeline, Gemini's explicit context cache can expire on the wall clock while nothing errors out, sending later calls back to full-token billing. Here is a small lease guard that decides whether to re-arm or run uncached based on cost.
API / SDK2026-07-05
Designing Batch Image Costs with Nano Banana 2 Lite: Decide by Measuring
How to fold the fastest, cheapest image model, Nano Banana 2 Lite, into high-volume generation: measuring per-image cost, a two-tier setup with a quality model, and retry handling grounded in real numbers.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →