Mise à jour bayesian (fonctionnel), histogramme + output

This commit is contained in:
yanis.bouarfa 2025-01-07 15:14:21 +01:00
parent ecd9ca1040
commit 0d15b16e9f
4 changed files with 189 additions and 255 deletions

68
main.py
View file

@ -1,31 +1,55 @@
import time
from src.classifiers.bayesian import BayesianClassifier
import os
import cv2
from src.pipeline import ObjectDetectionPipeline
from src.classifiers.bayesian import BayesianClassifier
if __name__ == "__main__":
# Chemin de l'image à traiter
image_path = "data/page.png"
# Initialisation du pipeline avec le chemin de l'image
pipeline = ObjectDetectionPipeline(image_path)
# Initialisation et chargement du modèle Bayésien
bayesian_model = BayesianClassifier()
# Chemin vers le modèle entraîné
model_path = "models/bayesian_model.pth"
pipeline.load_model(model_path, bayesian_model)
# Chargement du modèle bayésien
print(f"Chargement du modèle bayésien depuis {model_path}")
bayesian_model = BayesianClassifier()
try:
bayesian_model.load_model(model_path)
print(f"Modèle bayésien chargé depuis {model_path}")
except Exception as e:
print(f"Erreur lors du chargement du modèle : {e}")
exit(1)
# Chemin de l'image de test
image_path = "data/page.png"
if not os.path.exists(image_path):
print(f"L'image de test {image_path} n'existe pas.")
exit(1)
# Initialisation du dossier de sortie
output_dir = "output"
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Initialisation de la pipeline
print("Initialisation de la pipeline...")
pipeline = ObjectDetectionPipeline(image_path=image_path, model=bayesian_model, output_dir=output_dir)
# Chargement de l'image
pipeline.load_image()
print("Chargement de l'image...")
try:
pipeline.load_image()
except FileNotFoundError as e:
print(e)
exit(1)
# Mesure du temps d'exécution pour la détection et classification
start_time = time.time()
class_counts, detected_objects = pipeline.detect_and_classify_objects()
end_time = time.time()
# Détection et classification des objets
print("Détection et classification des objets...")
try:
class_counts, detected_objects = pipeline.detect_and_classify_objects()
except Exception as e:
print(f"Erreur lors de la détection/classification : {e}")
exit(1)
# Résultats
print(f"Temps d'exécution: {end_time - start_time:.2f} secondes")
print("Comptage des classes :", class_counts)
print("Nombre d'objets détectés :", len(detected_objects))
# Affichage des résultats
# Sauvegarde et affichage des résultats
print("Sauvegarde et affichage des résultats...")
pipeline.display_results(class_counts, detected_objects)
print(f"Les résultats ont été sauvegardés dans le dossier : {output_dir}")

View file

@ -1 +1,6 @@
opencv-python pytorch tensorflow numpy pandas matplotlib
opencv-python
torch
tensorflow
numpy
pandas
matplotlib

View file

@ -5,66 +5,69 @@ import torch
from collections import defaultdict
import matplotlib.pyplot as plt
class BayesianClassifier:
def __init__(self):
"""
Initialisation du classificateur Bayésien avec les paramètres nécessaires.
"""
self.feature_means = {} # Moyennes des caractéristiques pour chaque classe
self.feature_variances = {} # Variances des caractéristiques pour chaque classe
self.class_priors = {} # Probabilités a priori pour chaque classe
self.classes = [] # Liste des classes disponibles
self.feature_means = {}
self.feature_variances = {}
self.class_priors = {}
self.classes = []
def extract_features(self, image):
"""
Extraire les caractéristiques d'une image donnée (Histogramme des Gradients Orientés - HOG).
:param image: Image en entrée
:return: Tableau des caractéristiques normalisées
"""
# Conversion de l'image en niveaux de gris si nécessaire
if len(image.shape) == 3 and image.shape[2] == 3:
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray_image = image
# Binarisation de l'image
binary_image = cv2.adaptiveThreshold(
gray_image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 2
# Initialize HOG descriptor with standard parameters
self.hog = cv2.HOGDescriptor(
_winSize=(28, 28),
_blockSize=(8, 8),
_blockStride=(4, 4),
_cellSize=(8, 8),
_nbins=9
)
# Extraction des contours
contours, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
def extract_features(self, image):
try:
# Convert image to grayscale
if len(image.shape) == 3 and image.shape[2] == 3:
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray_image = image
features = []
for contour in contours:
if cv2.contourArea(contour) < 22:
continue
# Apply adaptive thresholding
binary_image = cv2.adaptiveThreshold(
gray_image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 2
)
x, y, width, height = cv2.boundingRect(contour)
letter_image = gray_image[y:y + height, x:x + width]
letter_image = cv2.resize(letter_image, (28, 28))
# Find contours
contours, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
print("No contours found.")
return np.array([])
# Calcul des caractéristiques HOG
hog = cv2.HOGDescriptor()
hog_features = hog.compute(letter_image)
features = []
for contour in contours:
if cv2.contourArea(contour) < 22:
continue
features.append(hog_features.flatten())
x, y, w, h = cv2.boundingRect(contour)
letter_image = gray_image[y:y + h, x:x + w]
letter_image = cv2.resize(letter_image, (28, 28))
features = np.array(features)
# Compute HOG features
hog_features = self.hog.compute(letter_image)
features.append(hog_features.flatten())
# Normalisation des caractéristiques
norms = np.linalg.norm(features, axis=1, keepdims=True)
features = features / np.where(norms > 1e-6, norms, 1)
features = np.array(features)
if features.size == 0:
print("No features extracted.")
return np.array([])
return features
norms = np.linalg.norm(features, axis=1, keepdims=True)
features = features / np.where(norms > 1e-6, norms, 1)
return features
except Exception as e:
print(f"Error in extract_features: {e}")
return np.array([])
def train(self, dataset_path):
"""
Entraîner le classificateur avec un catalogue d'images organisées par classe.
:param dataset_path: Chemin vers le dossier contenant les images classées
"""
class_features = defaultdict(list)
total_images = 0
@ -74,17 +77,24 @@ class BayesianClassifier:
if class_name not in self.classes:
self.classes.append(class_name)
for image_name in os.listdir(class_folder_path):
image_path = os.path.join(class_folder_path, image_name)
if os.path.isfile(image_path):
image = cv2.imread(image_path)
if image is not None:
features = self.extract_features(image)
for feature in features:
class_features[class_name].append(feature)
total_images += 1
for img_name in os.listdir(class_folder_path):
img_path = os.path.join(class_folder_path, img_name)
if os.path.isfile(img_path):
try:
image = cv2.imread(img_path)
if image is not None:
features = self.extract_features(image)
if features.size > 0:
for feature in features:
class_features[class_name].append(feature)
total_images += 1
else:
print(f"No features extracted for {img_path}")
else:
print(f"Failed to load image: {img_path}")
except Exception as e:
print(f"Error processing {img_path}: {e}")
# Calcul des moyennes, variances et probabilités a priori
for class_name in self.classes:
if class_name in class_features:
features = np.array(class_features[class_name])
@ -94,50 +104,7 @@ class BayesianClassifier:
print("Training completed for classes:", self.classes)
def predict(self, image):
"""
Prédire la classe d'une image donnée.
:param image: Image à classer
:return: Classe prédite
"""
rotation_weights = {
0: 1.0,
90: 0.5,
180: 0.5,
270: 0.5
}
posterior_probabilities = {}
for rotation, weight in rotation_weights.items():
k = rotation // 90
rotated_image = np.rot90(image, k)
features = self.extract_features(rotated_image)
for class_name in self.classes:
mean = self.feature_means[class_name]
variance = self.feature_variances[class_name]
prior = self.class_priors[class_name]
likelihood = -0.5 * np.sum((features - mean) ** 2 / variance) + np.log(2 * np.pi * variance)
posterior = likelihood + np.log(prior)
weighted_posterior = posterior * (1 - weight * 0.5)
if class_name not in posterior_probabilities:
posterior_probabilities[class_name] = weighted_posterior
else:
posterior_probabilities[class_name] = max(posterior_probabilities[class_name], weighted_posterior)
return max(posterior_probabilities, key=posterior_probabilities.get)
def save_model(self, model_path):
"""
Sauvegarder le modèle Bayésien dans un fichier.
:param model_path: Chemin du fichier de sauvegarde
"""
model_data = {
"feature_means": self.feature_means,
"feature_variances": self.feature_variances,
@ -147,37 +114,51 @@ class BayesianClassifier:
if not os.path.exists(os.path.dirname(model_path)):
os.makedirs(os.path.dirname(model_path))
torch.save(model_data, model_path)
print("Model saved in {}".format(model_path))
print(f"Model saved to {model_path}")
def load_model(self, model_path):
"""
Charger un modèle Bayésien sauvegardé.
:param model_path: Chemin du fichier de modèle
"""
if os.path.exists(model_path):
model_data = torch.load(model_path)
model_data = torch.load(model_path, weights_only=False)
self.feature_means = model_data["feature_means"]
self.feature_variances = model_data["feature_variances"]
self.class_priors = model_data["class_priors"]
self.classes = model_data["classes"]
print("Model loaded from {}".format(model_path))
print(f"Model loaded from {model_path}")
else:
print("Model does not exist: {}".format(model_path))
print(f"No model found at {model_path}.")
def predict(self, image):
try:
features = self.extract_features(image)
if features.size == 0:
print("Empty features, skipping prediction.")
return None
posteriors = {}
for class_name in self.classes:
mean = self.feature_means[class_name]
variance = self.feature_variances[class_name]
prior = self.class_priors[class_name]
likelihood = -0.5 * np.sum(((features - mean) ** 2) / variance + np.log(2 * np.pi * variance))
posterior = likelihood + np.log(prior)
posteriors[class_name] = posterior
return max(posteriors, key=posteriors.get)
except Exception as e:
print(f"Error in prediction: {e}")
return None
def visualize(self):
"""
Visualiser les moyennes des caractéristiques pour chaque classe.
"""
if not self.classes:
print("No classes to visualize")
print("No classes to visualize.")
return
for class_name in self.classes:
mean_features = self.feature_means[class_name]
plt.figure(figsize=(10, 4))
plt.title("Mean features for class: {}".format(class_name))
plt.title(f"Mean features for class: {class_name}")
plt.plot(mean_features)
plt.xlabel("Feature Index")
plt.ylabel("Mean Value")

View file

@ -3,182 +3,106 @@ import os
from matplotlib import pyplot as plt
from collections import defaultdict
class ObjectDetectionPipeline:
def __init__(self, image_path, model=None):
def __init__(self, image_path, model=None, output_dir="output", min_contour_area=50, binary_threshold=127):
"""
Initialisation de la pipeline de détection d'objets avec le chemin de l'image et le modèle.
Initialisation de la pipeline de détection d'objets.
:param image_path: Chemin de l'image à traiter
:param model: Modèle de classification à utiliser (par défaut, aucun modèle n'est chargé)
:param model: Modèle de classification à utiliser
:param output_dir: Dossier les résultats seront sauvegardés
:param min_contour_area: Aire minimale des contours à prendre en compte
:param binary_threshold: Seuil de binarisation pour les canaux
"""
self.image_path = image_path
self.image = None
self.binary_image = None
self.model = model # Le modèle personnalisé à utiliser
self.model = model
self.output_dir = output_dir
self.min_contour_area = min_contour_area
self.binary_threshold = binary_threshold
def load_model(self, model_path: str, instance_classifier=None):
"""
Charger un modèle pré-entraîné ou un classifieur bayésien.
:param model_path: Chemin du fichier du modèle
:param instance_classifier: Instance du classifieur à utiliser
"""
if os.path.exists(model_path):
self.model = instance_classifier # Créer une instance du classifieur
print(f"Chargement du modèle bayésien depuis {model_path}")
self.model.load_model(model_path) # Charger les paramètres du modèle
print(f"Modèle bayésien chargé depuis {model_path}")
else:
print(f"Aucun modèle trouvé à {model_path}. Un nouveau modèle sera créé.")
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)
def load_image(self):
"""
Charger l'image spécifiée par le chemin d'accès.
:return: Image chargée
"""
"""Charge l'image spécifiée."""
self.image = cv2.imread(self.image_path)
if self.image is None:
raise FileNotFoundError(f"L'image {self.image_path} est introuvable.")
return self.image
def preprocess_image(self):
"""
Prétraiter l'image pour la préparer à l'inférence.
:return: Image binarisée
"""
# Binarisation de l'image par canaux
"""Prétraite l'image pour la préparer à l'inférence."""
channels = cv2.split(self.image)
binary_images = []
for channel in channels:
_, binary_channel = cv2.threshold(channel, 127, 255, cv2.THRESH_BINARY_INV)
_, binary_channel = cv2.threshold(channel, self.binary_threshold, 255, cv2.THRESH_BINARY_INV)
binary_images.append(binary_channel)
# Fusionner les canaux binarisés
binary_image = cv2.bitwise_or(binary_images[0], binary_images[1])
binary_image = cv2.bitwise_or(binary_image, binary_images[2])
self.binary_image = binary_image
return binary_image
def detect_and_classify_objects(self):
"""
Détecter et classer les objets présents dans l'image en fonction des contours détectés.
:return: Dictionnaire des classes détectées et objets détectés
"""
"""Détecte et classe les objets présents dans l'image."""
if self.model is None:
print("Aucun modèle de classification fourni.")
return {}
raise ValueError("Aucun modèle de classification fourni.")
self.binary_image = self.preprocess_image()
# Trouver les contours dans l'image binarisée
contours, _ = cv2.findContours(self.binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
class_counts = defaultdict(int)
detected_objects = []
for contour in contours:
if cv2.contourArea(contour) < 50:
if cv2.contourArea(contour) < self.min_contour_area:
continue
x, y, w, h = cv2.boundingRect(contour)
letter_image = self.image[y:y + h, x:x + w]
# Prédiction avec le modèle
predicted_class = self.model.predict(letter_image)
if predicted_class is None:
print("Skipping object with invalid prediction.")
continue
# Incrémenter le comptage de la classe prédite
class_counts[predicted_class] += 1
# Ajouter les coordonnées et la classe prédite
detected_objects.append((x, y, w, h, predicted_class))
return dict(sorted(class_counts.items())), detected_objects
def display_results(self, class_counts, detected_objects):
"""
Afficher les résultats de la détection et classification.
:param class_counts: Dictionnaire des classes détectées et leurs occurrences
:param detected_objects: Liste des objets détectés avec leurs coordonnées et classes prédites
"""
self.display_binary_image()
self.display_image_with_classes(detected_objects)
self.display_image_with_annotations(detected_objects)
self.display_classes_count(class_counts)
def display_binary_image(self):
"""
Afficher l'image binaire résultant du prétraitement.
"""
plt.figure(figsize=(self.binary_image.shape[1] / 100, self.binary_image.shape[0] / 100))
plt.imshow(self.binary_image, cmap='gray')
plt.axis('off')
plt.show()
def display_image_with_classes(self, detected_objects):
"""
Afficher l'image avec les classes prédites annotées.
:param detected_objects: Liste des objets détectés avec leurs coordonnées et classes prédites
"""
image_with_classes_only = self.image.copy()
def save_results(self, class_counts, detected_objects):
"""Sauvegarde les résultats de détection et classification."""
# Sauvegarder l'image binaire
binary_output_path = os.path.join(self.output_dir, "binary_image.jpg")
cv2.imwrite(binary_output_path, self.binary_image)
# Sauvegarder l'image annotée
annotated_image = self.image.copy()
for (x, y, w, h, predicted_class) in detected_objects:
if predicted_class[-1] == "_":
text = predicted_class.split("_")[0].upper()
else:
text = predicted_class.lower()
cv2.rectangle(image_with_classes_only, (x, y), (x + w, y + h), (255, 255, 255), -1)
font_scale = 0.7
font_thickness = 2
text_size = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, font_thickness)[0]
text_x = x + (w - text_size[0]) // 2
text_y = y + (h + text_size[1]) // 2
cv2.putText(image_with_classes_only, text, (text_x, text_y),
cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 0), font_thickness)
fig = plt.figure(figsize=(image_with_classes_only.shape[1] / 100, image_with_classes_only.shape[0] / 100))
plt.imshow(cv2.cvtColor(image_with_classes_only, cv2.COLOR_BGR2RGB))
plt.axis('off')
plt.show()
def display_image_with_annotations(self, detected_objects):
"""
Afficher l'image avec les annotations des objets détectés (rectangles et classes).
:param detected_objects: Liste des objets détectés avec leurs coordonnées et classes prédites
"""
image_with_annotations = self.image.copy()
for (x, y, w, h, predicted_class) in detected_objects:
if predicted_class[-1] == "_":
text = predicted_class.split("_")[0].upper()
else:
text = predicted_class.lower()
cv2.rectangle(image_with_annotations, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.putText(image_with_annotations, text, (x, y - 10),
cv2.rectangle(annotated_image, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.putText(annotated_image, str(predicted_class), (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
annotated_output_path = os.path.join(self.output_dir, "annotated_image.jpg")
cv2.imwrite(annotated_output_path, annotated_image)
fig = plt.figure(figsize=(image_with_annotations.shape[1] / 100, image_with_annotations.shape[0] / 100))
plt.imshow(cv2.cvtColor(image_with_annotations, cv2.COLOR_BGR2RGB))
plt.axis('off')
plt.show()
# Sauvegarder les classes et leurs occurrences
results_text_path = os.path.join(self.output_dir, "results.txt")
with open(results_text_path, "w") as f:
for class_name, count in class_counts.items():
f.write(f"{class_name}: {count}\n")
def display_classes_count(self, class_counts):
"""
Afficher le nombre d'objets détectés par classe.
def display_results(self, class_counts, detected_objects):
"""Affiche et sauvegarde les résultats."""
self.save_results(class_counts, detected_objects)
:param class_counts: Dictionnaire des classes détectées et leurs occurrences
"""
plt.figure(figsize=(10, 5))
plt.bar(class_counts.keys(), class_counts.values())
plt.xlabel("Classes")
plt.ylabel("Nombre de lettres")
plt.title("Classes détectées et leur nombre")
plt.ylabel("Nombre d'objets")
plt.title("Distribution des classes détectées")
plt.show()