# utils/ocr_utils.py from PIL import Image, ImageEnhance, ImageFilter import pytesseract import logging import os import io import numpy as np import cv2 from langdetect import detect, LangDetectException logger = logging.getLogger("OCR") def pretraiter_image(image_path: str, optimize_for_text: bool = True) -> Image.Image: """ Prétraite l'image pour améliorer la qualité de l'OCR avec des techniques avancées. Args: image_path: Chemin de l'image optimize_for_text: Si True, applique des optimisations spécifiques pour le texte Returns: Image prétraitée """ try: # Ouvrir l'image with Image.open(image_path) as img: # Convertir en niveaux de gris si l'image est en couleur if img.mode != 'L': img = img.convert('L') # Conversion en array numpy pour traitement avec OpenCV img_np = np.array(img) if optimize_for_text: # Appliquer une binarisation adaptative pour améliorer la lisibilité du texte # Cette technique s'adapte aux variations de luminosité dans l'image if img_np.dtype != np.uint8: img_np = img_np.astype(np.uint8) # Utiliser OpenCV pour la binarisation adaptative img_np = cv2.adaptiveThreshold( img_np, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 ) # Débruitage pour éliminer les artefacts img_np = cv2.fastNlMeansDenoising(img_np, None, 10, 7, 21) # Reconvertir en image PIL img = Image.fromarray(img_np) # Améliorer le contraste enhancer = ImageEnhance.Contrast(img) img = enhancer.enhance(2.0) # Augmenter le facteur de contraste # Augmenter la netteté enhancer = ImageEnhance.Sharpness(img) img = enhancer.enhance(2.0) # Augmenter le facteur de netteté # Agrandir l'image si elle est petite width, height = img.size if width < 1000 or height < 1000: ratio = max(1000 / width, 1000 / height) new_width = int(width * ratio) new_height = int(height * ratio) img = img.resize((new_width, new_height), Image.Resampling.LANCZOS) return img except Exception as e: logger.error(f"Erreur lors du prétraitement de l'image {image_path}: {e}") # En cas d'erreur, retourner l'image originale try: return Image.open(image_path) except: # Si même l'ouverture simple échoue, retourner une image vide logger.critical(f"Impossible d'ouvrir l'image {image_path}") return Image.new('L', (100, 100), 255) def detecter_langue_texte(texte: str) -> str: """ Détecte la langue principale d'un texte. Args: texte: Texte à analyser Returns: Code de langue ('fr', 'en', etc.) ou 'unknown' en cas d'échec """ if not texte or len(texte.strip()) < 10: return "unknown" try: return detect(texte) except LangDetectException: return "unknown" def extraire_texte(image_path: str, lang: str = "auto") -> tuple: """ Effectue un OCR sur une image avec détection automatique de la langue. Args: image_path: Chemin vers l'image lang: Langue pour l'OCR ('auto', 'fra', 'eng', 'fra+eng') Returns: Tuple (texte extrait, langue détectée) """ if not os.path.exists(image_path) or not os.access(image_path, os.R_OK): logger.warning(f"Image inaccessible ou introuvable: {image_path}") return "", "unknown" logger.info(f"Traitement OCR pour {image_path} (langue: {lang})") # Prétraiter l'image avec différentes configurations img_standard = pretraiter_image(image_path, optimize_for_text=False) img_optimized = pretraiter_image(image_path, optimize_for_text=True) # Configurer pytesseract config = '--psm 3 --oem 3' # Page segmentation mode: 3 (auto), OCR Engine mode: 3 (default) # Déterminer la langue pour l'OCR ocr_lang = lang if lang == "auto": # Essayer d'extraire du texte avec plusieurs langues try: texte_fr = pytesseract.image_to_string(img_optimized, lang="fra", config=config) texte_en = pytesseract.image_to_string(img_optimized, lang="eng", config=config) texte_multi = pytesseract.image_to_string(img_optimized, lang="fra+eng", config=config) # Choisir le meilleur résultat basé sur la longueur et la qualité results = [ (texte_fr, "fra", len(texte_fr.strip())), (texte_en, "eng", len(texte_en.strip())), (texte_multi, "fra+eng", len(texte_multi.strip())) ] results.sort(key=lambda x: x[2], reverse=True) best_text, best_lang, _ = results[0] # Détection secondaire basée sur le contenu detected_lang = detecter_langue_texte(best_text) if detected_lang in ["fr", "fra"]: ocr_lang = "fra" elif detected_lang in ["en", "eng"]: ocr_lang = "eng" else: ocr_lang = best_lang logger.info(f"Langue détectée: {ocr_lang}") except Exception as e: logger.warning(f"Détection de langue échouée: {e}, utilisation de fra+eng") ocr_lang = "fra+eng" # Réaliser l'OCR avec la langue choisie try: # Essayer d'abord avec l'image optimisée pour le texte texte = pytesseract.image_to_string(img_optimized, lang=ocr_lang, config=config) # Si le résultat est trop court, essayer avec l'image standard if len(texte.strip()) < 10: texte_standard = pytesseract.image_to_string(img_standard, lang=ocr_lang, config=config) if len(texte_standard.strip()) > len(texte.strip()): texte = texte_standard logger.info("Utilisation du résultat de l'image standard (meilleur résultat)") except Exception as ocr_err: logger.warning(f"OCR échoué: {ocr_err}, tentative avec l'image originale") # En cas d'échec, essayer avec l'image originale try: with Image.open(image_path) as original_img: texte = pytesseract.image_to_string(original_img, lang=ocr_lang, config=config) except Exception as e: logger.error(f"OCR échoué complètement: {e}") return "", "unknown" # Nettoyer le texte texte = texte.strip() # Détecter la langue du texte extrait pour confirmation detected_lang = detecter_langue_texte(texte) if texte else "unknown" # Sauvegarder l'image prétraitée pour debug si OCR réussi if texte: try: debug_dir = "debug_ocr" os.makedirs(debug_dir, exist_ok=True) img_name = os.path.basename(image_path) img_optimized.save(os.path.join(debug_dir, f"optimized_{img_name}"), format="JPEG") img_standard.save(os.path.join(debug_dir, f"standard_{img_name}"), format="JPEG") # Sauvegarder aussi le texte extrait with open(os.path.join(debug_dir, f"ocr_{img_name}.txt"), "w", encoding="utf-8") as f: f.write(f"OCR Langue: {ocr_lang}\n") f.write(f"Langue détectée: {detected_lang}\n") f.write("-" * 50 + "\n") f.write(texte) logger.info(f"Images prétraitées et résultat OCR sauvegardés dans {debug_dir}") except Exception as e: logger.warning(f"Impossible de sauvegarder les fichiers de débogage: {e}") # Journaliser le résultat logger.info(f"OCR réussi [{image_path}] — {len(texte)} caractères: {texte[:100]}...") else: logger.warning(f"OCR vide (aucun texte détecté) pour {image_path}") return texte, detected_lang def extraire_texte_fr(image_path: str) -> str: """ Effectue un OCR sur une image en langue française (pour compatibilité). Utilise la nouvelle fonction plus avancée avec détection automatique. Args: image_path: Chemin vers l'image Returns: Texte extrait """ texte, _ = extraire_texte(image_path, lang="auto") return texte