Robot Delta avec asservissement visuel

De fablab
Aller à : navigation, rechercher

Description du sujet

  • Responsable : Stéphane Mancini ; stephane.mancini@imag.fr
  • Sujet:

Les robots delta sont les plus rapides car ce sont des robots dits 'parallèles', semblables à celui visible sur cette page: [1]

Robot delta.jpg
omron

Chacun des 3 bras du robot est contrôlé par un moteur fixe et la position de la tête du robot est directement liée aux angles de rotation de chacun des moteurs.

En comparaison, un robot séquentiel tel que celui de cette vidéo [2] nécessite le réglage de 5 axes de rotation et les moteurs de chaque axe se déplacent avec le robot. Les robots parallèles ont cependant l'inconvénient de supporter moins de charge et sont essentiellement utilisé pour la manipulation de petits objets.

Dans ce projet de Fablab, l'objectif est de réaliser un robot delta dont la position de la tête est asservie visuellement: une caméra est placée sur la tête du robot et le logiciel de contrôle du robot fera en sorte qu'elle soit capable de suivre un point lumineux qui se déplace.

Un objectif bonus serait de déplacer la tête du robot pour atteindre un objet détecté (sardine, patate, etc ...).

Concrètement, le robot delta sera construit à partir de pièces de modélisme (servomoteurs et rotules) et sera contrôlé par un logiciel sur carte Raspberry-Pi [3], avec sa caméra. [4].

RPI camera.jpg

La Raspberry-Pi est une carte constituée d'un processeur ARM connecté à de nombreux périphériques (entrée/sortie vidéo, etc ...). Il est possible d'installer un système d'exploitation Linux , distribution Raspbian, et se programme donc comme un PC. La caméra Raspberry-Pi est compatible avec toutes les bibliothèques classiques de traitement d'image dont OpenCV. Il est également possible de programmer les applications de traitement d'image et contrôle/commande en Python.

  • Vous serez formé aux différents aspects du projet et il n'est pas nécessaire d'être un initié, seule votre motivation pour découvrir de nouvelles applications suffit !
  • Les différentes tâches (mécanique, programmation, câblage), seront réparties dans l'équipe.

Assemblage du robot

Après discussion entre les membres du groupe et avec le professeur encadrant, nous avons décidé dès le début du projet de ne pas modéliser nous même la structure du robot Delta.

En effet, créer une structure fiable prend un temps non négligeable et le temps disponible pour ce projet était assez restreint.

Nous avons donc cherché sur internet une structure de Robot Delta qui nous semblait correcte. Le site Thingiverse est un bon moyen de trouver des modèles 3D pour un peu tout type de projets.

Notre choix s'est arrêté sur le modèle EEZYbotDELTA créé par l'utilisateur daGHIZmo sur Thingiverse. (http://www.thingiverse.com/thing:1249297)

RobotDelta.png OurRobotDelta.jpg
A gauche une photo prise par le créateur du robot, à droite notre robot.

Le modèle EEZYbotDELTA est fournit avec la liste des pièces à imprimer en 3D, ainsi que la liste de pièces autres (servos, tiges filetées, tube en aluminium).

L'une des pièces faisant office de jointures entre les tiges filetées et les pièces imprimées n'était pas trouvable chez les fournisseurs du FabLab, nous avons donc décidé de la modéliser par nous même (fichier au format STL compressé : Fichier:LiaisonRotule.zip).

Jointure.png
Photo de la jointure modélisée sur Blender

Les impressions de pièces demandent un temps conséquent (environ 50h en tout), c'est donc à prévoir lorsqu'on veut se lancer dans un projet comme celui-ci.

En regroupant correctement les pièces lors des impressions on peut commencer le début du montage assez rapidement, la notice de montage est également fournie par le créateur du modèle.

Ce montage demande un travail assez conséquent de bricolage (ébavurage, perçage, coupe des tubes en alu, etc).

NB : Nous avons choisi d'utiliser comme carte de contrôle une carte RaspberryPi v3, cette carte dispose de ports PWM capables de contrôler des servomoteurs. Cependant nous avons remarqué un problème de tremblements des moteurs. En effet le Raspberry ne comporte qu'un port PWM Hardware les autres sont logiciels, mais le fait qu'il soit logiciel nous empêche d'avoir un signal propre (puisque le système switche constamment entre tous les processus lancés sur le RaspberryPi).

Pour pallier à ce problème il était indispensable d'utiliser un contrôleur externe pour créer les signaux pour les servomoteurs. Nous avons décidé d'utiliser ce contrôleur : Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA9685.

Ce contrôleur est compatible avec le RaspberryPi (par le biais de la liaison I2C) et il est possible de l'utiliser avec Python qui est le langage avec lequel nous avons travaillé pour la partie OpenCV.

Asservissement des moteurs

Avant de s'intéresser directement à l'asservissement des moteurs, il faut dans un premier temps bien comprendre l'aspect mécanique du robot, sur lequel sont basées les différentes équations du mouvement. Ce sont ces équations qui seront ensuite exploitées.

Mécanique du robot

Voici un schéma très simplifié qui va nous permettre de visualiser les principales liaisons mécaniques du système :

Base schema.png Nacelle schema.png Delta schema.png Delta2 schema.png
schémas correspondants respectivement à la base du robot, la nacelle mobile et au système complet
http://www.ohio.edu/people/williar4/html/pdf/DeltaKin.pdf

Comme on peut le voir, le système reste très basique. Chaque bras est constitué de 2 tiges reliées à la base, à la nacelle et entre elles par des liaisons pivot. Remarque : par la suite, nous garderons le même système de coordonnées.

Maintenant, comment à partir de cela allons nous déduire les équations qui nous servirons pour l'asservissement ? On sait déjà que c'est la position du centre de la base de la nacelle qui nous intéresse ainsi que les différents angles thêta (ces derniers interviendrons dans les équations par projection dans nos bases).

On se sert alors de l'égalité vectorielle suivante :

Egalite vectorielle.png
mise en évidence de l'égalité vectorielle

Les équations se déduisent simplement des projections des différents vecteurs.

Equations

D'après l'image précédente, on a l'équation suivante :

Equation globale.png

A noter que, l'exposant sur chaque vecteur correspond à la base dans laquelle il est exprimé (base B du bâti et base P de la nacelle - platform en anglais) et l'indice, correspond au numéro du bras considéré. La dernière égalité vient du fait que le robot Delta n'effectue aucune rotation.

On passe ensuite à la norme et on obtient :

Passage norme.png

Après cela, on projette les distances utiles dans la base B.

L projete.png
Autre l projete.png

Avec les différentes constantes :

Constants.png

Finalement, les équations résultantes sont :

Equations finales.png

Elles sont toutes de la forme :

Modele equation.png

Pour résoudre ce type d'équation, on utilise la technique de substitution par la tangente de l'angle moitié (voir ci-dessous).

Angle moitie.png

En substituant dans le modèle d'équation précédent, on obtient les solutions suivantes :

Solutions.png

On a alors deux solutions de thêta par bras ce qui correspond aux positions bras levés et bras baissés (nous n'exploiterons que la position bras baissés).

Cette méthode nous a permis de déduire les angles pour une position donné (Inverse Position Kinematic IPK Solution). Il existe aussi la méthode inverse (Forward Position Kinematic Solution FPKS) qui, à partir des angles, détermine la position. Cette dernière méthode servira a confirmer la valeur de nos angles lors de nos tests.

Validation du modèle

Reste maintenant à valider la pertinence de notre méthode. Ces équations ont été testées pour L = 20 cm, l = 20 cm, et ??? (bien évidemment, ces valeurs sont à modifier selon les dimensions du robot).

Voici comment les angles sont repérés :

Reperage angle.png Base schema.png

Et voici les résultats obtenus :

AngleX.png

On constate sur ce schéma que les angles des servos B2 et B3 sont "complémentaires". Le servo B1 lui va former un angle correctif afin de garantir le fait que la nacelle suive bien l'axe X. On remarque aussi qu'au delà d'une certaine distance, les valeurs ne sont plus cohérentes (logique car on ne peut pas les atteindre).

AngleY.png

Ici, les valeurs concordent pour les servos B2 et B3 ce qui est logique par symétrie. C'est ensuite le servo B1 qui va guider la nacelle à la bonne position.

AngleZ.png

On constate cette fois-ci que les trois angles sont les mêmes (logique car simple translation selon Z). Par contre, on peut décomposer la courbe en 3 parties environ: la première non linéaire correspond à des valeurs que le robot ne pourra jamais atteindre (ou alors pour des dispositions de bras/avant-bras particulières au départ), la deuxième partie, linéaire correspond aux valeurs que l'on pourra exploiter et enfin une ultime parie non linéaire qui là aussi correspond à des valeurs non atteignables compte tenu de nos paramètres initiaux.

Contrôle des servomoteurs

Une fois le modèle cinématique validé, il fallait se pencher sur la question du contrôle des différents servomoteurs (trois servomoteurs, un pour chaque bras du robot). Pour cela, nous avions besoin d'établir une correspondance entre les angles de rotation des différents bras pour déplacer la nacelle à une position donnée (obtenu grâce au modèle inverse de la partie précédente) et les impulsions pour piloter les servomoteurs. La figure suivante donne les correspondances entre la largeur des impulsions (qui est comprise entre 1 et 2ms) et la position du servomoteur.

TiemposServo.svg.png

On calcule donc le rapport cyclique (qui représente le ratio entre la durée du signal à l'état haut et la période des PWM des servomoteurs) pour n'importe quel angle grâce à la formule suivante :

                                        rapport_cyclique = (angle/180 + 1)*5

C'est ce rapport cyclique qu'il faut donc envoyer au servomoteur pour régler le signal PWM et ainsi positionner le bras du robot à la position souhaitée. Avec la carte Adafruit qui nous permettait de contrôler plusieurs PWM à la fois, nous avions besoin d'exprimer ce rapport cyclique en nombre d'octet en sachant que sur la carte les instants où le signal est soit haut ou bas sont mesurés par des ticks avec 0 correspondant à la plus petite valeur et 4096 la valeur maximale.

Analyse de l'image capturée par la caméra

OpenCV

OpenCV est une bibliothèque multi-plateformes, disponible en C++, Java et Pyhton, et spécialisée dans le traitement d'images. Elle met à disposition du programmeur des opérations liées aux matrices (multiplication, transposition, calcul des valeurs propres, ...), aux images (lecture, écriture, affichage, lissage, détection de contours, ...) et aux vidéos (lecture, écriture, affichage, détection de visage, détection de mouvements, ...). Elle contient également des algorithmes liés au machine learning. Pour ce projet, le code utilisant OpenCV va être exécuté sur la Raspberry Pi. Il est donc plus simple d'utiliser Python, qui est installé par défaut, plutôt que C++ qui nécessite soit l'installation d'un compilateur sur la Raspberry Pi, soit l'installation d'un cross-compilateur sur une autre machine. Seules les opérations de lecture, de conversion, de redimensionnement et de détection decontours d'une image vont nous être utiles ici. En effet, la caméra va nous servir à capturer des images de temps en temps plutôt qu'une vidéo en continu.

Installation de OpenCV

Dans cette partie, on reprend les étapes à effectuer pour l'installation de OpenCV sur la Raspberry Pi.

Etape 1: Télécharger OpenCV

NB: l'outil Git est un préréquis OpenCV est un projet open source disponible sur GitHub. Pour le télécharger, il faut donc cloner le dépont à l'adresse suivante : https://github.com/opencv/opencv. Pour cela, il faut créer un répertoire pour contenir la source et ensuite y cloner le dépôt :

                                      git clone https://github.com/opencv/opencv

Etape 2: Compilation des sources OpenCV

NB: l'outil CMAKE est un préréquis Pour compiler OpenCV, il faut créer un dossier temporaire dans le fichier contenant les sources pour contenir les Makefiles, les fichiers objets et les autres fichiers générés par la compilation. Nous le dénommons release (par exemple). On utilise les commandes suivantes:

                             cd ~/opencv
                             mkdir release
                             cd release
                             cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..

Etape 3: Installation

Enfin, il faut se placer dans le dossier temporaire créé (release) et effectuer les commandes suivantes :

                                          Make
                                          sudo make install

Principe général de l'algorithme de reconnaissance

Récupération de l'image

La première étape consiste simplement à récupérer l'image à partir de la caméra. Néanmoins, les pixels de cette image sont codés en RGB (une composante rouge, une composante verte et une composante bleue), ce qui n'est pas le plus approprié pour effectuer une analyse. Les calculs qui vont suivre doivent se faire sur une seule composante, et le choix d'une de ces trois-là va dépendre de l'image.

Il faut donc effectuer une conversion en une image en niveau de gris. OpenCV s'occupe de cela en calculant la luminance Y' de chaque pixel grâce à la formule : Y' = 0.299*R+0.587*G+0.114*B.

Ce choix vient du fait que la luminance est liée à la luminosité perçue, et que l'oeil y est très sensible, comme on peut le voir ci-dessous. Quand on décompose une image en luminance/chrominance, on voit que la plus grande partie de l'information est contenue dans la luminance. On obtient en général des résultats très satisfaisants avec ce choix, et ce quelle que soit l'image considérée.

Boule1.png
Exemple d'image pour montrer les décompositions possibles de la couleur des pixels
Boule2.png
Décomposition de la couleur en rouge, vert et bleu
Boule3.png
Décomposition de la couleur en luminance (gauche), chrominance rouge (centre) et chrominance bleue (droite)

Détection des contours

De nombreuses méthodes existent pour détecter les contours d'une image, mais certaines sont plus ou moins efficaces en fonction du bruit. La méthode de détection de contours de Canny donne des résultats satisfaisants la plupart du temps. Elle consiste tout d'abord à appliquer un filtrage gaussien pour se débarrasser des bruits de haute fréquence. Ensuite, on calcule le gradient en chaque pixel et on décide si un pixel appartient à un contour en fonction du gradient et de deux valeurs seuils.

Contours.png
Détection de contours de Canny appliquée à l'image précédente

On voit sur l'image ci-dessus que certains pixels sont considérés comme des contours alors qu'ils correspondent à des reflets, ce qui ne nous intéresse pas nécessairement. Lorsque l'image est bruitée, la détection a également tendance à "rajouter" des contours. Pour notre projet, on suppose que le lissage inclus dans la méthode de Canny suffit pour éliminer les bruits éventuels.

Reconnaissance des formes

Une fois les contours de l'image calculés, il faut les utiliser pour tenter de reconnaitre des formes. Pour une question de simplicité, nous n'avons cherché qu'à reconnaitre des "points".

Avant de commencer l'interprétation des contours, nous avons choisi de redimensionner l'image à 600 pixels de largeur (la hauteur est choisie de manière à conserver le ratio de l'image de départ) afin de rendre le calcul plus rapide pour les images de haute résolution.

L'idée de l'algorithme implémenté est de regrouper les pixels des contours par groupes, qui vont correspondre à des rectangles. On parcourt l'image pixel par pixel. Si un pixel est proche d'un groupe, on le rattache à celui-ci et on augmente sa taille si besoin. Sinon, on crée un nouveau groupe. On peut ainsi récupérer rapidement le centre d'une forme détectée en prenant le centre du rectangle correspondant à un groupe. De plus, on peut détecter des formes quelconques avec cette méthode, et pas que des points.

En gardant en mémoire le nombre de points présents dans chaque groupe, on peut éliminer à la fin ceux qui sont petits et qui correspondent probablement à du bruit. On peut également faire varier la distance maximale entre un pixel et un groupe pour que ceux-ci puissent être fusionnés, ce afin d'avoir un résultat plus ou moins précis.

Une des images que nous avons choisies pour nos test est la suivante :

Sample.png
Image choisie pour tester notre algorithme

Il s'agit d'une photographie prise d'une feuille de papier sur laquelle ont été dessinés des points de différentes tailles. Elle est intéressante car elle présente plusieurs aspects "réalistes" :

  • elle a été créée au départ au format JPEG, ce qui a entrainé une certaine perte de données
  • les "points" ne sont pas uniformes ni parfaitement circulaires
  • la feuille est pliée à certains endroits
  • on peut voir qu'il y a du texte imprimé sur l'autre face de la feuille

Nous nous sommes servi de cette image pour déterminer les deux coefficients nécessaires lors de l'appel à la méthode de Canny : il s'agit de deux seuils permettant de déterminer la présence d'un contour en un pixel en comparant la valeur du gradient en ce point à ces deux seuils. Il faut les choisir tels que les éléments listés ci-dessus ne perturbent pas la détection de points. Nous avons ainsi choisi les valeurs 100 et 200, ce qui donne le résultat suivant :

Contours2.png Result.png
A gauche les contours calculés par la méthode de Canny, à droite la comparaison entre les formes détectées et l'image de départ

Les résultats obtenus avec cette méthode sont satisfaisants pour notre projet, mais il faudrait choisir un autre algorithme si l'on souhaitait reconnaitre des formes particulières, plus complexes ou bien imbriquées les unes dans les autres.

Code source

Nos fichiers de code ainsi que quelques images de test peut être récupérés dans l'archive suivante : Fichier:CodeSource.tar.gz .

NB : Aucune bibliothèque de programmation n'a été jointe à cette archive et le code est compatible avec la version 2.7 de Python, mais pas nécessairement avec une version 3.X.