💻 Développement

dev-ocr-document-scanner

OCR mobile et extraction de champs depuis CIN, passeport, permis, factures.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-ocr-document-scanner --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-ocr-document-scanner -Launch"

🚀 Déjà installé ?

claude "/dev-ocr-document-scanner"

Ou tapez /dev-ocr-document-scanner dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

OCRscanner documentextraire texteCINpasseportMRZID carddocument verificationKYC document

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-ocr-document-scanner ~/.claude/skills/

Payload du plugin : skills/dev-ocr-document-scanner · source éditable : dev-skills/ocr-document-scanner

đź“– Manuel

OCR Document Scanner

Workflow en 8 étapes

1. Prétraitement d'image

Corriger l'image AVANT d'envoyer à l'OCR — c'est l'étape qui impacte le plus la précision.

import cv2
import numpy as np

def preprocess_document(img: np.ndarray) -> np.ndarray:
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # CLAHE pour le contraste adaptatif
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    enhanced = clahe.apply(gray)
    # Débruitage
    denoised = cv2.fastNlMeansDenoising(enhanced, h=10)
    # Binarisation Otsu
    _, binary = cv2.threshold(denoised, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    return binary

Critères qualité à vérifier avant OCR :

Si l'image ne passe pas les critères → demander une nouvelle capture avec feedback précis ("document trop petit", "image floue", "reflet détecté").


2. Détection du type de document

Classifier avant d'appliquer les regex/modèles spécialisés.

Heuristiques rapides (sans ML) :

SignalDocument probable
2 lignes OCR-A en basPasseport (TD3) ou CIN biométrique
3 lignes OCR-A 30 charsTitre de voyage TD1
Champ IBAN / RIBRelevé bancaire
QR code + montantFacture

Avec ML Kit (on-device) : utiliser un MobileNetV2 TFLite quantifié INT8 pour ≤ 15 ms d'inférence. Retourner { "document_type": "CIN_MA_RECTO", "confidence": 0.94 }.


3. Extraction OCR — choix du moteur

MoteurCas d'usagePrécisionDéploiement
Google ML Kit v2Mobile Kotlin/Flutter, arabe+latin~96 %On-device
Tesseract 5 LSTMBackend Python, arabe+français~92 %Serveur / Edge
EasyOCRPython, texte incliné/diagonal~90 %Backend
AWS TextractDocuments complexes, tableaux~98 %Cloud
Azure Form RecognizerFormulaires structurés avec labels~97 %Cloud

Tesseract — configuration minimale pour documents marocains :

tesseract image.png output --oem 3 --psm 3 -l ara+fra \
  --dpi 300 -c preserve_interword_spaces=1

ML Kit (Kotlin) :

val recognizer = TextRecognition.getClient(
    TextRecognizerOptions.Builder()
        .setExecutor(Dispatchers.Default.asExecutor())
        .build()
)
val result = recognizer.process(InputImage.fromBitmap(bitmap, 0)).await()
result.textBlocks.forEach { block ->
    val text = block.text
    val boundingBox = block.boundingBox
}

4. Parsing MRZ (Machine Readable Zone)

Formats ICAO 9303 à connaître :

# Lib recommandée : passporteye ou mrz-python
from mrz.checker.td3 import TD3CodeChecker

checker = TD3CodeChecker("P<MARBENBRAHIM<<KHALIL<<<<<<<<<<<<<<<<<<<<<\nAB1234567MAR9001011M3012315<<<<<<<<<<<<<<02")
if checker.valid():
    fields = checker.fields()
    print(fields.surname, fields.name, fields.date_of_birth)

Pièges MRZ :


5. Extraction de champs structurés

Regex par document marocain :

import re

# CIN marocaine
CIN_PATTERN = re.compile(r'\b([A-Z]{1,2}[0-9]{5,6})\b')

# Numéro passeport marocain
PASSPORT_PATTERN = re.compile(r'\b([A-Z]{2}[0-9]{7})\b')

# Date format DD/MM/YYYY ou DD-MM-YYYY
DATE_PATTERN = re.compile(r'\b(\d{2})[/-](\d{2})[/-](\d{4})\b')

# CIN recto — extraction NER via spaCy (fr_core_news_md)
import spacy
nlp = spacy.load("fr_core_news_md")
doc = nlp(raw_text)
names = [ent.text for ent in doc.ents if ent.label_ == "PER"]

Pour les champs structurés complexes (LayoutLM) :


6. Post-processing et validation

def clean_ocr_field(value: str, field_type: str) -> dict:
    value = value.strip().upper()
    # Corrections OCR typiques
    ocr_fixes = {"0": "O", "1": "I", "5": "S", "8": "B"}
    if field_type in ("NAME", "SURNAME"):
        for wrong, right in ocr_fixes.items():
            value = value.replace(wrong, right)
    # Dates → format ISO
    if field_type == "DATE":
        match = re.match(r'(\d{2})[/-](\d{2})[/-](\d{4})', value)
        if match:
            value = f"{match.group(3)}-{match.group(2)}-{match.group(1)}"
    confidence = compute_field_confidence(value, field_type)
    return {"value": value, "confidence": confidence, "needs_review": confidence < 0.7}

Seuils de confiance recommandés :


7. Feedback UX mobile en temps réel

Implémenter un live preview overlay dans CameraX (Kotlin) :

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

imageAnalysis.setAnalyzer(executor) { imageProxy ->
    val laplacianVariance = computeBlurScore(imageProxy.toBitmap())
    runOnUiThread {
        when {
            laplacianVariance < 100 -> showOverlay("Image floue — stabilisez l'appareil", RED)
            documentCoverage < 0.60 -> showOverlay("Rapprochez l'appareil du document", YELLOW)
            else -> showOverlay("Capture prĂŞte", GREEN)
        }
    }
    imageProxy.close()
}

8. Intégration API backend

Format d'entrée recommandé (REST) :

POST /api/v1/ocr/scan
Content-Type: multipart/form-data

image: <fichier JPEG/PNG/WebP, max 5 MB>
document_type: CIN_MA | PASSPORT_MA | DRIVING_LICENSE | INVOICE | AUTO

Format de sortie :

{
  "document_type": "CIN_MA_RECTO",
  "mrz_valid": true,
  "extracted_fields": {
    "cin_number": { "value": "AB123456", "confidence": 0.98 },
    "surname": { "value": "BENBRAHIM", "confidence": 0.95 },
    "date_of_birth": { "value": "1990-01-01", "confidence": 0.92, "needs_review": false }
  },
  "processing_ms": 340,
  "image_quality": { "blur_score": 210, "coverage": 0.73 }
}

Garde-fous et anti-patterns

Ne jamais faire :

Pièges fréquents :

Limites de précision par type :

DocumentMRZImpriméManuscrit
Passeport~99.5 %~97 %N/A
CIN biométrique~99 %~95 %~75 %
Permis de conduireN/A~93 %~70 %
FactureN/A~95 %~65 %

Bonnes pratiques 2026