-
Aperçu du projet
-
Installation des bibliothèques nécessaires
-
Conception de l'interface graphique
-
Chargement du modèle et prédiction
-
Flux de prétraitement d'image
-
Analyse du code complet
-
Démonstration des résultats
-
Aperçu du projet
Cet projet met en œuvre une application de reconnaissance de chiffres manuscrits basée sur PyTorch et Tkinter, permettant aux utilisateurs d'écrire des chiffres sur une toile qui seront identifiés en temps réel par le système. Les technologies principales utilisées sont :
- Framework d'apprentissage profond PyTorch
- Développement d'interface graphique Tkinter
- Traitement d'images OpenCV
- Bibliothèque de traitement d'images PIL
- Installation des bibliothèques nécessaires
Installation avec pip
# Installation des bibliothèques de base
pip install torch torchvision pillow numpy opencv-python
# Bibliothèques liées à l'interface graphique
pip install tk
Vérification de l'installation
import torch
print(torch.__version__) # Devrait afficher 1.x.x
import tkinter as tk
print(tk.TkVersion) # Devrait afficher 8.6
- Conception de l'interface graphique
class ApplicationManuscrite:
def __init__(self, racine):
self.racine = racine
self.racine.title("Reconnaissance de chiffres manuscrits")
# Création d'une toile de 280x280 pixels avec fond noir
self.toile = tk.Canvas(racine, width=280, height=280, bg="black")
self.toile.pack()
# Zone des boutons
cadre_boutons = tk.Frame(racine)
cadre_boutons.pack(pady=10)
# Boutons de reconnaissance et de nettoyage
self.bouton_reconnaître = tk.Button(cadre_boutons, text="Reconnaître", command=self.reconnaître_chiffre)
self.bouton_reconnaître.pack(side=tk.LEFT, padx=5)
self.bouton_effacer = tk.Button(cadre_boutons, text="Effacer", command=self.nettoyer_toile)
self.bouton_effacer.pack(side=tk.LEFT, padx=5)
# Étiquette d'affichage des résultats
self.etiquette_resultat = tk.Label(racine, text="Veuillez dessiner un chiffre dans la zone ci-dessus", font=("Arial", 16))
self.etiquette_resultat.pack(pady=10)
Caractéristiques de l'interface :
- Interface utilisateur simple et intuitive
- Toile de 280x280 pixels simulant la taille du jeu de données MNIST
- Boutons de reconnaissance et de nettoyage pour les interactions de base
- Affichage des résultats en caractères de grande taille
- Chargement du modèle et prédiction
def __init__(self, racine):
# ...Autre code d'initialisation...
# Chargement du modèle pré-entraîné (sélection automatique GPU ou CPU)
self.dispositif = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
self.modele = ReseauCNN().to(self.dispositif) # Initialisation et déplacement du modèle
self.modele = torch.load("poids/model_9.pth", map_location=self.dispositif) # Chargement des poids
self.modele.to(self.dispositif).eval() # Passage en mode évaluation
def reconnaître_chiffre(self):
# ...Prétraitement d'image...
# Conversion de l'image au format d'entrée du modèle
transformation = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)) # Paramètres de normalisation MNIST
])
tenseur_img = transformation(img).unsqueeze(0).to(self.dispositif)
# Prédiction
with torch.no_grad():
sortie = self.modele(tenseur_img)
_, prediction = torch.max(sortie, 1)
self.etiquette_resultat.config(text=f"Résultat : {prediction.item()}")
Points clés :
- Détection automatique et utilisation du GPU pour l'accélération
- Chargement d'un modèle CNN pré-entraîné
- Utilisation du mode
eval()pour assurer la stabilité des prédictions torch.no\_grad()pour réduire l'utilisation de la mémoire
- Flux de prétraitement d'image
def reconnaître_chiffre(self):
# 1. Récupération de l'image de la toile (blanc sur fond noir)
img = self.image
# 2. Binarisation
img_cv = np.array(img)
_, img_bin = cv2.threshold(img_cv, 128, 255, cv2.THRESH_BINARY)
# 3. Opération de dilatation pour épaissir les traits
noyau = np.ones((3, 3), np.uint8)
img_dilatée = cv2.dilate(img_bin, noyau, iterations=1)
# 4. Recadrage de la région du chiffre
img = Image.fromarray(img_dilatée)
boite = img.getbbox()
if boite:
marge = 15 # Extension des bordures
x0, y0, x1, y1 = boite
x0, y0 = max(0, x0-marge), max(0, y0-marge)
x1, y1 = min(img.width, x1+marge), min(img.height, y1+marge)
img = img.crop((x0, y0, x1, y1))
# 5. Redimensionnement centré à 28x28
img.thumbnail((20, 20))
img = ImageOps.pad(img, (28, 28), color='black')
# 6. Redimensionnement final
img = img.resize((28, 28))
Étapes de prétraitement :
-
Récupération de l'image de la toile
-
Traitement de binarisation
-
Épaississement des traits par dilatation morphologique
-
Recadrage intelligent de la région du chiffre
-
Redimensionnement centré préservant les proportions
-
Standardisation finale des dimensions
-
Analyse du code complet
import tkinter as tk
from PIL import Image, ImageDraw, ImageOps
import torch
from torchvision import transforms
from ReseauCNN import ReseauCNN # Modèle CNN personnalisé
import numpy as np
import cv2
class ApplicationManuscrite:
def __init__(self, racine):
self.racine = racine
self.racine.title("Reconnaissance de chiffres manuscrits")
# Création de la toile de dessin
self.toile = tk.Canvas(racine, width=280, height=280, bg="black")
self.toile.pack()
# Cadre pour les boutons
cadre_boutons = tk.Frame(racine)
cadre_boutons.pack(pady=10)
# Bouton de reconnaissance
self.bouton_reconnaître = tk.Button(
cadre_boutons, text="Reconnaître", command=self.reconnaître_chiffre)
self.bouton_reconnaître.pack(side=tk.LEFT, padx=5)
# Bouton d'effacement
self.bouton_effacer = tk.Button(
cadre_boutons, text="Effacer", command=self.nettoyer_toile)
self.bouton_effacer.pack(side=tk.LEFT, padx=5)
# Étiquette d'affichage des résultats
self.etiquette_resultat = tk.Label(
racine, text="Veuillez dessiner un chiffre dans la zone ci-dessus", font=("Arial", 16))
self.etiquette_resultat.pack(pady=10)
# Initialisation des variables de dessin
self.image = Image.new("L", (280, 280), 0) # Image en niveaux de gris, initialement noire
self.dessin = ImageDraw.Draw(self.image)
self.derniere_x = None
self.derniere_y = None
# Liaison des événements de la souris
self.toile.bind("<b1-motion>", self.dessiner)
self.toile.bind("<buttonrelease-1>", self.reinitialiser)
# Chargement du modèle pré-entraîné (sélection automatique GPU ou CPU)
self.dispositif = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
self.modele = ReseauCNN().to(self.dispositif) # Initialisation et déplacement du modèle
self.modele = torch.load("poids/model_9.pth", map_location=self.dispositif) # Chargement des poids
self.modele.to(self.dispositif).eval() # Placement sur le bon dispositif et passage en mode évaluation
def dessiner(self, evenement):
x, y = evenement.x, evenement.y
if self.derniere_x and self.derniere_y:
# Dessin sur la toile (blanc pur, sans anti-aliasing)
self.toile.create_line(
self.derniere_x, self.derniere_y, x, y,
width=15, # Lignes épaissies
fill="white", # Blanc pur
capstyle=tk.ROUND,
smooth=False # Désactivation de l'anti-aliasing
)
# Dessin sur l'image (blanc pur)
self.dessin.line(
[self.derniere_x, self.derniere_y, x, y],
fill=255, # Blanc pur (255)
width=15 # Épaississement
)
self.derniere_x = x
self.derniere_y = y
def reinitialiser(self, evenement):
self.derniere_x = None
self.derniere_y = None
def nettoyer_toile(self):
self.toile.delete("all")
self.image = Image.new("L", (280, 280), 0)
self.dessin = ImageDraw.Draw(self.image)
self.etiquette_resultat.config(text="Veuillez dessiner un chiffre dans la zone ci-dessus")
def reconnaître_chiffre(self):
# 1. Récupération directe de l'image (déjà blanc sur fond noir)
img = self.image
# 2. Conversion en tableau numpy pour binarisation
img_cv = np.array(img)
_, img_bin = cv2.threshold(img_cv, 128, 255, cv2.THRESH_BINARY) # Binarisation
# 3. Épaississement des traits (opération de dilatation)
noyau = np.ones((3, 3), np.uint8) # Noyau 3x3
img_dilatée = cv2.dilate(img_bin, noyau, iterations=1) # Une dilatation
# 4. Conversion vers PIL pour traitement ultérieur
img = Image.fromarray(img_dilatée)
# 5. Trouver la boîte englobante du chiffre et le centrer (recadrage plus agressif)
boite = img.getbbox()
if boite:
# Extension de la zone de recadrage pour éviter de couper les traits
marge = 15
x0, y0, x1, y1 = boite
x0 = max(0, x0 - marge)
y0 = max(0, y0 - marge)
x1 = min(img.width, x1 + marge)
y1 = min(img.height, y1 + marge)
img = img.crop((x0, y0, x1, y1))
# Remplissage centré à 28x28, préservation du ratio
img.thumbnail((20, 20)) # D'abord redimensionner à 20x20 maximum
img = ImageOps.pad(img, (28, 28), color='black') # Remplissage à 28x28
# 6. Redimensionnement à 28x28 (assurer la cohérence des dimensions)
img = img.resize((28, 28))
# 7. Normalisation (cohérent avec l'entraînement)
transformation = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)) # Moyenne et écart-type MNIST
])
# Déplacement du tenseur d'entrée vers le dispositif actuel (GPU/CPU)
tenseur_img = transformation(img).unsqueeze(0).to(self.dispositif)
# Prédiction
with torch.no_grad():
sortie = self.modele(tenseur_img)
_, prediction = torch.max(sortie, 1)
self.etiquette_resultat.config(text=f"Résultat : {prediction.item()}")
# Sauvegarde des images d'entrée et prétraitées (pour débogage)
img.save("debug_entree.png") # Sauvegarde de l'entrée originale
img.save("debug_traitee.png") # Sauvegarde après prétraitement
if __name__ == "__main__":
racine = tk.Tk()
app = ApplicationManuscrite(racine)
racine.mainloop()
</buttonrelease-1></b1-motion>
- Démonstration des résultats
À travers la mise en œuvre de ce projet, nous avons développé une application complète de reconnaissance de chiffres manusrcits, couvrant l'ensemble du processus allant de la conception de l'interface graphique au déploiement du modèle d'apprentissage profond. Les lecteurs peuvent étendre et optimiser ce cadre de base en fonction de leurs besoins spécifiques.