Spaces:
Sleeping
Sleeping
File size: 17,069 Bytes
547e622 6048e44 547e622 8e1bf05 547e622 6048e44 8e1bf05 6048e44 8e1bf05 6048e44 8e1bf05 6048e44 8e1bf05 6048e44 547e622 8e1bf05 547e622 8e1bf05 547e622 8e1bf05 547e622 8e1bf05 547e622 8e1bf05 547e622 8e1bf05 547e622 8e1bf05 547e622 8e1bf05 547e622 8e1bf05 6048e44 8e1bf05 6048e44 8e1bf05 547e622 8e1bf05 547e622 6048e44 8e1bf05 6048e44 547e622 8e1bf05 547e622 8e1bf05 547e622 8e1bf05 6048e44 547e622 8e1bf05 6048e44 547e622 6048e44 8e1bf05 6048e44 8e1bf05 6048e44 8e1bf05 6048e44 8e1bf05 6048e44 547e622 8e1bf05 6048e44 8e1bf05 6048e44 8e1bf05 6048e44 547e622 8e1bf05 6048e44 547e622 8e1bf05 547e622 8e1bf05 547e622 6048e44 547e622 8e1bf05 547e622 6048e44 547e622 6048e44 547e622 8e1bf05 547e622 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
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>") |