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