"The same image worked yesterday but fails today" — that's how my early experiments with Claude Vision felt. I tried rewriting prompts in every direction, and nothing stuck. What finally moved the needle wasn't the prompt at all. It was how I was sending the image.
The official docs mention that images get resized to fit within a 1568px long edge, but the things that actually trip developers up live in the spaces between those guidelines. This article shares what I learned while shipping a wallpaper-tagging feature and a receipt-scanning side project: the preprocessing patterns that quietly determine whether Claude Vision succeeds or fails.
Why Preprocessing Beats Prompt Engineering
Claude Vision normalizes every incoming image before processing. That normalization isn't a simple resize — it accounts for aspect ratio, noise, and surrounding whitespace. The implication is uncomfortable: information your image loses at this stage cannot be recovered through better prompts.
In my wallpaper app experiments, sending 3,000×4,000px artwork directly produced less consistent color and motif descriptions than first downscaling the long edge to 1,500px. Why? Claude's internal normalization happens to land on a more useful representation when the input sits in the 1,500–1,600px range. Push beyond that and the algorithm compresses too aggressively for fine detail.
The official numbers are guidelines, not laws. The optimal value depends on what's in your image — how much text, how detailed the subjects, how busy the composition.
The Five Things to Check When Accuracy Drops
When results are off, I work through these five suspects in order:
- Resolution mismatch — too high or too low. The 1,200–1,600px long-edge range is a reliable starting point. Don't upscale low-resolution screenshots
- Extreme aspect ratios — very tall or very wide images shrink the subject during normalization. Crop to the part that matters
- Wrong file format — text and diagrams want PNG, photos and gradients want JPEG (quality 85–90)
- Low contrast — small luminance differences between subject and background tank recognition rates. A modest contrast boost often fixes it
- Wrong model for the job — using
claude-opus-4-6for a basic OCR task is overkill. Oftenclaude-haiku-4-5produces equivalent results at a fraction of the cost
These factors interact in real images. The next sections walk through how to handle each one in code.
A Practical Preprocessing Pipeline in Python
Here's a Pillow-based preprocessing function that I drop in front of any Claude Vision call. In my own measurements, it improves result consistency by 20–30% on average.
from PIL import Image, ImageEnhance, ImageOps
import base64
import io
def prepare_image_for_claude(
image_path: str,
max_long_side: int = 1500,
enhance_contrast: bool = False,
output_format: str = "auto",
) -> tuple[str, str]:
"""
Returns an image optimized for Claude Vision.
Returns: (base64-encoded image data, MIME type)
"""
img = Image.open(image_path)
# Apply EXIF rotation — critical for phone photos
img = ImageOps.exif_transpose(img)
# Handle transparency before JPEG conversion
if img.mode in ("RGBA", "LA"):
background = Image.new("RGB", img.size, (255, 255, 255))
background.paste(img, mask=img.split()[-1])
img = background
elif img.mode != "RGB":
img = img.convert("RGB")
# Downscale only — never upscale
long_side = max(img.size)
if long_side > max_long_side:
ratio = max_long_side / long_side
new_size = (int(img.size[0] * ratio), int(img.size[1] * ratio))
img = img.resize(new_size, Image.LANCZOS)
# Contrast boost — helps with text and diagrams
if enhance_contrast:
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(1.2) # 1.0 is no change; above 1.5 looks unnatural
# Auto-pick format
if output_format == "auto":
# Few colors or smaller images go to PNG; rich photos go to JPEG
if long_side < 800 or len(img.getcolors(maxcolors=256) or []) > 0:
output_format = "PNG"
else:
output_format = "JPEG"
buffer = io.BytesIO()
if output_format == "JPEG":
img.save(buffer, format="JPEG", quality=88, optimize=True)
mime = "image/jpeg"
else:
img.save(buffer, format="PNG", optimize=True)
mime = "image/png"
encoded = base64.standard_b64encode(buffer.getvalue()).decode("utf-8")
return encoded, mime
# Usage
image_data, mime_type = prepare_image_for_claude(
"screenshot.png",
max_long_side=1500,
enhance_contrast=True, # Set to True for screenshots and diagrams
)
print(f"Preprocessed: {mime_type}, {len(image_data)} bytes")The clever bit here is getcolors(). Screenshots and diagrams typically have a limited color palette, which makes them ideal candidates for lossless PNG. Photos, with their rich color depth, return None from getcolors() and automatically fall through to JPEG. It's a small heuristic that removes a constant source of human error.
Calling the API — Message Order Matters
Here's the API call that consumes the preprocessed output. Using the official Anthropic Python SDK, but with a few production-tested choices:
import anthropic
client = anthropic.Anthropic(api_key="YOUR_ANTHROPIC_API_KEY")
def analyze_image_with_claude(
image_data: str,
mime_type: str,
task: str,
model: str = "claude-haiku-4-5-20251001",
) -> str:
"""
Send a preprocessed image to Claude Vision.
Use Haiku for simple classification or OCR; reserve Sonnet/Opus for complex analysis.
"""
response = client.messages.create(
model=model,
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": mime_type,
"data": image_data,
},
},
{
"type": "text",
"text": task,
},
],
}
],
)
return response.content[0].text
# Usage: extract dominant colors from artwork
result = analyze_image_with_claude(
image_data,
mime_type,
task="Identify the three dominant colors in this image and describe the mood it evokes. Keep it concise.",
model="claude-haiku-4-5-20251001", # Plenty for a simple classification task
)
print(result)One detail that's easy to miss: put the image first, the instruction second. The Anthropic docs note this, but the impact is bigger than it sounds. With the image leading, Claude treats the text that follows as commentary about that specific image. Reverse the order and the model sometimes anchors on the text and skims the visual.
Picking the Right Model
My production rule of thumb:
claude-haiku-4-5— basic object recognition, color extraction, low-resolution OCR (receipts, business cards)claude-sonnet-4-6— composition analysis, mixed text-and-structure extraction from diagrams, moderate handwritingclaude-opus-4-6— complex technical illustrations requiring inference, difficult handwriting, scene reasoning
When accuracy disappoints, the right first move is fix preprocessing, not "throw a bigger model at it." A poorly preprocessed image fed to Opus will burn budget without much improvement. A well-prepared image often runs fine on Haiku.
This matters for cost too. If you're calling Vision in an app loop, develop your preprocessing on Haiku and only graduate to Sonnet or Opus where your evaluations show it's needed.
Failure Modes I Actually Hit
A few mistakes I made so you don't have to:
Pitfall 1: sending the original 4K image
Higher resolution does not mean better recognition for Claude Vision. Send 4K and the internal normalization may compress past the point where fine details survive. Downscale to ~1,500px on the long edge first — this is the sweet spot in my experience.
Pitfall 2: saving screenshots as JPEG
JPEG compression introduces artifacts around sharp text edges. For UI screenshots or diagrams, this destroys OCR accuracy. Always use PNG for text-bearing images. The preprocessing function above picks the right format automatically, but it's worth understanding why.
Pitfall 3: ignoring EXIF rotation
Photos from phones store their pixel data one way and use EXIF metadata to indicate the correct orientation. If you load the file naively, Claude sees a 90-degree-rotated image. Always pipe through ImageOps.exif_transpose().
Pitfall 4: mixing multiple images without labels
Send three images and ask "find the one that matches X" and Claude will sometimes confuse which image is which. When sending multiple images, label them in adjacent text — "Image 1: …", "Image 2: …" — to keep the references straight.
Pitfall 5: aggressive contrast on photos
The contrast boost in the preprocessing function (enhance_contrast=True) helps screenshots and diagrams, but it can hurt photos and artwork. Boosted contrast clips highlights and shadows, which Claude may then misinterpret as flat regions or solid colors. Reserve the contrast flag for clearly text-bearing or chart-heavy images.
Building a Quick Evaluation Loop
If you ship Vision in production, you need a way to know when accuracy regresses. I keep a small evaluation set — 20 to 30 representative images with known correct answers — and run them through whenever I change preprocessing parameters or models. The setup is minimal:
test_cases = [
{"image": "samples/receipt_001.jpg", "expected": "TOTAL: $42.50"},
{"image": "samples/diagram_002.png", "expected": "throughput"},
# ... 20-30 cases covering your real input distribution
]
for case in test_cases:
image_data, mime = prepare_image_for_claude(case["image"])
result = analyze_image_with_claude(
image_data, mime,
"Extract the text shown in the image.",
)
expected = case["expected"].lower()
passed = expected in result.lower()
status = "PASS" if passed else "FAIL"
print(f"[{status}] {case['image']}: {result[:60]}")This is not a sophisticated evaluation framework, but it catches regressions before they reach users. When I changed my default max_long_side from 2,000 to 1,500, this loop revealed which image categories improved and which regressed. Without it, I would have shipped a "fix" that helped some users and hurt others.
When the API Returns an Error
Even with clean preprocessing, you'll occasionally hit API errors. The common ones:
invalid_request_error(image too large) — single-file limit is 5MB. Base64 encoding inflates further. Resize or lower JPEG qualityinvalid_request_error(unsupported media type) — WebP works, but AVIF and HEIC need conversion to PNG/JPEG firstoverloaded_error(503) — transient load. Use exponential backoff or fall back toclaude-haiku-4-5
For Tool Use scenarios that interact with Vision results, see Claude API Tool Use Errors: Complete Troubleshooting Guide for systematic debugging patterns.
Going Deeper
For a broader introduction to Vision, Claude Vision Multimodal Guide is a good starting point. If you're working with PDFs, Claude API: PDF Analysis with Vision and Extended Thinking covers the document-specific patterns.
If you've been ready to give up on Vision because results felt unreliable, try this first: take one of your problem images, run it through a 1,500px long-edge resize, and re-pick the format with the snippet above. Five minutes of work, and your next API call will tell you whether preprocessing was the missing piece.