Projet

Général

Profil

Lib yarp APP » Historique » Version 34

Frederic Elisei, 05/05/2025 16:27

1 1 Frederic Elisei
h1. Objectif
2
3 17 Frederic Elisei
Pouvoir réaliser rapidement une maquette, en python, d'une interaction *avec le robot Furhat,* en faisant intervenir :
4
* synthèse et reconnaissance de parole via Furhat (et le cloud)
5 1 Frederic Elisei
* détection des interlocuteurs avec la caméra de Furhat
6 17 Frederic Elisei
* détection visuelle *avec une caméra externe et YOLO* (cartes et main vues de dessus par exemple)
7
* gestion du regard/orientation de la tête du robot vers les interlocuteurs ou les cartes détectées par YOLO
8 4 Frederic Elisei
9
10 15 Frederic Elisei
On découpera l'application sous forme d'un automate réactif, composé d'états simples (des objets python). 
11 17 Frederic Elisei
L'environnement d'exécution s'appuie aussi sur le middleware YARP [https://yarp.it/latest/], pour permettre la répartition des tâches entre plusieurs machines (Windows, Linux...) qui communiqueront par messages.
12 1 Frederic Elisei
13 27 Frederic Elisei
!{width:300px}VideoCapture_20250126-104416.jpg!:https://redmine.gipsa-lab.grenoble-inp.fr/attachments/1046
14
{ <<< Cliquez sur l'image pour voir la vidéo résultat de l'après-midi Kaleidoscope avec 4 étudiants} 
15
Crédits "vidéo :":https://redmine.gipsa-lab.grenoble-inp.fr/attachments/1046 Frédéric ELISEI (GIPSA-lab), avec Benjamin POIREAULT (Ense3), Alexis LE MEUR (Ensimag), Kinjy BIALADE (Ense3) et Ivan PETERSCHMITT (Ensimag)
16 21 Frederic Elisei
17 1 Frederic Elisei
h1. Squelette minimal :
18
19 17 Frederic Elisei
On va s'appuyer sur lib_yarp_APP.py (et indirectement sur my_yarp.py) pour créer nos états, programmer des actions réactives et gérer les transitions entre états.
20 1 Frederic Elisei
21
<pre><code class="python">
22
#! /usr/bin/env python3
23
24
# import app, furhat, State, AnyKeywords, yolo_center, yolo_target
25
from lib_yarp_APP import *
26
27
# create and run initial state, through its name
28
State("main")
29
app.run("main")
30
</code></pre>
31
32 10 Frederic Elisei
h1. Gestion des évènements et perception :
33 1 Frederic Elisei
34 17 Frederic Elisei
Les états -- comme celui nommé "main" dans l'exemple précédent -- sont des objets dérivés de la classe State (ou d'une sous-classe). *Les états doivent avoir des noms différents.*
35 10 Frederic Elisei
Pour implémenter des réactions à des évènements, il faut qu'ils fournissent une implémentation de tout ou partie des prototypes suivants :
36 1 Frederic Elisei
37 10 Frederic Elisei
| <code class="python">do_in(self)</code>
38
| appelée lorsqu'on rentre nouvellement dans l'état. Par exemple pour dire un message de bienvenue.|
39
#
40
| <code class="python">do_out(self)</code>
41
| appelée lorsqu'on quitte l'état.|
42
#
43
| <code class="python">do_reco(self, key, msg)</code>
44
| *msg* contient la chaîne de parole reconnue par le robot.
45 1 Frederic Elisei
*key* peut faciliter la détection de certaines classes d'intention, indépendamment de leur formulation exacte. Il n'y en a pas beaucoup par défaut : "Oui{}" ou "Non{}" ou "Bonjour{}" "Fini{}"
46
...
47 10 Frederic Elisei
  "Oui{}" "je suis d'accord"
48
  "Oui{}" "oui"
49
  "Oui{}" "ok"
50
  "unknown{}" "C'est complètement l'idée que je me faisais de la chose."
51 1 Frederic Elisei
|
52
#
53
| <code class="python">do_user_in(self, user)</code> 
54
| un nouvel utilisateur a été détecté par le robot en face de lui.
55
...
56 17 Frederic Elisei
l'objet reçu a plusieurs champs:
57 10 Frederic Elisei
*user.id* qui peut être utilisé avec *app.track()* ou *app.glance()*
58
*user.location* une chaine (str), directement utilisable avec *app.look3D()*
59
*user.visible* vaudra *True*
60 12 Frederic Elisei
*user.total* rappelle combien d'utilisateurs sont détectés, celui-là compris
61
*user.mouthOpen* peut aider à détecter qui parle (peu fiable...)
62
*user.attending* vaut *"system"* si le robot pense qu'il est regardé par cet utilisateur. Il peut correspondre à un autre *user-id* si le robot croit détecter un regard dirigé vers un autre des interlocuteurs.
63 1 Frederic Elisei
|
64
#
65 10 Frederic Elisei
| <code class="python">do_user_out(self, user)</code> 
66 12 Frederic Elisei
| l'utilisateur précédemment détecté comme *user.id* n'est plus visible (et *user.visible* vaudra donc *False*)
67 1 Frederic Elisei
On ne peut bien sûr plus localiser ou regarder cet utilisateur !
68 17 Frederic Elisei
*user.total* vous informe s'il reste d'autres utilisateurs, et combien.
69 12 Frederic Elisei
...
70
Si c'était le dernier utilisateur vers qui vous aviez fait un *app.attend()* votre cible est perdue. C'est peut-être le moment de faire un *app.attend("All")* pour ne pas donner l'impression aux autres que vous les ignorez...
71 10 Frederic Elisei
|
72
#
73 13 Frederic Elisei
| <code class="python">do_detect(self,yins,ydel,observations)</code>
74
| Cette méthode est appelée lorsque YOLO détecte des apparitions ou disparitions (de cartes, de mains...), ou régulièrement pour mettre à jour les observations et leurs positions. 
75 10 Frederic Elisei
*yins* et *ydel* sont des sets python (itérables mais non indexables comme une liste ou un tableau !), possiblement vides.
76 25 Frederic Elisei
Par exemple *{}* ou *{0,4}* ou <code class="python">yins.pop()</code> si *yins* n'est pas vide. 
77 8 Frederic Elisei
...
78 10 Frederic Elisei
Les labels (str) sont accessibles en indexant *app.yolo_classes[]* : 0 correspond à "HAND" (une main), 1 à "CARD" (une carte inconnue), les suivants sont des cartes identifiées ("Bouilloire verte"...). 
79 13 Frederic Elisei
Si deux mains deviennent visibles/invisibles, un set ne contiendra 0 qu'une fois au maximum (au moment de l'apparition ou de la disparition), mais *observations* contiendra les références multiples, ainsi que les coordonnées.
80 10 Frederic Elisei
...
81 24 Frederic Elisei
*observations* est un tableau vide ou un tableau de chaînes, dont chacune contient les informations et coordonnées de chaque objet vu, *sous forme de chaîne* :
82
avec le format *"id1 confidence x y w h"*
83 10 Frederic Elisei
...
84 24 Frederic Elisei
<code class="python">len(observations)</code> vous donne le nombre d'objets observés (peut-être 0...)
85 14 Frederic Elisei
...
86 24 Frederic Elisei
pour retrouver s'il y a une/des mains : <code class="python">(d for d in observations if d.startswith("0 ") )</code>
87 30 Frederic Elisei
|
88 29 Frederic Elisei
#
89
| <code class="python">do_aruco(self,ains,adel, data)</code>
90
| Cette méthode est appelée lorsque des marqueurs ARUCO ont été détectés sur le flux vidéo fourni au service (même flux que pour le détecteur fourni par YOLO). 
91
...
92
*data* est un tableau vide ou un tableau de chaînes, dont chacune contient les informations et coordonnées de chaque objet vu, *sous forme de chaîne* :
93
<code class="python">obs=set(int(d.split()[0]) for d in data)</code>
94 10 Frederic Elisei
|
95 1 Frederic Elisei
96 11 Frederic Elisei
97 15 Frederic Elisei
On peut par exemple attacher une telle méthode directement à un ou plusieurs objets état :
98 1 Frederic Elisei
<pre><code class="python">
99 2 Frederic Elisei
State("main").set_behaviour(State.do_reco,catch_default_msg)
100 10 Frederic Elisei
State("waiting").set_behaviour(State.do_reco,catch_default_msg)
101 1 Frederic Elisei
</code></pre>
102 17 Frederic Elisei
Plus classiquement, on peut sous-classer State (ou une de ses sous-classes), comme ici:
103 10 Frederic Elisei
<pre><code class="python">
104 1 Frederic Elisei
class StateParrot(State):
105 10 Frederic Elisei
106 1 Frederic Elisei
  def do_reco(self,key,msg):
107
      app.say(msg)
108
109
StateParrot("repeat_as_parrot")
110 2 Frederic Elisei
app.run("repeat_as_parrot")
111 1 Frederic Elisei
</code></pre>
112 17 Frederic Elisei
113 1 Frederic Elisei
Si on a besoin de plusieurs méthodes dans le même état, ou beaucoup d'états différents, il est probablement plus simple/lisible d'utiliser des sous-classes.
114 11 Frederic Elisei
Si une méthode est utilisée dans plusieurs états, l'attacher aux instances est peut-être plus lisible, ou permet des modifications dynamiques.
115 17 Frederic Elisei
Commencer par sous-classer State avec tous les comportements par défaut est aussi une possibilité.
116 1 Frederic Elisei
117
h1. Génération d'actions
118
119 17 Frederic Elisei
Pour générer des actions/comportements, on s'appuie sur les états, à réception d'un événement ainsi que lorsqu'on y entre ou en sort.
120 11 Frederic Elisei
On ne peut *pas* générer d'action régulière (idle), ou au bout d'un certain temps sans passer par ces évènements ou transitions. C'est un choix lié à la vitesse d'exécution sous Python et pour éviter de se retrouver avec beaucoup d'évènements en retard non traités.
121 1 Frederic Elisei
122
Voici les actions possibles :
123
124
| <code class="python">app.switch("etat2"</code>) 
125 19 Frederic Elisei
| prépare la transition vers un autre état, via *son nom* (pas un objet). Elle ne sera pas instantanée, laissant le temps aux évènements déjà en attente d'être dépilés. En clair, c'est la même file d'attente pour les évènements et le changement d'état.
126 10 Frederic Elisei
|
127 1 Frederic Elisei
#
128
| <code class="python">app.sayNB("texte à prononcer")</code>
129 17 Frederic Elisei
| fait prononcer au robot le texte voulu. La suite du traitement des évènements va reprendre dès que la phrase *commencera* à être prononcée _(NB = non blocking)_
130 10 Frederic Elisei
|
131 1 Frederic Elisei
#
132
| <code class="python">app.say("texte à prononcer")</code>
133 10 Frederic Elisei
| fait prononcer au robot le texte voulu. *Attention, l'appel est bloquant*, jusqu'à ce que la phrase soit prononcée en entier : plus la phrase est longue, plus des évènements en retard vont s'empiler dans la pile de traitement...
134 32 Frederic Elisei
...
135 34 Frederic Elisei
La syntaxe suivante permet de synchroniser le regard sur certains mots, éventuellement pour certaines durées (en millisecondes) : *"Regarde cette personne\|glance user_0\| ou là\|glance (1,2,3) 200\|"*
136 33 Frederic Elisei
Pour éviter les erreur sur les espacements obligatoires ou non autorisés (dans le triplet de coordonnées), il est conseillée d'utiliser la syntaxe avec la fonction auxiliaire suivante :
137 1 Frederic Elisei
<code class="python">app.sayNB("regarde ici|%s|ou |%s|la", glancing("user_0")+glancing((1.0,2.0,3.0),200))</code>
138 33 Frederic Elisei
...
139 10 Frederic Elisei
|
140 1 Frederic Elisei
#
141 10 Frederic Elisei
| <code class="python">app.track(target)</code>
142
| suit du regard la cible *target*
143
Les  valeurs possible sont *"All",* *"Nobody"* ou le *user-id* reçu lors d'un évènement USERIN
144
|
145
#
146
| <code class="python">app.glance(target)</code>
147 31 Frederic Elisei
<code class="python">app.glance(target, duration=sec)</code>
148 10 Frederic Elisei
| comme précédemment, mais seulement en jetant un coup d’œil
149 1 Frederic Elisei
|
150
#
151 32 Frederic Elisei
| <code class="python">app.look3D([x y z],duration=Tsec)</code>
152
<code class="python">app.gaze3D([x y z],duration=Tsec)</code>
153
| demande au robot de regarder aux coordonnées correspondant au triplet *[ x, y, z ]* en paramètre.
154 1 Frederic Elisei
Le repère est centré sur les yeux au repos, les coordonnées sont en mètres (x-axis to the robot's left, the y-axis up, and z-axis to the front). 
155
Si *duration* est spécifiée (en secondes), le regard reviendra sur sa cible initiale une fois ce délai écoulé. Sans ce paramètre, le regard et l'orientation de la tête du robot seront changés de façon plus définitive.
156 32 Frederic Elisei
...
157
Dans la version *gaze3D* seuls les yeux sont recrutés. Avec *look3D*, le cou peut être recruté aussi.
158 10 Frederic Elisei
|
159
160 1 Frederic Elisei
h1. Méthodes auxiliaires
161 10 Frederic Elisei
162
| <code class="python">AnyKeyword(**strings).isin(message)</code>
163 1 Frederic Elisei
| pour tester la présence de l'un des mots clefs (synonymes) dans un message reçu par le robot.
164 11 Frederic Elisei
...
165 10 Frederic Elisei
par exemple : if AnyKeyword('rouge','vert','bleu').isin(msg):
166 1 Frederic Elisei
|
167 10 Frederic Elisei
#
168 11 Frederic Elisei
| <code class="python">x,y = yolo_center(yolo_data)</code>
169
| à partir d'une des lignes détectées par YOLO, retourne la position détectée *(x,y)*
170 10 Frederic Elisei
Utile pour comparer des positions de cartes (droite, gauche, proches d'une autre ou de la main...).
171
|
172
#
173 25 Frederic Elisei
| <code class="python">id = yolo_target(yolo_data,duration=Tsec)</code>
174 28 Frederic Elisei
<code class="python">id = yolo_target(yolo_data,cmd="Glance3D")</code>
175 25 Frederic Elisei
| à partir d'une des lignes détectées par YOLO, oriente tête et regard du robot vers la carte (ou main) correspondante, soit définitivement, soit temporairement si *duration* est passé en paramètre (durée en secondes).
176 17 Frederic Elisei
La valeur retournée *id* permet d'avoir le label correspondant via *app.yolo_classes[id]*
177 10 Frederic Elisei
|