Projet 4A - Repérage par camera

Le but du projet

Le but du projet est de concevoir un système qui utilise les tags ArUco pour améliorer la performance de notre équipe.

ArUco est une librairie OpenSource qui permet la détection des marqueurs rectangulaires dans une image. Le projet est développé à l'Université de Cordoba en Espagne. Il s’agit des librairies des marqueurs (tags) et des algorithmes qui permettent leur détection. En suite d'une procédure d'étalonnage l’estimation précise de la position des tags et de la caméra qui les détecte est possible.

Suivant le règlement de la coupe de robotique de France, tous les robots verront attribuer un marqueur, qui est placé au sommet du support de balises.

Ces marqueurs permettent à un système de vision présent sur le mât central d’identifier et de localiser chaque robot.

Le but est de créer ce système de vision pour identifier le positionnement de chaque robot, et un réseau pour permettre la communication entre robots et système de repérage. Cette information peut être utilisée pour mieux synchroniser les deux robots à notre disposition, leur permettant de mieux collaborer, et pour éviter des collisions avec des robots adversaires.

Le système peut aussi être utilisé pour identifier des éléments de jeu et changer les actions des robots. Par exemple, cette année, le terrain de jeu dispose une girouette (élément 3) qui s’arrête de tourner après le début du match pour indiquer dans quel sens souffle le vent. La direction du vent (nord ou sud), indique l’emplacement d'arrêt des robots à la fin du match (éléments 1&2) ainsi que le positionnement des certains éléments du jeu. La girouette est disposée d’un tag ARUCO.

Notre plan est de placer une caméra sur un de nos robots, capter l’angle d'arrêt de la girouette et transmettre l’information à l'autre robot afin d'exécuter les correctes actions. Le fait d’avoir cette information a notre disposition va nous permettre de gagner de points.

Participants

Prénom & Nom Filière
Errikos Messara IESE-4
Pedro Lopes IESE-4

Tuteur: Sylvain Toru

Cahier des charges

  • Développer un système capable de calculer la position et la rotation relative des marqueurs ArUco par rapport à la caméra de détection. Utile par exemple sur la détection de la direction du vent de l’édition « 2019/2020 Sail the world ».
  • Détecter la position des robots (qui disposent d’un marqueur ArUco) sur l’aire de jeu, avec une meilleure précision que le projet 4A de l’année 2018-2019, qui utilise des balises de positionnement avec des émetteurs et détecteurs ultrasons. Performance de l’ancien système :
Résolution
Spatiale ###
Temporelle ###
  • Créer un système de communication entre robots et systèmes de commande (superviseurs/systèmes de repérage). Ce réseau doit être flexible, capable de supporter nos outils actuelles (STM32/Python) et surtout fiable.

Architecture du système

Travail réalisé

Le choix du matériel

Le système utilisé le langage Python et la librairie OpenCV et il est principalement composé d’un ordinateur Raspberry Pi et d’une caméra.

Nous avons choisi le Raspberry car ça petite taille et faible consommation d’énergie le rendre idéal pour les applications embarquées. Nous avons utilisé la version la plus performante, le Raspberry Pi 4 4GB. Le Raspberry est équipé d’un processeur ARM quadricoeur de Broadcom, d’un module WiFi dont nous utilisons pour la communication, et d’une interface CSI utilisée pour connecter une caméra.

La caméra du dispositif de repérage centrale, comme elle est positionnée à 1m du terrain et le terrain fait 3m de longueur et 2m de largeur, doit avoir un champ de vue assez large. Plus précisément, nous avons calculé le champ de vue nécessaire pour capter tout le terrain en prenant en compte la hauteur des robots avec leur balises:

Côté Angle minimale
Longueur (3m) 136,4°
Largeur (2m) 73,3°

D’après ce calcul nous avons choisi une caméra avec un champ de vue de 160°. Cela nous donne un peu de marge pour pouvoir capter les marqueurs qui sont placés sur les robots. De plus le capteur de la caméra ne produit pas des images en plein champ de vue en mode vidéo, donc nous avons besoin de marge complémentaire pour pouvoir visualiser tout le terrain. Malheureusement, cet objectif grand angle, produit une image aux fortes distorsions, dont nous devons prendre un compte plus tard. Une autre caractéristique perturbante des objectifs grand angle de est le fait que les coins des images peuvent apparaître floues même si la mise au point est bien réglée.

Nous avons placé le Raspberry dans un boîtier en aluminium qui le protège et qui dispose d’un ventilateur pour refroidire le processeur. Cela nous permet d'augmenter la fréquence maximale du processeur de 1,5 à 2,0 GHz, et gagner en temps de calcul.

Nous alimentons la Raspberry par son port USB C. D’après le constructeur, l’alimentation doit être capable de fournir 3A, pour supporter la carte et les périphériques connectés à elle. Pour mesurer la consommation d’énergie nous avons utilisé un multimètre USB, capable de mesurer la tension et le courant consommé d’une appareille connectée. Suite à nos tests, même avec l’overclock, la carte du système de repérage ne consomme jamais plus que 1,5A (1,38A maxi observé). Cela, peut venir du fait que nous connectons aucun périphérique. Ainsi, le courant maximale de l’alimentation n’est pas sa caractéristique la plus importante, comme la majorité des alims modernes USB peuvent fournir >2A.

Par contre, nous avons observé que c’était très important d’utilisé une source capable de tenir sa tension vraiment à 5V, c’est à dire avec une faible résistance interne (câble inclus). En fait, même un chargeur qui descend jusqu’à 4,45 V est compatible avec la norme USB 3.0 (tension minimale pas précisée pour les normes USB-C et USB PD). La plupart des chargeurs USB sont fait pour charger des batteries lithium, qui ont une tension nominale de 3,7V, donc la tension du chargeur sur charge peut descendre un peu, sans poser beaucoup des problèmes.

Néanmoins, un ordinateur comme le Raspberry, a des besoins plus spécifiques à termes de tension. Plus précisément, quand la tension descend au-dessous de 4,8V à l’entrée de la Raspberry, un mode de protection est activé et la fréquence du processeur diminue à 600-800Mhz. Alors, c’est extrêmement important d’utiliser une source d’alimentation capable de tenir sa tension au-dessus de ce seuil pour le courant souhaité.

Les Raspberries des robots vont être alimentés par leur cartes d’alimentation et celle du dispositif de repérage par une batterie USB. Nous avons choisi d’utiliser une solution commerciale, au lieu de créer notre propre batterie avec des cellules et un contrôleur pour des raisons pratiques et économiques. La batterie dispose d’une capacite de 55,5Wh, deux sorties de 2.4A, d’un indicateur de charge et elle peut être chargée facilement pendent la coupe avec un chargeur USB. Mais surtout, la batterie est capable de fournir le courant nécessaire sans une forte une forte baisse de tension. La tension a été mesurée directement sur les pins de la Raspberry avec un multimètre et elle faisait >4,8V.

Capture des images

La librairie picamera nous permet de recevoir des images et de définir des paramètres comme la résolution, le nombre des images par seconde, la sensibilité ISO, etc. Nous avons choisi les paramètres suivantes :

  • Résolution : 1280x960 px, compromis entre la précision et le temps de calcul
  • Images par seconde (fps) : 30 fps, la valeur minimale supportée par la caméra, vu que la Raspberry n’est pas capable d’analyse plus que ≈10 fps
  • ISO : 800, pour forcer la caméra à utiliser une courte durée d’exposition qui produit des images plus nettes et rendre la détection des tags en mouvement plus fiable. Le désavantage de ce réglage est l'augmentation du bruit.

Nous analysons l'efficacité de ce réglage dans la partie “Évaluation de la performance”

La librairie produit des matrices numpy de taille 1280x960x3 qui représente l’image capté, contenant l'information pour chaque pixel. A ce point, nous pouvons créer une boucle qui effectue les opérations de capture et de traitement en série.

Pour mieux gérer le flux des images générées par la caméra nous avons créer la classe python PiVideoStream. Cette classe, à l’aide de la librairie threading de python, permet à notre programme de paralléliser les tâches de la capture et du traitement des images. Cette technique de parallélisation a doublé la performance du système (~4fps à ~8fps)

Calibrage de la caméra

Détection et estimation de la pose des marqueurs

Détection de l'angle de la girouette

Traitement des données

A l’aide des fonctions utilisées pour l’estimation de la pose des marqueurs nous obtenons deux vecteurs qui décrivent leur translation (tvec) et leur rotation (rvec), par rapport à un système des coordonnées. Ce systeme de coordonnees a comme origine le centre de l’objectif de la caméra (modèle de caméra sténopé), les axes xy selon le plan de la caméra et l’axe z sortant de l'objectif. Ce système de coordonnées serait parfaitement adapté si nous souhaitons de mesurer la rotation des tags ou leur distance par rapport à la caméra, mais notre objectif est de repérer les robots sur leur terrain. Ainsi, le système de coordonnées le plus adapté aurait son plan xy coïncident avec le terrain de jeu, son axe z normal à ce plan et son origine au point (0,0) du terrain. Nous arrivons du coup au système exact cité dans le règlement de la coupe, utilisé pour repérer tous les objets du terrain.

Pour effectuer ce changement de base, il suffit de trouver la matrice de passage de la base de la caméra sur la basse du terrain. Pour construire cette matrice de passage nous pouvons utiliser le marqueur numéro 42, situé sur le terrain de jeu, comme référence.

Au début du programme nous construisons la matrice de passage vers le système de coordonnées du tag no 42, suivant le méthode suivante:

  1. Nous effectuons une suite des mesures de la position du marqueur de référence au et nous calculons la moyenne des vecteurs tvec et rvec. Cette partie de la procédure de calibrage et faite en boucle et les mesures sont moyennées pour améliorer sa précision. La qualité de la matrice produite va directement influencer la précision de toutes
  2. En utilisant ces vecteurs tvec et rvec moyennés, nous construisons la matrice 4x4 de passage à l’aide de la fonction cv2.Rodrigues().
  1. Cette matrice décrit le passage de la base du tag de calibration vers la base de la caméra. Pour obtenir la transformation inverse, il suffit de calculer la matrice inverse.
  2. Finalement, pour obtenir les coordonnées d’un autre marqueur dans la nouvelle base nous multiplions son vecteur de translation tvec, avec la matrice de passage.

Le réseau

Pour bien transmettre les coordonnées de tous les robots en jeu à nos deux robots, nous dévions établir une réseau de communication capable de supporter au moins deux clients, qui ces deux clients puissent se connecter à n'import quelle moment, et que tant le serveur comme les robots soient capables de supporter des éventuelles erreurs pendant la communication de forme qu'ils n’arrêtent pas son exécution. Pour la création de cette réseau, nous utilisons la librairie socket de Python, qui nous permet de créer les voies de communication entre le système de repérage (serveur) et les deux robots (clients). Nous créons donc des sockets de la famille d'adresse IPv4 du type SOCKET_STREAM pour le protocole TCP. La transmission des données via ce voie se fait avec des bytes. Python nous offre quelques options pour convertir des entiers et strings en bytes avec des simples fonctions, mais pour convertir des autres types d'objets, comme dans notre cas des matrices numpy, on besoin de la librairie pcikle. Pickle est capable de sérialiser n'import quel type d'objet sous forme binaire. Avec cette sérialisation, nous pouvons désormais envoyer notre table de coordonnées facilement avec les sockets. Pour la gestion de nos deux clients, on utilise la librairie select. Grâce à cette librairie, on peut manipuler le flux entrée/sortie pour tous les clients qui sont connectés. Après cette bref description des librairies nécessaires, on part sur l'architecture de cet système de communication.

En commençant par le serveur, nous devons d'abord lui associer une adresse IP et un numéro de port, qui seront utilisés par les clients pour se connecter. Après cela, nous avons mis le serveur en attente de nouvelles demandes de connexion. Le serveur étant prêt, il gère les demandes de connexion et les clients déjà connectés avec select.select. Si on a une nouvelle client, on accepte sa connexion et reçoit son adresse et son identifiant. Dés que les clients sont connectes, le serveur reçoit l’information de l'orientation du vent du robot 1, ajoute cette information au tableau de coordonnées, et il repasse cette information vers les deux robots. Bien que cela n'apparaisse pas dans le diagramme, le serveur gère la déconnexion d'un client en cas d'erreur dans ce processus. La gestion d'erreur c'est important, parce que si quelque arrive au robot pendant le jeu, cela peut cause des erreurs d’exécution du programme, et faire que le serveur tombe. Évidemment que nous ne voulons pas que cela se passe, donc, dès que le serveur percevoir une erreur soit dans la connexion, envoi ou réception des messages, etc, il ferme la connexion avec le robot en question et continue son fonctionnement normalement.

Du côte client, le procédure de création du socket c'est similaire à celui du serveur. Mais, au lieu de l'associer à un adresse, on utilise socket.connnect pour envoyer une demande de connexion, avec son propre adresse et son identifiant. Dés connecte, le Robo 1 commence a envoyer l'orientation du vent au système de repérage, et après, il reçoit le nouveau tableau de coordonnées. Par contre, le Robo 2 ne fait que recevoir les données du système. Pour la gestion d'erreurs des robots, nous utilisons aussi la librairie errNO, qui nous permet de gérer des erreurs d'entrée / sortie quand du système d'exploitation quand il n'y a plus de messages à recevoir du serveur.

Support mécanique

 
Modèle 3d de la boite conceptualisée pour supporter la caméra, la Raspberry Pi et une batterie usb

En suivant les contraintes d’homologation nous avons modélisé une boîte pour contenir Raspberry, la caméra et la batterie du dispositif de repérage central. Nous avons utilisé le maximum de volume autorisé pour pouvoir placer la caméra le plus haut possible pour maximiser le champ de vue et minimiser la distorsion de la caméra. La caméra est mis en angle de 130° pour cibler le centre du terrain.

Afin d’assurer que le système soit homologable nous l’avons modélisé avec la plateforme pour vérifier ces dimensions et sa compatibilité. Nous allons probablement imprimer la boîte en utilisant une imprimante 3d et en suite nous allons ajouter un boulon de 8mm et un écrou papillon, pour fixer la boîte sur la plateforme.

Evaluation de la performance

En finissant le projet, nous pensons que c’est important d’essayer le mesurer la précision du système, pour évaluer si sa performance est satisfaisante par rapport au cahier des charges. Donc, dans cette partie du projet nous vous présentons notre méthodologie pour mesurer la précision du système de repérage en position et en temps ainsi que son autonomie.

Résolution spatiale

Il faut bien valider le méthode d'estimation de la position des tags Arucos sur le terrain de jeu. Des erreurs de l'estimation peuvent causer des collisions entres le robots et conséquemment notre déclassification, ou une perte de points pendant que les robots se gare. Donc on veut une estimation précise, avec un erreur bas par rapport à la vrai position des tags

Méthodologie

On mesure sur un terrain en échelle 1:5, le même utilisé pour la calibration, la position estimée d'un tag Aruco par le système. Ensuite, on le compare avec la position réel dans notre terrain, et nous répétons cet processus plusieurs fois, en différents positions du terrain.

Résultats

En faisant la moyenne, nous arrivons a une valeur d'erreur dans notre terrain de jeu. Mais comme il est en échelle 1:5, il faut le multiplier par 5 pour arriver au erreur qu'on aurait dans le vraie terrain de jeu, pendant la compétition.

Nous avons comme résultat une erreur en x de: Δx = et en y : Δy = Il faut noter que, sans la matrice de passage nous avions une erreur d'estimation d'environ 10 cm dans ces axis, et donc notre estimation n'était pas de tout fiable. Cela montre l'impact de la différence entre la caméra et le terrain de jeu, et l'importance de la matrice de passage.

Résolution temporelle

En ce qui concerne la résolution temporelle nous avons deux grandeurs à évaluer : la fréquence de rafraîchissement et le délai de détection/transmission.

Méthodologie

Pour mesurer la fréquence de rafraîchissement nous avons utilisé le module video.FPS de la librairie imutils. En appelant la méthode fps.update() à chaque tour de la boucle principale, nous pouvons obtenir la valeur moyenne d’images par seconde (fps) pendant l’exécution du programme.

Généralement, la fréquence est fortement influencée par le nombre d’opérations exécutées dans la boucle principale, alors c’est important de réduire sa charge en faisant d’autres opérations en parallèle. Comme nous avons fait pour la capture d’images avec la classe PiVideoCapture().

Pour mesurer le délai entre la détection des tags, le traitement des données par le serveur (système de repérage) et la transmission vers un client (robot) nous avons utilisé le montage suivant : Premièrement nous dirigeons la caméra vers un écran (machine 1). Sur cette machine nous lançons un diaporama qui passe d’une diapo vide à une diapo avec un tag, ensuite d’un appui d’un buton. A côté, nous lançons sur une deuxième machine un programme client qui affiche dans un terminal les coordonnes envoyés par le serveur. Ensuite nous filmons les deux écrans avec un smartphone en mode slowmotion (240 fps).

Le but est de mesurer le temps entre l’apparition du marquer sur l’écran de la première machine et l’apparition des coordonnées sur l’autre, en comptant le nombre d’images entre les deux évènements. De cette manière nous obtenons le délai total entre la détection/traitement et la transmission. Le serveur tournait sur la Raspberry, le client sur un laptop et les deux dispositifs étaient connectées à un point d’accès WiFi 5GHz dans la même pièce.

Nous avons effectué la mesure plusieurs fois pour déduire sa valeur moyenne et son incertitude (par son écart type).

Résultats

f = 7 fps
Δt = 465 ± 22 ms

Dans cette phase de test, ce délai peut être acceptable. Cependant, c'est quelque chose à garder à l'esprit, nous devons l'améliorer pour avoir une communication entre système de repérage et les robots, plus efficace. Vu que ce test a été fait avec un laptop comme client, nous attendons que le résultat des tests délai avec les robots soient encore plus hauts, parce que le programme du client va y avoir toute la partie de commande du robot aussi. Cette délai peut nous causer des éventuelles pertes de points pendant la coupe.

Temps d'autonomie

Méthodologie

Nous avons mesuré la puissance consommée par la Raspberry à l’aide d’un multimètre USB, pendant l’exécution continuée du programme principal (charge maximale). Ensuite nous calculons le temps d’autonomie en utilisant l’énergie indiquée de la batterie. Nous estimons aussi l’efficacité du convertisseur de la batterie à 85% (estimation conservatrice).

Résultats

Consommation : 7 W maxi
Autonomie éstimée (batterie 55,5 Wh) : 55,5*0,85/7 = 6,73 h

En général

Globalement le système et plus performant par rapport à l’ancienne solution en termes de fréquence de rafraîchissement, comparable en erreur de position, mais il est probablement moins efficace en termes de délai (nous n’avons pas des données pour le système ultrasons). Il reste à voir si sa performance est suffisante en faisant des tests en conditions réelles.

Améliorations possibles

Nous devions principalement diminuer le délai. C'est probable qu'une inefficacité dans la communication serveur/client cause un délai, donc il faut optimiser cette partie là. Nous pouvons aussi essayer de mettre des autres opérations en parallèle comme la classe PiVideoCapture, cela peut relâcher l’exécution de la boucle principale du programme.

Mode d'emploi

Installation par image disque (recommandé)

Une image de l’OS avec la librairie OpenCV précompilée peut être trouvée sur le drive du club ici. Pour flasher l’image :

  1. Dézipper
  2. Flasher sur une carte SD (au moins 16 GB) en utilisant un outil comme le "Raspberry Pi Imager" (option "CHOISISSEZ L’OS" > "Utiliser image personnalisée")

Installation manuelle

  1. Mise à jour du système d’exploitation et des paquets installés:
    sudo apt update, sudo apt upgrade
  2. Recompiler et installer la plus récente version de OpenCV et ses depandances avec des options d’optimisation (procédure longue >1h). Guide à suivre
    1. Pour activer des options de multithreading il faut ajouter les flags WITH_TBB=ON, WITH_OPENMP=ON, pendant la génération du makefile. Ces flags de compilation permettent à OpenCV de fonctionner en multithreading. De plus, les flags ENABLE_NEON=ON, ENABLE_VFPV3=ON, forcent la librairie d’utiliser d’instructions spécifiques à l'architecture ARM, qui en conséquence améliorent la performance.
      Liste des flags complète :
cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
    -D ENABLE_NEON=ON \
    -D ENABLE_VFPV3=ON \
    -D BUILD_TESTS=OFF \
    -D INSTALL_PYTHON_EXAMPLES=OFF \
    -D OPENCV_ENABLE_NONFREE=ON \
    -D CMAKE_SHARED_LINKER_FLAGS=-latomic \
    -D WITH_TBB=ON \
    -D WITH_OPENMP=ON \
    -D BUILD_EXAMPLES=OFF ..
3. Installer imutils
python3 -m pip install imutils

Programmes de base

Pendant la coupe