llm_ticket3/utils/ocr_utils.py
2025-04-23 16:45:15 +02:00

222 lines
8.6 KiB
Python

# 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