mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-16 03:47:49 +01:00
222 lines
8.6 KiB
Python
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
|