Intégration d'OpenCV et EasyOCR pour l'extraction de texte d'images et le renommage automatisé de fichiers

Cet article détaille une méthode technique pour exploiter OpenCV et EasyOCR dans le but de reconnaître du texte dans des images, puis d'automatiser le renommage de fichiers en fonction des données extraites. Le processus comprend le prétraitement des images, l'extraction textuelle, la correspondance par noms et le traitement par lot.

Configuration de l'environnement

Pour implémenter cette solution, les bibliothèques suivantes sont nécessaires : OpenCV pour le traitement d'images, EasyOCR pour la reconnaissance optique de caractères, le module re pour les expressions régulières, os et pathlib pour la gestion des fichiers, time pour le chronométrage, et fuzzywuzzy pour la correspondance floue des chaînes de caractères.

Initialisation du moteur OCR

La reconnaissance de texte débute par l'initialisation du lecteur EasyOCR avec des paramètres spécifiques pour optimiser la performance sur des images contenant du texte coloré.


import easyocr
import ssl

# Contournement des problèmes potentiels de connexion SSL
ssl._create_default_https_context = ssl._create_unverified_context

# Configuration du moteur de reconnaissance
ocr_engine = easyocr.Reader(
    ['ch_sim', 'en'],
    gpu=True,  # Désactiver si aucune unité GPU CUDA n'est disponible
    model_storage_directory=r"C:\Users\mc254\.EasyOCR\model",
    download_enabled=False,  # Passer à True pour le téléchargement automatique des modèles
    recog_network='zh_sim_g2'
)

Extraction du texte à partir d'une image

La fonction ci-dessous applique un prétraitement en espace colorimétrique LAB pour isoler et lisser le canal de luminosité, préservant ainsi les couleurs du texte tout en réduisant le bruit de fond. Elle utilise ensuite EasyOCR pour extraire le texte, avec filtrage des éléments courts susceptibles d'être des artefacts.


import cv2
import re

def extraire_texte_image(chemin_image):
    try:
        img_src = cv2.imread(chemin_image)
        if img_src is None:
            raise ValueError("Échec de la lecture de l'image")

        # Conversion vers l'espace LAB et traitement du canal L
        espace_lab = cv2.cvtColor(img_src, cv2.COLOR_BGR2LAB)
        canal_l, canal_a, canal_b = cv2.split(espace_lab)
        canal_l_lisse = cv2.GaussianBlur(canal_l, (1, 1), 0)
        lab_traite = cv2.merge((canal_l_lisse, canal_a, canal_b))
        img_traitee = cv2.cvtColor(lab_traite, cv2.COLOR_LAB2BGR)

        # Exécution de la reconnaissance OCR
        resultat_brut = ocr_engine.readtext(
            img_traitee,
            detail=0,
            low_text=0.3,
            link_threshold=0.35,
            paragraph=False,
            width_ths=0.5
        )
        # Filtrage pour éliminer les textes trop courts (moins de 2 caractères)
        texte_filtre = [segment for segment in resultat_brut if len(segment) >= 2]
        return "".join(texte_filtre)
    except Exception as erreur:
        print(f"Erreur lors du traitement de {chemin_image} : {str(erreur)}")
        return f"【Erreur d'extraction】 : {str(erreur)}"

Logique de correspondance des noms

Cette fonction compare un nom cible au texte extrait d'une image. Elle utilise d'abord une correspondance stricte après nettoyage des chaînes, puis recourt à une correspondance floue via l'algorithme de fuzzywuzzy pour gérer les légères erreurs de reconnaissance.


from fuzzywuzzy import fuzz

def verifier_nom_dans_captures(chemin_image, nom_cible):
    texte_extrait = extraire_texte_image(chemin_image)
    if not texte_extrait or "Erreur d'extraction" in texte_extrait:
        return False, texte_extrait

    def nettoyer_texte(chaine):
        """Supprime les espaces, ponctuations et caractères spéciaux"""
        separateurs = [" ", " ", "-", "_", ":", ":", "(", ")", "(", ")", ",", ","]
        chaine_nettoyee = chaine.lower()
        for sep in separateurs:
            chaine_nettoyee = chaine_nettoyee.replace(sep, "")
        chaine_nettoyee = re.sub(r'[^\u4e00-\u9fff0-9a-z]', '', chaine_nettoyee)
        return chaine_nettoyee

    nom_nettoye = nettoyer_texte(nom_cible)
    texte_nettoye = nettoyer_texte(texte_extrait)

    correspondance_trouvee = False
    # Correspondance stricte
    if nom_nettoye in texte_nettoye:
        correspondance_trouvee = True
    else:
        # Correspondance floue sur des sous-chaînes de longueur similaire
        longueur_min = max(1, len(nom_nettoye) - 1)
        longueur_max = len(nom_nettoye) + 1
        for i in range(len(texte_nettoye) - longueur_min + 1):
            for l in range(longueur_min, longueur_max + 1):
                if i + l > len(texte_nettoye):
                    continue
                sous_chaine = texte_nettoye[i:i+l]
                if fuzz.ratio(nom_nettoye, sous_chaine) >= 75:
                    correspondance_trouvee = True
                    break
            if correspondance_trouvee:
                break

    return correspondance_trouvee, texte_extrait

Traitement par lot et renommage des fichiers

Cette fonction procède à la vérification en masse des images dans un dossier par rapport à une liste de noms. Pour chaque nom correspondant, elle renomme le fichier image en conséquence, en gérant les conflits de noms par l'ajout d'un compteur. Un rapport détaillé est généré avec les statistiques de soumission et le temps d'exécution.


import time
from pathlib import Path

def traitement_lot_avec_liste_noms(chemin_dossier, fichier_noms, fichier_sortie="resultat_verification.txt", afficher_temps_unitaire=False):
    debut_lot = time.time()

    try:
        with open(fichier_noms, "r", encoding="utf-8") as f:
            liste_noms = [ligne.strip() for ligne in f if ligne.strip()]
    except FileNotFoundError:
        print(f"Fichier de noms non trouvé : {fichier_noms}")
        return

    extensions_valides = ['.png', '.jpg', '.jpeg', '.bmp']
    fichiers_image = [f for f in Path(chemin_dossier).iterdir() if f.suffix.lower() in extensions_valides]

    with open(fichier_sortie, "w", encoding="utf-8") as rapport:
        rapport.write("Rapport de vérification des soumissions\n")
        rapport.write(f"Nombre total d'étudiants : {len(liste_noms)}\n")
        rapport.write(f"Nombre total d'images : {len(fichiers_image)}\n\n")

        etudiants_soumis = []
        images_non_associees = list(fichiers_image)

        for nom in liste_noms:
            image_correspondante = None
            nouveau_nom_fichier = None
            for img_fichier in images_non_associees:
                if afficher_temps_unitaire:
                    debut_unite = time.time()

                correspondance, _ = verifier_nom_dans_captures(str(img_fichier), nom)

                if afficher_temps_unitaire:
                    fin_unite = time.time()
                    print(f"Traitement de {img_fichier.name} pour {nom} en {round(fin_unite - debut_unite, 2)}s")

                if correspondance:
                    image_correspondante = img_fichier.name
                    nouveau_nom_fichier = f"{nom}{img_fichier.suffix}"
                    chemin_nouveau = img_fichier.parent / nouveau_nom_fichier
                    compteur = 1
                    while chemin_nouveau.exists():
                        nouveau_nom_fichier = f"{nom}_{compteur}{img_fichier.suffix}"
                        chemin_nouveau = img_fichier.parent / nouveau_nom_fichier
                        compteur += 1
                    img_fichier.rename(chemin_nouveau)
                    images_non_associees.remove(img_fichier)
                    break

            if image_correspondante:
                etudiants_soumis.append(nom)
                rapport.write(f"✓ {nom} - Soumis (image : {nouveau_nom_fichier})\n")
            else:
                rapport.write(f"✗ {nom} - Non trouvé\n")

        if images_non_associees:
            rapport.write("\nImages non associées (texte extrait) :\n")
            for idx, img in enumerate(images_non_associees, 1):
                texte_img = extraire_texte_image(str(img))
                rapport.write(f"{idx}. {img.name} : {texte_img[:500]}{'...' if len(texte_img)>500 else ''}\n")

        fin_lot = time.time()
        duree_totale = round(fin_lot - debut_lot, 2)
        rapport.write(f"\nSoumissions réussies : {len(etudiants_soumis)}\n")
        rapport.write(f"Non soumissions : {len(liste_noms) - len(etudiants_soumis)}\n")
        rapport.write(f"Durée totale du traitement : {duree_totale} secondes\n")

    print(f"Vérification terminée. Résultat sauvegardé dans : {fichier_sortie}")
    print(f"Durée totale : {duree_totale} secondes")

Vérification rapide pour une seule image

Pour le débogage, cette fonction permet de tester la reconnaissance et la correspondance sur une image unique avec chronométrage.


def verification_rapide_image_unique(chemin_image, nom):
    debut = time.time()
    resultat, texte = verifier_nom_dans_captures(chemin_image, nom)
    fin = time.time()
    duree = round(fin - debut, 2)

    print(f"\nRésultat pour {nom}")
    print(f"Correspondance : {'Oui' if resultat else 'Non'}")
    print(f"Texte extrait (extrait) : {texte[:500]}...")
    print(f"Durée de traitement : {duree} secondes")

Programme principal

Le point d'entrée principal exécute le traitement par lot avec les chemins spécifiés pour le dossier d'images et le fichier de liste de noms.


if __name__ == "__main__":
    dossier_captures = r"E:\assignment"
    fichier_liste_noms = r"E:\ClassMenu.txt"
    traitement_lot_avec_liste_noms(dossier_captures, fichier_liste_noms, afficher_temps_unitaire=False)
    # Pour tester une seule image : verification_rapide_image_unique(r"E:\assignment\exemplo.png", "Exemplo")

Étiquettes: OpenCV EasyOCR Python reconnaissance de texte traitement d'images

Publié le 8 juin à 00h22