IMA3/IMA4 2021/2023 P6

De Wiki de Projets IMA
LogoTrashy.png

Présentation du projet

Trashy.png

🌍 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

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 :

  • 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

Pinout pince.png

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) :

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) :

Trashy alternative1.jpg
Trashy alternative1 essai2.jpg
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 :

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 :
Trashy resIA1.png

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.

L'arborescence du dossier de notre IA ressemble ainsi à ça :
Trashy arbo.png
Nous avons donc lancer notre premier test et entraînement de notre IA et nous obtenons le graphique ci-dessous :
Trashy res IA.png

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")
   

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 :

Test IA resultat cig.png
Test IA resultat leaf.png


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
Trashy reconnaissance.png

⚡ 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.

Souduredriver.jpg

Nous avons ensuite mis les moteurs sur les pins de la carte en ayant serti les câbles au préalable.

Cartedrivermoteur.jpg

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

Stm32cube.jpg
Sondest.jpg
Sondest2.jpg

🚗 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 :

TableauGCode.png

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 :

Trashy moteur.jpg

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 :

Trashy formule.png

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

Convertisseurtrashy.png
Rouereelle.jpg

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 !

Decoupeplateau.png
Inkscapeplaque.png


📎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 :

Trashy Asservissement.png

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 :

Gestion de projet

Semestre 7

Diagramme de Gantt prévisionnelle du 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

Diagramme de Gantt prévisionnelle du 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 :




Tutoriel Tensorflow :




Dépôt Git utilisé :




Divers documentations utilisées :




Dataset :


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()