imageVersTexte / ocr_module.py
j-r-b's picture
Upload 7 files
6048e44 verified
import os
import sys
import pytesseract
import cv2
import numpy as np
from PIL import Image
import json
import logging
import tempfile
import io
# Configuration du logging plus détaillé
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Variable pour suivre les méthodes OCR disponibles
tesseract_available = False
easyocr_available = False
# Essayer de charger EasyOCR
try:
import easyocr
easyocr_reader = None # Sera initialisé à la demande
easyocr_available = True
logger.info("EasyOCR est disponible")
except ImportError:
logger.warning("EasyOCR n'est pas disponible, l'OCR pourrait être moins efficace")
# Vérifier Tesseract
try:
# Chemin vers Tesseract (à ajuster selon l'environnement)
if sys.platform.startswith('win'):
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
logger.info(f"Tesseract configuré pour Windows: {pytesseract.pytesseract.tesseract_cmd}")
elif sys.platform.startswith('linux'):
logger.info("Système Linux détecté, utilisation du chemin Tesseract par défaut")
else:
logger.info(f"Système détecté: {sys.platform}, utilisation du chemin Tesseract par défaut")
# Vérifier que Tesseract est installé
tesseract_version = pytesseract.get_tesseract_version()
logger.info(f"Version de Tesseract: {tesseract_version}")
tesseract_available = True
except Exception as e:
logger.error(f"Tesseract n'est pas installé ou inaccessible: {e}")
logger.warning("Le module OCR utilisera des alternatives à Tesseract")
# Répertoire temporaire
TEMP_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "temp")
if not os.path.exists(TEMP_DIR):
try:
os.makedirs(TEMP_DIR)
logger.info(f"Dossier temporaire créé: {TEMP_DIR}")
except Exception as e:
logger.error(f"Impossible de créer le dossier temporaire: {e}")
# Utiliser le dossier temporaire du système
TEMP_DIR = tempfile.gettempdir()
logger.info(f"Utilisation du dossier temporaire système: {TEMP_DIR}")
def preprocess_image(image_path):
"""Prétraitement de l'image pour améliorer la reconnaissance OCR"""
try:
logger.info(f"Prétraitement de l'image: {image_path}")
# Vérifier que le fichier existe
if not os.path.isfile(image_path):
logger.error(f"Le fichier image n'existe pas: {image_path}")
return None
# Lire l'image avec OpenCV
img = cv2.imread(image_path)
if img is None:
logger.error(f"Impossible de lire l'image avec OpenCV: {image_path}")
# Essayer avec PIL et convertir
try:
pil_img = Image.open(image_path)
img_array = np.array(pil_img)
# Convertir RGB à BGR pour OpenCV
if len(img_array.shape) == 3 and img_array.shape[2] == 3:
img = img_array[:, :, ::-1].copy()
else:
img = img_array.copy()
logger.info("Image chargée avec PIL et convertie pour OpenCV")
except Exception as pil_error:
logger.error(f"Échec également avec PIL: {pil_error}")
return None
# Enregistrer les dimensions de l'image
height, width = img.shape[:2]
logger.debug(f"Dimensions de l'image: {width}x{height}")
# Vérifier si l'image est trop petite
if width < 100 or height < 100:
logger.warning(f"Image trop petite ({width}x{height}), agrandissement appliqué")
scale_factor = max(100 / width, 100 / height)
img = cv2.resize(img, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_CUBIC)
height, width = img.shape[:2]
logger.debug(f"Nouvelles dimensions: {width}x{height}")
# Convertir en niveaux de gris
if len(img.shape) == 3:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
logger.debug("Conversion en niveaux de gris effectuée")
else:
gray = img # Déjà en niveaux de gris
logger.debug("Image déjà en niveaux de gris")
# Appliquer un filtre bilatéral pour réduire le bruit tout en préservant les bords
try:
blur = cv2.bilateralFilter(gray, 9, 75, 75)
logger.debug("Filtre bilatéral appliqué")
except Exception as e:
logger.warning(f"Échec du filtre bilatéral: {e}, utilisation de l'image originale")
blur = gray
# Normaliser la luminosité et le contraste
try:
normalized = cv2.normalize(blur, None, 0, 255, cv2.NORM_MINMAX)
logger.debug("Normalisation effectuée")
except Exception as e:
logger.warning(f"Échec de la normalisation: {e}, utilisation de l'image filtrée")
normalized = blur
# Tester plusieurs méthodes de prétraitement et choisir la meilleure
preprocessed_images = []
# Méthode 1: Seuillage adaptatif
try:
thresh = cv2.adaptiveThreshold(normalized, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
preprocessed_images.append(("adaptive_threshold", thresh))
logger.debug("Seuillage adaptatif appliqué")
except Exception as e:
logger.warning(f"Échec du seuillage adaptatif: {e}")
# Méthode 2: Seuillage simple
try:
_, thresh_simple = cv2.threshold(normalized, 127, 255, cv2.THRESH_BINARY)
preprocessed_images.append(("simple_threshold", thresh_simple))
logger.debug("Seuillage simple appliqué")
except Exception as e:
logger.warning(f"Échec du seuillage simple: {e}")
# Méthode 3: Seuillage Otsu
try:
_, thresh_otsu = cv2.threshold(normalized, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
preprocessed_images.append(("otsu_threshold", thresh_otsu))
logger.debug("Seuillage Otsu appliqué")
except Exception as e:
logger.warning(f"Échec du seuillage Otsu: {e}")
# Si aucune méthode n'a fonctionné, utiliser l'image normalisée
if not preprocessed_images:
preprocessed_images.append(("normalized", normalized))
logger.warning("Aucune méthode de seuillage n'a fonctionné, utilisation de l'image normalisée")
# Méthode 4: Image originale en couleur (pour EasyOCR)
if easyocr_available:
# Sauvegarder aussi l'image originale en couleur pour EasyOCR
preprocessed_images.append(("original", img))
logger.debug("Image originale sauvegardée pour EasyOCR")
# Enregistrer toutes les versions prétraitées
processed_images_paths = []
for name, img_processed in preprocessed_images:
processed_image_path = os.path.join(TEMP_DIR, f"processed_{name}_{os.path.basename(image_path)}")
try:
cv2.imwrite(processed_image_path, img_processed)
processed_images_paths.append((name, processed_image_path))
logger.debug(f"Image prétraitée '{name}' sauvegardée: {processed_image_path}")
except Exception as e:
logger.error(f"Impossible de sauvegarder l'image prétraitée '{name}': {e}")
return processed_images_paths
except Exception as e:
logger.error(f"Erreur inattendue lors du prétraitement de l'image: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return None
def perform_easyocr(image_path, langs=['fr', 'en']):
"""Extraire le texte d'une image en utilisant EasyOCR"""
global easyocr_reader
try:
logger.info(f"Début OCR avec EasyOCR sur: {image_path}")
# Initialiser le lecteur si ce n'est pas déjà fait
if easyocr_reader is None:
logger.info(f"Initialisation d'EasyOCR avec les langues: {langs}")
easyocr_reader = easyocr.Reader(langs, gpu=False)
# Effectuer l'OCR
result = easyocr_reader.readtext(image_path)
# Extraire le texte et la confiance
text_blocks = []
confidence_sum = 0
for detection in result:
bbox, text, confidence = detection
text_blocks.append(text)
confidence_sum += confidence
# Calculer la confiance moyenne
avg_confidence = confidence_sum / len(result) if result else 0
# Joindre tous les blocs de texte
full_text = ' '.join(text_blocks)
logger.info(f"EasyOCR a extrait {len(text_blocks)} blocs de texte avec une confiance moyenne de {avg_confidence:.2f}")
return full_text, avg_confidence
except Exception as e:
logger.error(f"Erreur EasyOCR: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return None, 0
def perform_ocr(image_path, lang='fra+eng'):
"""Extraire le texte d'une image en utilisant Tesseract OCR ou alternatives"""
try:
logger.info(f"Début de l'OCR sur: {image_path}")
# Prétraiter l'image
processed_image_paths = preprocess_image(image_path)
if not processed_image_paths:
logger.warning(f"Aucune image prétraitée disponible, tentative avec l'image originale")
processed_image_paths = [("original", image_path)]
best_text = ""
best_conf = -1
best_method = "none"
# Si EasyOCR est disponible, l'essayer en premier
if easyocr_available:
logger.info("Tentative d'OCR avec EasyOCR")
# Trouver l'image originale ou la première disponible
original_img_path = next((path for name, path in processed_image_paths if name == "original"),
processed_image_paths[0][1])
try:
easyocr_text, easyocr_conf = perform_easyocr(original_img_path)
if easyocr_text:
logger.info(f"EasyOCR a réussi avec une confiance de {easyocr_conf:.2f}")
best_text = easyocr_text
best_conf = easyocr_conf * 100 # Convertir en pourcentage pour être cohérent avec Tesseract
best_method = "easyocr"
except Exception as easyocr_error:
logger.warning(f"Échec d'EasyOCR: {easyocr_error}")
# Si Tesseract est disponible et qu'EasyOCR n'a pas donné de résultat satisfaisant
if tesseract_available and (best_conf < 50 or not best_text):
logger.info("Tentative d'OCR avec Tesseract")
# Essayer différentes configurations OCR sur les images prétraitées
for img_name, img_path in processed_image_paths:
if img_name == "original" and best_method == "easyocr":
continue # Sauter l'original si déjà traité par EasyOCR
logger.debug(f"Tesseract OCR sur: {img_path} ({img_name})")
# Configurations OCR à essayer
configs = [
r'--oem 3 --psm 6', # Configuration par défaut
r'--oem 3 --psm 4', # Texte sur une seule colonne
r'--oem 3 --psm 1', # Détection automatique
r'--oem 1 --psm 6', # Mode Legacy + bloc de texte
]
for config in configs:
logger.debug(f"Essai avec configuration: {config}")
try:
# Effectuer l'OCR
text = pytesseract.image_to_string(Image.open(img_path), lang=lang, config=config)
# Nettoyer le texte
text = text.strip()
# Vérifier les données de confiance
try:
conf_data = pytesseract.image_to_data(Image.open(img_path), lang=lang, config=config, output_type=pytesseract.Output.DICT)
if 'conf' in conf_data and conf_data['conf']:
# Calculer la confiance moyenne (ignorer les valeurs -1)
valid_confs = [c for c in conf_data['conf'] if c != -1]
if valid_confs:
avg_conf = sum(valid_confs) / len(valid_confs)
logger.debug(f"Confiance moyenne: {avg_conf}% pour la configuration {config}")
# Garder le meilleur résultat
if text and (avg_conf > best_conf or not best_text):
best_text = text
best_conf = avg_conf
best_method = f"tesseract_{img_name}_{config}"
except Exception as conf_error:
logger.warning(f"Impossible d'obtenir les données de confiance: {conf_error}")
# Si le texte est non vide et qu'on n'a pas encore de texte, le conserver
if text and not best_text:
best_text = text
best_conf = 0
best_method = f"tesseract_{img_name}_{config}_no_conf"
except Exception as ocr_error:
logger.warning(f"Échec OCR avec configuration {config}: {ocr_error}")
# Si on a trouvé un texte valide avec une bonne confiance, arrêter les essais
if best_text and best_conf > 70:
logger.info(f"Texte trouvé avec une bonne confiance: {best_conf}%")
break
# Nettoyer les images temporaires
for _, path in processed_image_paths:
if path != image_path and os.path.exists(path):
try:
os.remove(path)
logger.debug(f"Fichier temporaire supprimé: {path}")
except Exception as e:
logger.warning(f"Impossible de supprimer le fichier temporaire {path}: {e}")
if best_text:
logger.info(f"Texte extrait avec méthode {best_method} et confiance {best_conf:.2f}%")
return best_text, best_conf, best_method
else:
logger.warning("Aucun texte extrait après tous les essais")
return None, 0, "none"
except Exception as e:
logger.error(f"Erreur OCR: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return None, 0, "error"
def process_image(image_path, lang='fra+eng'):
"""Traiter une image et extraire son texte"""
try:
logger.info(f"Traitement de l'image: {image_path}")
# Extraire le texte
extracted_text, confidence, method = perform_ocr(image_path, lang)
if not extracted_text:
logger.warning("Échec de l'extraction de texte")
return {"error": "Aucun texte n'a pu être extrait de l'image"}
# Préparer le résultat
result = {
"text": extracted_text,
"confidence": confidence if confidence > 0 else 0.5, # Valeur par défaut si aucune confiance n'est calculée
"ocr_method": method
}
logger.info(f"Texte extrait avec succès: {len(extracted_text)} caractères avec la méthode {method}")
return result
except Exception as e:
logger.error(f"Erreur lors du traitement de l'image: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return {"error": str(e)}
# Test direct du module
if __name__ == "__main__":
if len(sys.argv) > 1:
image_path = sys.argv[1]
result = process_image(image_path)
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
print("Usage: python ocr_module.py <chemin_image>")