""" 🚀 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 )