Spaces:
Sleeping
Sleeping
| 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>") |