id-ocr-engine / README.md
Kevin-KES
Deploy current main to HF Space
50c6ee2
|
Raw
History Blame Contribute Delete
12.9 kB
metadata
title: ID OCR Engine
emoji: πŸ”
colorFrom: blue
colorTo: green
sdk: docker
app_port: 7860

Python 3.12+ Flask 3.1 Docker Ready PaddleOCR FastMRZ zxing-cpp

REST API JSON Logging Rate limiting API Key Auth License

ID OCR Engine

A Flask microservice that extracts identity fields from ID cards, passports, and product serial numbers using a multi-engine pipeline: VLM (Vision Language Model) + PaddleOCR + barcode/MRZ reading.

Highlights

  • Multi-engine ID extraction (barcode/MRZ + VLM + OCR) with cross-validation
  • Product serial number scanning via VLM with digit-density scoring
  • Live camera scanning with auto-detection (barcode + serial number modes)
  • Built-in web UI at / with file upload and camera support
  • JSON logs with request IDs
  • Rate limiting and optional API key auth
  • No persistent storage of uploaded images (files are deleted immediately after processing)
  • Models used:
    • VLM: Qwen/Qwen3-VL-8B-Instruct via Hugging Face Inference API (configurable)
    • OCR: PaddleOCR (English)
    • MRZ: FastMRZ (ONNX)

Supported Document Types

Type doc_type Input Machine-readable
SA Smart Card sa_id_card Front (required) + Back (optional, PDF417 barcode) Barcode on back
SA Green Book sa_id_book Front (required) None
Passport (any country) passport Front (required) MRZ on front
Product Serial Number product_serial Product image None

Extraction Pipelines

sa_id_card:      Barcode (back) β†’ VLM (front) β†’ PaddleOCR (front) β†’ Cross-validate
sa_id_book:      VLM (front) β†’ PaddleOCR (front) β†’ Cross-validate
passport:        MRZ (front) β†’ VLM (front) β†’ PaddleOCR (front) β†’ Cross-validate
product_serial:  VLM (center-cropped frame) β†’ digit-density scoring β†’ validation

Web UI

  • Extracts ID number, surname, names, date of birth, sex, country of birth, and citizenship status
  • Validates the 13-digit SA ID number using the SA-specific Luhn algorithm
  • Cross-validates OCR-extracted fields (DOB, gender, citizenship) against values encoded in the ID number
  • Multi-pass OCR with fallback preprocessing (CLAHE, adaptive thresholding, high-contrast) for difficult images
  • Automatic front/back card detection
  • Built-in web dashboard with drag-and-drop upload, batch processing, history, and CSV export
  • API key authentication, rate limiting, and request tracking
  • Automatic cleanup of uploaded images (personal data is never persisted)

Web Dashboard

Navigate to http://localhost:5001 to access the interactive dashboard:

  • Drag-and-drop or browse to upload ID card images
  • Batch processing queue for multiple images
  • Real-time results display with validation status
  • Processing history and statistics
  • CSV export of results

API

GET /health

Health check (no auth).

GET /warm

Warm up VLM + PaddleOCR + MRZ models (no auth). Call before first OCR request.

POST /api/ocr

Extract fields from an ID document image.

Headers:

Header Required Description
X-API-Key Yes (if configured) API authentication key
X-Request-ID No Client-provided request ID (echoed back)

Request: multipart/form-data

Field Required Description
front Yes Front image (or file for legacy)
back No Back image (only for sa_id_card)
doc_type Yes sa_id_card, sa_id_book, or passport

Accepted formats: png, jpg, jpeg, bmp, webp (max 10MB).

Response:

The response includes:

  • fields extracted from barcode/MRZ/VLM/OCR
  • checks for validation and cross-checks
  • confidence scores from OCR
  • quality metrics (brightness, blur, resolution)
  • raw_text OCR text

Example (SA ID):

{
  "doc_type": "sa_id_card",
  "fields": {
    "id_number": "8801015800082",
    "surname": "SMITH",
    "names": "JOHN PETER",
    "date_of_birth": "1988-01-01",
    "sex": "Male",
    "nationality": "South African",
    "country_of_birth": "RSA",
    "citizenship_status": "SA Citizen"
  },
  "barcode_data": { "...": "..." },
  "mrz_data": null,
  "extraction_method": "barcode+vlm",
  "checks": {
    "image_quality": "passed",
    "id_number_valid": "passed",
    "barcode_valid": "passed",
    "data_crosscheck": "passed",
    "dob_crosscheck": "passed",
    "gender_crosscheck": "passed",
    "mrz_valid": "not_applicable"
  },
  "overall_result": "pass"
}

Example (Passport):

{
  "doc_type": "passport",
  "fields": {
    "passport_number": "A12345678",
    "surname": "Smith",
    "given_names": "John Peter",
    "date_of_birth": "1988-01-01",
    "sex": "Male",
    "nationality": "ZAF",
    "expiry_date": "2030-01-01",
    "issuing_country": "ZAF",
    "id_number": "8801015800082"
  },
  "mrz_data": { "...": "..." },
  "extraction_method": "mrz+vlm",
  "checks": {
    "image_quality": "passed",
    "mrz_valid": "passed",
    "id_number_valid": "passed"
  },
  "overall_result": "pass"
}

POST /api/serial

Extract a product serial number from an image.

Request: multipart/form-data

Field Required Description
image Yes Product image file

Response:

{
  "serial_number": "WPHK002510002632",
  "bounding_box": null,
  "cropped_image": null,
  "extraction_source": "vlm",
  "confidence": "medium",
  "validation": {
    "valid": true,
    "normalized": "WPHK002510002632",
    "issues": [],
    "prefix_match": null
  },
  "failure_reason": null,
  "regions_detected": 0,
  "quality": { "width": 640, "height": 480, "usable": true, "issues": [] }
}

The serial extraction pipeline uses VLM to read all visible text, then scores candidates by digit density, rejects known non-serial patterns (IMEI, MAC, English phrases, sequential digits), and boosts values near S/N or Serial labels.

POST /api/barcode/parse

Parse raw barcode text (decoded client-side) into structured SA ID fields. Used by the web UI when the browser's BarcodeDetector API decodes a PDF417 barcode from the camera feed.

Request: application/json

{ "text": "<raw barcode data>" }

Response: Same format as /api/ocr with extraction_method: "barcode".

Checks and Quality

The checks object includes a per-check status: passed, failed, or not_applicable.

  • image_quality: minimum resolution check per doc type
  • id_number_valid: country-specific validation (Luhn for SA, regex for others)
  • mrz_valid: MRZ check digit validation
  • barcode_valid: barcode extraction succeeded
  • data_crosscheck: compare machine-readable data against VLM/OCR values
  • dob_crosscheck, gender_crosscheck: SA ID number-encoded validation

The quality object includes:

  • resolution_ok, width, height
  • brightness and blur_score
  • issues list for low-light, blur, or very small images

Country ID Validation

ID numbers are validated per detected nationality:

Country Code Format Validation
South Africa ZAF 13 digits SA Luhn + encoded fields
Nigeria NGA 11 digits Regex
Kenya KEN 1-9 digits Regex
Zimbabwe ZWE 8-9 digits + letter + 2 digits Regex
Uganda UGA 14 alphanumeric Regex
Zambia ZMB 10 digits Regex

Configuration

Variable Default Description
OCR_API_KEY (empty) API key (disabled if empty)
OCR_HOST 0.0.0.0 Bind address
OCR_PORT 7860 Bind port
OCR_DEBUG false Enable Flask debug mode
OCR_MAX_REQUEST_SIZE 10485760 Max request size in bytes (default 10MB)
OCR_UPLOAD_DIR ./uploads Temporary upload directory
HF_API_TOKEN (empty) HF Inference API token (for VLM)
OCR_VLM_MODEL Qwen/Qwen3-VL-8B-Instruct VLM model ID (for ID documents)
OCR_SERIAL_VLM_MODEL Qwen/Qwen3-VL-8B-Instruct VLM model ID (for serial numbers)
OCR_LOG_LEVEL INFO Logging level
OCR_LOG_FORMAT json Log format (json or text)
OCR_MAX_REQUEST_SIZE 10485760 Max upload size in bytes (default 10MB)
OCR_UPLOAD_DIR ./uploads Temporary file storage directory
OCR_WORKERS min(CPU_count, 2) Gunicorn worker count

Running

Development:

python main.py

Production:

gunicorn -c gunicorn.conf.py wsgi:app

Docker Compose:

docker compose up --build

Docker:

docker build -t id-ocr .
docker run -p 7860:7860 -e OCR_API_KEY=your-key -e HF_API_TOKEN=hf_xxx id-ocr

Example Requests

# SA Smart Card (front only)
curl -X POST -H "X-API-Key: key" \
  -F "front=@front.jpg" -F "doc_type=sa_id_card" \
  http://localhost:7860/api/ocr

# SA Smart Card (front + back)
curl -X POST -H "X-API-Key: key" \
  -F "front=@front.jpg" -F "back=@back.jpg" -F "doc_type=sa_id_card" \
  http://localhost:7860/api/ocr

# Passport
curl -X POST -H "X-API-Key: key" \
  -F "front=@passport.jpg" -F "doc_type=passport" \
  http://localhost:7860/api/ocr

# Product serial number
curl -X POST -H "X-API-Key: key" \
  -F "image=@product.jpg" \
  http://localhost:7860/api/serial

Data Handling and Privacy

β”œβ”€β”€ main.py                  # Flask app factory, routes, middleware
β”œβ”€β”€ config.py                # Environment-based configuration
β”œβ”€β”€ wsgi.py                  # Gunicorn WSGI entry point
β”œβ”€β”€ gunicorn.conf.py         # Gunicorn worker configuration
β”œβ”€β”€ logging_config.py        # Structured JSON logging
β”œβ”€β”€ engine/
β”‚   β”œβ”€β”€ ocr.py               # Image preprocessing + EasyOCR wrapper
β”‚   └── id_parser.py         # SA ID parsing, Luhn validation, field extraction
β”œβ”€β”€ static/
β”‚   β”œβ”€β”€ index.html           # Web dashboard UI
β”‚   β”œβ”€β”€ css/
β”‚   β”‚   └── style.css        # Dashboard styles
β”‚   └── js/
β”‚       β”œβ”€β”€ app.js           # App initialization
β”‚       β”œβ”€β”€ api.js           # API client wrapper
β”‚       β”œβ”€β”€ upload.js        # File upload & batch processing
β”‚       β”œβ”€β”€ results.js       # Results rendering
β”‚       β”œβ”€β”€ history.js       # Processing history management
β”‚       β”œβ”€β”€ stats.js         # Statistics calculation
β”‚       └── utils.js         # Utility functions
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ test_api.py          # API integration tests
β”‚   └── test_id_parser.py    # ID parser unit tests
β”œβ”€β”€ Dockerfile
└── docker-compose.yaml

How It Works

OCR Pipeline

  1. Image intake β€” validates format and size, saves to a temporary directory
  2. Preprocessing β€” auto-brightness correction (gamma), CLAHE contrast enhancement, bilateral noise filtering, intelligent resizing (800–2000px width)
  3. OCR pass 1 β€” standard preprocessing with EasyOCR
  4. Fallback passes β€” if the ID number is not found, retries with aggressive preprocessing (sharpening + adaptive thresholding) and high-contrast preprocessing (histogram stretch + Otsu's thresholding)
  5. Field extraction β€” spatial matching of bounding boxes to locate field values relative to labels
  6. Validation β€” Luhn check on ID number, cross-validation of DOB/gender/citizenship against encoded values
  7. Cleanup β€” temporary image is deleted immediately after processing

OCR Error Correction

Common character confusions are automatically corrected during ID number extraction (e.g., O→0, I→1, S→5).

SA ID Number Format

  • Logs can be JSON or text format.
  • X-Request-ID is accepted on requests and echoed in responses.
  • Each request is logged with latency and request ID.

Testing

python -m pytest tests/ -v