PMPedro Masi Burgos
← Projets

Simulation d'agent intelligent autonome

Agent Python en trois étapes pour un cours d'IA : classer des pièces industrielles à partir de photos de téléphone (KNN et K-Means écrits à la main), planifier leur réempilement avec STRIPS/PDDL et traverser un labyrinthe aléatoire avec A*.

Projet final individuel d'Intelligence Artificielle I (UNCuyo, 2025).

  • Python
  • OpenCV
  • scikit-learn
  • NumPy
  • PyQt5
  • KNN
  • K-Means
  • STRIPS / PDDL
  • A*
  • pygame
Pipeline de vision par ordinateur pour la reconnaissance de pièces industrielles

Le problème et l'agent

Le sujet demande un agent basé sur des objectifs qui fait trois choses dans l'ordre : reconnaître quatre types de pièces (clous, vis, écrous et rondelles) à partir d'une photo, réordonner les boîtes qui les contiennent en une pile cible et transporter la pile à travers une salle jusqu'à un point cible par le chemin le plus court.

J'ai spécifié l'agent avec le tableau PEAS (performance, environnement, actionneurs, capteurs) et traité chaque étape comme un problème à part. Les trois étapes sont des scripts séparés : elles partagent le fil conducteur (les noms des classes deviennent les objets du planificateur) mais aucun code ne les relie, et il n'y a pas de robot physique.

Étape 1 — Photos et jeu de données

Toutes les photos ont été prises avec un téléphone, flash activé, sur un carton vert uniforme. Ainsi la pièce est la seule chose qui n'est pas verte et la segmentation reste simple. Le prix à payer est la robustesse : la chaîne attend ce fond et ne fonctionnerait pas sur une table encombrée.

J'ai pris environ 30 photos par classe (clous, vis, écrous, rondelles), 120 au total, et fait tourner les pièces asymétriques (clous et vis) entre les prises pour que le résultat ne dépende pas de l'orientation. J'ai aussi gardé des séries supplémentaires prises à l'université (vert brillant, vert mat, table d'examen).

Photos originales des quatre classes sur fond vert
Une photo originale par classe, telle que prise avec le téléphone.

Étape 1 — Traitement d'image

Toute la chaîne se trouve dans preprocess.py et suit l'ordre dans lequel je l'ai construite :

  • HSV : la couleur moyenne de la photo est prise comme fond et sert à construire une plage de couleurs ; le masque est inversé pour garder la pièce et noircir le fond.
  • Ouverture avec un noyau elliptique pour supprimer les petites taches.
  • Niveaux de gris, filtre médian et seuil de Li pour obtenir une image binaire de la pièce.
  • Fermeture et remplissage (flood fill) pour combler les petits trous internes, en gardant le plus grand trou pour que les écrous et les rondelles conservent leur centre.
  • Contours : on garde les trois plus grands (au moins 100 px de long), on recadre la boîte englobante, une seconde ouverture (15×15) nettoie les bords et seul le plus grand contour survit. On calcule son enveloppe convexe pour l'étape suivante.
Étapes de traitement pour chaque classe : original, masque binaire, contour, enveloppe convexe et recadrage final
Chaque ligne est une classe ; les colonnes sont l'original, le masque binaire, le contour, l'enveloppe et le recadrage final (contour en rouge, enveloppe en vert).

Étape 1 — Caractéristiques

À partir du contour et de l'enveloppe de chaque pièce, je calcule 17 valeurs (features.py) : les sept moments de Hu de l'enveloppe (en échelle log) plus l'aire, le périmètre, la circularité, la circularité de l'enveloppe, la solidité, la convexité, le rapport d'aire au cercle, le rapport d'axes, l'excentricité et le rapport périmètre/aire. Tout est standardisé avec StandardScaler avant l'analyse.

Les boîtes à moustaches par classe montrent quelles caractéristiques séparent les quatre pièces et lesquelles non. Par exemple, écrous et rondelles ont presque le même Hu1 : cette caractéristique seule ne suffit pas à les distinguer.

Boîtes à moustaches des trois caractéristiques retenues pour chaque classe
Distribution des trois caractéristiques retenues par classe (valeurs standardisées).

Étape 1 — Choix de trois caractéristiques

J'ai classé les caractéristiques avec deux scores univariés : le score F de l'ANOVA (f_classif) et l'information mutuelle (mutual_info_classif). J'ai retenu le Hu1 de l'enveloppe, la circularité de l'enveloppe et le rapport d'aire de l'enveloppe au cercle.

La solidité de l'enveloppe a en fait un score F plus élevé que Hu1 (1205,7 contre 832,7) mais une information mutuelle bien plus faible (0,90 contre 1,35), et elle recoupe les deux caractéristiques de type circularité, donc je l'ai écartée. Avec trois caractéristiques, je peux aussi afficher chaque photo en 3D et voir les classes avant tout entraînement.

Diagramme en barres du score F des caractéristiques retenues
Score F des caractéristiques retenues.
Diagramme en barres de l'information mutuelle des caractéristiques retenues
Information mutuelle des caractéristiques retenues.
Nuage 3D de l'espace de caractéristiques par classe
Chaque photo dans l'espace des trois caractéristiques retenues, colorée par classe.

Étape 1 — KNN et K-Means from scratch

Les deux algorithmes sont écrits à la main avec NumPy dans models.py ; scikit-learn ne sert que pour le scaler, les scores des caractéristiques et la validation croisée.

  • KNN : distance euclidienne, k = 7, vote majoritaire.
  • K-Means : 10 initialisations aléatoires, en gardant celle de plus faible variance intra-cluster ; un cluster vide garde son centroïde précédent. Comme K-Means ne connaît pas les étiquettes, j'associe clusters et classes avec l'algorithme hongrois (linear_sum_assignment) sur la matrice de confusion pour mesurer sa précision.

Avec une séparation 80/20 des 120 photos, KNN a classé correctement toutes les photos de test (matrice de confusion diagonale) ; K-Means a obtenu un score plus faible. Dans les clusters 3D, deux d'entre eux se chevauchent presque, ce qui correspond à des écrous et des rondelles aux caractéristiques proches. Ce 100 % vient de photos prises dans les mêmes conditions que l'entraînement, donc il dit peu de chose sur d'autres fonds.

Matrice de confusion de KNN sur le jeu de test
Matrice de confusion de KNN sur les 24 photos de test.
Clusters et centroïdes de K-Means dans l'espace 3D des caractéristiques
Clusters et centroïdes de K-Means dans le même espace 3D.

Étape 1 — Application de bureau

Pour essayer le classifieur, je l'ai encapsulé dans une application PyQt5 séparée en modèle, vue et contrôleur. On choisit le dossier d'entraînement, on entraîne, on choisit une photo et on voit le masque traité avec son contour et son enveloppe, les trois valeurs, le point affiché parmi les données d'entraînement, la prédiction KNN avec les étiquettes de ses 7 voisins et sa précision en validation croisée, et la prédiction K-Means.

Étape 2 — Planification STRIPS

J'ai modélisé les boîtes comme un monde de blocs : les quatre pièces sont les blocs, avec les prédicats on, ontable, clear, handempty et holding, et quatre actions (pick-up, put-down, stack, unstack), chacune avec ses préconditions et ses effets.

Un script Python (etapa2.py) écrit les fichiers PDDL du domaine et du problème avec une pile initiale et une pile cible aléatoires. Je n'ai pas écrit le planificateur : j'ai chargé les deux fichiers dans un solveur PDDL en ligne. Pour l'exemple des captures, il a renvoyé un plan de 10 étapes après avoir développé 13 états.

Domaine et problème PDDL chargés dans un solveur en ligne
Domaine (à gauche) et problème généré (à droite) dans le solveur.
Journal du solveur et plan résultant de 10 étapes
Journal du solveur et plan résultant : unstack, put-down, stack, pick-up.

Étape 3 — Labyrinthe et navigation A*

La salle est une grille 50×50 de cellules libres et de murs. Le labyrinthe est généré aléatoirement par creusement en profondeur, donc il existe toujours un chemin de l'entrée à la sortie.

A* s'exécute sur cette grille avec des déplacements dans quatre directions et la distance de Manhattan comme heuristique, avec une file de priorité (heapq). Une fenêtre pygame montre la recherche en direct derrière un bouton Start : cellules en attente en bleu, cellules explorées en jaune et chemin final en rouge. Le labyrinthe remplace la salle avec tables et chaises : c'est une simplification du problème réel.

Labyrinthe résolu avec le chemin A*
Labyrinthe résolu : cellules explorées en jaune, en attente en bleu, chemin final en rouge.

Limites et améliorations

  • L'étape de vision est réglée pour un montage contrôlé (carton vert, flash). Un autre fond demanderait une autre segmentation.
  • Une précision parfaite sur quatre classes bien séparées, testée dans les mêmes conditions que l'entraînement, ne prouve pas la généralisation.
  • Les trois étapes ne sont pas reliées entre elles et rien ne tourne sur un vrai robot : vision, planificateur et labyrinthe sont simulés séparément.
  • Prochaine étape : alimenter le planificateur avec la sortie du classifieur et tester avec des photos sur d'autres fonds et éclairages.