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