Compare Images with the Vision API
On this page
Visual Comparative Analysis
Image comparison is a powerful use case that leverages the multi-image capability covered in Lesson 6. The question shifts from “What does this image show?” to “What has changed?”—an operation that a semantic model handles fundamentally differently than a pixel comparator. Where a pixel difference signals a divergence without qualifying it, the model can state that a label shifted two millimeters and a logo disappeared, then judge which of the two matters.
Quality control, evolution tracking, visual regression detection: this lesson shows how to structure comparison prompts and leverage the results.
Before/After Comparison
The most common scenario involves verifying what changed between two states. The system message defines the comparison method, while the user message identifies the images:
from mistralai.client import Mistral
import os
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
response = client.chat.complete(
model="mistral-large-latest",
messages=[
{
"role": "system",
"content": """You are an expert in comparative image analysis. When presented with two images:
1. Briefly describe each image
2. List identified differences (with location)
3. Assess the significance of each difference (minor/major)
4. Provide an overall similarity score (0-100%)"""
},
{
"role": "user",
"content": [
{"type": "text", "text": "BEFORE Image (reference):"},
{"type": "image_url", "image_url": "https://exemple.com/avant.jpg"},
{"type": "text", "text": "AFTER Image (current state):"},
{"type": "image_url", "image_url": "https://exemple.com/apres.jpg"},
{"type": "text", "text": "Identify all differences between these two images."}
]
}
]
)
print(response.choices[0].message.content)
Interleaving text (“BEFORE Image”, “AFTER Image”) helps the model identify the role of each image. The third step in the system prompt is what makes the report actionable: without prioritization, you receive a list where a shadow change weighs as much as a missing part.
Product Quality Control
At the end of the pipeline, this report must be read by a program, not a human. We therefore combine comparison with the JSON mode from Lesson 9: the model returns a verdict, a score, and a list of qualified defects that code can immediately exploit.
import base64
import json
def encode_image(path):
with open(path, "rb") as f:
b64 = base64.standard_b64encode(f.read()).decode("utf-8")
return f"data:image/jpeg;base64,{b64}"
def quality_control(reference_image, sample_image):
"""Compares a sample to its reference and detects defects."""
response = client.chat.complete(
model="mistral-large-latest",
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": "You are a quality inspector. Compare the sample to the reference and return a JSON report."
},
{
"role": "user",
"content": [
{"type": "text", "text": "Reference (compliant product):"},
{"type": "image_url", "image_url": encode_image(reference_image)},
{"type": "text", "text": "Sample to inspect:"},
{"type": "image_url", "image_url": encode_image(sample_image)},
{
"type": "text",
"text": """Compare and return:
{
"compliant": true/false,
"compliance_score": 0-100,
"defects": [
{"description": "...", "severity": "minor/major/critical", "location": "..."}
],
"recommendation": "accept/review/reject"
}"""
}
]
}
]
)
return json.loads(response.choices[0].message.content)
# Usage
result = quality_control("reference/product-ok.jpg", "batch-42/sample-7.jpg")
if result["compliant"]:
print(f"COMPLIANT - Score: {result['compliance_score']}%")
else:
print(f"NON-COMPLIANT - {len(result['defects'])} defect(s) detected")
for defect in result["defects"]:
print(f" - [{defect['severity']}] {defect['description']}")
The recommendation field is more interesting than it appears: by distinguishing “review” from “reject,” it reserves human intervention for ambiguous cases instead of imposing it everywhere or nowhere.
Tracking Evolution Over Time
A time series—construction site, crop growth, structure degradation—is treated as an extended comparison, provided you label each image with its date. The model then has the temporal axis and can qualify a pace, not just a gap.
def analyze_evolution(chronological_images, context=""):
"""Analyzes visible evolution across a series of ordered images."""
content = [
{
"type": "text",
"text": f"Here are {len(chronological_images)} images taken at different times, in chronological order. {context}"
}
]
for i, (date, path) in enumerate(chronological_images):
content.append({"type": "text", "text": f"Image {i+1} ({date}):"})
content.append({"type": "image_url", "image_url": encode_image(path)})
content.append({
"type": "text",
"text": "Describe the evolution observed between each step. Identify major changes and the general trend."
})
response = client.chat.complete(
model="mistral-large-latest",
messages=[{"role": "user", "content": content}]
)
return response.choices[0].message.content
# Construction site tracking
images = [
("2025-01", "site/jan-2025.jpg"),
("2025-06", "site/jun-2025.jpg"),
("2026-01", "site/jan-2026.jpg"),
]
evolution = analyze_evolution(images, context="Tracking a construction site.")
print(evolution)
The final question is added only after all images, once the complete file is laid out.
Comparing User Interfaces
Web development offers an immediate use case: confronting the approved mockup with the actually produced render. Where a pixel regression test fails on a one-pixel shift of no importance, the model qualifies the impact of each discrepancy.
def compare_interfaces(expected_screenshot, actual_screenshot):
"""Compares two interface screenshots to detect visual regressions."""
response = client.chat.complete(
model="mistral-large-latest",
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": "You are a QA tester specializing in visual testing of web interfaces."
},
{
"role": "user",
"content": [
{"type": "text", "text": "Expected design (mockup):"},
{"type": "image_url", "image_url": encode_image(expected_screenshot)},
{"type": "text", "text": "Current render (screenshot):"},
{"type": "image_url", "image_url": encode_image(actual_screenshot)},
{
"type": "text",
"text": """Compare and return:
{
"pixel_perfect": true/false,
"differences": [
{"element": "...", "expected": "...", "actual": "...", "impact": "minor/moderate/major"}
],
"fidelity_score": 0-100,
"verdict": "compliant/minor_adjustments/regression"
}"""
}
]
}
]
)
return json.loads(response.choices[0].message.content)
Plugged into your continuous integration pipeline, this check blocks delivery only on a regression verdict.
Comparison Best Practices
These examples share a few principles worth making explicit. Always name your images with an interleaved label—“Reference,” “Sample,” “Before,” “After”—because without it, the model assigns roles based on order randomness. Ensure shot comparability: at different angles and lighting, the model will signal discrepancies that stem only from photo conditions. Use Mistral Large 3, because comparison mobilizes fine-grained reasoning that smaller models do not always master. Limit yourself to two or three images per call; beyond that, comparative precision dilutes. And systematically request a structured format, with a score, listed differences, and a verdict, if the result must be processed automatically.
Key Takeaways
- Image comparison leverages the API’s multi-image capability
- Interleave explanatory text between images to clarify their roles
- JSON mode (
response_format) is ideal for automated comparison reports - Primary use cases: quality control, site tracking, visual QA, before/after
- Always use Mistral Large 3 for demanding comparison tasks