Master 1 VMI - Analyse d'Images Auteurs : Sebastian STRAUT, Vicent TAN, Yanis HEMDANE Encadrant : Camille Kurtz Année universitaire : 2025-2026
- Introduction
- Objectifs du projet
- Architecture du projet
- Jeu de données
- Méthodologie
- Résultats et évaluation
- Difficultés rencontrées
- Pistes d'amélioration
- Installation et utilisation
- Technologies utilisées
Ce projet s'inscrit dans le cadre du cours d'Analyse d'Images du Master 1 VMI. Il consiste à développer un système capable de détecter, compter et classifier automatiquement des pièces de monnaie en Euro à partir de photographies, en utilisant exclusivement des techniques classiques de traitement d'images.
Le système prend en entrée une image contenant un nombre variable de pièces d'Euro (de 1 centime à 2 euros) disposées sur un fond quelconque, et produit en sortie le nombre de pièces détectées, leur dénomination respective, ainsi que la somme totale en euros.
- Détecter les pièces présentes dans une image via la transformée de Hough circulaire
- Identifier le matériau de chaque pièce (cuivre, or, argent, bicolore) par analyse colorimétrique dans les espaces HSV et LAB
- Classifier la dénomination de chaque pièce (1ct, 2cts, 5cts, 10cts, 20cts, 50cts, 1euro, 2euro) grâce à une calibration par les pièces bicolores
- Calculer la somme totale en euros des pièces détectées
- Évaluer quantitativement les performances du système sur un dataset annoté
Projet_Analyse_Images/
│
├── README.md # Ce fichier
├── requirements.txt # Dépendances Python
│
├── data/
│ ├── annotations/
│ │ └── annotations.csv # Vérité terrain (filename, count, amount, group_id)
│ └── raw/ # Images brutes organisées par groupe
│ ├── gp1/ (14 images)
│ ├── gp2/ (15 images)
│ ├── gp3/ (10 images)
│ ├── gp4/ (10 images)
│ ├── gp5/ (25 images)
│ ├── gp6/ (10 images)
│ ├── gp7/ (12 images)
│ └── gp8/ (10 images)
│
├── notebooks/
│ ├── Traitement_des_Images.ipynb # Exploration du prétraitement
│ ├── Hough_estimate.ipynb # Réglage des paramètres HoughCircles
│ └── Evaluation.ipynb # Évaluation complète du pipeline
│
└── src/
├── __init__.py
├── constants.py # Constantes des pièces d'Euro (aires, valeurs)
├── loadator.py # Chargement du dataset et annotations
├── detector.py # Détection de cercles avec auto-zoom
├── analyzer.py # Analyse colorimétrique du matériau
├── classifier.py # Classification par dénomination
├── evaluator.py # Calcul des métriques de performance
└── pipeline.py # Orchestration du pipeline complet
Le code source est organisé de manière modulaire : chaque étape du pipeline est encapsulée dans une classe dédiée, ce qui facilite le test, la maintenance et l'évolution de chaque composant indépendamment.
Le dataset est composé de 106 images réparties en 8 groupes (gp1 à gp8). Chaque groupe correspond à un contexte de prise de vue différent (fond, éclairage, résolution, disposition des pièces).
Le fichier annotations.csv contient pour chaque image :
| Colonne | Description |
|---|---|
filename |
Nom du fichier image |
count |
Nombre réel de pièces dans l'image |
amount |
Montant total réel en euros |
group_id |
Identifiant du groupe (gp1 à gp8) |
| Groupe | Nombre d'images | Formats |
|---|---|---|
| gp1 | 14 | PNG |
| gp2 | 15 | PNG |
| gp3 | 10 | PNG |
| gp4 | 10 | PNG |
| gp5 | 25 | JPG / JPEG |
| gp6 | 10 | PNG |
| gp7 | 12 | PNG |
| gp8 | 10 | PNG |
Les images présentent une variété de situations : nombre de pièces allant de 1 à 48, pièces partiellement superposées, fonds variés, et conditions d'éclairage différentes selon les groupes.
Le traitement d'une image suit les étapes suivantes :
Image brute
│
▼
Redimensionnement (largeur cible = 1024 px)
│
▼
Détection des cercles (HoughCircles + auto-zoom itératif)
│
▼
Pour chaque cercle détecté :
├── Extraction de la ROI (Region of Interest)
├── Analyse colorimétrique → Matériau (Cuivre / Or / Argent / Bicolore)
└── Filtrage du bruit (rejet des faux positifs)
│
▼
Classification par dénomination (calibration via bicolores ou ratios relatifs)
│
▼
Calcul du montant total + rendu visuel
L'image d'entrée est d'abord redimensionnée à une largeur cible de 1024 pixels tout en conservant le ratio d'aspect. Cette normalisation permet d'assurer que les paramètres de détection (rayons min/max, distances entre cercles) restent cohérents quelle que soit la résolution d'origine.
Un flou médian (noyau 5×5) est ensuite appliqué à l'image en niveaux de gris. Le flou médian est particulièrement adapté car il préserve les contours nets (bords des pièces) tout en éliminant le bruit impulsionnel (sel et poivre).
Le notebook Traitement_des_Images.ipynb explore aussi l'application de CLAHE (Contrast Limited Adaptive Histogram Equalization) avec un clipLimit de 2.0 et une grille de 8×8, pour améliorer le contraste local dans les zones sous-exposées.
La détection des pièces repose sur la transformée de Hough circulaire (cv2.HoughCircles) avec la méthode du gradient (HOUGH_GRADIENT). Les paramètres utilisés sont :
| Paramètre | Valeur | Rôle |
|---|---|---|
dp |
1.2 | Résolution de l'accumulateur (rapport image/accumulateur) |
minDist |
60 | Distance minimale entre les centres de deux cercles (px) |
param1 |
200 | Seuil haut du détecteur de Canny |
param2 |
40 | Seuil de l'accumulateur (sensibilité de détection) |
minRadius |
20 | Rayon minimal des cercles recherchés (px) |
maxRadius |
80 | Rayon maximal des cercles recherchés (px) |
Le choix de ces paramètres résulte d'un travail expérimental documenté dans le notebook Hough_estimate.ipynb, où différentes combinaisons ont été testées pour trouver le meilleur compromis entre détection correcte et faux positifs.
Un problème récurrent est que les pièces peuvent apparaître trop grandes ou trop petites selon la distance de prise de vue, sortant ainsi de la plage de rayons attendue [20, 80] px. Pour y remédier, un mécanisme d'auto-zoom itératif a été implémenté dans detector.py :
- Après une première détection, le système analyse les rayons trouvés
- Si des pièces bicolores ont un rayon > 60 px → l'image est dézoomée (facteur = 55 / rayon_max)
- Si des pièces en cuivre ont un rayon < 35 px → l'image est zoomée (facteur = 38 / rayon_min)
- Le processus est répété jusqu'à 3 itérations ou jusqu'à ce que tous les rayons soient dans la plage cible [35, 60] px
Le facteur de zoom cumulé est conservé pour remettre l'image à l'échelle originale lors du rendu final. L'intérêt de cette approche est que la classification par aire est plus fiable lorsque les pièces occupent une taille normalisée en pixels.
L'identification du matériau est réalisée dans analyzer.py via une analyse des espaces couleur HSV et LAB. Pour chaque pièce détectée, une ROI carrée est extraite et un masque circulaire est appliqué pour ignorer le fond.
Valeur moyenne HSV (V) < 40 ?
┌─── Oui ──→ BRUIT (rejeté)
│
└─── Non
│
Ratio argenté > 0.85 ?
┌─── Oui ──→ ARGENTÉ (Silverish)
│
└─── Non
│
0.10 < Ratio argenté < 0.80 ?
┌─── Oui ──→ BICOLORE
│ ├── Densité argent coeur > anneau → 1 Euro
│ └── Densité argent anneau > coeur → 2 Euro
│
└─── Non
│
Canal A (LAB) > 136 ?
┌─── Oui ──→ CUIVRE (1ct, 2cts, 5cts)
└─── Non ──→ OR (10cts, 20cts, 50cts)
Pourquoi ces espaces couleur ?
- HSV : La composante Saturation permet de séparer efficacement les pièces argentées (faible saturation, aspect métallique gris) des pièces colorées (cuivre, or). Le seuil est fixé à S = 55.
- LAB : Le canal A (axe vert-rouge) permet de distinguer le cuivre (tonalité rouge, A > 136) de l'or (tonalité jaune, A <= 136), une distinction difficile en HSV seul.
Distinction 1 Euro / 2 Euro :
Les pièces bicolores ont une structure concentrique (coeur + anneau). Le système compare la densité de pixels argentés dans le coeur (rayon < 45% du total) versus l'anneau (rayon > 55% du total) :
- 1 Euro : coeur argenté + anneau doré → densité argent plus forte dans le coeur
- 2 Euro : coeur doré + anneau argenté → densité argent plus forte dans l'anneau
La classification de la dénomination exacte est réalisée dans classifier.py et repose sur la taille relative des pièces au sein de chaque groupe de matériau.
Lorsque des pièces bicolores sont présentes dans l'image, elles servent d'étalon de référence. Leur aire théorique en mm² étant connue (1 Euro = 424.55 mm², 2 Euro = 520.77 mm²), on calcule un ratio px_per_mm² :
px_per_mm² = aire_en_pixels / aire_théorique_mm²
Ce ratio est moyenné sur toutes les pièces bicolores détectées pour plus de robustesse. Ensuite, pour chaque pièce cuivre ou or, on convertit son aire en mm² et on cherche la dénomination la plus proche parmi les candidats :
| Matériau | Dénominations candidates |
|---|---|
| Cuivre | 1 ct (207 mm²), 2 cts (276 mm²), 5 cts (355 mm²) |
| Or | 10 cts (306 mm²), 20 cts (389 mm²), 50 cts (462 mm²) |
En l'absence de pièces bicolores (pas d'étalon), le système trie les pièces par rayon croissant au sein de chaque matériau et applique des seuils de ratio :
- Cuivre : ratio < 1.07 → 1 ct, ratio < 1.22 → 2 cts, sinon → 5 cts
- Or : ratio < 1.06 → 10 cts, ratio < 1.17 → 20 cts, sinon → 50 cts
Les pièces argentées (Silverish) sont systématiquement classées comme 1 Euro, car c'est le seul type de pièce entièrement argenté parmi les pièces d'Euro courantes. Les pièces de 1 ct et 2 cts, bien qu'ayant un aspect métallique, sont normalement identifiées comme cuivre par le canal A du LAB.
Une fois chaque pièce classifiée, le montant total est simplement la somme des valeurs nominales :
| Dénomination | Valeur (Euro) |
|---|---|
| 1 ct | 0.01 |
| 2 cts | 0.02 |
| 5 cts | 0.05 |
| 10 cts | 0.10 |
| 20 cts | 0.20 |
| 50 cts | 0.50 |
| 1 Euro | 1.00 |
| 2 Euro | 2.00 |
Le rendu visuel final affiche l'image annotée avec des cercles colorés par matériau (orange = cuivre, jaune = or, gris = argent, magenta = bicolore) et des labels indiquant l'identifiant et la dénomination de chaque pièce.
L'évaluation est réalisée dans le notebook Evaluation.ipynb sur l'ensemble des 106 images du dataset.
| Métrique | Description |
|---|---|
| MAE | Erreur absolue moyenne (nombre de pièces) |
| RMSE | Racine de l'erreur quadratique moyenne (pénalise les grosses erreurs) |
| Accuracy | Taux de prédiction parfaite (comptage exact) |
| Acc ±1 | Taux de prédiction à ±1 pièce près |
| MAPE | Erreur relative moyenne en pourcentage |
| Métrique | Description |
|---|---|
| MAE | Erreur absolue moyenne (en euros) |
| RMSE | Racine de l'erreur quadratique moyenne (en euros) |
| Std_Error | Écart-type des erreurs (mesure la stabilité des prédictions) |
| MAPE | Erreur relative moyenne en pourcentage |
| Métrique | Valeur |
|---|---|
| MAE | 1.24 |
| RMSE | 4.04 |
| Accuracy | 71.70% |
| Accuracy ±1 | 83.02% |
| MAPE | 15.47% |
Le système compte correctement les pièces dans environ 72% des images (comptage exact), et dans 83% des cas l'erreur est d'au plus 1 pièce. L'erreur moyenne est de 1.24 pièce par image.
| Métrique | Valeur |
|---|---|
| MAE | 3.00 euro |
| RMSE | 5.77 euro |
| Std_Error | 5.76 euro |
| MAPE | 94.17% |
La MAPE élevée du montant (94%) s'explique par le fait que les erreurs de classification de dénomination se cumulent : une pièce de 2 Euro classée comme 1 Euro génère une erreur de 1.00 euro sur une seule pièce. De plus, les images avec peu de pièces (petit montant) amplifient le MAPE lorsque l'erreur absolue est comparée à un montant de référence faible.
Les performances varient sensiblement d'un groupe à l'autre, ce qui reflète la diversité des conditions de prise de vue :
| Groupe | Images | MAE Comptage | Accuracy | Acc ±1 | Observations |
|---|---|---|---|---|---|
| gp1 | 14 | ~1.57 | 64.3% | - | Fond complexe, éclairage variable |
| gp2 | 15 | Variable | Variable | - | Résolution mixte |
| gp3 | 10 | Variable | Variable | - | Superpositions fréquentes |
| gp4 | 10 | 0.00 | 100% | 100% | Conditions idéales |
| gp5 | 25 | Variable | Variable | - | Grand nombre de pièces (jusqu'à 48) |
| gp6 | 10 | Élevé | Faible | - | Cas les plus difficiles |
| gp7 | 12 | 0.08 | 91.7% | - | Très bonnes conditions |
| gp8 | 10 | 0.10 | 90.0% | - | Bonnes conditions |
Les groupes gp4 (100% d'accuracy), gp7 (91.7%) et gp8 (90%) montrent que le système fonctionne très bien dans des conditions favorables. Les difficultés apparaissent principalement avec les images à fort nombre de pièces (gp5, gp6) et celles avec des conditions d'éclairage défavorables (gp1).
Points forts :
- Le comptage est globalement fiable (83% à ±1 pièce), ce qui valide l'approche par transformée de Hough
- Le mécanisme d'auto-zoom améliore significativement la robustesse face aux variations d'échelle
- L'analyse colorimétrique HSV/LAB distingue correctement les 4 familles de matériaux dans la majorité des cas
- La calibration par les pièces bicolores offre une classification par dénomination cohérente lorsque ces pièces sont présentes
Points faibles :
- L'estimation du montant total est moins fiable que le comptage, car les erreurs de classification de dénomination s'accumulent
- Les images avec beaucoup de pièces (>20) posent problème : superpositions, ombres portées, pièces en contact
- Les conditions d'éclairage non contrôlées perturbent les seuils colorimétrique fixes
- En l'absence de pièces bicolores, la classification par ratios relatifs est moins précise
Les paramètres de la transformée de Hough sont très sensibles au contexte. Un jeu de paramètres optimal pour un groupe d'images peut produire des faux positifs ou des détections manquées sur un autre groupe. Le compromis retenu (param1=200, param2=40) est un équilibre global, mais il ne convient pas parfaitement à toutes les situations.
Les seuils colorimétriques (saturation S=55, canal A=136) ont été calibrés empiriquement. Sous un éclairage chaud (lumière jaune), les pièces argentées peuvent prendre une teinte dorée, faussant la classification. Inversement, un éclairage froid peut rendre les pièces dorées plus grises.
HoughCircles suppose des cercles distincts et bien séparés. Lorsque les pièces se touchent ou se chevauchent, le paramètre minDist peut fusionner deux pièces proches en une seule détection, ou les occlusions partielles empêchent la détection.
Les pièces de 1 ct (diamètre 16.25 mm) et 2 cts (diamètre 18.75 mm) sont très proches en taille, tout comme les 10 cts (19.75 mm) et 20 cts (22.25 mm). À la résolution de travail, la différence en pixels est souvent inférieure à la marge d'erreur de la détection de cercle.
Dans certaines images, aucune pièce bicolore n'est présente, privant le système de son étalon de calibration. La méthode de repli par ratios relatifs est moins fiable car elle dépend de la présence d'au moins deux dénominations différentes dans l'image.
- Pré-segmentation du fond : Utiliser une détection de contours ou un seuillage adaptatif pour isoler la zone contenant les pièces avant d'appliquer HoughCircles, ce qui réduirait les faux positifs sur les fonds texturés.
- Correction automatique de la balance des blancs : Normaliser les couleurs de l'image avant l'analyse colorimétrique pour réduire la sensibilité aux conditions d'éclairage.
- Combinaison de descripteurs : En complément de l'aire, utiliser des descripteurs de texture (LBP, motifs de gravure) pour différencier les dénominations proches en taille.
- Calibration multi-références : Utiliser non seulement les bicolores mais aussi la connaissance du ratio entre pièces de cuivre (1ct / 5cts = 0.58 en aire) pour affiner la calibration.
L'utilisation de réseaux de neurones profonds constituerait une évolution naturelle de ce projet :
- Détection d'objets (YOLO, Faster R-CNN) : Détection et classification simultanées des pièces, y compris en cas de superposition.
- Segmentation d'instance (Mask R-CNN) : Segmentation pixel par pixel de chaque pièce, permettant de gérer les occultations partielles.
- Classification par CNN : Un réseau convolutif entraîné sur des vignettes de pièces pourrait atteindre une bien meilleure précision de dénomination que les seuils colorimétriques manuels.
Ces approches nécessiteraient cependant un dataset annoté plus conséquent (avec bounding boxes ou masques de segmentation par pièce) et des ressources de calcul supérieures pour l'entraînement.
- Python 3.10+
- pip
# Cloner le dépôt
git clone <url_du_depot>
cd Projet_Analyse_Images
# Installer les dépendances
pip install -r requirements.txtfrom src.pipeline import CoinPipeline
pipeline = CoinPipeline(target_width=1024)
result_img, total_euros, coins_list, debug_view = pipeline.process("data/raw/gp4/image.png")
print(f"Pièces détectées : {len(coins_list)}")
print(f"Montant total : {total_euros:.2f} euro")Ouvrir et exécuter le notebook notebooks/Evaluation.ipynb qui :
- Charge l'ensemble du dataset (106 images)
- Exécute le pipeline sur chaque image
- Calcule les métriques de comptage et de montant
- Génère les graphiques de performance (scatter plots, histogrammes, tableaux par groupe)
| Bibliothèque | Version | Rôle |
|---|---|---|
| OpenCV | 4.13.0 | Traitement d'images, HoughCircles, espaces couleur |
| NumPy | 2.4.2 | Calcul matriciel et opérations numériques |
| Pandas | 3.0.0 | Chargement des annotations CSV, manipulation de données |
| Scikit-learn | 1.8.0 | Métriques d'évaluation (MAE, RMSE) |
| Matplotlib | 3.10.8 | Visualisation des résultats et graphiques |
| Pillow | 12.1.1 | Support d'entrée/sortie d'images |
| SciPy | 1.17.0 | Fonctions scientifiques complémentaires |