IMA3/IMA4 2021/2023 P6 : Différence entre versions
(→Rapport d'avancement 3 (15/02/2023)) |
(→🦾 Mise en place des mouvements de la pince) |
||
(120 révisions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
− | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: # | + | [[Fichier:LogoTrashy.png|200px|right]] |
+ | |||
+ | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: #64d19b; vertical-align: top; width: 96%;"> Présentation du projet</div>= | ||
+ | |||
− | |||
[[Fichier:Trashy.png|500px|right]] | [[Fichier:Trashy.png|500px|right]] | ||
− | == | + | ==🌍 Contexte== |
Pour notre étude, nous avons d’abord établi une analyse des besoins de notre projet. | Pour notre étude, nous avons d’abord établi une analyse des besoins de notre projet. | ||
Ligne 15 : | Ligne 17 : | ||
Il s’agit de la responsabilité élargie des producteurs. Les cigarettiers doivent alors prendre leurs mesures de manière à ce que les mégots soient recyclés. Très polluantes, ces substances prennent beaucoup de temps pour se décomposer à cause de leurs composants en plastiques.'' | Il s’agit de la responsabilité élargie des producteurs. Les cigarettiers doivent alors prendre leurs mesures de manière à ce que les mégots soient recyclés. Très polluantes, ces substances prennent beaucoup de temps pour se décomposer à cause de leurs composants en plastiques.'' | ||
− | == | + | ==🎯 Objectifs== |
Notre projet consiste à concevoir et à mettre en place un système mobile autonome (volant ou roulant) permettant la collecte de déchets (initialement de mégots) sur le campus grâce à de la reconnaissance d’image. Ce dossier va vous présenter nos pistes de recherches et de développement ainsi que notre organisation pour mener à bien ce projet. Nous avons décidé de nommer notre robot : “Trashy” (en référence au mot angais Trash). | Notre projet consiste à concevoir et à mettre en place un système mobile autonome (volant ou roulant) permettant la collecte de déchets (initialement de mégots) sur le campus grâce à de la reconnaissance d’image. Ce dossier va vous présenter nos pistes de recherches et de développement ainsi que notre organisation pour mener à bien ce projet. Nous avons décidé de nommer notre robot : “Trashy” (en référence au mot angais Trash). | ||
Ligne 21 : | Ligne 23 : | ||
Pour mener à bien ce projet, nous avons mis en place un Google Drive pour organiser notre travail pendant la période de recherche et d’étude de ce projet. Durant ce semestre, nous avons donc consacré notre temps aux phases de pré-étude et d’étude du projet, ainsi qu’à la réalisation du cahier des charges nous permettant de mieux continuer le projet pour les semestres à venir. | Pour mener à bien ce projet, nous avons mis en place un Google Drive pour organiser notre travail pendant la période de recherche et d’étude de ce projet. Durant ce semestre, nous avons donc consacré notre temps aux phases de pré-étude et d’étude du projet, ainsi qu’à la réalisation du cahier des charges nous permettant de mieux continuer le projet pour les semestres à venir. | ||
− | == | + | ==💬 Description== |
Nous avons donc déterminé les fonctions contraintes du robot qu’il doit pouvoir réaliser telle la détection des mégots ou encore la contrainte de devoir évoluer sur tout type de terrain. Et c’est ainsi, et après concertation avec tous les membres du projets, que nous avons écrit le cahier des charges suivants : | Nous avons donc déterminé les fonctions contraintes du robot qu’il doit pouvoir réaliser telle la détection des mégots ou encore la contrainte de devoir évoluer sur tout type de terrain. Et c’est ainsi, et après concertation avec tous les membres du projets, que nous avons écrit le cahier des charges suivants : | ||
Ligne 33 : | Ligne 35 : | ||
C’est ainsi qu’après réflexions et brainstorming entre les membres du groupe, nous avons trouvé les premières solutions et design de Trashy. Ainsi, nous allons concevoir une carte mère adaptée au robot permettant toutes les fonctionnalités décrites ci-dessus. Nous allons donc avoir recours à un processeur ATMEGA 2560 en communication avec une Raspberry PI. La Raspberry est là pour les traitements des capteurs plus gourmands (dont l’analyse d’image de la caméra) que l’arduino ne peut faire. | C’est ainsi qu’après réflexions et brainstorming entre les membres du groupe, nous avons trouvé les premières solutions et design de Trashy. Ainsi, nous allons concevoir une carte mère adaptée au robot permettant toutes les fonctionnalités décrites ci-dessus. Nous allons donc avoir recours à un processeur ATMEGA 2560 en communication avec une Raspberry PI. La Raspberry est là pour les traitements des capteurs plus gourmands (dont l’analyse d’image de la caméra) que l’arduino ne peut faire. | ||
− | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: # | + | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: #64d19b; vertical-align: top; width: 96%;"> Réalisation et résultats </div>= |
− | == | + | ==🤖 Matériel à disposition== |
− | <gallery perrow=" | + | <gallery perrow="3" mode="nolines"> |
File:BaseRobot.jpeg|thumb|400px|'''Base du Robot''' | File:BaseRobot.jpeg|thumb|400px|'''Base du Robot''' | ||
File:PinceRobot.jpeg|thumb|400px|'''Pince du Robot''' | File:PinceRobot.jpeg|thumb|400px|'''Pince du Robot''' | ||
Ligne 43 : | Ligne 45 : | ||
</gallery> | </gallery> | ||
− | Pour pouvoir mettre en œuvre notre projet, nous avons à disposition une plateforme mobile avec 4 roues omnidirectionnelle et contrôlé par une '''Raspberry PI | + | Pour pouvoir mettre en œuvre notre projet, nous avons à disposition une plateforme mobile avec 4 roues omnidirectionnelle et contrôlé par une '''Raspberry PI 4''' ainsi qu'une '''Arduino Mega 2560'''. Nous disposons aussi d'un kit de robot Pince. Afin de pouvoir gérer efficacement la partie intelligence artificielle, Mr Othman Lakhal nous a prêté une '''Nvidia Jetson Nano''' |
− | == | + | Ayant eu un problème avec le robot, nous avons repartir de zéro. Nous avons reçu un châssis, des moteurs ainsi que 4 roues omnidirectionnelles. Le groupe de la coupe de France de robotique nous as également gentiment prêté une carte driver moteur conçu par Albin Mouton |
+ | |||
+ | ==⚙️ Principe de fonctionnement de Trashy== | ||
[[Fichier:Trashy Fonctionnement.png|400px|thumb|'''Interconnexions entres les différents éléments]] | [[Fichier:Trashy Fonctionnement.png|400px|thumb|'''Interconnexions entres les différents éléments]] | ||
Afin de mieux nous organiser durant ce projet, nous avons établis le schéma d'interconnexions et la fonctions de chacun des composants que l'on dispose. Ainsi : | Afin de mieux nous organiser durant ce projet, nous avons établis le schéma d'interconnexions et la fonctions de chacun des composants que l'on dispose. Ainsi : | ||
*'''Nvidia Jetson Nano''' : Gère la reconnaissance des mégots de cigarette et envoie à la Raspberry, via liaison série, les coordonnées du mégot de cigarette sur l'image <br/> | *'''Nvidia Jetson Nano''' : Gère la reconnaissance des mégots de cigarette et envoie à la Raspberry, via liaison série, les coordonnées du mégot de cigarette sur l'image <br/> | ||
− | *''' | + | *'''RaspBerry Pi 4''' : Asservis en position le robot pour que le mégot de cigarette soit au centre de l'image et demande, via une liaison série avec l'arduino, l'activation de la pince<br/> |
*'''Arduino Mega 2560''' : Gère le contrôle de la pince <br/> | *'''Arduino Mega 2560''' : Gère le contrôle de la pince <br/> | ||
+ | Pour la suite du projet, le schéma d'interconnexions a changé pour : | ||
− | == | + | *'''RaspBerry Pi 4''' : Gère la reconnaissance et l'analyse des images de la caméra. La RaspBerry est relié aux autres modules et communique avec eux via une liaison série <br/> |
+ | *'''Carte de contrôle moteur''' : Gère la commande des moteurs <br/> | ||
+ | *'''Arduino Mega 2560''' : Gère le contrôle de la pince <br/> | ||
+ | |||
+ | ==🦾 Mise en place des mouvements de la pince== | ||
[[Fichier:Pinout_pince.png|center|400px]] | [[Fichier:Pinout_pince.png|center|400px]] | ||
Ligne 60 : | Ligne 69 : | ||
Pour cette partie, nous utilisons la bibliothèque '''<Servo.h>''' permettant de contrôler les servo-moteurs de notre pince. Nous en disposons de 4 que nous déclarons avec les lignes de codes ci-dessous : | Pour cette partie, nous utilisons la bibliothèque '''<Servo.h>''' permettant de contrôler les servo-moteurs de notre pince. Nous en disposons de 4 que nous déclarons avec les lignes de codes ci-dessous : | ||
+ | <pre> | ||
Servo base, claw, up_down, front_back; | Servo base, claw, up_down, front_back; | ||
Ligne 70 : | Ligne 80 : | ||
Serial.begin(9600); | Serial.begin(9600); | ||
} | } | ||
− | + | </pre> | |
Pour les contrôler, nous avons une fonction générique qui prend en paramètre le servo-moteur à actionner ainsi que l'angle de départ et l'angle d'arriver : | Pour les contrôler, nous avons une fonction générique qui prend en paramètre le servo-moteur à actionner ainsi que l'angle de départ et l'angle d'arriver : | ||
Ligne 87 : | Ligne 97 : | ||
* | * | ||
* Exemple | * Exemple | ||
− | * to_move(claw,0,95); pour | + | * to_move(claw,0,95); pour fermer la pince |
− | * to_move(claw,95,0); pour | + | * to_move(claw,95,0); pour ouvrir la pince |
− | * to_move(front_back,180,0); pour | + | * to_move(front_back,180,0); pour reculer la pince |
* to_move(front_back,0,180); pour avancer la pince | * to_move(front_back,0,180); pour avancer la pince | ||
* to_move(up_down,180,0); pour baisser la pince | * to_move(up_down,180,0); pour baisser la pince | ||
Ligne 110 : | Ligne 120 : | ||
} | } | ||
− | + | Nous codons ainsi une fonction permettant d'attraper un objet : | |
+ | |||
+ | void attrape_object(){ | ||
+ | to_move(claw,95,0); | ||
+ | delay(1000); | ||
+ | to_move(front_back,0,180); | ||
+ | delay(1000); | ||
+ | to_move(claw,0,95); | ||
+ | delay(1000); | ||
+ | to_move(front_back,180,0); | ||
+ | delay(1000); | ||
+ | } | ||
+ | |||
+ | Nous obtenons les résultats suivant : | ||
+ | [[Fichier:Trashy_Pince_v2.mp4|400px|thumb|left|Mouvement de la pince]] | ||
+ | [[Fichier:Trashy_Pince_Objet.mp4|400px|thumb|right|Test de saisie d'objet]] | ||
− | + | <br/> | |
− | + | <br/> | |
− | + | <br/> | |
− | + | <br/> | |
− | + | <br/> | |
− | + | <br/> | |
− | + | <br/> | |
− | + | <br/> | |
− | + | <br/> | |
+ | <br/> | ||
+ | <br/> | ||
+ | <br/> | ||
+ | <br/> | ||
+ | <br/> | ||
+ | <br/> | ||
+ | <br/> | ||
+ | <br/> | ||
+ | <br/> | ||
+ | |||
+ | ==🗑️ Algorithme du bac== | ||
+ | |||
+ | |||
+ | int lightPin = 0; | ||
+ | int ledPin1 = 9; | ||
+ | int ledPin2 = 10; | ||
+ | int bref =0; | ||
+ | int loong=0; | ||
+ | void setup(){ | ||
+ | pinMode(ledPin1, OUTPUT); | ||
+ | pinMode(ledPin2, OUTPUT); | ||
+ | Serial.begin(9600); | ||
} | } | ||
− | + | void loop(){ | |
− | + | int seuil = 850; | |
− | bref=( | + | bref=(analogRead(lightPin)< seuil) ; |
− | + | delay(20); | |
− | loong=( | + | loong=(analogRead(lightPin)< seuil); |
− | + | if(bref==1 && loong==1){ | |
− | + | digitalWrite(ledPin1, HIGH); | |
− | + | delay(1000); | |
− | } | + | }else if(bref==1 && loong==0){ |
− | + | digitalWrite(ledPin2, HIGH); | |
− | + | delay(1000); | |
− | } | + | }else{ |
− | + | (ledPin2, LOW); | |
− | + | digitalWrite(ledPin1, LOW); | |
} | } | ||
} | } | ||
− | == | + | ==🚬 Algorithme de détection de mégots de cigarette== |
Pour l'algorithme de détection de mégots de cigarette, nous avons à notre disposition une Nvidia Jetson Nano. | Pour l'algorithme de détection de mégots de cigarette, nous avons à notre disposition une Nvidia Jetson Nano. | ||
Ligne 157 : | Ligne 204 : | ||
Afin d'entrainer notre modèle, nous allons simplement parcourir l'ensemble des données. 1 parcours de toute les données est appelé un '''epoch'''. Pour avoir un modèle fiable à 80% (avec 5000 données), il est conseillé d'utiliser entre 30 et 60 epochs. | Afin d'entrainer notre modèle, nous allons simplement parcourir l'ensemble des données. 1 parcours de toute les données est appelé un '''epoch'''. Pour avoir un modèle fiable à 80% (avec 5000 données), il est conseillé d'utiliser entre 30 et 60 epochs. | ||
− | ===Rapport d'avancement 1 (25/01/2023)=== | + | ===👨💻 Rapport d'avancement 1 (25/01/2023)=== |
Le premier obstacle rencontré dans la réalisation de l'IA est la connexion à internet de la '''Nvidia Jetson Nano''' pour pouvoir cloner le dépôt git nous permettant de créer nos premiers IA. Nous avons donc procédé à différentes solutions pour combler à cette obstacle : | Le premier obstacle rencontré dans la réalisation de l'IA est la connexion à internet de la '''Nvidia Jetson Nano''' pour pouvoir cloner le dépôt git nous permettant de créer nos premiers IA. Nous avons donc procédé à différentes solutions pour combler à cette obstacle : | ||
Ligne 179 : | Ligne 226 : | ||
Du à une impossibilité de se connecter en Ethernet à l'école, le développement de l'IA se fera depuis chez Jason sur son temps libre | Du à une impossibilité de se connecter en Ethernet à l'école, le développement de l'IA se fera depuis chez Jason sur son temps libre | ||
− | ===Rapport d'avancement 2 (08/02/2023)=== | + | ===👨💻 Rapport d'avancement 2 (08/02/2023)=== |
Lors de cette séance, nous avons d'abord continuer de travailler sur la '''Nvidia Jetson Nano'''. Nous commençons donc par cloner le git associé sur notre PC : | Lors de cette séance, nous avons d'abord continuer de travailler sur la '''Nvidia Jetson Nano'''. Nous commençons donc par cloner le git associé sur notre PC : | ||
Ligne 251 : | Ligne 298 : | ||
Nous allons donc pouvoir utiliser ce blog pour confectionner notre modèle. | Nous allons donc pouvoir utiliser ce blog pour confectionner notre modèle. | ||
− | ===Rapport d'avancement 3 (15/02/2023)=== | + | ===👨💻 Rapport d'avancement 3 (15/02/2023)=== |
Lors de cette séance, nous avons avancé sur l'alternative 2 trouvé à la précédente séance. Nous avons donc commencé par cloner le dépôt git : | Lors de cette séance, nous avons avancé sur l'alternative 2 trouvé à la précédente séance. Nous avons donc commencé par cloner le dépôt git : | ||
Ligne 305 : | Ligne 352 : | ||
A l'heure actuelle, nous n'avons pas encore fini de débugger le programme et n'avons pas pu lancer de première itération. | A l'heure actuelle, nous n'avons pas encore fini de débugger le programme et n'avons pas pu lancer de première itération. | ||
− | ==''Carte Driver Moteur | + | ===👨💻 Rapport d'avancement 4 (01/03/2023)=== |
+ | |||
+ | En continuant de débugger le programme pendant les vacances, nous avons découvert que ce programme a été écrit avec des versions obsolètes de Tensorflow. Ainsi, et en ayant eu confirmation sur le blog associé au code utilisé, il nous est impossible de modifier le code disponible pour fonctionner sous Tensorflow 2 car nous allons juste avoir une succession infinie de bug à corriger. <br/> | ||
+ | Nous avons donc pris la décision de recommencer de zéro et en créant notre propre programme fonctionnant avec Tensorflow 2 pour pouvoir créer notre modèle. Pour les images d'entraînement, nous allons utilisé le dataset trouvé plus haut. <br/> | ||
+ | Pour pouvoir mieux nous familiariser avec la librairie '''Tensorflow''', nous allons dans un premier temps suivre tutoriel suivant permettant d'entrainer un modèle de reconnaissance de fleur sur Tensorflow disponible ici : https://www.geeksforgeeks.org/image-recognition-using-tensorflow/ <br/> | ||
+ | Dans un premier temps, nous récupérons les images de fleurs qui vont nous servir pour notre entrainement : | ||
+ | |||
+ | <pre> | ||
+ | dataset_url = <nowiki>"https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"</nowiki> | ||
+ | data_dir = tf.keras.utils.get_file('flower_photos', origin=dataset_url, untar=True) | ||
+ | data_dir = pathlib.Path(data_dir) | ||
+ | </pre> | ||
+ | |||
+ | Nous allons ensuite split nos données en 2 parties : une partie pour l'entrainement et une partie pour la validation : | ||
+ | |||
+ | <pre> | ||
+ | # Training split | ||
+ | |||
+ | train_ds = tf.keras.utils.image_dataset_from_directory( | ||
+ | data_dir, | ||
+ | validation_split=0.2, | ||
+ | subset="training", | ||
+ | seed=123, | ||
+ | image_size=(180, 180), | ||
+ | batch_size=32 | ||
+ | ) | ||
+ | |||
+ | # Testing or validation split | ||
+ | |||
+ | val_ds = tf.keras.utils.image_dataset_from_directory( | ||
+ | data_dir, | ||
+ | validation_split=0.2, | ||
+ | subset="validation", | ||
+ | seed=123, | ||
+ | image_size=(180,180), | ||
+ | batch_size=32 | ||
+ | ) | ||
+ | </pre> | ||
+ | |||
+ | Nous allons ensuite créer un modèle : | ||
+ | |||
+ | <pre> | ||
+ | class_names = train_ds.class_names | ||
+ | num_classes = len(class_names) | ||
+ | |||
+ | model = Sequential([ | ||
+ | layers.Rescaling(1./255, input_shape=(180,180,3)), | ||
+ | layers.Conv2D(16,3,padding='same', activation='relu'), | ||
+ | layers.MaxPooling2D(), | ||
+ | layers.Conv2D(32,3,padding='same', activation='relu'), | ||
+ | layers.MaxPooling2D(), | ||
+ | layers.Conv2D(64,3,padding='same',activation='relu'), | ||
+ | layers.MaxPooling2D(), | ||
+ | layers.Flatten(), | ||
+ | layers.Dense(128,activation='relu'), | ||
+ | layers.Dense(num_classes) | ||
+ | ]) | ||
+ | |||
+ | model.compile(optimizer='adam', | ||
+ | loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), | ||
+ | metrics=['accuracy']) | ||
+ | </pre> | ||
+ | |||
+ | Et nous lançons enfin l'entrainement de notre modèle avec 10 epochs : | ||
+ | |||
+ | <pre> | ||
+ | epochs = 10 | ||
+ | history = model.fit( | ||
+ | train_ds, | ||
+ | validation_data=val_ds, | ||
+ | epochs=epochs | ||
+ | ) | ||
+ | </pre> | ||
+ | |||
+ | Nous obtenons les résultats suivant : [[Fichier:Trashy_resIA1.png|center|400px]] | ||
+ | |||
+ | Il ne nous reste plus qu'à modifier notre code pour récupérer les images de cigarettes dont nous disposons et de faire l'entrainement dessus. | ||
+ | |||
+ | |||
+ | ===👨💻 Rapport d'avancement 5 (08/03/2023)=== | ||
+ | |||
+ | Lors de cette séance, nous avons augmenté notre dataset en rajoutant des images de cigarette disponible sur "https://www.kaggle.com/datasets/estebanpacanchique/cigarette-butt?resource=download" et des images de feuilles mortes disponible sur "https://www.kaggle.com/datasets/gunarakulan/cashewleavesdataset". L'ajout des images de feuilles mortes permet à l'IA de savoir qu'est-ce qui n'est pas un mégot de cigarette. De plus, comme les feuilles mortes ont une teinte orangé, on apprends à l'IA à les différencier. <br/> | ||
+ | L'arborescence du dossier de notre IA ressemble ainsi à ça : [[Fichier:Trashy_arbo.png|center|150px]] | ||
+ | |||
+ | Nous avons donc lancer notre premier test et entraînement de notre IA et nous obtenons le graphique ci-dessous : [[Fichier:Trashy_res_IA.png|center|300px]] | ||
+ | |||
+ | Maintenant que notre IA s'entraine, nous devons enregistrer le modèle pour pouvoir l'utiliser. Pour faire ça, nous rajoutons ces quelques lignes au programme '''models.py''' : | ||
+ | |||
+ | |||
+ | <pre> | ||
+ | #Saving the model | ||
+ | |||
+ | MODEL_DIR = tempfile.gettempdir(); | ||
+ | version = input("Numéro de version :") | ||
+ | export_path = os.path.join(MODEL_DIR, str(version)) | ||
+ | print("export_path = {}\n".format(export_path)) | ||
+ | |||
+ | tf.keras.models.save_model( | ||
+ | model, | ||
+ | export_path, | ||
+ | overwrite=True, | ||
+ | include_optimizer=True, | ||
+ | save_format=None, | ||
+ | signatures=None, | ||
+ | options=None | ||
+ | ) | ||
+ | |||
+ | print('\nSaved model:') | ||
+ | os.system('ls -l ' + export_path) | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | ===👨💻 Rapport d'avancement 6 (15/03/2023)=== | ||
+ | |||
+ | Durant cette séance, nous nous sommes concentré sur le test du bon fonctionnement de notre IA. Pour ce faire, nous créons un script en python nommé '''test.py''' (donc différent du programme qui permet l'entrainement de notre IA). Nous devons donc d'abord charger notre modèle afin de pouvoir l'utiliser : | ||
+ | |||
+ | <pre> | ||
+ | IA = tf.keras.models.load_model("model/v_3") | ||
+ | </pre> | ||
+ | |||
+ | où ''model/v_3'' est le dossier où nous avons compilé la dernière version de notre IA. </br> | ||
+ | Afin de pouvoir tester cette IA, nous devons choisir une image au hasard parmi notre dataset et la lui envoyer. Pour choisir cette image au hasard, nous avons codé ce script python : | ||
+ | |||
+ | <pre> | ||
+ | # Define the directory containing the images | ||
+ | dir_path = "test_img/" | ||
+ | |||
+ | # Get a list of all the image file names in the directory | ||
+ | img_files = [f for f in os.listdir(dir_path) if f.endswith(".jpg") or f.endswith(".jpeg") or f.endswith(".png")] | ||
+ | |||
+ | # Load each image into a numpy array and append it to a list | ||
+ | img_list = [] | ||
+ | for filename in img_files: | ||
+ | img = np.array(Image.open(os.path.join(dir_path, filename))) | ||
+ | img_list.append(img) | ||
+ | |||
+ | rand_idx = random.randint(0, len(img_list)-1) | ||
+ | rand_img = img_list[rand_idx] | ||
+ | img_to_show = rand_img | ||
+ | rand_img = cv2.resize(rand_img,(180,180)) | ||
+ | rand_img = rand_img.reshape(1,180,180,3) | ||
+ | </pre> | ||
+ | |||
+ | Pour tester notre IA et faire une prédiction sur l'image, nous utilisons la fonction '''model.predict()''' de tensorflow. Cependant, nous avions eu un problème de taille d'image. En effet, toute les images de notre dataset n'avait pas la bonne taille pour l'entrainement. Nous avons donc codé un petit script en python nommé '''resizer.py''' qui permet de modifier la taille de nos images : | ||
+ | |||
+ | <pre> | ||
+ | from PIL import Image | ||
+ | import os | ||
+ | |||
+ | # Définir le répertoire contenant les images à redimensionner | ||
+ | repertoire_images = "dataset/dead_leave" | ||
+ | |||
+ | # Définir la taille de sortie pour les images | ||
+ | taille_sortie = (180, 180) | ||
+ | i = 1 | ||
+ | |||
+ | # Parcourir tous les fichiers dans le répertoire des images | ||
+ | for fichier in os.listdir(repertoire_images): | ||
+ | # Vérifier que le fichier est une image | ||
+ | if fichier.endswith(".jpg") or fichier.endswith(".jpeg") or fichier.endswith(".png"): | ||
+ | # Ouvrir l'image | ||
+ | chemin_image = os.path.join(repertoire_images, fichier) | ||
+ | with Image.open(chemin_image) as image: | ||
+ | # Redimensionner l'image | ||
+ | image = image.resize(taille_sortie) | ||
+ | # Enregistrer l'image redimensionnée | ||
+ | image.save(chemin_image) | ||
+ | print(i) | ||
+ | i = i + 1 | ||
+ | </pre> | ||
+ | |||
+ | Une fois les images à la bonne taille, un entrainement refait, nous pouvons ainsi faire nos prédictions avec les lignes de code ci-dessous : | ||
+ | |||
+ | <pre> | ||
+ | plt.imshow(img_to_show) | ||
+ | id_label = np.argmax(IA.predict(rand_img),axis=1) | ||
+ | if id_label == 0: | ||
+ | print("It's a cigarette butt") | ||
+ | elif id_label == 1: | ||
+ | print("It's a dead leaf") | ||
+ | plt.show() | ||
+ | </pre> | ||
+ | |||
+ | Nous obtenons les résultats suivant : | ||
+ | [[Fichier:Test_IA_resultat_cig.png|center|500px]] | ||
+ | [[Fichier:Test_IA_resultat_leaf.png|center|500px]] | ||
+ | |||
+ | |||
+ | Il ne nous reste donc plus qu'à récupérer les coordonnées des objets détectés. | ||
+ | |||
+ | ===👨💻 Rapport d'avancement 7 (21/04/2023)=== | ||
+ | |||
+ | Suite à de nombreux problèmes avec le développement de l'IA, nous repartons sur l'alternative 1 qui nous permets de détecter un objet orange grâce à la caméra. Nous modifions le code vu plus haut pour dessiner un rectangle autour de l'objet détecté et de renvoyer les coordonnées (x,y) du coin supérieur gauche ainsi que de la largeur w et de la hauteur h du rectange : | ||
+ | |||
+ | <pre> | ||
+ | while(cap.isOpened()): | ||
+ | # Capture frame-by-frame | ||
+ | ret, frame = cap.read() | ||
+ | |||
+ | # Our operations on the frame come here | ||
+ | hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) | ||
+ | |||
+ | light_orange = (0,90,200) | ||
+ | dark_orange = (18,180,255) | ||
+ | |||
+ | #Creation of the mask to separate the orange object | ||
+ | mask = cv2.inRange(hsv, light_orange, dark_orange) | ||
+ | |||
+ | #Creation of the image by comparing the initial frame with the mask | ||
+ | res = cv2.bitwise_and(frame,frame, mask= mask) | ||
+ | |||
+ | contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) | ||
+ | for cnt in contours: | ||
+ | [x, y, w, h] = cv2.boundingRect(cnt) | ||
+ | if w > 50: | ||
+ | print("< x :",x,"| y :",y,"| w :",w,"| h :",h,">"); | ||
+ | cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 0) | ||
+ | |||
+ | # Display the resulting frame | ||
+ | cv2.imshow('frame',frame) | ||
+ | if cv2.waitKey(1) & 0xFF == ord('q'): | ||
+ | break | ||
+ | </pre> | ||
+ | |||
+ | [[Fichier:Trashy_reconnaissance.png|center|400px]] | ||
+ | |||
+ | ==⚡ Carte Driver Moteur== | ||
Etant donné qu'un des composants de la carte du robot d'origine à brulé suite à une erreur de câblage, nous avons reçu du groupe : Coupe de France de Robotique, une carte driver moteur qui sert à alimenter les moteurs en puissance car la carte Raspberry ne pourrait pas fournir à elle seule suffisamment de courant. | Etant donné qu'un des composants de la carte du robot d'origine à brulé suite à une erreur de câblage, nous avons reçu du groupe : Coupe de France de Robotique, une carte driver moteur qui sert à alimenter les moteurs en puissance car la carte Raspberry ne pourrait pas fournir à elle seule suffisamment de courant. | ||
Ligne 324 : | Ligne 597 : | ||
[[Fichier:sondest2.jpg|center|400px]] | [[Fichier:sondest2.jpg|center|400px]] | ||
− | =='' | + | ==🚗 Motorisation de Trashy== |
+ | |||
+ | Maintenant que nous avons une carte de contrôle moteur, nous allons nous concentrer sur le contrôle des moteurs. Afin de contrôler les moteurs, la carte fonctionne en liaison série avec des requêtes G-Code : | ||
+ | |||
+ | [[Fichier:TableauGCode.png|center|400px]] | ||
+ | |||
+ | Ainsi, nous pouvons définir la vitesse des moteurs avec la commande : | ||
+ | |||
+ | G0 X40 Y40 Z40 U40 | ||
+ | |||
+ | Où les moteurs X,Y,Z et U sont définis comme sur cette photo : | ||
+ | |||
+ | [[Fichier:Trashy_moteur.jpg|center|400px]] | ||
+ | |||
+ | Nous pouvons aussi lire la vitesse de rotations des moteurs avec la commande : | ||
+ | |||
+ | G10 X Y Z U | ||
+ | |||
+ | Cette commande renvoie la vitesse sous la forme : | ||
+ | |||
+ | G10 X48.529 Y35.588 Z38.824 U42.059 | ||
+ | |||
+ | Maintenant que nous arrivons à faire fonctionner nos moteurs, nous allons programmer une fonction qui prend en paramètre la vitesse que l'on veut sur l'axe X, l'axe Y et l'axe Z et qui définit la vitesse de chaque moteur. Pour ceci, nous nous inspirons de ce document : https://research.ijcaonline.org/volume113/number3/pxc3901586.pdf et des formules ci-dessous : | ||
+ | |||
+ | [[Fichier:Trashy_formule.png|center|400px]] | ||
+ | |||
+ | Nous programmons donc la fonction ci-dessous : | ||
+ | <pre> | ||
+ | def translation(Vx, Vy, Wz): | ||
+ | """ | ||
+ | Define the parameter for the G-Code to make a movement | ||
+ | depending on the speed Vx, Vy, Wz | ||
+ | :param Vx: (float) Speed on the X axis | ||
+ | :param Vy: (float) Speed on the Y axis | ||
+ | :param Wz: (float) Speed on the Z axis | ||
+ | :return: A float-list of size 4 with each parameter for the G-Code [X, Y, Z, U] | ||
+ | |||
+ | CU : None | ||
+ | """ | ||
+ | X = (1/R) * (Vx - Vy - (Lx + Ly)*Wz) | ||
+ | Y = (1/R) * (Vx + Vy + (Lx + Ly)*Wz) | ||
+ | Z = (1/R) * (Vx - Vy + (Lx + Ly)*Wz) | ||
+ | U = (1/R) * (Vx + Vy - (Lx + Ly)*Wz) | ||
+ | print("Entrée : < Vx =",Vx,"| Vy =",Vy,"| Wz =",Wz," >") | ||
+ | print("Sortie : < X =",X,"| Y =",Y,"| Z =",Z,"| U =",U," >") | ||
+ | return [X, Y, Z, U] | ||
+ | </pre> | ||
+ | |||
+ | Avec R = 5.0, le rayon de la roue, Lx = 19.0, la largeur du robot et Ly = 27.0 la longueur du robot. Maintenant que nous avons convertit la vitesse voulu en la vitesse des moteurs, nous devons communiquer avec la carte de contrôle en liaison série. Pour cela, nous utilisons la librairie "Serial" de python. | ||
+ | Nous initialisons d'abord la liaison série avec les lignes : | ||
+ | <pre> | ||
+ | # Initialization of Motor_controller serial connection | ||
− | + | port_Moteur = "/dev/ttyACM0" | |
+ | baudrate_Moteur = 115200 | ||
− | + | moteur = serial.Serial(port_Moteur,baudrate_Moteur,timeout=1) | |
+ | </pre> | ||
− | Nous | + | Nous pouvons ainsi écrire une fonction qui prend en paramètre un tableau avec les vitesses de chaque moteur et envoie sur la liaison série, la commande nécessaire : |
− | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: # | + | <pre> |
+ | def send_G_Code(w): | ||
+ | command = "G0 X{:.3f} Y{:.3f} Z{:.3f} U{:.3f}\n".format(w[0],w[1],w[2],w[3]) | ||
+ | print("Command sent :",command) | ||
+ | moteur.write(command.encode('ascii')) | ||
+ | </pre> | ||
+ | |||
+ | Nous faisons ensuite en sorte que le programme se lance avec l'entrée des vitesses voulu sur l'axe X,Y et Z et exécute le mouvement voulu : | ||
+ | |||
+ | <pre> | ||
+ | nb_arg = len(sys.argv) | ||
+ | if(nb_arg == 4): | ||
+ | test = [] | ||
+ | test = translation(int(sys.argv[1]),int(sys.argv[2]),int(sys.argv[3])) | ||
+ | send_G_Code(test) | ||
+ | else: | ||
+ | print("Missing parameters") | ||
+ | </pre> | ||
+ | |||
+ | Nous pouvons ainsi générer les mouvements de base du robot | ||
+ | |||
+ | [[Fichier:Trashy test mobilite.mp4|center|200px]] | ||
+ | |||
+ | ==🖨️ Impression 3D du convertisseur Roue/Moteur== | ||
+ | |||
+ | Nous avons reçu de nouvelles roues pour créer notre robot, cependant, ces roues ne s'adaptent pas au moteur qui nous avait été fourni. Nous avons donc du créer une pièce en 3D afin de convertir la forme hexagonale en forme de cercle coupé qui s'adapte au moteur | ||
+ | |||
+ | [[Fichier:convertisseurtrashy.png|500px|center]] | ||
+ | |||
+ | [[Fichier:rouereelle.jpg|300px|center]] | ||
+ | |||
+ | Nous avons crée un trou afin de viser les adaptateurs aux roues, de plus, nous avons du coller au pisto-colle les adaptateurs aux moteurs afin qu'il n'y ai pas de jeu dans les mouvements des roues lors des déplacements | ||
+ | |||
+ | ==✂️ Découpe du plateau de la pince== | ||
+ | |||
+ | Dans notre optique de réparer notre robot, nous nous sommes occupé de refaire le plateau qui supporte la pince. Nous avons donc commencé par la modélisé sur '''Fusion360''' puis nous avons préparé le fichier pour l'envoyer à la découpeuse laser ! | ||
+ | |||
+ | [[Fichier:decoupeplateau.png|500px|center]] | ||
+ | |||
+ | [[Fichier:inkscapeplaque.png|500px|center]] | ||
+ | |||
+ | |||
+ | ==📎Liaison entre les fonctions== | ||
+ | |||
+ | |||
+ | ===Liaison entre la RaspBerry PI 4 et la Pince=== | ||
+ | |||
+ | Pour la liaison entre la RaspBerry PI 4 et la Pince, nous allons passer par une liaison série. En effet, nous programmons l'arduino pour qu'elle enclenche le programme d'attraper l'objet seulement à la réception du message 'o' sur la liaison série : | ||
+ | |||
+ | <pre> | ||
+ | void loop(){ | ||
+ | |||
+ | if(Serial.available() > 0) { | ||
+ | incomingByte = Serial.read(); | ||
+ | |||
+ | Serial.print("I received :"); | ||
+ | Serial.println((char) incomingByte); | ||
+ | if((char) incomingByte == 'o'){ | ||
+ | Serial.println("Je dois attraper"); | ||
+ | attrape_object(); | ||
+ | } | ||
+ | delay(500); | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | Nous testons ensuite via la Raspberry si la liaison marche avec le script suivant : | ||
+ | |||
+ | <pre> | ||
+ | import serial | ||
+ | import time | ||
+ | |||
+ | port = "/dev/ttyACM0" | ||
+ | baudrate = 9600 | ||
+ | |||
+ | arduino = serial.Serial(port,baudrate) | ||
+ | |||
+ | print("Enter 1 to test :") | ||
+ | x = input() | ||
+ | print(x) | ||
+ | |||
+ | if x=='1': | ||
+ | print("On envoie") | ||
+ | arduino.write(b'o') | ||
+ | time.sleep(2) | ||
+ | x = 7 | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | ===Liaison entre l'IA et les moteurs=== | ||
+ | |||
+ | Dans la fonction de reconnaissance d'objet orange vu plus haut, nous faisons en sorte de localiser l'objet orange et de se déplacer pour que l'objet soit au centre de l'image. Nous faisons donc en sorte que la coordonnée X de l'objet soit dans l'intervalle [670;770]. | ||
+ | Si la coordonnée X de l'objet est inférieur à 670, cela veut dire que notre robot est sur la droite de l'objet. On doit donc se déplacer sur la gauche pour se recentrer. Si la coordonnée X de l'objet est supérieur à 770, cela veut dire que notre robot est sur la gauche de l'objet. On doit donc se déplacer sur la droite. On défini bien sûr une vitesse de déplacement faible pour laisser le temps à la RaspBerry de faire l'acquisition de l'image et l'analyse : | ||
+ | |||
+ | <pre> | ||
+ | for cnt in contours: | ||
+ | [x, y, w, h] = cv2.boundingRect(cnt) | ||
+ | if w > 50: | ||
+ | print("< x :",x,"| y :",y,"| w :",w,"| h :",h,">"); | ||
+ | cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 0) | ||
+ | if x < x_min: | ||
+ | # Must go left | ||
+ | p = subprocess.run(["python3","motor_controller.py","0",str(Vy),"0"]) | ||
+ | elif x > x_max: | ||
+ | # Must go right | ||
+ | p = subprocess.run(["python3","motor_controller.py","0",str(-Vy),"0"]) | ||
+ | else: | ||
+ | # Centered on x axis | ||
+ | p = subprocess.run(["python3","motor_controller.py","0","0","0"]) | ||
+ | print("We are centered") | ||
+ | </pre> | ||
+ | |||
+ | Nous arrivons donc à faire en sorte que Trashy se centre devant la balle orange comme sur la vidéo exemple ci-dessous : | ||
+ | |||
+ | [[Fichier:Trashy_centrage.mp4|center|400px]] | ||
+ | |||
+ | Afin de pouvoir mieux contrôler les mouvements de notre robot, nous allons asservir la position de la balle sur les coordonnées X,Y et sur sa largeur W : | ||
+ | |||
+ | [[Fichier:Trashy_Asservissement.png|400px|center]] | ||
+ | |||
+ | Nous modifions ainsi le code : | ||
+ | |||
+ | <pre> | ||
+ | if not((x > X_min) and (x < X_max)): | ||
+ | Vx_consigne = (X_consigne - x) * P | ||
+ | print("Vx_consigne :",Vx_consigne) | ||
+ | action = subprocess.run(["python3",Motor,"0",str(Vx_consigne),"0"]) | ||
+ | elif not((y > Y_min) and (y < Y_max)): | ||
+ | Vy_consigne = (y - Y_consigne) * P | ||
+ | print("Vy_consigne :",Vy_consigne) | ||
+ | action = subprocess.run(["python3",Motor,str(Vy_consigne),"0","0",]) | ||
+ | elif not((w > W_min) and (w < W_max)): | ||
+ | Vw_consigne = (W_consigne - w) * P | ||
+ | print("Vw_consigne :",Vw_consigne) | ||
+ | action = subprocess.run(["python3",Motor,str(Vw_consigne),"0","0",]) | ||
+ | else: | ||
+ | print("Catch sequence") | ||
+ | action = subprocess.run(["python3",Motor,"0","0","0"]) | ||
+ | time.sleep(2) | ||
+ | to_catch = True | ||
+ | break | ||
+ | </pre> | ||
+ | |||
+ | Pour notre asservissement, nous utilisons un correcteur proportionnelle P. Après différents test, nous paramétrons '''P = 0.15'''. De plus, nous modifions les valeurs des bornes min, max et consigne des 3 paramètres utilisé pour asservir. On obtient les résultats suivants : | ||
+ | |||
+ | [[Fichier:Trashy_Correction_Loin_2.mp4|400px|thumb|center|Le Robot s'arrête trop loin]] | ||
+ | [[Fichier:Trashy_Corretion_Proche.mp4|400px|thumb|center|Le Robot s'arrête trop proche]] | ||
+ | [[Fichier:Trashy_Correction_Good.mp4|400px|thumb|center|Le Robot s'arrête relativement bien]] | ||
+ | |||
+ | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: #64d19b; vertical-align: top; width: 96%;"> Bilan </div>= | ||
Dans cette partie, vous retrouverez nos rapports de projet pour chaque semestre : | Dans cette partie, vous retrouverez nos rapports de projet pour chaque semestre : | ||
*'''Semestre 6 : ''' [[Fichier:Rapport_Trashy_S6.pdf]] | *'''Semestre 6 : ''' [[Fichier:Rapport_Trashy_S6.pdf]] | ||
*'''Semestre 7 : ''' [[Fichier:Rapport_Trashy_S7.pdf]] | *'''Semestre 7 : ''' [[Fichier:Rapport_Trashy_S7.pdf]] | ||
− | *'''Semestre 8 : ''' | + | *'''Semestre 8 : ''' [[Fichier:Rapport_de_Projet_-_Trashy_S8.pdf]] |
− | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: # | + | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: #64d19b; vertical-align: top; width: 96%;"> Gestion de projet </div>= |
==''Semestre 7''== | ==''Semestre 7''== | ||
Ligne 383 : | Ligne 858 : | ||
* Prévu par : Gabriel + Vianney | * Prévu par : Gabriel + Vianney | ||
* Fait par : Gabriel + Vianney | * Fait par : Gabriel + Vianney | ||
− | * Etat : | + | * Etat : Fini |
*'''Liaison entre moteur et pince''' : | *'''Liaison entre moteur et pince''' : | ||
* Prévu par : Gabriel + Vianney | * Prévu par : Gabriel + Vianney | ||
− | * Fait par : | + | * Fait par : Jason |
− | * Etat : | + | * Etat : Fini |
*'''IA de reconnaissance de mégots''' : | *'''IA de reconnaissance de mégots''' : | ||
* Prévu par : Jason | * Prévu par : Jason | ||
* Fait par : Jason | * Fait par : Jason | ||
− | * Etat : | + | * Etat : Fini |
*'''Liaison entre moteur et IA''' : | *'''Liaison entre moteur et IA''' : | ||
* Prévu par : Jason | * Prévu par : Jason | ||
− | * Fait par : | + | * Fait par : Jason |
− | * Etat : | + | * Etat : En cours |
*'''Liaison de tout les programmes''' : | *'''Liaison de tout les programmes''' : | ||
* Prévu par : Tout le monde | * Prévu par : Tout le monde | ||
− | * Etat : | + | * Etat : En cours |
− | |||
*'''Réglage des derniers paramètres''' : | *'''Réglage des derniers paramètres''' : | ||
* Prévu par : Tout le monde | * Prévu par : Tout le monde | ||
Ligne 409 : | Ligne 883 : | ||
* Etat : | * Etat : | ||
− | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: # | + | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: #64d19b; vertical-align: top; width: 96%;"> Chronologie et rapport de scéances </div>= |
=='''Semestre S6'''== | =='''Semestre S6'''== | ||
− | ==Mars== | + | ===Mars=== |
'''Mardi 1er Mars 2022 :''' | '''Mardi 1er Mars 2022 :''' | ||
Ligne 457 : | Ligne 931 : | ||
*Recherche d'un modèle de diagramme de Gantt pour l’organisation du projet | *Recherche d'un modèle de diagramme de Gantt pour l’organisation du projet | ||
− | ==Avril== | + | ===Avril=== |
'''Mardi 5 Avril 2022 :''' | '''Mardi 5 Avril 2022 :''' | ||
Ligne 471 : | Ligne 945 : | ||
*Concertation en groupe sur le design et l'aspect technique du robot | *Concertation en groupe sur le design et l'aspect technique du robot | ||
− | ==Mai== | + | ===Mai=== |
'''Mardi 3 Mai 2022 :''' | '''Mardi 3 Mai 2022 :''' | ||
Ligne 481 : | Ligne 955 : | ||
*Choix d’un logo | *Choix d’un logo | ||
*Design sur papier du robot avec mesures | *Design sur papier du robot avec mesures | ||
+ | |||
+ | ---- | ||
=='''Semestre S7'''== | =='''Semestre S7'''== | ||
− | ==Octobre== | + | ===Octobre=== |
'''Lundi 10 octobre 2022:''' | '''Lundi 10 octobre 2022:''' | ||
Ligne 492 : | Ligne 968 : | ||
*Réception du robot et des pinces sur lequel nous allons travailler | *Réception du robot et des pinces sur lequel nous allons travailler | ||
*Organisation du Semestre et des tâches à faire | *Organisation du Semestre et des tâches à faire | ||
− | |||
'''Vendredi 21 octobre 2022:''' | '''Vendredi 21 octobre 2022:''' | ||
Ligne 500 : | Ligne 975 : | ||
*Recherche de solution pour optimiser la détection d'objet | *Recherche de solution pour optimiser la détection d'objet | ||
*Construction et débat sur le bac de remplissage | *Construction et débat sur le bac de remplissage | ||
− | |||
'''Vendredi 28 octobre 2022:''' | '''Vendredi 28 octobre 2022:''' | ||
Ligne 509 : | Ligne 983 : | ||
*Programmation des mouvements de la pince | *Programmation des mouvements de la pince | ||
− | ==Novembre== | + | ===Novembre=== |
'''Vendredi 18 Novembre 2022:''' | '''Vendredi 18 Novembre 2022:''' | ||
Ligne 533 : | Ligne 1 007 : | ||
*Contrôle de la Raspberry | *Contrôle de la Raspberry | ||
*Continuation sur l'IA | *Continuation sur l'IA | ||
− | |||
'''Lundi 28 Novembre 2022:''' | '''Lundi 28 Novembre 2022:''' | ||
Personnes présentes : Gabriel - Vianney - Jason | Personnes présentes : Gabriel - Vianney - Jason | ||
+ | |||
*Schéma d'interconnexion et des tâches de chaque module du robot | *Schéma d'interconnexion et des tâches de chaque module du robot | ||
*Mise à jour du Wiki et du Git | *Mise à jour du Wiki et du Git | ||
*Connexion de la Raspberry | *Connexion de la Raspberry | ||
− | ==Décembre== | + | ===Décembre=== |
'''Jeudi 8 Décembre 2022:''' | '''Jeudi 8 Décembre 2022:''' | ||
Ligne 558 : | Ligne 1 032 : | ||
*Démarrage de l'algorithme de commande des moteurs | *Démarrage de l'algorithme de commande des moteurs | ||
− | + | ---- | |
− | |||
− | |||
=='''Semestre S8'''== | =='''Semestre S8'''== | ||
− | ==Janvier== | + | ===Janvier=== |
'''Mercredi 18 Janvier 2023:''' | '''Mercredi 18 Janvier 2023:''' | ||
Ligne 571 : | Ligne 1 043 : | ||
*Reprogrammation de l'algorithme de la pince | *Reprogrammation de l'algorithme de la pince | ||
*Continuation sur le contrôle moteur | *Continuation sur le contrôle moteur | ||
− | |||
*Problème rencontré : Le robot a cramé | *Problème rencontré : Le robot a cramé | ||
− | |||
'''Mercredi 25 Janvier 2023:''' | '''Mercredi 25 Janvier 2023:''' | ||
Ligne 582 : | Ligne 1 052 : | ||
*Réparation de la carte | *Réparation de la carte | ||
− | ==Février== | + | ===Février=== |
'''Mercredi 1 Février 2023:''' | '''Mercredi 1 Février 2023:''' | ||
Ligne 601 : | Ligne 1 071 : | ||
*Installation d'un nouvel OS sur la Raspberry | *Installation d'un nouvel OS sur la Raspberry | ||
*Installation de ROS 2 | *Installation de ROS 2 | ||
+ | |||
+ | '''Mercredi 15 Février 2023:''' | ||
+ | Personnes présentes : Gabriel - Vianney - Jason | ||
+ | |||
+ | *Dernière mise en place des moteurs sur la carte drive | ||
+ | *Installation du programme sur le STM32 | ||
+ | *Continuation de l'installation de ROS | ||
+ | *Création du nœud des roues Mécanum | ||
+ | *Programmation de l'IA | ||
+ | *Débuggage du code de l'IA | ||
+ | *Recherche bibliographique | ||
+ | *Schématisation de Trashy MKII | ||
+ | |||
+ | ===Mars=== | ||
+ | |||
+ | '''Mercredi 1 Mars 2023:''' | ||
+ | Personnes présentes : Gabriel - Vianney - Jason | ||
+ | |||
+ | *Recommencement de l'IA avec Tensorflow | ||
+ | *Lancement d'un entrainement d'IA de reconnaissance de fleurs pour s'entrainer | ||
+ | *Commencement des modifications pour la reconnaissance des mégots de cigarette | ||
+ | *Création d'un modèle 3D pour relier la roue au moteur sous fusion 360 | ||
+ | *Perçage de trous sur le support en métal | ||
+ | *Création d'un support en plexiglass pour la pince | ||
+ | *Création du code de la pince sous ROS | ||
+ | |||
+ | '''Mercredi 8 Mars 2023:''' | ||
+ | Personnes présentes : Gabriel - Vianney - Jason | ||
+ | |||
+ | *Entrainement de l'IA | ||
+ | *Impression des adaptateurs pour les roues | ||
+ | *Continuation du support en plexi pour la pince | ||
+ | *Continuation du code des roues sous ROS | ||
+ | |||
+ | '''Mercredi 15 Mars 2023:''' | ||
+ | Personnes présentes : Gabriel - Vianney - Jason | ||
+ | |||
+ | *Découpe de la plaque en plexi | ||
+ | *Bataille avec l'IA | ||
+ | *Resize des images | ||
+ | *Code pour faire avancer le robot | ||
+ | |||
+ | |||
+ | ===Avril=== | ||
+ | |||
+ | '''Vacance d'Avril:''' | ||
+ | |||
+ | *Etablissement de la liaison série entre la Raspbery et la pince | ||
+ | *Fix des bugs du code de détection d'objet orange | ||
+ | *Contrôle moteur par liaison série | ||
+ | *'''Résultat :''' Le robot se centre sur l'axe x de l'objet | ||
+ | |||
+ | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: #64d19b; vertical-align: top; width: 96%;"> Bibliographie et documentation</div>= | ||
+ | |||
+ | Chaine Youtube Nvidia Developper : | ||
+ | |||
+ | *Real-Time Object Detection in 10 Lines of Python Code on Jetson Nano : https://www.youtube.com/watch?v=bcM5AQSAzUY <br/> | ||
+ | *Jetson AI Fundamentals Playlist : https://www.youtube.com/playlist?list=PL5B692fm6--uQRRDTPsJDp4o0xbzkoyf8 <br/> | ||
+ | *Training our own model : https://www.youtube.com/watch?v=sN6aT9TpltU&list=PL5B692fm6--uQRRDTPsJDp4o0xbzkoyf8&index=11 (19:07) <br/> | ||
+ | |||
+ | |||
+ | ----- | ||
+ | |||
+ | |||
+ | Tutoriel Tensorflow : | ||
+ | |||
+ | *Enregistrer et charger des modèles : https://www.tensorflow.org/js/guide/save_load?hl=fr <br/> | ||
+ | *Formation personnalisée : procédure pas à pas : https://www.tensorflow.org/tutorials/customization/custom_training_walkthrough?hl=fr <br/> | ||
+ | *How to Train Your Own Object Detector Using TensorFlow Object Detection API : https://neptune.ai/blog/how-to-train-your-own-object-detector-using-tensorflow-object-detection-api <br/> | ||
+ | *Custom Object Detection using TensorFlow from Scratch : https://towardsdatascience.com/custom-object-detection-using-tensorflow-from-scratch-e61da2e10087 <br/> | ||
+ | *Documentation Tensorflow : https://www.tensorflow.org/api_docs <br/> | ||
+ | *Introduction à Tensorflow : https://www.tensorflow.org/learn?hl=fr <br/> | ||
+ | *Image Recognition using TensorFlow : https://www.tutorialspoint.com/tensorflow/image_recognition_using_tensorflow.htm# <br/> | ||
+ | *Image Recognition using TensorFlow : https://www.geeksforgeeks.org/image-recognition-using-tensorflow/ <br/> | ||
+ | *Former et servir un modèle TensorFlow avec TensorFlow Serving : https://www.tensorflow.org/tfx/tutorials/serving/rest_simple?hl=fr <br/> | ||
+ | |||
+ | |||
+ | ----- | ||
+ | |||
+ | |||
+ | Dépôt Git utilisé : | ||
+ | |||
+ | *Jetson Inference : https://github.com/dusty-nv/jetson-inference <br/> | ||
+ | *Mask RCNN : https://github.com/matterport/Mask_RCNN <br/> | ||
+ | *Raspberry : https://github.com/3sigma?tab=repositories <br/> | ||
+ | *Git du projet : https://gitlab.com/project-trashy/trashy <br/> | ||
+ | |||
+ | |||
+ | ----- | ||
+ | |||
+ | |||
+ | Divers documentations utilisées : | ||
+ | |||
+ | *Cigarette butt dataset & trained weights : https://www.immersivelimit.com/datasets/cigarette-butts <br/> | ||
+ | *Using Mask R-CNN with a Custom COCO-like Dataset : https://www.immersivelimit.com/tutorials/using-mask-r-cnn-on-custom-coco-like-dataset <br/> | ||
+ | *Documentation ROS : https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debians.html <br/> | ||
+ | *Wiki du projet '''Coupe de France de Robotique''' : https://projets-ima.plil.fr/mediawiki/index.php/IMA3/IMA4_2021/2023_P16 <br/> | ||
+ | *Building an object detector in TensorFlow using bounding-box regression : https://medium.com/nerd-for-tech/building-an-object-detector-in-tensorflow-using-bounding-box-regression-2bc13992973f <br/> | ||
+ | *Kinematic Model of a Four Mecanum Wheeled Mobile Robot : https://research.ijcaonline.org/volume113/number3/pxc3901586.pdf <br/> | ||
+ | |||
+ | |||
+ | |||
+ | ----- | ||
+ | |||
+ | |||
+ | Dataset : | ||
+ | |||
+ | *Cigarette butt : https://www.kaggle.com/datasets/estebanpacanchique/cigarette-butt?resource=download <br/> | ||
+ | *Dead leaf : https://www.kaggle.com/datasets/gunarakulan/cashewleavesdataset <br/> | ||
+ | |||
+ | |||
+ | =<div class="mcwiki-header" style="border-radius: 15px; padding: 15px; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 80%; background: #64d19b; vertical-align: top; width: 96%;"> Code </div>= | ||
+ | |||
+ | ==='''Code Arduino de la pince :'''=== | ||
+ | |||
+ | <pre> | ||
+ | #include <Servo.h> | ||
+ | |||
+ | int angle = 0; | ||
+ | int step_time = 15; | ||
+ | int incomingByte = 0; | ||
+ | |||
+ | |||
+ | Servo base, claw, up_down, front_back; | ||
+ | |||
+ | void setup() { | ||
+ | up_down.attach(7); | ||
+ | base.attach(8); | ||
+ | claw.attach(6); | ||
+ | front_back.attach(9); | ||
+ | init_servo(); | ||
+ | |||
+ | Serial.begin(9600); | ||
+ | } | ||
+ | |||
+ | void init_servo(){ | ||
+ | up_down.write(0); | ||
+ | base.write(0); | ||
+ | claw.write(0); | ||
+ | front_back.write(0); | ||
+ | } | ||
+ | |||
+ | void to_move(Servo obj, int start_angle, int stop_angle){ | ||
+ | /* Ce programme permet de contrôler les servo-moteurs de la pince de Trashy | ||
+ | * | ||
+ | * Liste des paramètres : | ||
+ | * :Servo obj: Servo-moteur à actionner | ||
+ | * :int start_angle: Angle de départ | ||
+ | * :int stop_angle: Angle d'arrivée | ||
+ | * | ||
+ | * CU : Avoir les servo-moteurs branché aux bons ports | ||
+ | * claw prend des angles de 0 à 95° | ||
+ | * Les autres de 0 à 180° | ||
+ | * | ||
+ | * Exemple | ||
+ | * to_move(claw,0,95); pour fermer la pince | ||
+ | * to_move(claw,95,0); pour ouvrir la pince | ||
+ | * to_move(front_back,180,0); pour reculer la pince | ||
+ | * to_move(front_back,0,180); pour avancer la pince | ||
+ | * to_move(up_down,180,0); pour baisser la pince | ||
+ | * to_move(up_down,0,180); pour monter la pince | ||
+ | * to_move(base,0,180); pour emmener la pince vers le depos | ||
+ | * to_move(base,180,0); pour ramener la pince devant | ||
+ | */ | ||
+ | if(start_angle < stop_angle){ | ||
+ | for(angle = start_angle; angle < stop_angle; angle += 1){ | ||
+ | obj.write(angle); | ||
+ | delay(step_time); | ||
+ | } | ||
+ | }else{ | ||
+ | for(angle = start_angle; angle >= stop_angle; angle -= 1){ | ||
+ | obj.write(angle); | ||
+ | delay(step_time); | ||
+ | } | ||
+ | } | ||
+ | delay(100); | ||
+ | } | ||
+ | |||
+ | void attrape_object(){ | ||
+ | to_move(claw,95,0); | ||
+ | delay(1000); | ||
+ | to_move(front_back,0,180); | ||
+ | delay(1000); | ||
+ | to_move(claw,0,95); | ||
+ | delay(1000); | ||
+ | to_move(front_back,180,0); | ||
+ | delay(1000); | ||
+ | } | ||
+ | |||
+ | void loop(){ | ||
+ | |||
+ | if(Serial.available() > 0) { | ||
+ | incomingByte = Serial.read(); | ||
+ | |||
+ | Serial.print("I received :"); | ||
+ | Serial.println((char) incomingByte); | ||
+ | if((char) incomingByte == 'o'){ | ||
+ | Serial.println("Je dois attraper"); | ||
+ | attrape_object(); | ||
+ | } | ||
+ | delay(500); | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ==='''Motor_controller.py :'''=== | ||
+ | |||
+ | <pre> | ||
+ | import serial | ||
+ | import sys | ||
+ | import time | ||
+ | |||
+ | # Define Variable | ||
+ | |||
+ | R = 5.0 | ||
+ | Lx = 19.0 | ||
+ | Ly = 27.0 | ||
+ | |||
+ | # Initialization of Motor_controller serial connection | ||
+ | |||
+ | port_Moteur = "/dev/ttyACM0" | ||
+ | baudrate_Moteur = 115200 | ||
+ | |||
+ | moteur = serial.Serial(port_Moteur,baudrate_Moteur,timeout=1) | ||
+ | |||
+ | # Function of movement | ||
+ | |||
+ | def translation(Vx, Vy, Wz): | ||
+ | """ | ||
+ | Define the parameter for the G-Code to make a movement | ||
+ | depending on the speed Vx, Vy, Wz | ||
+ | :param Vx: (float) Speed on the X axis | ||
+ | :param Vy: (float) Speed on the Y axis | ||
+ | :param Wz: (float) Speed on the Z axis | ||
+ | :return: A float-list of size 4 with each parameter for the G-Code [X, Y, Z, U] | ||
+ | |||
+ | CU : None | ||
+ | """ | ||
+ | X = (1/R) * (Vx - Vy - (Lx + Ly)*Wz) | ||
+ | Y = (1/R) * (Vx + Vy + (Lx + Ly)*Wz) | ||
+ | Z = (1/R) * (Vx - Vy + (Lx + Ly)*Wz) | ||
+ | U = (1/R) * (Vx + Vy - (Lx + Ly)*Wz) | ||
+ | print("Entrée : < Vx =",Vx,"| Vy =",Vy,"| Wz =",Wz," >") | ||
+ | print("Sortie : < X =",X,"| Y =",Y,"| Z =",Z,"| U =",U," >") | ||
+ | return [X, Y, Z, U] | ||
+ | |||
+ | def send_G_Code(w): | ||
+ | command = "G0 X{:.3f} Y{:.3f} Z{:.3f} U{:.3f}\n".format(w[0],w[1],w[2],w[3]) | ||
+ | print("Command sent :",command) | ||
+ | moteur.write(command.encode('ascii')) | ||
+ | |||
+ | |||
+ | nb_arg = len(sys.argv) | ||
+ | if(nb_arg == 4): | ||
+ | vitesse = [] | ||
+ | vitesse = translation(float(sys.argv[1]),float(sys.argv[2]),float(sys.argv[3])) | ||
+ | send_G_Code(vitesse) | ||
+ | else: | ||
+ | print("Missing parameters") | ||
+ | </pre> | ||
+ | |||
+ | ==='''Object_detection.py :'''=== | ||
+ | |||
+ | <pre> | ||
+ | # capture.py | ||
+ | import numpy as np | ||
+ | import cv2 | ||
+ | import subprocess | ||
+ | import serial | ||
+ | import time | ||
+ | |||
+ | # Capture video from camera | ||
+ | cap = cv2.VideoCapture(0) | ||
+ | |||
+ | # Variable | ||
+ | X_consigne = 300 | ||
+ | X_min = 220 | ||
+ | X_max = 380 | ||
+ | |||
+ | Y_consigne = 370 | ||
+ | Y_min = 270 | ||
+ | Y_max = 470 | ||
+ | |||
+ | W_consigne = 670 | ||
+ | W_min = 570 | ||
+ | W_max = 770 | ||
+ | |||
+ | P = 0.15 | ||
+ | |||
+ | to_catch = False | ||
+ | |||
+ | Motor = "Motors_code/motor_controller.py" | ||
+ | |||
+ | port_Arduino = "/dev/ttyACM1" | ||
+ | baudrate_Arduino = 9600 | ||
+ | |||
+ | Pince = serial.Serial(port_Arduino,baudrate_Arduino,timeout=1) | ||
+ | |||
+ | while(cap.isOpened()): | ||
+ | ret, frame = cap.read() | ||
+ | hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) | ||
+ | light_orange = (0,90,200) | ||
+ | dark_orange = (18,180,255) | ||
+ | mask = cv2.inRange(hsv, light_orange, dark_orange) | ||
+ | res = cv2.bitwise_and(frame,frame, mask= mask) | ||
+ | contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) | ||
+ | for cnt in contours: | ||
+ | [x, y, w, h] = cv2.boundingRect(cnt) | ||
+ | if w > 50: | ||
+ | print("< x :",x,"| y :",y,"| w :",w,"| h :",h,">"); | ||
+ | cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 0) | ||
+ | if not((x > X_min) and (x < X_max)): | ||
+ | Vx_consigne = (X_consigne - x) * P | ||
+ | print("Vx_consigne :",Vx_consigne) | ||
+ | action = subprocess.run(["python3",Motor,"0",str(Vx_consigne),"0"]) | ||
+ | elif not((y > Y_min) and (y < Y_max)): | ||
+ | Vy_consigne = (y - Y_consigne) * P | ||
+ | print("Vy_consigne :",Vy_consigne) | ||
+ | action = subprocess.run(["python3",Motor,str(Vy_consigne),"0","0",]) | ||
+ | elif not((w > W_min) and (w < W_max)): | ||
+ | Vw_consigne = (W_consigne - w) * P | ||
+ | print("Vw_consigne :",Vw_consigne) | ||
+ | action = subprocess.run(["python3",Motor,str(Vw_consigne),"0","0",]) | ||
+ | else: | ||
+ | print("Catch sequence") | ||
+ | action = subprocess.run(["python3",Motor,"0","0","0"]) | ||
+ | time.sleep(2) | ||
+ | to_catch = True | ||
+ | break | ||
+ | |||
+ | if to_catch == True: | ||
+ | print("Ici") | ||
+ | Pince.write(b'o') | ||
+ | time.sleep(2) | ||
+ | Pince.write(b'f') | ||
+ | time.sleep(2) | ||
+ | action = subprocess.run(["python3",Motor,"0","0","0"]) | ||
+ | to_catch = False | ||
+ | break | ||
+ | |||
+ | cv2.imshow('frame',frame) | ||
+ | if cv2.waitKey(1) & 0xFF == ord('q'): | ||
+ | action = subprocess.run(["python3",Motor,"0","0","0"]) | ||
+ | break | ||
+ | |||
+ | # When everything done, release the capture | ||
+ | cap.release() | ||
+ | |||
+ | cv2.destroyAllWindows() | ||
+ | </pre> |
Version actuelle datée du 14 mai 2023 à 19:39
Sommaire
- 1 Présentation du projet
- 2 Réalisation et résultats
- 2.1 🤖 Matériel à disposition
- 2.2 ⚙️ Principe de fonctionnement de Trashy
- 2.3 🦾 Mise en place des mouvements de la pince
- 2.4 🗑️ Algorithme du bac
- 2.5 🚬 Algorithme de détection de mégots de cigarette
- 2.5.1 👨💻 Rapport d'avancement 1 (25/01/2023)
- 2.5.2 👨💻 Rapport d'avancement 2 (08/02/2023)
- 2.5.3 👨💻 Rapport d'avancement 3 (15/02/2023)
- 2.5.4 👨💻 Rapport d'avancement 4 (01/03/2023)
- 2.5.5 👨💻 Rapport d'avancement 5 (08/03/2023)
- 2.5.6 👨💻 Rapport d'avancement 6 (15/03/2023)
- 2.5.7 👨💻 Rapport d'avancement 7 (21/04/2023)
- 2.6 ⚡ Carte Driver Moteur
- 2.7 🚗 Motorisation de Trashy
- 2.8 🖨️ Impression 3D du convertisseur Roue/Moteur
- 2.9 ✂️ Découpe du plateau de la pince
- 2.10 📎Liaison entre les fonctions
- 3 Bilan
- 4 Gestion de projet
- 5 Chronologie et rapport de scéances
- 6 Bibliographie et documentation
- 7 Code
Présentation du projet
🌍 Contexte
Pour notre étude, nous avons d’abord établi une analyse des besoins de notre projet. Après recherche, nous avons découvert, qu’en France, près de 68 Milliards de cigarettes étaient fumés par an. Et parmi ces 68 Milliards, on estime à 40 Milliards de mégots jetés au sol. Comme un exemple sera beaucoup plus parlant, prenons l’exemple d’un défi réalisé par 230 participants aux Champs-Elysées. Pour sensibiliser sur le nombre de mégots jetés aux Champs-Elysées, 230 bénévoles se sont mis pour défis de ramasser pendant 1h30 tous les mégots qu’ils trouvaient sur leurs chemins. Une fois ramassés, ils ont compté un total d’environ 100000 mégots. Et ça, ce n’est qu’à l'échelle des Champs-Elysées. A partir de là, on peut s’imaginer la quantité de mégots qui se trouve actuellement sur le sol de nos villes. Nous avons donc pour projet de créer un robot autonome capable de se déplacer vers le mégot et de le ramasser en le disposant dans un récipient La somme nécessaire au ramassage des mégots est de 80.000.000 euros, selon le ministère de la Transition écologique et solidaire. Lors de la première année de sa mise en œuvre, les usines de production de cigarettes ont alloué 10.000.000 euros. L’an suivant, ce montant sera doublé. La collecte de ces sommes d’argent est dans le cadre de l’application de la loi sortie en février 2020. Cette dernière stipule que le fabricant est garant de ses produits jusqu’à leur fin de vie. Toutefois, il faut attendre l’année 2023 pour que les cigarettiers financent en totalité ces ramassages de mégots. Le coût du ramassage des mégots varie suivant la superficie de la localité. Les agglomérations rurales dont la population n’excède pas les 5.000 habitants, il est de 0,50 euro. Les municipalités ne dépassant pas 50.000 habitants perçoivent 1,08 euro par personne. Tandis que dans les communes ayant une population supérieure à 50.000 habitants, ce montant est de 2,08 euros. Il s’agit de la responsabilité élargie des producteurs. Les cigarettiers doivent alors prendre leurs mesures de manière à ce que les mégots soient recyclés. Très polluantes, ces substances prennent beaucoup de temps pour se décomposer à cause de leurs composants en plastiques.
🎯 Objectifs
Notre projet consiste à concevoir et à mettre en place un système mobile autonome (volant ou roulant) permettant la collecte de déchets (initialement de mégots) sur le campus grâce à de la reconnaissance d’image. Ce dossier va vous présenter nos pistes de recherches et de développement ainsi que notre organisation pour mener à bien ce projet. Nous avons décidé de nommer notre robot : “Trashy” (en référence au mot angais Trash).
Pour mener à bien ce projet, nous avons mis en place un Google Drive pour organiser notre travail pendant la période de recherche et d’étude de ce projet. Durant ce semestre, nous avons donc consacré notre temps aux phases de pré-étude et d’étude du projet, ainsi qu’à la réalisation du cahier des charges nous permettant de mieux continuer le projet pour les semestres à venir.
💬 Description
Nous avons donc déterminé les fonctions contraintes du robot qu’il doit pouvoir réaliser telle la détection des mégots ou encore la contrainte de devoir évoluer sur tout type de terrain. Et c’est ainsi, et après concertation avec tous les membres du projets, que nous avons écrit le cahier des charges suivants : Le robot devra rouler à une vitesse de 4km/h et devra être capable de passer sur des obstacles de 2cm maximum. De plus, lorsque le robot détecte un obstacle qu’il ne peut peut franchir, il doit s’arrêter et le contourner. Enfin, en cas d’obstacle dangereux, le robot devra émettre un son. Lorsque le robot détecte un déchet, il doit l’attraper à l’aide de ses pinces puis le mettre dans son réceptacle. Si le réservoir est plein, ou si la maximale que le robot peut transporter est atteinte, le robot devra partir vers une station de déchargement (faite par le client, hors du cadre de notre projet) pour se vider. De plus, lorsque le robot ramasse un déchet, il devra cartographier ce déchet afin de connaître les zones les plus polluées. A noter que le robot ne fait pas le tri des déchets. La position du robot doit être connue en temps réel afin d’analyser les données du parcours et le robot devra connaître son orientation à l’aide de son gyroscope. Le robot doit avoir une autonomie de 8h et doit pouvoir repartir vers la station de charge si sa batterie devient faible. Les bras du robot sont en caoutchouc pour plus d’adhérence et sa trappe (de 41L) est concave afin d’éviter que les déchets ne se coincent. Le robot doit être capable d’évoluer dans des terrains semi-accidentés et de monter un trottoir. En cas d’urgence, le robot doit immédiatement s’arrêter.
C’est ainsi qu’après réflexions et brainstorming entre les membres du groupe, nous avons trouvé les premières solutions et design de Trashy. Ainsi, nous allons concevoir une carte mère adaptée au robot permettant toutes les fonctionnalités décrites ci-dessus. Nous allons donc avoir recours à un processeur ATMEGA 2560 en communication avec une Raspberry PI. La Raspberry est là pour les traitements des capteurs plus gourmands (dont l’analyse d’image de la caméra) que l’arduino ne peut faire.
Réalisation et résultats
🤖 Matériel à disposition
Pour pouvoir mettre en œuvre notre projet, nous avons à disposition une plateforme mobile avec 4 roues omnidirectionnelle et contrôlé par une Raspberry PI 4 ainsi qu'une Arduino Mega 2560. Nous disposons aussi d'un kit de robot Pince. Afin de pouvoir gérer efficacement la partie intelligence artificielle, Mr Othman Lakhal nous a prêté une Nvidia Jetson Nano
Ayant eu un problème avec le robot, nous avons repartir de zéro. Nous avons reçu un châssis, des moteurs ainsi que 4 roues omnidirectionnelles. Le groupe de la coupe de France de robotique nous as également gentiment prêté une carte driver moteur conçu par Albin Mouton
⚙️ Principe de fonctionnement de Trashy
Afin de mieux nous organiser durant ce projet, nous avons établis le schéma d'interconnexions et la fonctions de chacun des composants que l'on dispose. Ainsi :
- Nvidia Jetson Nano : Gère la reconnaissance des mégots de cigarette et envoie à la Raspberry, via liaison série, les coordonnées du mégot de cigarette sur l'image
- RaspBerry Pi 4 : Asservis en position le robot pour que le mégot de cigarette soit au centre de l'image et demande, via une liaison série avec l'arduino, l'activation de la pince
- Arduino Mega 2560 : Gère le contrôle de la pince
Pour la suite du projet, le schéma d'interconnexions a changé pour :
- RaspBerry Pi 4 : Gère la reconnaissance et l'analyse des images de la caméra. La RaspBerry est relié aux autres modules et communique avec eux via une liaison série
- Carte de contrôle moteur : Gère la commande des moteurs
- Arduino Mega 2560 : Gère le contrôle de la pince
🦾 Mise en place des mouvements de la pince
Pour cette partie, nous utilisons la bibliothèque <Servo.h> permettant de contrôler les servo-moteurs de notre pince. Nous en disposons de 4 que nous déclarons avec les lignes de codes ci-dessous :
Servo base, claw, up_down, front_back; void setup() { up_down.attach(22); base.attach(24); claw.attach(26); front_back.attach(28); init_servo(); Serial.begin(9600); }
Pour les contrôler, nous avons une fonction générique qui prend en paramètre le servo-moteur à actionner ainsi que l'angle de départ et l'angle d'arriver :
void to_move(Servo obj, int start_angle, int stop_angle){ /* Ce programme permet de contrôler les servo-moteurs de la pince de Trashy * * Liste des paramètres : * :Servo obj: Servo-moteur à actionner * :int start_angle: Angle de départ * :int stop_angle: Angle d'arrivée * * CU : Avoir les servo-moteurs branché aux bons ports * claw prend des angles de 0 à 95° * Les autres de 0 à 180° * * Exemple * to_move(claw,0,95); pour fermer la pince * to_move(claw,95,0); pour ouvrir la pince * to_move(front_back,180,0); pour reculer la pince * to_move(front_back,0,180); pour avancer la pince * to_move(up_down,180,0); pour baisser la pince * to_move(up_down,0,180); pour monter la pince * to_move(base,0,180); pour emmener la pince vers le depos * to_move(base,180,0); pour ramener la pince devant */ if(start_angle < stop_angle){ for(angle = start_angle; angle < stop_angle; angle += 1){ obj.write(angle); delay(step_time); //step_time = 15 } }else{ for(angle = start_angle; angle >= stop_angle; angle -= 1){ obj.write(angle); delay(step_time); //step_time = 15 } } delay(100); }
Nous codons ainsi une fonction permettant d'attraper un objet :
void attrape_object(){ to_move(claw,95,0); delay(1000); to_move(front_back,0,180); delay(1000); to_move(claw,0,95); delay(1000); to_move(front_back,180,0); delay(1000); }
Nous obtenons les résultats suivant :
🗑️ Algorithme du bac
int lightPin = 0; int ledPin1 = 9; int ledPin2 = 10; int bref =0; int loong=0; void setup(){ pinMode(ledPin1, OUTPUT); pinMode(ledPin2, OUTPUT); Serial.begin(9600); } void loop(){ int seuil = 850; bref=(analogRead(lightPin)< seuil) ; delay(20); loong=(analogRead(lightPin)< seuil); if(bref==1 && loong==1){ digitalWrite(ledPin1, HIGH); delay(1000); }else if(bref==1 && loong==0){ digitalWrite(ledPin2, HIGH); delay(1000); }else{ (ledPin2, LOW); digitalWrite(ledPin1, LOW); } }
🚬 Algorithme de détection de mégots de cigarette
Pour l'algorithme de détection de mégots de cigarette, nous avons à notre disposition une Nvidia Jetson Nano. Afin de mieux comprendre comment tout ceci marche, nous nous sommes inspirés des vidéos ci-dessous (toute provenant de la chaine youtube Nvidia Developper) :
- Real-Time Object Detection in 10 Lines of Python Code on Jetson Nano : https://www.youtube.com/watch?v=bcM5AQSAzUY
- Jetson AI Fundamentals Playlist : https://www.youtube.com/playlist?list=PL5B692fm6--uQRRDTPsJDp4o0xbzkoyf8
- Training our own model : https://www.youtube.com/watch?v=sN6aT9TpltU&list=PL5B692fm6--uQRRDTPsJDp4o0xbzkoyf8&index=11 (19:07)
Nous nous inspirons aussi du dépôt git associé : https://github.com/dusty-nv/jetson-inference
Nous allons donc créer un modèle permettant à la Nvidia de détecter les mégots de cigarette et les cigarettes. Pour ceci, nous allons avoir besoin de données répertoriés dans 3 dossier :
- train : Ce sont les données qui vont entrainer notre modèle
- test : Ce sont les données qui vont tester notre modèle
- val : Ce sont les données qui vont valider notre modèle
Afin d'entrainer notre modèle, nous allons simplement parcourir l'ensemble des données. 1 parcours de toute les données est appelé un epoch. Pour avoir un modèle fiable à 80% (avec 5000 données), il est conseillé d'utiliser entre 30 et 60 epochs.
👨💻 Rapport d'avancement 1 (25/01/2023)
Le premier obstacle rencontré dans la réalisation de l'IA est la connexion à internet de la Nvidia Jetson Nano pour pouvoir cloner le dépôt git nous permettant de créer nos premiers IA. Nous avons donc procédé à différentes solutions pour combler à cette obstacle :
- Solution 1 : Utilisation d'une clé WiFi relié en USB à la carte. Mr Lakhal (notre tuteur de projet) nous avait prêtés une clé Wifi mais malheureusement, le logiciel permettant la configuration n'est pas compatible sous Linux (OS de la Nvidia). Une solution possible était de passer par Wine qui permet sous Linux d'exécuter un .exe mais sans Internet, impossible de l'installer. Nous sommes donc en train d'essayer avec d'autres clé WiFi
- Solution 2 : Téléchargement des fichiers du Git sur un PC personnelle que l'on va transférer ensuite à la carte. Le problème de cette solution est que les programmes disponibles sur le dépôt Git ont besoin d'Internet pour fonctionner
Nous allons donc commencer à développer l'IA sur un PC personnelle puis migré le tout lorsque nous aurons trouvé une solution pour relier la Nvidia à Internet.
Nous commençons donc par cloner le git associé sur notre PC :
jasondelannoy@Zeus : /trashy/Image_recognition/IA$ git clone https://github.com/dusty-nv/jetson-inference.git
Ensuite, nous installons les modèles et fichiers nécessaire dans le dossier créer suite au clonage et démarrons le conteneur docker avec la commande :
jasondelannoy@Zeus : /trashy/Image_recognition/IA/jetson-inference$ docker/run.sh
Un autre obstacle rencontré est l'incapacité de WSL 2 (le client Ubuntu de chez Windows) d'accéder aux ports USB et donc à la caméra branché. Les principaux tests ne peuvent donc pas être fait via WSL sur notre machine personnelle. Une première solution pour régler ce problème serait de passer par une machine virtuel. Pour tester, nous utilisons une machine virtuelle sous Ubuntu 22.04 grâce à VirtualBox mais après configuration, la VM a bien accès à mes ports USB cependant lorsque nous voulons connecter la caméra à la VM, une erreur apparait et il est impossible de l'utiliser dans la VM. Nous devons donc rapidement résoudre le problème d'internet de la Nvidia Une solution trouvé mais à tester serait de branché la Nvidia via Ethernet à ma box internet et de me SSH depuis Polytech pour bosser sur la Nvidia depuis mon PC personnel.
Du à une impossibilité de se connecter en Ethernet à l'école, le développement de l'IA se fera depuis chez Jason sur son temps libre
👨💻 Rapport d'avancement 2 (08/02/2023)
Lors de cette séance, nous avons d'abord continuer de travailler sur la Nvidia Jetson Nano. Nous commençons donc par cloner le git associé sur notre PC :
dlinano@dlinano : /trashy/Image_recognition/IA$ git clone https://github.com/dusty-nv/jetson-inference.git
Et une fois le dépôt git cloné sur notre PC, nous commençons l'installation du docker via la commande :
dlinano@dlinano : /trashy/Image_recognition/IA/jetson-inference$ docker/run.sh
Cependant, l'installation ne marche pas sur la Nvidia Jetson Nano. Après quelques recherches et lecture sur le git associé, nous avons remarqué que la Nvidia Jetson Nano que nous possédons n'est pas sur le bon OS. Le problème pourrait être réglé en changeant l'OS mais comme la carte est celle personnelle du prof, nous attendons son accord pour le faire.
Pendant ce temps, nous avons travaillé sur des alternatives pour pouvoir produire une première version pour notre preuve de conception. Nous d'abord essayé une version où l'ordinateur reconnaitrait les objets orange afin de mieux voir les mégots de cigarette. Nous avons ensuite continué nos recherches pour trouver d'autres alternatives
Alternative 1
La première alternative est de mettre en valeur les objets oranges reconnue par la caméra. Pour ça, nous utilisons la librairie OpenCV de Python. Tout d'abord, nous récupérons les images de la caméra puis nous créons un masque pour récupérer seulement les objets oranges. Puis nous comparons l'image initiale avec le masque. Voici le code :
# capture.py import numpy as np import cv2 # Capture video from camera cap = cv2.VideoCapture(0) while(cap.isOpened()): # Capture frame-by-frame ret, frame = cap.read() # Our operations on the frame come here hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) light_orange = (0,90,200) dark_orange = (18,255,255) #Creation of the mask to separate the orange object mask = cv2.inRange(hsv, light_orange, dark_orange) #Creation of the image by comparing the initial frame with the mask res = cv2.bitwise_and(frame,frame, mask= mask) # Display the resulting frame cv2.imshow('frame',frame) cv2.imshow('mask',mask) cv2.imshow('res',res) k = cv2.waitKey(5) & 0xFF if k == 27: break # When everything done, release the capture cap.release() cv2.destroyAllWindows()
Nous obtenons les résultat suivant (le premier avec un T-Shirt orange et le 2eme avec un bouchon d'oreille orange) :
Alternative 2
En parallèle, nous avons continué nos recherches sur d'autres alternatives d'IA pour reconnaitre les mégots de cigarette et nous avons trouvé un dataset complet comportant 2300 images pour entraîner notre modèles ainsi qu'un blog comportant le travail ainsi que les résultats d'une personnes ayant fait un IA de reconnaissance de mégot de cigarette :
- Dataset : https://www.immersivelimit.com/datasets/cigarette-butts
- Blog : https://www.immersivelimit.com/tutorials/using-mask-r-cnn-on-custom-coco-like-dataset
Nous allons donc pouvoir utiliser ce blog pour confectionner notre modèle.
👨💻 Rapport d'avancement 3 (15/02/2023)
Lors de cette séance, nous avons avancé sur l'alternative 2 trouvé à la précédente séance. Nous avons donc commencé par cloner le dépôt git :
jasondelannoy@Zeus : /trashy/Image_recognition/IA$ git clone https://github.com/akTwelve/tutorials.git
Puis nous installons les bibliothèques nécessaire au fonctionnement du programme avec les commandes :
jasondelannoy@Zeus : /trashy/Image_recognition/IA/tutorial$ pip install -r requirements.txt jasondelannoy@Zeus : /trashy/Image_recognition/IA/tutorial$ sudo python3 setup.py install
Nous allons récupérer ensuite le dataset de 2200 images de mégots de cigarette (disponible ici : https://www.immersivelimit.com/datasets/cigarette-butts) et nous déposons le dossier /cig_butts dans le dossier /dataset créé après le clonage du dépôt git. Ensuite nous lançons le programme principal :
jasondelannoy@Zeus : /trashy/Image_recognition/IA/tutorial$ python3 training.py
Et là, Erreur !!! Après recherche, nous remarquons qu'ils nous manquent une librairie custom appelé mrcnn. Cette librairie est disponible sur le git qui a inspiré le git qui nous inspire disponible ici : https://github.com/matterport/Mask_RCNN De plus, certaines librairie installé n'était pas les bonnes versions, nous modifions donc ceci avec les commandes suivantes :
jasondelannoy@Zeus : /trashy/Image_recognition/IA/tutorial$ pip uninstall tensorflow -y jasondelannoy@Zeus : /trashy/Image_recognition/IA/tutorial$ pip uninstall keras -y jasondelannoy@Zeus : /trashy/Image_recognition/IA/tutorial$ pip install tensorflow==2.4.3 jasondelannoy@Zeus : /trashy/Image_recognition/IA/tutorial$ pip install keras==2.4.0
Nous avons aussi dû modifier les codes models.py et utils.py du dossier /mrcnn.
Modification apporté à models.py :
- Ajout de :
class AnchorsLayer(KL.Layer): def __init__(self, anchors, name="anchors", **kwargs): super(AnchorsLayer, self).__init__(name=name, **kwargs) self.anchors = tf.Variable(anchors) def call(self, dummy): return self.anchors def get_config(self): config = super(AnchorsLayer, self).get_config() return config
- Remplacement de la ligne :
anchors = KL.Lambda(lambda x: tf.Variable(anchors), name="anchors")(input_image)
par
anchors = AnchorsLayer(anchors, name="anchors")(input_image)
- Modification de tout les tf.random_shuffle par tf.random.shuffle
- Modification de tout les tf.log par tf.math.log
Modification apporté à utils.py :
- Modification de tout les tf.log par tf.math.log
A l'heure actuelle, nous n'avons pas encore fini de débugger le programme et n'avons pas pu lancer de première itération.
👨💻 Rapport d'avancement 4 (01/03/2023)
En continuant de débugger le programme pendant les vacances, nous avons découvert que ce programme a été écrit avec des versions obsolètes de Tensorflow. Ainsi, et en ayant eu confirmation sur le blog associé au code utilisé, il nous est impossible de modifier le code disponible pour fonctionner sous Tensorflow 2 car nous allons juste avoir une succession infinie de bug à corriger.
Nous avons donc pris la décision de recommencer de zéro et en créant notre propre programme fonctionnant avec Tensorflow 2 pour pouvoir créer notre modèle. Pour les images d'entraînement, nous allons utilisé le dataset trouvé plus haut.
Pour pouvoir mieux nous familiariser avec la librairie Tensorflow, nous allons dans un premier temps suivre tutoriel suivant permettant d'entrainer un modèle de reconnaissance de fleur sur Tensorflow disponible ici : https://www.geeksforgeeks.org/image-recognition-using-tensorflow/
Dans un premier temps, nous récupérons les images de fleurs qui vont nous servir pour notre entrainement :
dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz" data_dir = tf.keras.utils.get_file('flower_photos', origin=dataset_url, untar=True) data_dir = pathlib.Path(data_dir)
Nous allons ensuite split nos données en 2 parties : une partie pour l'entrainement et une partie pour la validation :
# Training split train_ds = tf.keras.utils.image_dataset_from_directory( data_dir, validation_split=0.2, subset="training", seed=123, image_size=(180, 180), batch_size=32 ) # Testing or validation split val_ds = tf.keras.utils.image_dataset_from_directory( data_dir, validation_split=0.2, subset="validation", seed=123, image_size=(180,180), batch_size=32 )
Nous allons ensuite créer un modèle :
class_names = train_ds.class_names num_classes = len(class_names) model = Sequential([ layers.Rescaling(1./255, input_shape=(180,180,3)), layers.Conv2D(16,3,padding='same', activation='relu'), layers.MaxPooling2D(), layers.Conv2D(32,3,padding='same', activation='relu'), layers.MaxPooling2D(), layers.Conv2D(64,3,padding='same',activation='relu'), layers.MaxPooling2D(), layers.Flatten(), layers.Dense(128,activation='relu'), layers.Dense(num_classes) ]) model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'])
Et nous lançons enfin l'entrainement de notre modèle avec 10 epochs :
epochs = 10 history = model.fit( train_ds, validation_data=val_ds, epochs=epochs )Nous obtenons les résultats suivant :
Il ne nous reste plus qu'à modifier notre code pour récupérer les images de cigarettes dont nous disposons et de faire l'entrainement dessus.
👨💻 Rapport d'avancement 5 (08/03/2023)
Lors de cette séance, nous avons augmenté notre dataset en rajoutant des images de cigarette disponible sur "https://www.kaggle.com/datasets/estebanpacanchique/cigarette-butt?resource=download" et des images de feuilles mortes disponible sur "https://www.kaggle.com/datasets/gunarakulan/cashewleavesdataset". L'ajout des images de feuilles mortes permet à l'IA de savoir qu'est-ce qui n'est pas un mégot de cigarette. De plus, comme les feuilles mortes ont une teinte orangé, on apprends à l'IA à les différencier.
Maintenant que notre IA s'entraine, nous devons enregistrer le modèle pour pouvoir l'utiliser. Pour faire ça, nous rajoutons ces quelques lignes au programme models.py :
#Saving the model MODEL_DIR = tempfile.gettempdir(); version = input("Numéro de version :") export_path = os.path.join(MODEL_DIR, str(version)) print("export_path = {}\n".format(export_path)) tf.keras.models.save_model( model, export_path, overwrite=True, include_optimizer=True, save_format=None, signatures=None, options=None ) print('\nSaved model:') os.system('ls -l ' + export_path)
👨💻 Rapport d'avancement 6 (15/03/2023)
Durant cette séance, nous nous sommes concentré sur le test du bon fonctionnement de notre IA. Pour ce faire, nous créons un script en python nommé test.py (donc différent du programme qui permet l'entrainement de notre IA). Nous devons donc d'abord charger notre modèle afin de pouvoir l'utiliser :
IA = tf.keras.models.load_model("model/v_3")
où model/v_3 est le dossier où nous avons compilé la dernière version de notre IA. </br> Afin de pouvoir tester cette IA, nous devons choisir une image au hasard parmi notre dataset et la lui envoyer. Pour choisir cette image au hasard, nous avons codé ce script python :
# Define the directory containing the images dir_path = "test_img/" # Get a list of all the image file names in the directory img_files = [f for f in os.listdir(dir_path) if f.endswith(".jpg") or f.endswith(".jpeg") or f.endswith(".png")] # Load each image into a numpy array and append it to a list img_list = [] for filename in img_files: img = np.array(Image.open(os.path.join(dir_path, filename))) img_list.append(img) rand_idx = random.randint(0, len(img_list)-1) rand_img = img_list[rand_idx] img_to_show = rand_img rand_img = cv2.resize(rand_img,(180,180)) rand_img = rand_img.reshape(1,180,180,3)
Pour tester notre IA et faire une prédiction sur l'image, nous utilisons la fonction model.predict() de tensorflow. Cependant, nous avions eu un problème de taille d'image. En effet, toute les images de notre dataset n'avait pas la bonne taille pour l'entrainement. Nous avons donc codé un petit script en python nommé resizer.py qui permet de modifier la taille de nos images :
from PIL import Image import os # Définir le répertoire contenant les images à redimensionner repertoire_images = "dataset/dead_leave" # Définir la taille de sortie pour les images taille_sortie = (180, 180) i = 1 # Parcourir tous les fichiers dans le répertoire des images for fichier in os.listdir(repertoire_images): # Vérifier que le fichier est une image if fichier.endswith(".jpg") or fichier.endswith(".jpeg") or fichier.endswith(".png"): # Ouvrir l'image chemin_image = os.path.join(repertoire_images, fichier) with Image.open(chemin_image) as image: # Redimensionner l'image image = image.resize(taille_sortie) # Enregistrer l'image redimensionnée image.save(chemin_image) print(i) i = i + 1
Une fois les images à la bonne taille, un entrainement refait, nous pouvons ainsi faire nos prédictions avec les lignes de code ci-dessous :
plt.imshow(img_to_show) id_label = np.argmax(IA.predict(rand_img),axis=1) if id_label == 0: print("It's a cigarette butt") elif id_label == 1: print("It's a dead leaf") plt.show()
Nous obtenons les résultats suivant :
Il ne nous reste donc plus qu'à récupérer les coordonnées des objets détectés.
👨💻 Rapport d'avancement 7 (21/04/2023)
Suite à de nombreux problèmes avec le développement de l'IA, nous repartons sur l'alternative 1 qui nous permets de détecter un objet orange grâce à la caméra. Nous modifions le code vu plus haut pour dessiner un rectangle autour de l'objet détecté et de renvoyer les coordonnées (x,y) du coin supérieur gauche ainsi que de la largeur w et de la hauteur h du rectange :
while(cap.isOpened()): # Capture frame-by-frame ret, frame = cap.read() # Our operations on the frame come here hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) light_orange = (0,90,200) dark_orange = (18,180,255) #Creation of the mask to separate the orange object mask = cv2.inRange(hsv, light_orange, dark_orange) #Creation of the image by comparing the initial frame with the mask res = cv2.bitwise_and(frame,frame, mask= mask) contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: [x, y, w, h] = cv2.boundingRect(cnt) if w > 50: print("< x :",x,"| y :",y,"| w :",w,"| h :",h,">"); cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 0) # Display the resulting frame cv2.imshow('frame',frame) if cv2.waitKey(1) & 0xFF == ord('q'): break
⚡ Carte Driver Moteur
Etant donné qu'un des composants de la carte du robot d'origine à brulé suite à une erreur de câblage, nous avons reçu du groupe : Coupe de France de Robotique, une carte driver moteur qui sert à alimenter les moteurs en puissance car la carte Raspberry ne pourrait pas fournir à elle seule suffisamment de courant. La carte présentait un défaut de conception et grâce aux conseils avisés de son créateur (Albin Mouton), nous avons soudé une résistance entre les pins de deux autres résistances de la carte.
Nous avons ensuite mis les moteurs sur les pins de la carte en ayant serti les câbles au préalable.
Nous avons ensuite téléversé le code fourni par Albin Mouton sur la carte afin de la programmer par défaut (localisation des moteurs, valeurs des PIDs...) grâce au logiciel STM32 Cube IDE
🚗 Motorisation de Trashy
Maintenant que nous avons une carte de contrôle moteur, nous allons nous concentrer sur le contrôle des moteurs. Afin de contrôler les moteurs, la carte fonctionne en liaison série avec des requêtes G-Code :
Ainsi, nous pouvons définir la vitesse des moteurs avec la commande :
G0 X40 Y40 Z40 U40
Où les moteurs X,Y,Z et U sont définis comme sur cette photo :
Nous pouvons aussi lire la vitesse de rotations des moteurs avec la commande :
G10 X Y Z U
Cette commande renvoie la vitesse sous la forme :
G10 X48.529 Y35.588 Z38.824 U42.059
Maintenant que nous arrivons à faire fonctionner nos moteurs, nous allons programmer une fonction qui prend en paramètre la vitesse que l'on veut sur l'axe X, l'axe Y et l'axe Z et qui définit la vitesse de chaque moteur. Pour ceci, nous nous inspirons de ce document : https://research.ijcaonline.org/volume113/number3/pxc3901586.pdf et des formules ci-dessous :
Nous programmons donc la fonction ci-dessous :
def translation(Vx, Vy, Wz): """ Define the parameter for the G-Code to make a movement depending on the speed Vx, Vy, Wz :param Vx: (float) Speed on the X axis :param Vy: (float) Speed on the Y axis :param Wz: (float) Speed on the Z axis :return: A float-list of size 4 with each parameter for the G-Code [X, Y, Z, U] CU : None """ X = (1/R) * (Vx - Vy - (Lx + Ly)*Wz) Y = (1/R) * (Vx + Vy + (Lx + Ly)*Wz) Z = (1/R) * (Vx - Vy + (Lx + Ly)*Wz) U = (1/R) * (Vx + Vy - (Lx + Ly)*Wz) print("Entrée : < Vx =",Vx,"| Vy =",Vy,"| Wz =",Wz," >") print("Sortie : < X =",X,"| Y =",Y,"| Z =",Z,"| U =",U," >") return [X, Y, Z, U]
Avec R = 5.0, le rayon de la roue, Lx = 19.0, la largeur du robot et Ly = 27.0 la longueur du robot. Maintenant que nous avons convertit la vitesse voulu en la vitesse des moteurs, nous devons communiquer avec la carte de contrôle en liaison série. Pour cela, nous utilisons la librairie "Serial" de python. Nous initialisons d'abord la liaison série avec les lignes :
# Initialization of Motor_controller serial connection port_Moteur = "/dev/ttyACM0" baudrate_Moteur = 115200 moteur = serial.Serial(port_Moteur,baudrate_Moteur,timeout=1)
Nous pouvons ainsi écrire une fonction qui prend en paramètre un tableau avec les vitesses de chaque moteur et envoie sur la liaison série, la commande nécessaire :
def send_G_Code(w): command = "G0 X{:.3f} Y{:.3f} Z{:.3f} U{:.3f}\n".format(w[0],w[1],w[2],w[3]) print("Command sent :",command) moteur.write(command.encode('ascii'))
Nous faisons ensuite en sorte que le programme se lance avec l'entrée des vitesses voulu sur l'axe X,Y et Z et exécute le mouvement voulu :
nb_arg = len(sys.argv) if(nb_arg == 4): test = [] test = translation(int(sys.argv[1]),int(sys.argv[2]),int(sys.argv[3])) send_G_Code(test) else: print("Missing parameters")
Nous pouvons ainsi générer les mouvements de base du robot
🖨️ Impression 3D du convertisseur Roue/Moteur
Nous avons reçu de nouvelles roues pour créer notre robot, cependant, ces roues ne s'adaptent pas au moteur qui nous avait été fourni. Nous avons donc du créer une pièce en 3D afin de convertir la forme hexagonale en forme de cercle coupé qui s'adapte au moteur
Nous avons crée un trou afin de viser les adaptateurs aux roues, de plus, nous avons du coller au pisto-colle les adaptateurs aux moteurs afin qu'il n'y ai pas de jeu dans les mouvements des roues lors des déplacements
✂️ Découpe du plateau de la pince
Dans notre optique de réparer notre robot, nous nous sommes occupé de refaire le plateau qui supporte la pince. Nous avons donc commencé par la modélisé sur Fusion360 puis nous avons préparé le fichier pour l'envoyer à la découpeuse laser !
📎Liaison entre les fonctions
Liaison entre la RaspBerry PI 4 et la Pince
Pour la liaison entre la RaspBerry PI 4 et la Pince, nous allons passer par une liaison série. En effet, nous programmons l'arduino pour qu'elle enclenche le programme d'attraper l'objet seulement à la réception du message 'o' sur la liaison série :
void loop(){ if(Serial.available() > 0) { incomingByte = Serial.read(); Serial.print("I received :"); Serial.println((char) incomingByte); if((char) incomingByte == 'o'){ Serial.println("Je dois attraper"); attrape_object(); } delay(500); } }
Nous testons ensuite via la Raspberry si la liaison marche avec le script suivant :
import serial import time port = "/dev/ttyACM0" baudrate = 9600 arduino = serial.Serial(port,baudrate) print("Enter 1 to test :") x = input() print(x) if x=='1': print("On envoie") arduino.write(b'o') time.sleep(2) x = 7
Liaison entre l'IA et les moteurs
Dans la fonction de reconnaissance d'objet orange vu plus haut, nous faisons en sorte de localiser l'objet orange et de se déplacer pour que l'objet soit au centre de l'image. Nous faisons donc en sorte que la coordonnée X de l'objet soit dans l'intervalle [670;770]. Si la coordonnée X de l'objet est inférieur à 670, cela veut dire que notre robot est sur la droite de l'objet. On doit donc se déplacer sur la gauche pour se recentrer. Si la coordonnée X de l'objet est supérieur à 770, cela veut dire que notre robot est sur la gauche de l'objet. On doit donc se déplacer sur la droite. On défini bien sûr une vitesse de déplacement faible pour laisser le temps à la RaspBerry de faire l'acquisition de l'image et l'analyse :
for cnt in contours: [x, y, w, h] = cv2.boundingRect(cnt) if w > 50: print("< x :",x,"| y :",y,"| w :",w,"| h :",h,">"); cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 0) if x < x_min: # Must go left p = subprocess.run(["python3","motor_controller.py","0",str(Vy),"0"]) elif x > x_max: # Must go right p = subprocess.run(["python3","motor_controller.py","0",str(-Vy),"0"]) else: # Centered on x axis p = subprocess.run(["python3","motor_controller.py","0","0","0"]) print("We are centered")
Nous arrivons donc à faire en sorte que Trashy se centre devant la balle orange comme sur la vidéo exemple ci-dessous :
Afin de pouvoir mieux contrôler les mouvements de notre robot, nous allons asservir la position de la balle sur les coordonnées X,Y et sur sa largeur W :
Nous modifions ainsi le code :
if not((x > X_min) and (x < X_max)): Vx_consigne = (X_consigne - x) * P print("Vx_consigne :",Vx_consigne) action = subprocess.run(["python3",Motor,"0",str(Vx_consigne),"0"]) elif not((y > Y_min) and (y < Y_max)): Vy_consigne = (y - Y_consigne) * P print("Vy_consigne :",Vy_consigne) action = subprocess.run(["python3",Motor,str(Vy_consigne),"0","0",]) elif not((w > W_min) and (w < W_max)): Vw_consigne = (W_consigne - w) * P print("Vw_consigne :",Vw_consigne) action = subprocess.run(["python3",Motor,str(Vw_consigne),"0","0",]) else: print("Catch sequence") action = subprocess.run(["python3",Motor,"0","0","0"]) time.sleep(2) to_catch = True break
Pour notre asservissement, nous utilisons un correcteur proportionnelle P. Après différents test, nous paramétrons P = 0.15. De plus, nous modifions les valeurs des bornes min, max et consigne des 3 paramètres utilisé pour asservir. On obtient les résultats suivants :
Bilan
Dans cette partie, vous retrouverez nos rapports de projet pour chaque semestre :
- Semestre 6 : Fichier:Rapport Trashy S6.pdf
- Semestre 7 : Fichier:Rapport Trashy S7.pdf
- Semestre 8 : Fichier:Rapport de Projet - Trashy S8.pdf
Gestion de projet
Semestre 7
Lorsque nous avons commencé le semestre 7, nous avons fait le point sur les tâches à faire et nous les avons répartie entre nous et dans le temps. Ainsi, nous devions faire les tâches suivantes :
- Modélisation du bac de récupération :
* Prévu par : Gabriel * Fait par : Jason * Etat : Fini (au 28/12/2022)
- Déplacement de la pince :
* Prévu par : Gabriel * Fait par : Gabriel * Etat : Fini (au 28/12/2022)
- Capteur du bac :
* Prévu par : Vianney * Fait par : Vianney * Etat : Fini (au 28/12/2022)
- IA de reconnaissance de mégots :
* Prévu par : Jason * Fait par : Jason * Etat : En cours (au 28/12/2022)
- Contrôle moteur du robot :
* Prévu par : Vianney et Jason * Fait par : Vianney et Gabriel * Etat : En cours (au 28/12/2022)
- Liaison de tout les programmes :
* Prévu par : Tout le monde * Etat : Pas commencé (au 28/12/2022) * Raison : Retard sur l'IA et le contrôle moteur
- Préparation de la soutenance :
* Prévu par : Tout le monde * Fait par : Tout le monde * Etat : En cours (au 28/12/2022)
Semestre 8
Nous avons ensuite organisé le travail pour le semestre 8 en réalisant le diagramme de Gantt ci-contre. Ainsi, nous devions faire les tâches suivantes :
- Contrôle moteur :
* Prévu par : Gabriel + Vianney * Fait par : Gabriel + Vianney * Etat : Fini
- Liaison entre moteur et pince :
* Prévu par : Gabriel + Vianney * Fait par : Jason * Etat : Fini
- IA de reconnaissance de mégots :
* Prévu par : Jason * Fait par : Jason * Etat : Fini
- Liaison entre moteur et IA :
* Prévu par : Jason * Fait par : Jason * Etat : En cours
- Liaison de tout les programmes :
* Prévu par : Tout le monde * Etat : En cours
- Réglage des derniers paramètres :
* Prévu par : Tout le monde * Etat : * Raison :
- Préparation de la soutenance :
* Prévu par : Tout le monde * Fait par : * Etat :
Chronologie et rapport de scéances
Semestre S6
Mars
Mardi 1er Mars 2022 :
- Découverte du cahier des charges
- Définition du sujet
- Mis en place des outils pour faciliter le travail de groupe
- Commencement de la réalisation de l’analyse fonctionnelle (diagramme bête à corne et diagramme des interacteurs)
- Etude de marché
- Recherche des premières solutions
- Problématique : Faisabilité niveau loi (les normes à respecter/ ce qu’on peut faire et peut pas faire)
Mardi 8 Mars 2022 :
- Etude de faisabilité
- Commencement de l’étude d’opportunité (diagramme SWOT)
- Définition des premières idées pour la conception du robot
- Commencement de l’analyse des risques
- Problématique : Difficulté pour faire l’analyse des risques financiers
Mardi 15 Mars 2022 :
- Définir l’organisation du projet
- Cahier des charges fonctionnel/technique a définir
- Brainstorming sur l’analyse SWOT et la solution du robot suite à un entretien avec notre prof d’économie
- Finition de l’analyse SWOT
- Finition de l’étude d’opportunité
- Analyse des risques technologiques
Mardi 22 Mars 2022 :
- Commencement du cahier des charges fonctionnelles et techniques
- Diagramme fonctionnelle
- Recherche des premiers composants
- Définition des caractéristiques du robot
Mardi 29 Mars 2022 :
- Définitions des risques techniques et des solutions envisagés
- Dimensionnement des principaux moteurs
- Commencement des recherches pour la carte mère
- Recherche des premiers composants
- Recherche d'un modèle de diagramme de Gantt pour l’organisation du projet
Avril
Mardi 5 Avril 2022 :
- Commencement du diagramme Gantt
- Etude de marché
- Finitions des études en cours
Mardi 26 Avril 2022 :
- Commencement et avancée du diapo de présentation
- Commencement de l'écriture du script de la vidéo de présentation
- Concertation en groupe sur le design et l'aspect technique du robot
Mai
Mardi 3 Mai 2022 :
- Commencement de la carte mère du robot
- Poursuite de l’écriture de la vidéo “Ma thèse en 180s”
- Finalisation des derniers détails pour certaines parties
- Création d’un doc sur les questions fréquemment posées (jury)
- Choix d’un logo
- Design sur papier du robot avec mesures
Semestre S7
Octobre
Lundi 10 octobre 2022: Personnes présentes : Gabriel - Vianney - Jason
- Mise en place du wiki
- Réception du robot et des pinces sur lequel nous allons travailler
- Organisation du Semestre et des tâches à faire
Vendredi 21 octobre 2022: Personnes présentes : Gabriel - Vianney - Jason
- Commencement de l'algorithme de détection d'objet
- Recherche de solution pour optimiser la détection d'objet
- Construction et débat sur le bac de remplissage
Vendredi 28 octobre 2022: Personnes présentes : Gabriel - Vianney - Jason
- Modélisation du support pour la pince et le bac de remplissage
- Lancement de son impression
- Programmation des mouvements de la pince
Novembre
Vendredi 18 Novembre 2022: Personnes présentes : Gabriel - Vianney - Jason
- Réparation des servomoteurs de la pince
- Finition de l'algorithme de contrôle de la pince
- Recherche sur l'IA
- Installation du laser et de la photo résistance
Lundi 21 Novembre 2022: Personnes présentes : Gabriel - Vianney - Jason
- Finition sur l'algorithme de contrôle de pince
- Commencent des programmes de contrôle moteur
- Continuation sur l'IA
- Création d'un Git pour le projet
Vendredi 25 Novembre 2022: Personnes présentes : Gabriel - Vianney - Jason
- Continuation des programmes de contrôle moteur
- Contrôle de la Raspberry
- Continuation sur l'IA
Lundi 28 Novembre 2022: Personnes présentes : Gabriel - Vianney - Jason
- Schéma d'interconnexion et des tâches de chaque module du robot
- Mise à jour du Wiki et du Git
- Connexion de la Raspberry
Décembre
Jeudi 8 Décembre 2022: Personnes présentes : Gabriel - Vianney - Jason
- Programmation et gestion des capteurs
- Paramétrage des Raspberry
- Collecte de mégot de cigarette pour la base d'image pour l'IA
- Commencement des soutenances et présentation
Jeudi 15 Décembre 2022: Personnes présentes : Gabriel - Vianney - Jason(depuis chez lui)
- Programmation de l'IA
- Téléchargements et appropriations des librairies
- Démarrage de l'algorithme de commande des moteurs
Semestre S8
Janvier
Mercredi 18 Janvier 2023: Personnes présentes : Gabriel - Vianney - Jason
- Reprogrammation de l'algorithme de la pince
- Continuation sur le contrôle moteur
- Problème rencontré : Le robot a cramé
Mercredi 25 Janvier 2023: Personnes présentes : Gabriel - Vianney - Jason
- Connexion de la Nvidia à Internet
- Changement de la carte pour le robot
- Réparation de la carte
Février
Mercredi 1 Février 2023: Personnes présentes : Gabriel - Vianney - Jason
- Adaptation des moteurs disponibles à la carte de contrôle moteur prêtée par Albin
- Routage d'une carte de remplacement pour le robot
- Continuation sur la partie Software du contrôle moteur (Raspberry et ROS)
Mercredi 8 Février 2023: Personnes présentes : Gabriel - Vianney - Jason (de chez lui)
- Continuation de l'IA
- Programmation d'alternative à l'IA
- Découverte d'un blog contenant un dataset pour une IA de reconnaissance de mégot de cigarette
- Mise en place des moteurs sur la carte d'Albin
- Remontage du robot d'origine
- Installation d'un nouvel OS sur la Raspberry
- Installation de ROS 2
Mercredi 15 Février 2023: Personnes présentes : Gabriel - Vianney - Jason
- Dernière mise en place des moteurs sur la carte drive
- Installation du programme sur le STM32
- Continuation de l'installation de ROS
- Création du nœud des roues Mécanum
- Programmation de l'IA
- Débuggage du code de l'IA
- Recherche bibliographique
- Schématisation de Trashy MKII
Mars
Mercredi 1 Mars 2023: Personnes présentes : Gabriel - Vianney - Jason
- Recommencement de l'IA avec Tensorflow
- Lancement d'un entrainement d'IA de reconnaissance de fleurs pour s'entrainer
- Commencement des modifications pour la reconnaissance des mégots de cigarette
- Création d'un modèle 3D pour relier la roue au moteur sous fusion 360
- Perçage de trous sur le support en métal
- Création d'un support en plexiglass pour la pince
- Création du code de la pince sous ROS
Mercredi 8 Mars 2023: Personnes présentes : Gabriel - Vianney - Jason
- Entrainement de l'IA
- Impression des adaptateurs pour les roues
- Continuation du support en plexi pour la pince
- Continuation du code des roues sous ROS
Mercredi 15 Mars 2023: Personnes présentes : Gabriel - Vianney - Jason
- Découpe de la plaque en plexi
- Bataille avec l'IA
- Resize des images
- Code pour faire avancer le robot
Avril
Vacance d'Avril:
- Etablissement de la liaison série entre la Raspbery et la pince
- Fix des bugs du code de détection d'objet orange
- Contrôle moteur par liaison série
- Résultat : Le robot se centre sur l'axe x de l'objet
Bibliographie et documentation
Chaine Youtube Nvidia Developper :
- Real-Time Object Detection in 10 Lines of Python Code on Jetson Nano : https://www.youtube.com/watch?v=bcM5AQSAzUY
- Jetson AI Fundamentals Playlist : https://www.youtube.com/playlist?list=PL5B692fm6--uQRRDTPsJDp4o0xbzkoyf8
- Training our own model : https://www.youtube.com/watch?v=sN6aT9TpltU&list=PL5B692fm6--uQRRDTPsJDp4o0xbzkoyf8&index=11 (19:07)
Tutoriel Tensorflow :
- Enregistrer et charger des modèles : https://www.tensorflow.org/js/guide/save_load?hl=fr
- Formation personnalisée : procédure pas à pas : https://www.tensorflow.org/tutorials/customization/custom_training_walkthrough?hl=fr
- How to Train Your Own Object Detector Using TensorFlow Object Detection API : https://neptune.ai/blog/how-to-train-your-own-object-detector-using-tensorflow-object-detection-api
- Custom Object Detection using TensorFlow from Scratch : https://towardsdatascience.com/custom-object-detection-using-tensorflow-from-scratch-e61da2e10087
- Documentation Tensorflow : https://www.tensorflow.org/api_docs
- Introduction à Tensorflow : https://www.tensorflow.org/learn?hl=fr
- Image Recognition using TensorFlow : https://www.tutorialspoint.com/tensorflow/image_recognition_using_tensorflow.htm#
- Image Recognition using TensorFlow : https://www.geeksforgeeks.org/image-recognition-using-tensorflow/
- Former et servir un modèle TensorFlow avec TensorFlow Serving : https://www.tensorflow.org/tfx/tutorials/serving/rest_simple?hl=fr
Dépôt Git utilisé :
- Jetson Inference : https://github.com/dusty-nv/jetson-inference
- Mask RCNN : https://github.com/matterport/Mask_RCNN
- Raspberry : https://github.com/3sigma?tab=repositories
- Git du projet : https://gitlab.com/project-trashy/trashy
Divers documentations utilisées :
- Cigarette butt dataset & trained weights : https://www.immersivelimit.com/datasets/cigarette-butts
- Using Mask R-CNN with a Custom COCO-like Dataset : https://www.immersivelimit.com/tutorials/using-mask-r-cnn-on-custom-coco-like-dataset
- Documentation ROS : https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debians.html
- Wiki du projet Coupe de France de Robotique : https://projets-ima.plil.fr/mediawiki/index.php/IMA3/IMA4_2021/2023_P16
- Building an object detector in TensorFlow using bounding-box regression : https://medium.com/nerd-for-tech/building-an-object-detector-in-tensorflow-using-bounding-box-regression-2bc13992973f
- Kinematic Model of a Four Mecanum Wheeled Mobile Robot : https://research.ijcaonline.org/volume113/number3/pxc3901586.pdf
Dataset :
- Cigarette butt : https://www.kaggle.com/datasets/estebanpacanchique/cigarette-butt?resource=download
- Dead leaf : https://www.kaggle.com/datasets/gunarakulan/cashewleavesdataset
Code
Code Arduino de la pince :
#include <Servo.h> int angle = 0; int step_time = 15; int incomingByte = 0; Servo base, claw, up_down, front_back; void setup() { up_down.attach(7); base.attach(8); claw.attach(6); front_back.attach(9); init_servo(); Serial.begin(9600); } void init_servo(){ up_down.write(0); base.write(0); claw.write(0); front_back.write(0); } void to_move(Servo obj, int start_angle, int stop_angle){ /* Ce programme permet de contrôler les servo-moteurs de la pince de Trashy * * Liste des paramètres : * :Servo obj: Servo-moteur à actionner * :int start_angle: Angle de départ * :int stop_angle: Angle d'arrivée * * CU : Avoir les servo-moteurs branché aux bons ports * claw prend des angles de 0 à 95° * Les autres de 0 à 180° * * Exemple * to_move(claw,0,95); pour fermer la pince * to_move(claw,95,0); pour ouvrir la pince * to_move(front_back,180,0); pour reculer la pince * to_move(front_back,0,180); pour avancer la pince * to_move(up_down,180,0); pour baisser la pince * to_move(up_down,0,180); pour monter la pince * to_move(base,0,180); pour emmener la pince vers le depos * to_move(base,180,0); pour ramener la pince devant */ if(start_angle < stop_angle){ for(angle = start_angle; angle < stop_angle; angle += 1){ obj.write(angle); delay(step_time); } }else{ for(angle = start_angle; angle >= stop_angle; angle -= 1){ obj.write(angle); delay(step_time); } } delay(100); } void attrape_object(){ to_move(claw,95,0); delay(1000); to_move(front_back,0,180); delay(1000); to_move(claw,0,95); delay(1000); to_move(front_back,180,0); delay(1000); } void loop(){ if(Serial.available() > 0) { incomingByte = Serial.read(); Serial.print("I received :"); Serial.println((char) incomingByte); if((char) incomingByte == 'o'){ Serial.println("Je dois attraper"); attrape_object(); } delay(500); } }
Motor_controller.py :
import serial import sys import time # Define Variable R = 5.0 Lx = 19.0 Ly = 27.0 # Initialization of Motor_controller serial connection port_Moteur = "/dev/ttyACM0" baudrate_Moteur = 115200 moteur = serial.Serial(port_Moteur,baudrate_Moteur,timeout=1) # Function of movement def translation(Vx, Vy, Wz): """ Define the parameter for the G-Code to make a movement depending on the speed Vx, Vy, Wz :param Vx: (float) Speed on the X axis :param Vy: (float) Speed on the Y axis :param Wz: (float) Speed on the Z axis :return: A float-list of size 4 with each parameter for the G-Code [X, Y, Z, U] CU : None """ X = (1/R) * (Vx - Vy - (Lx + Ly)*Wz) Y = (1/R) * (Vx + Vy + (Lx + Ly)*Wz) Z = (1/R) * (Vx - Vy + (Lx + Ly)*Wz) U = (1/R) * (Vx + Vy - (Lx + Ly)*Wz) print("Entrée : < Vx =",Vx,"| Vy =",Vy,"| Wz =",Wz," >") print("Sortie : < X =",X,"| Y =",Y,"| Z =",Z,"| U =",U," >") return [X, Y, Z, U] def send_G_Code(w): command = "G0 X{:.3f} Y{:.3f} Z{:.3f} U{:.3f}\n".format(w[0],w[1],w[2],w[3]) print("Command sent :",command) moteur.write(command.encode('ascii')) nb_arg = len(sys.argv) if(nb_arg == 4): vitesse = [] vitesse = translation(float(sys.argv[1]),float(sys.argv[2]),float(sys.argv[3])) send_G_Code(vitesse) else: print("Missing parameters")
Object_detection.py :
# capture.py import numpy as np import cv2 import subprocess import serial import time # Capture video from camera cap = cv2.VideoCapture(0) # Variable X_consigne = 300 X_min = 220 X_max = 380 Y_consigne = 370 Y_min = 270 Y_max = 470 W_consigne = 670 W_min = 570 W_max = 770 P = 0.15 to_catch = False Motor = "Motors_code/motor_controller.py" port_Arduino = "/dev/ttyACM1" baudrate_Arduino = 9600 Pince = serial.Serial(port_Arduino,baudrate_Arduino,timeout=1) while(cap.isOpened()): ret, frame = cap.read() hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) light_orange = (0,90,200) dark_orange = (18,180,255) mask = cv2.inRange(hsv, light_orange, dark_orange) res = cv2.bitwise_and(frame,frame, mask= mask) contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: [x, y, w, h] = cv2.boundingRect(cnt) if w > 50: print("< x :",x,"| y :",y,"| w :",w,"| h :",h,">"); cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 0) if not((x > X_min) and (x < X_max)): Vx_consigne = (X_consigne - x) * P print("Vx_consigne :",Vx_consigne) action = subprocess.run(["python3",Motor,"0",str(Vx_consigne),"0"]) elif not((y > Y_min) and (y < Y_max)): Vy_consigne = (y - Y_consigne) * P print("Vy_consigne :",Vy_consigne) action = subprocess.run(["python3",Motor,str(Vy_consigne),"0","0",]) elif not((w > W_min) and (w < W_max)): Vw_consigne = (W_consigne - w) * P print("Vw_consigne :",Vw_consigne) action = subprocess.run(["python3",Motor,str(Vw_consigne),"0","0",]) else: print("Catch sequence") action = subprocess.run(["python3",Motor,"0","0","0"]) time.sleep(2) to_catch = True break if to_catch == True: print("Ici") Pince.write(b'o') time.sleep(2) Pince.write(b'f') time.sleep(2) action = subprocess.run(["python3",Motor,"0","0","0"]) to_catch = False break cv2.imshow('frame',frame) if cv2.waitKey(1) & 0xFF == ord('q'): action = subprocess.run(["python3",Motor,"0","0","0"]) break # When everything done, release the capture cap.release() cv2.destroyAllWindows()