SuperAPI AI Directory

ocr

Design & creation

View repository

Updated 10-15 18:52

About this skill

OCR

Engine Comparison

Engine Accuracy Speed Languages Layout Cost
Tesseract 5 Good Fast 100+ Basic (PSM) Free
EasyOCR Good Medium 80+ Basic Free
PaddleOCR Very good Medium 80+ PP-Structure Free
AWS Textract Excellent Fast (API) Many Forms+tables+queries Per-page
Azure Doc Intelligence Excellent Fast (API) Many Prebuilt + layout Per-page
Google Document AI Excellent Fast (API) Many Processors Per-page
Claude vision Excellent Medium Many Semantic Per-token

Tesseract

import pytesseract
from PIL import Image
Basic

text = pytesseract.image_to_string(Image.open("scan.png"), lang="eng")

Tuned: Page Segmentation Mode + OCR Engine Mode

config = r"--oem 3 --psm 6 -c preserve_interword_spaces=1" text = pytesseract.image_to_string(Image.open("scan.png"), lang="eng+deu", config=config)

Word-level boxes + confidences

data = pytesseract.image_to_data( Image.open("scan.png"), output_type=pytesseract.Output.DICT, config="--psm 6", ) for i, word in enumerate(data["text"]): if word.strip() and int(data["conf"][i]) > 60: print(word, data["left"][i], data["top"][i], data["conf"][i])


### PSM Modes (Page Segmentation)

| PSM | Use |
|-----|-----|
| 3 | Default: auto page segmentation |
| 4 | Single column of text of variable sizes |
| 6 | Single uniform block of text (most common) |
| 7 | Single text line |
| 8 | Single word |
| 11 | Sparse text, find as much as possible |
| 12 | Sparse text with OSD |

## Image Preprocessing

```python
import cv2
import numpy as np

def preprocess(path: str) -> np.ndarray:
    img = cv2.imread(path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Denoise
    gray = cv2.fastNlMeansDenoising(gray, h=15)

    # Deskew
    coords = np.column_stack(np.where(gray < 200))
    angle = cv2.minAreaRect(coords)[-1]
    angle = -(90 + angle) if angle < -45 else -angle
    (h, w) = gray.shape
    M = cv2.getRotationMatrix2D((w // 2, h // 2), angle, 1.0)
    rotated = cv2.warpAffine(gray, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)

    # Binarize (Otsu + adaptive fallback)
    _, binary = cv2.threshold(rotated, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # Morphological cleanup
    kernel = np.ones((1, 1), np.uint8)
    binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
    return binary

EasyOCR

import easyocr

reader = easyocr.Reader(["en", "es"], gpu=True)
results = reader.readtext("scan.jpg", detail=1, paragraph=False)
for bbox, text, conf in results:
    print(conf, text)

# Paragraph mode groups lines
paragraphs = reader.readtext("scan.jpg", paragraph=True)

PaddleOCR

from paddleocr import PaddleOCR

ocr = PaddleOCR(use_angle_cls=True, lang="en", use_gpu=True, show_log=False)
result = ocr.ocr("scan.jpg", cls=True)
for line in result[0]:
    bbox, (text, conf) = line
    print(conf, text)

# PP-Structure for tables + layout
from paddleocr import PPStructure
structure = PPStructure(table=True, ocr=True, show_log=False)
layout = structure("page.png")
for region in layout:
    print(region["type"], region["bbox"])

AWS Textract (Sync + Async)

import boto3

textract = boto3.client("textract", region_name="us-east-1")

# Sync - single page image/PDF <= 5MB
with open("scan.png", "rb") as f:
    resp = textract.detect_document_text(Document={"Bytes": f.read()})
lines = [b["Text"] for b in resp["Blocks"] if b["BlockType"] == "LINE"]

# Async - multi-page PDFs in S3
start = textract.start_document_analysis(
    DocumentLocation={"S3Object": {"Bucket": "docs", "Name": "big.pdf"}},
    FeatureTypes=["TABLES", "FORMS"],
)
job_id = start["JobId"]

import time
while True:
    status = textract.get_document_analysis(JobId=job_id)
    if status["JobStatus"] in ("SUCCEEDED", "FAILED"):
        break
    time.sleep(5)

Azure Document Intelligence

from azure.ai.documentintelligence import DocumentIntelligenceClient
from azure.core.credentials import AzureKeyCredential
import os

client = DocumentIntelligenceClient(
    endpoint=os.environ["AZURE_DOC_INTEL_ENDPOINT"],
    credential=AzureKeyCredential(os.environ["AZURE_DOC_INTEL_KEY"]),
)

with open("invoice.pdf", "rb") as f:
    poller = client.begin_analyze_document(
        model_id="prebuilt-read",   # or prebuilt-layout, prebuilt-invoice, ...
        body=f,
    )
result = poller.result()
for page in result.pages:
    for line in page.lines:
        print(line.content)

Google Document AI

from google.cloud import documentai_v1 as documentai

client = documentai.DocumentProcessorServiceClient()
name = "projects/my-proj/locations/us/processors/abc123"

with open("scan.pdf", "rb") as f:
    raw = documentai.RawDocument(content=f.read(), mime_type="application/pdf")

request = documentai.ProcessRequest(name=name, raw_document=raw)
result = client.process_document(request=request)
doc = result.document
print(doc.text[:500])
for page in doc.pages:
    for paragraph in page.paragraphs:
        segment = paragraph.layout.text_anchor.text_segments[0]
        print(doc.text[int(segment.start_index):int(segment.end_index)])

Anthropic Claude Vision for OCR

import anthropic
import base64
from pathlib import Path

client = anthropic.Anthropic()

def claude_ocr(image_path: str) -> str:
    data = base64.standard_b64encode(Path(image_path).read_bytes()).decode()
    media_type = "image/png" if image_path.endswith(".png") else "image/jpeg"
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        messages=[{
            "role": "user",
            "content": [
                {"type": "image", "source": {
                    "type": "base64", "media_type": media_type, "data": data,
                }},
                {"type": "text", "text": (
                    "Transcribe all text from this image exactly as it appears. "
                    "Preserve line breaks, tables as markdown, and mark unreadable "
                    "text as [UNCLEAR]. Do not summarize or paraphrase."
                )},
            ],
        }],
    )
    return response.content[0].text

Strengths: excellent on messy handwriting, tables, mixed layouts. Weaknesses: slower, token cost, occasional hallucination (mitigate with strict prompt).

Hybrid Pipeline (fast + accurate fallback)

def ocr_with_fallback(image_path: str, conf_threshold: int = 70) -> str:
    data = pytesseract.image_to_data(Image.open(image_path),
                                     output_type=pytesseract.Output.DICT)
    confs = [int(c) for c in data["conf"] if c != "-1"]
    avg = sum(confs) / max(len(confs), 1)
    if avg < conf_threshold:
        return claude_ocr(image_path)
    return pytesseract.image_to_string(Image.open(image_path))

Anti-Patterns

Anti-Pattern Fix
OCR on born-digital PDFs Try text extraction first, OCR only on failure
Tesseract on unpreprocessed scans Deskew, denoise, binarize before OCR
Default PSM 3 for forms Use PSM 6 or 11 depending on layout
Ignoring confidence scores Flag low-conf pages for human or LLM re-OCR
No language pack specified Pass lang="eng+fra" for multilingual docs
Using vision LLM for all pages Use it only as fallback - it is expensive

Production Checklist

  • Language packs installed for target locales
  • Preprocessing pipeline (deskew, denoise, binarize)
  • Confidence threshold triggers fallback to cloud/LLM OCR
  • Async API used for multi-page documents
  • Cost monitoring per page (Textract, Azure, Claude)
  • PII detection after OCR (SSN, credit cards, etc.)
  • Page-level caching keyed by image hash
  • Dead-letter queue for OCR failures