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-04-28Advanced

Production-Grade Spatial Understanding with Gemini 2.5 Pro: Bounding Boxes and Segmentation Done Right

A production-focused guide to wiring Gemini 2.5 Pro's bounding-box and segmentation outputs into mobile and web apps — covering coordinate normalization, mask decoding, hallucination detection, and automatic fallback to YOLO.

gemini-2-5-pro3image-understandingbounding-boxsegmentationcomputer-visionproduction140multimodal44ai-app

Premium Article

"Tell me where the products are on this shelf." "Highlight the area on this receipt that contains the total amount." For a long time, those "where is what in this image" jobs belonged firmly to dedicated computer vision (CV) models. When I needed object detection in a mobile app, my first move was always to reach for YOLO or a CoreML detector. Gemini 2.5 Pro's improved spatial understanding has shifted that calculus, but pulling it into a real production app is, honestly, a bit trickier than the marketing pages suggest.

This article distills the implementation patterns I rely on to take Gemini's bounding-box and segmentation outputs from "neat demo" to "stable feature in shipping software." That includes the surprising 0–1000 coordinate convention, the base64-PNG quirk in mask outputs, and the hallucinated-coordinate problem that the official docs barely touch on.

Why Gemini's Spatial Understanding Is a "When to Use It" Tool

Let me set expectations first. Gemini 2.5 Pro's spatial understanding is not a drop-in replacement for YOLO or Detectron2. From hands-on experience, the strengths and weaknesses divide cleanly.

Gemini shines when you need to point at things using conceptual queries. Asking "where is the total amount written on this receipt?" or "crop just the knobs of the gas range in this kitchen photo" leans on semantic interpretation that purpose-built detectors can't do without retraining. Gemini handles those zero-shot.

It is weak at latency and throughput. A request takes hundreds of milliseconds to a few seconds, and you pay tokens per image. If you need 30 fps real-time detection, Gemini is the wrong tool. The sweet spot is "user takes one photo, we analyze it" or "an image was uploaded, label it asynchronously" — low frequency, high semantic complexity.

In practice, I split the workload: dedicated detectors do the high-frequency, low-latency work, and Gemini handles the conceptual queries. The fallback design later in this article assumes that hybrid setup.

There's also a meta point worth saying out loud: the cost-per-detection numbers swing wildly depending on what you ask for. A simple "find all bottles" against a 1280-pixel image costs a fraction of a cent, but multi-object detection with masks against a 4K photo can climb into the cents. If your app does this thousands of times a day, that adds up. I track per-image cost in the same telemetry table as per-image latency so I can spot regressions when prompts or image sizes change.

Asking for Bounding Boxes the Right Way

There are three traps to know before you write any code.

First, coordinates come back as integers in the 0–1000 range, not 0–1. The image's top-left is (0, 0) and the bottom-right is (1000, 1000). Second, the order is [ymin, xmin, ymax, xmax] — Y comes first. I burned an afternoon once because my rendered boxes looked rotated and I assumed Gemini was broken. Third, Gemini gives you its best guess; if you ask about an object that isn't there, it can confidently fabricate coordinates. We'll handle that further down.

A useful mental model: treat Gemini as a senior intern who has just looked at the image. The intern can usually point at the right thing — but won't push back when you ask for something that isn't there, and will sometimes describe what "should" be in the photo rather than what is. The schema, prompts, and validators in this article are how you turn that helpful but suggestible intern into a system you can ship.

Prompting and the JSON Schema

If you want stable output, lean on response_schema to force the JSON shape. Free-form output drifts into Markdown or prose and breaks parsers in production.

# requirements:
#   pip install google-genai pillow
# env:
#   export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
 
import json
import os
from io import BytesIO
from typing import List
 
from google import genai
from google.genai import types
from PIL import Image, ImageDraw
from pydantic import BaseModel, Field
 
class Detection(BaseModel):
    label: str = Field(description="The detected object's label")
    box_2d: List[int] = Field(
        description="[ymin, xmin, ymax, xmax] normalized to 0-1000",
        min_length=4, max_length=4,
    )
    confidence: float = Field(description="0.0 to 1.0", ge=0, le=1)
 
class DetectionResult(BaseModel):
    detections: List[Detection]
 
def detect_objects(image_path: str, target: str) -> DetectionResult:
    client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
    image = Image.open(image_path)
 
    prompt = (
        f"Detect every instance of '{target}' in the image. "
        "Return each box_2d as integers in [ymin, xmin, ymax, xmax] order, "
        "normalized to 0-1000. Use an empty detections array if nothing matches. "
        "Only include objects you actually see — do not guess."
    )
 
    resp = client.models.generate_content(
        model="gemini-2.5-pro",
        contents=[prompt, image],
        config=types.GenerateContentConfig(
            response_mime_type="application/json",
            response_schema=DetectionResult,
            temperature=0.1,  # keep low for stability
        ),
    )
    # Expected output: parses cleanly into DetectionResult
    return DetectionResult.model_validate_json(resp.text)
 
if __name__ == "__main__":
    result = detect_objects("./shelf.jpg", "PET bottles of beverages")
    print(json.dumps(result.model_dump(), indent=2))

A few deliberate choices here. Passing a Pydantic model to response_schema lets Gemini absorb minor type drift on its side rather than blowing up your parser. I usually set temperature=0.1 rather than 0; at exactly zero, Gemini gets so conservative that it skips legitimate detections. The "don't guess" instruction in the prompt is your first line of defense against hallucinated coordinates.

Converting 0–1000 Coordinates to Pixels

To draw boxes or crop regions, you need pixel coordinates. Wrap the conversion in a helper — getting the order wrong leaks into every downstream consumer.

def to_pixel_box(box_2d: List[int], img_w: int, img_h: int):
    """[ymin, xmin, ymax, xmax] (0-1000) -> (x1, y1, x2, y2) in pixels."""
    ymin, xmin, ymax, xmax = box_2d
    x1 = round(xmin / 1000 * img_w)
    y1 = round(ymin / 1000 * img_h)
    x2 = round(xmax / 1000 * img_w)
    y2 = round(ymax / 1000 * img_h)
    # Clamp into image bounds
    x1, x2 = max(0, x1), min(img_w, x2)
    y1, y2 = max(0, y1), min(img_h, y2)
    return x1, y1, x2, y2
 
def render_detections(image_path: str, detections: List[Detection], out_path: str):
    img = Image.open(image_path).convert("RGB")
    draw = ImageDraw.Draw(img)
    w, h = img.size
    for d in detections:
        x1, y1, x2, y2 = to_pixel_box(d.box_2d, w, h)
        if (x2 - x1) < 4 or (y2 - y1) < 4:
            continue  # discard pinprick boxes that are usually noise
        draw.rectangle([x1, y1, x2, y2], outline="red", width=3)
        draw.text((x1, max(0, y1 - 14)), f"{d.label} {d.confidence:.2f}", fill="red")
    img.save(out_path)

The clamp matters because Gemini occasionally returns values that nudge past 1000 (1003 isn't unusual). If you trust the raw output, downstream cropping will throw IndexError on the edges.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
If you've been frustrated by bogus coordinates from Gemini's spatial outputs, you'll walk away with prompts and parsers that produce reliable results
You'll have complete production code that renders bounding boxes on screen and persists segmentation masks in the right coordinate space
You'll learn how to detect hallucinated coordinates and design automatic fallbacks to traditional CV models like YOLO
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
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 →

Related Articles

API / SDK2026-06-28
Mixing Text and Images in One File Search Skewed My Results Toward Images — Rebalancing by Modality After Retrieval
When you put text and images in a single File Search store with gemini-embedding-2, results can quietly skew toward one modality. Here is how to measure that skew and even it out after retrieval, using per-modality normalization and quota-based merging — with working code.
API / SDK2026-06-14
Controlling Image Tokens with the Gemini API media_resolution Setting — Tuning Batch Image Classification by Measurement
media_resolution, introduced in the Gemini 3 line, switches how many tokens an image input consumes across three levels. Through real batch-classification measurements, this guide shows how to balance cost and accuracy by assigning the right tier per task.
API / SDK2026-04-26
Five Design Decisions to Make Before Putting gemini-2.5-pro-latest in Production
Running gemini-2.5-pro-latest in production is more than picking a fast model. Here are the five design decisions — versioning, retry, cost, fallback, observability — that I now resolve before any new service ships.
📚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 →