Système embarqué de triage intelligent de graines
Classification binaire de haricots blancs (bonne/mauvaise graine) en temps réel, CNN Keras sur PC, ESP32-CAM pour la capture et le tri. Stage de fin d'études du DTS (niveau L2), Fablab Solidaire Mamiratra.
- L'objectif
- Trier automatiquement des haricots (bonne ou mauvaise graine) sur une chaîne, avec une caméra et une machine de tri.
- Ma démarche
- Un CNN Keras entraîné sur PC, un ESP32-CAM pour la capture et la commande du servomoteur, les images échangées en HTTP.
- Le résultat
- 98 % d'exactitude sur le jeu de test ; mon premier projet mêlant mécanique, systèmes embarqués et machine learning.
Contexte
Stage de fin d'études du DTS Génie Industriel et Maintenance (niveau L2, IST-T Antananarivo), réalisé au Fablab Solidaire Mamiratra de juillet à août 2023. C'était mon premier projet qui mélangeait mécanique, systèmes embarqués et machine learning.
Objectif : concevoir une machine compacte et économique pour trier automatiquement les haricots blancs (bonnes vs mauvaises graines) en remplaçant le tri manuel par une classification CNN en temps réel : l'ESP32-CAM capture l'image et actionne le tri, l'inférence tourne sur un ordinateur.
Dataset
340 images de haricots blancs capturées en conditions contrôlées (luminosité constante, même angle, même appareil) :
| Classe | Images |
|---|---|
| Mauvaise graine | 209 |
| Bonne graine | 131 |
| Total | 340 |
Split : 60% entraînement / 30% validation / 10% test. Les images sont redimensionnées à 224×224 pixels avant entraînement.
Architecture du modèle CNN
Réseau convolutif binaire avec extracteur de caractéristiques (blocs Conv2D + MaxPooling) suivi d'un classifieur dense. Sortie : 1 neurone sigmoïde (classification binaire). 31 561 441 paramètres entraînables.
model = Sequential([
Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)),
MaxPooling2D(2, 2),
Conv2D(64, (3, 3), activation='relu'),
MaxPooling2D(2, 2),
Conv2D(128, (3, 3), activation='relu'),
MaxPooling2D(2, 2),
Flatten(),
Dense(512, activation='relu'),
Dropout(0.5),
Dense(1, activation='sigmoid')
])
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy']
)Entraînement
10 époques sur les 204 images d'entraînement, avec validation en temps réel sur les 102 images de validation.
| Époque | Loss | Accuracy | Val Loss | Val Accuracy |
|---|---|---|---|---|
| 1 | 2.3908 | 0.4479 | 0.6265 | 0.6250 |
| 2 | 0.6637 | 0.5938 | 0.5801 | 0.6146 |
| 3 | 0.5558 | 0.6979 | 0.5058 | 0.9792 |
| 4 | 0.4434 | 0.9167 | 0.3162 | 0.9583 |
| 5 | 0.2651 | 0.9740 | 0.2464 | 0.9062 |
| 6 | 0.1485 | 0.9583 | 0.0746 | 0.9792 |
| 7 | 0.0566 | 0.9948 | 0.0919 | 0.9688 |
| 8 | 0.0502 | 0.9948 | 0.0161 | 1.0000 |
| 9 | 0.0207 | 0.9896 | 0.0102 | 1.0000 |

Résultats
98% d'accuracy sur le jeu de test (34 images non vues pendant l'entraînement). Le modèle est sauvegardé sous image_model.h5 et rechargé par le script d'inférence Python qui pilote la bascule via HTTP.
model = load_model('image_model.h5')
resp = requests.get('http://192.168.1.100/capture')
img = preprocess(resp.content) # resize 224x224, normalize
pred = model.predict(img)[0][0] # sigmoid → [0, 1]
direction = 'droite' if pred > 0.5 else 'gauche' # seuil 0.5, version fonctionnelle initiale
requests.get(f'http://192.168.1.100/servo?dir={direction}')Seuil de décision fixé à 0.5, frontière naturelle de la fonction sigmoid pour une classification binaire. Valeur choisie pour cette première version fonctionnelle.
Pipeline système
Limites
- Dataset limité, 340 images au total, jeu de test de seulement 34 images. La valeur de 98% d'accuracy est statistiquement fragile sur un si petit échantillon de test.
- Conditions contrôlées, luminosité fixe, angle fixe, même appareil. La robustesse du modèle en conditions réelles (variations d'éclairage, position des graines) n'a pas été testée.
- Modèle surdimensionné, 31 561 441 paramètres pour une classification binaire simple. Un modèle beaucoup plus léger serait suffisant et plus adapté à un contexte embarqué.
- Métriques basiques, seule l'accuracy a été mesurée. Matrice de confusion, F1-score et courbe ROC n'ont pas été calculés dans le cadre de ce projet L2.
Perspectives
- Data augmentation, rotations, variations de luminosité, flips pour enrichir le dataset sans nouvelles captures
- Transfer learning, remplacer le CNN custom par MobileNetV2, 10× plus léger et plus robuste sur petits datasets
- Edge AI, déployer le modèle directement sur l'ESP32 via TensorFlow Lite pour éliminer la dépendance à l'ordinateur
- Métriques complètes, calculer la matrice de confusion, précision et rappel par classe pour mieux évaluer les erreurs réelles du modèle
Système embarqué et mécanique
Un cylindre + disque perforé alimente les haricots un par un sur un plateau d'acquisition. L'ESP32-CAM capture la photo, l'ordinateur fait l'inférence CNN, puis commande le servomoteur SG90 qui bascule à droite (bonne graine) ou à gauche (mauvaise graine).
L'ESP32-CAM est programmée en C++ et configurée en point d'accès WiFi agissant comme serveur HTTP. L'ordinateur Python se connecte comme client.
Communication :
GET /capture.jpg, ESP32-CAM déclenche la rotation du disque, capture l'image et la retourne au PCPOST /prediction, PC envoie la décision CNN (0 ou 1), ESP32-CAM commande le servomoteur en conséquence
Composants partie opérative :
| Composant | Rôle |
|---|---|
| Moteur CC JgA25-370 (12V, 10 RPM) | Rotation du disque perforé |
| Driver L298N | Régulation vitesse et sens du moteur |
| Servomoteur SG90 (0°–180°) | Bascule de tri droite/gauche |



Projets proches
Analyse et injection sur bus CAN automobile
Reverse engineering des trames CAN d'une maquette véhicule, cartographie des bus, décodage des signaux moteur/éclairage/confort, base DBC et DLL d'injection en C simulant un système ADAS.
2026Bus CANReverse EngineeringAutomotiveMuxTrace
Conception numérique en VHDL, de la simulation au FPGA Basys 3
En licence, une boîte à musique et le jeu de la vie décrits en VHDL et simulés sous ModelSim. En master, le passage sur FPGA Xilinx avec Vivado et une carte Basys 3, jusqu'à la conception complète d'un processeur 16 bits.
2026VHDLFPGAVivadoBasys 3
Distribution Linux embarquée avec Yocto pour une box domotique
Construction d'une distribution Linux sur mesure avec Yocto (Poky) pour la carte STM32MP135F-DK, avec une layer dédiée, une recette image systemd, un SDK et une application C qui lit un capteur I²C.
2026YoctoPokyBitBakeLinux embarqué