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