Optimizing OCR Accuracy
On this page
Update November 2, 2026 — OCR. OCR 4.0 (
mistral-ocr-4-0) was retired on September 30, 2026. Use OCR 4.1 (mistral-ocr-4-1), which has been generally available since August 31 at the same price, or the aliasmistral-ocr-latest, which has pointed to it since July 16. OCR 4.1 adds confidence scores at the block level. Addendum November 3, 2026: A local PDF is transmitted in base64 as{"type": "document_url", "document_url": "data:application/pdf;base64,…"}; thedocument_base64type from older examples exists neither in the documentation nor in themistralaiSDK, and those examples would have failed. Sources: OCR Processor · Document QnA.
Input Quality Determines Output Quality
The Mistral team put it bluntly during their Document AI webinar: it’s “garbage in, garbage out.” No matter how powerful the model, it cannot recover information the scan failed to capture. When an extraction project underperforms, the culprit is almost always upstream of the API—a faded photocopy, a re-scanned fax, or a photo taken at an angle in a poorly lit warehouse. This lesson covers the levers that actually impact accuracy.
Resolution and Pre-processing
The first lever is scan resolution. The targets depend on what the document contains:
- Printed documents: 200 DPI minimum, 300 DPI recommended
- Handwriting: 300 DPI minimum
- Complex tables: 300 DPI recommended
- Historical documents: 400 DPI or higher
Below these values, fine lines disappear, and no software processing can bring them back. Going higher mostly just increases file size. If you control the scanning chain, setting up the scanner correctly will save you far more trouble than any coding trick.
When the document arrives already digitized, image pre-processing can recover some ground. The function below chains four classic operations—grayscale conversion, contrast enhancement, sharpening, and binarization—before returning the image encoded in base64, ready for the OCR call:
from PIL import Image, ImageEnhance, ImageFilter
import io
import base64
def pre_process_image(image_path: str) -> str:
"""Pre-processes an image to improve OCR extraction."""
img = Image.open(image_path)
# 1. Convert to grayscale
img = img.convert("L")
# 2. Increase contrast
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(1.5)
# 3. Increase sharpness
img = img.filter(ImageFilter.SHARPEN)
# 4. Binarization (thresholding)
img = img.point(lambda x: 0 if x < 128 else 255, "1")
# Convert to base64
buffer = io.BytesIO()
img.save(buffer, format="PNG")
return base64.b64encode(buffer.getvalue()).decode("utf-8")
Test this processing on a sample before applying it to an entire batch. Binarization, in particular, is brutal: on an already sharp document, it adds nothing, and on a pencil document, it can erase the text entirely.
Orientation is the other correction you shouldn’t neglect. Scans often arrive rotated by 90 or 180 degrees, and the orientation information is usually stored in the image’s EXIF metadata:
from PIL import Image
def correct_orientation(image_path: str) -> Image.Image:
"""Corrects the orientation of a scanned image via EXIF metadata."""
img = Image.open(image_path)
# Use EXIF data to correct orientation
try:
exif = img._getexif()
if exif:
orientation = exif.get(274) # EXIF orientation tag
rotations = {3: 180, 6: 270, 8: 90}
if orientation in rotations:
img = img.rotate(rotations[orientation], expand=True)
except (AttributeError, KeyError):
pass
return img
The broad try block is justified here: many images have no EXIF data, and missing orientation data shouldn’t interrupt batch processing.
Adapting Configuration to Document Type
For common printed documents—invoices, contracts, reports—the optimal configuration boils down to explicitly requesting table extraction rather than relying on default settings:
# Optimal configuration for printed documents
ocr_response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "document_url",
"document_url": f"data:application/pdf;base64,{document_base64}"
},
table_format="markdown" # Explicit table extraction
)
Handwriting requires more upstream care. Scan at 300 DPI minimum, increase the contrast between ink and paper, but skip the aggressive binarization from the previous script: it erases fine ballpoint pen strokes, and you’ll lose more information than you gain. The model itself handles handwriting well, including multilingual text, which wasn’t a given with previous OCR generations.
Then there are severely degraded scans—archives, crumpled documents, copies of copies. The strategy changes: start by reducing noise with a median filter before boosting contrast, then increase brightness if the whole thing remains too dark.
from PIL import Image, ImageFilter, ImageEnhance
def improve_scan(path: str) -> Image.Image:
"""Improves a low-quality scan."""
img = Image.open(path)
# Noise reduction
img = img.filter(ImageFilter.MedianFilter(size=3))
# Increase contrast
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(2.0)
# Increase brightness if too dark
enhancer = ImageEnhance.Brightness(img)
img = enhancer.enhance(1.2)
return img
Order matters: boosting contrast before removing noise just strengthens the stains as much as the letters.
Processing Volumes
Once the number of files exceeds a handful, you need batch processing with per-file error handling. For very large volumes, Mistral offers the Batch Inference service; the following script shows the basic logic for a folder of PDFs, where each failure is logged in the results instead of breaking the loop:
import os
import json
from pathlib import Path
from mistralai.client import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
def process_folder(folder: str) -> dict:
"""Processes all PDFs in a folder."""
results = {}
path = Path(folder)
for file in path.glob("*.pdf"):
print(f"Processing {file.name}...")
with open(file, "rb") as f:
import base64
doc_b64 = base64.b64encode(f.read()).decode("utf-8")
try:
response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "document_url",
"document_url": f"data:application/pdf;base64,{doc_b64}"
},
table_format="markdown"
)
results[file.name] = {
"pages": len(response.pages),
"content": "\n\n".join(
p.markdown for p in response.pages
)
}
except Exception as e:
results[file.name] = {"error": str(e)}
return results
This results dictionary gives you, at the end of processing, the exact list of files to retry. On a batch of several hundred documents, that’s the difference between re-running three files and starting over from scratch.
Verify Before Exploiting
An extraction that succeeded technically may have failed in practice: a blank page returned for an unreadable scan, or a flood of garbage characters on a noisy image. A few simple heuristics are enough to trigger an automatic alert.
def verify_quality(markdown: str) -> dict:
"""Verifies the quality of an OCR extraction."""
indicators = {
"length": len(markdown),
"words": len(markdown.split()),
"empty_lines_ratio": markdown.count("\n\n") / max(len(markdown), 1),
"special_chars_ratio": sum(
1 for c in markdown if not c.isalnum() and c not in " \n\t.,;:!?-"
) / max(len(markdown), 1),
}
# Alerts
if indicators["words"] < 10:
indicators["alert"] = "Very little text extracted"
elif indicators["special_chars_ratio"] > 0.3:
indicators["alert"] = "High ratio of special characters"
return indicators
The two thresholds are indicative and must be calibrated to your corpus: ten words for a contract page signals a problem, but not for a delivery slip that only contains fifteen.
Operational Summary
Remember the chain in its logical order. Scan at 300 DPI minimum for any non-trivial document, and increase contrast before sending if the document is faded. Correct the rotation angle of scans. Always prefer the native PDF to a scan when available: its quality is structurally superior. Explicitly specify table_format="markdown" or "html" whenever tables are involved. Process in batches and parallelize calls for large volumes. And systematically validate extraction quality before feeding it into your information system—bad data costs more than missing data.
Key Takeaways
- Source document quality is the primary factor in accuracy
- Image pre-processing (contrast, sharpness, binarization) improves results
- Adapt your approach to the document type (printed, handwritten, degraded)
- Use batch processing for large volumes
- Always validate extraction quality before use