passport_ocr / app.py
imamsaid's picture
Update app.py
2ba88b2 verified
"""
🚀 Application OCR Passeport/CIN Ultra-Rapide
Optimisée pour Hugging Face Spaces avec GPU Tesla T4
Temps de traitement: < 2 secondes
"""
import gradio as gr
import cv2
import re
import numpy as np
from paddleocr import PaddleOCR
from datetime import datetime
from typing import Dict, Optional
import json
import warnings
warnings.filterwarnings('ignore')
class PassportOCRApp:
def __init__(self):
"""Initialisation avec optimisations GPU"""
print("🚀 Initialisation OCR avec GPU...")
# PaddleOCR optimisé pour GPU Tesla T4
self.ocr = PaddleOCR(
lang='en',
use_angle_cls=True,
use_gpu=True, # GPU activé automatiquement sur HF Spaces
show_log=False,
det_db_thresh=0.3,
det_db_box_thresh=0.5,
rec_batch_num=16,
drop_score=0.3
)
# Précompilation des regex
self.patterns = {
'mrz_line1': re.compile(r'P<([A-Z]{3})([A-Z<]+)'),
'mrz_line2': re.compile(r'([A-Z0-9<]{9})\d([A-Z]{3})(\d{6})\d([MF<])\d{6}\d'),
'passport_num': re.compile(r'\b[A-Z]{1,2}\d{6,9}\b'),
'date_format': re.compile(r'\b(\d{2})[./-](\d{2})[./-](\d{4})\b'),
'country_code': re.compile(r'\b([A-Z]{3})\b'),
}
print("✅ OCR initialisé avec succès!")
def process_image(self, image):
"""
Traitement principal de l'image
Args:
image: numpy array de l'image
Returns:
dict avec résultats formatés
"""
start_time = datetime.now()
try:
if image is None:
return self._format_error("Aucune image fournie")
# Conversion si nécessaire
if len(image.shape) == 3:
# Conversion RGB → BGR pour OpenCV
image_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
else:
image_bgr = image
# Sauvegarder temporairement pour PaddleOCR
temp_path = "/tmp/temp_passport.jpg"
cv2.imwrite(temp_path, image_bgr)
# OCR extraction
result = self.ocr.ocr(temp_path, cls=True)
if not result or not result[0]:
return self._format_error("Aucun texte détecté dans l'image")
# Extraction du texte
texts = []
full_text = []
for line in result[0]:
text = line[1][0]
score = line[1][1]
if score > 0.6 and len(text.strip()) > 1:
texts.append((text, score))
full_text.append(text)
combined_text = "\n".join(full_text)
# Parsing des données
data = self._parse_passport_data(combined_text, texts)
# Calcul du temps
processing_time = (datetime.now() - start_time).total_seconds()
data['processing_time'] = f"{processing_time:.2f}s"
return self._format_success(data)
except Exception as e:
return self._format_error(f"Erreur: {str(e)}")
def _parse_passport_data(self, text: str, texts_with_scores) -> Dict:
"""Parse les données du passeport/CIN"""
data = {}
text_clean = text.replace(' ', '').replace('\n', ' ').upper()
# 1. Extraction MRZ (priorité haute)
mrz1 = self.patterns['mrz_line1'].search(text_clean)
if mrz1:
data['pays_emetteur'] = mrz1.group(1)
# Extraction nom/prénom
names = mrz1.group(2).replace('<', ' ').strip().split()
if len(names) >= 2:
data['nom'] = names[0]
data['prenom'] = ' '.join(names[1:])
mrz2 = self.patterns['mrz_line2'].search(text_clean)
if mrz2:
# Numéro passeport
passport_num = mrz2.group(1).replace('<', '').strip()
if len(passport_num) >= 6:
data['numero_passeport'] = passport_num
# Nationalité
if 'pays_emetteur' not in data:
data['pays_emetteur'] = mrz2.group(2)
# Date de naissance
dob_raw = mrz2.group(3)
parsed_date = self._parse_mrz_date(dob_raw)
if parsed_date:
data['date_naissance'] = parsed_date
# Sexe
sexe = mrz2.group(4).replace('<', '')
if sexe in ['M', 'F']:
data['sexe'] = 'Masculin' if sexe == 'M' else 'Féminin'
data['confidence'] = 'Haute (MRZ détectée)'
return data
# 2. Fallback - parsing standard
# Numéro passeport
if 'numero_passeport' not in data:
passport_match = self.patterns['passport_num'].search(text_clean)
if passport_match:
data['numero_passeport'] = passport_match.group(0)
# Date de naissance
if 'date_naissance' not in data:
date_match = self.patterns['date_format'].search(text)
if date_match:
day, month, year = date_match.groups()
try:
date_obj = datetime(int(year), int(month), int(day))
if 1900 <= date_obj.year <= datetime.now().year:
data['date_naissance'] = f"{day}/{month}/{year}"
except ValueError:
pass
# Pays émetteur
if 'pays_emetteur' not in data:
countries = self.patterns['country_code'].findall(text_clean)
valid_countries = ['GBR', 'FRA', 'DEU', 'ITA', 'ESP', 'USA', 'CAN',
'BEL', 'NLD', 'CHE', 'AUT', 'PRT', 'POL', 'SWE']
for country in countries:
if country in valid_countries:
data['pays_emetteur'] = country
break
data['confidence'] = 'Moyenne (texte standard)' if data else 'Faible'
return data
def _parse_mrz_date(self, date_str: str) -> Optional[str]:
"""Parse date MRZ format YYMMDD"""
if len(date_str) != 6:
return None
try:
yy, mm, dd = int(date_str[:2]), int(date_str[2:4]), int(date_str[4:6])
current_year = datetime.now().year % 100
year = 1900 + yy if yy > current_year + 10 else 2000 + yy
datetime(year, mm, dd)
return f"{dd:02d}/{mm:02d}/{year}"
except ValueError:
return None
def _format_success(self, data: Dict) -> str:
"""Formatage des résultats en texte lisible"""
lines = ["✅ EXTRACTION RÉUSSIE", "=" * 50]
fields = {
'nom': '👤 Nom',
'prenom': '👤 Prénom',
'pays_emetteur': '🌍 Pays émetteur',
'numero_passeport': '🔢 Numéro passeport',
'date_naissance': '🎂 Date de naissance',
'sexe': '⚧️ Sexe',
'confidence': '📊 Confiance',
'processing_time': '⏱️ Temps de traitement'
}
for key, label in fields.items():
if key in data and data[key]:
lines.append(f"{label}: {data[key]}")
elif key not in ['confidence', 'processing_time']:
lines.append(f"{label}: ❌ Non détecté")
lines.append("=" * 50)
# Ajout du JSON pour export
lines.append("\n📋 FORMAT JSON:")
lines.append(json.dumps(data, indent=2, ensure_ascii=False))
return "\n".join(lines)
def _format_error(self, message: str) -> str:
"""Formatage des erreurs"""
return f"❌ ERREUR\n{'=' * 50}\n{message}\n{'=' * 50}"
# Initialisation de l'application
print("🔄 Chargement de l'application...")
app = PassportOCRApp()
# Interface Gradio
def create_interface():
"""Création de l'interface Gradio responsive"""
with gr.Blocks(
theme=gr.themes.Soft(primary_hue="blue", secondary_hue="cyan"),
title="🛂 OCR Passeport & CIN Ultra-Rapide",
css="""
.container {max-width: 900px; margin: auto;}
.output-text {font-family: monospace; font-size: 14px;}
"""
) as interface:
gr.Markdown("""
# 🛂 OCR Passeport & Carte d'Identité
### ⚡ Extraction ultra-rapide avec GPU Tesla T4 (< 2 secondes)
**📸 Scannez votre document depuis votre téléphone ou uploadez une image**
✅ Supporte : Passeports, CIN, Cartes d'identité de tous pays
✅ Détection automatique de la zone MRZ (Machine Readable Zone)
✅ Export JSON disponible
""")
with gr.Row():
with gr.Column(scale=1):
# Input image avec support caméra mobile
image_input = gr.Image(
label="📷 Capturez ou uploadez votre document",
type="numpy",
sources=["upload", "webcam"], # Support caméra
height=400
)
with gr.Row():
submit_btn = gr.Button(
"🚀 Analyser le document",
variant="primary",
size="lg"
)
clear_btn = gr.Button("🗑️ Effacer", size="lg")
with gr.Column(scale=1):
# Output
output_text = gr.Textbox(
label="📊 Résultats de l'extraction",
lines=20,
max_lines=30,
elem_classes=["output-text"]
)
# Exemples
gr.Markdown("### 📝 Exemples de documents supportés")
gr.Examples(
examples=[
# Ajoutez vos images d'exemple ici
],
inputs=image_input,
label="Cliquez pour tester"
)
gr.Markdown("""
---
### 💡 Conseils pour de meilleurs résultats:
- ✅ Assurez-vous que l'image est nette et bien éclairée
- ✅ Capturez le document entier (incluant la zone MRZ en bas)
- ✅ Évitez les reflets et les ombres
- ✅ La zone MRZ (2 lignes en bas) améliore la précision
### 🔒 Sécurité et confidentialité:
- ✅ Aucune image n'est sauvegardée
- ✅ Traitement en mémoire uniquement
- ✅ Connexion HTTPS sécurisée
### 🌍 Pays supportés:
Tous les passeports avec zone MRZ standard (OACI/ICAO)
""")
# Actions
submit_btn.click(
fn=app.process_image,
inputs=image_input,
outputs=output_text
)
clear_btn.click(
fn=lambda: (None, ""),
outputs=[image_input, output_text]
)
return interface
# Lancement de l'application
if __name__ == "__main__":
print("✅ Application prête!")
interface = create_interface()
interface.launch(
share=False, # True pour générer un lien public temporaire
server_name="0.0.0.0",
server_port=7860
)