Projet

Général

Profil

Lib yarp APP » Historique » Version 10

Frederic Elisei, 23/01/2025 23:49

1 1 Frederic Elisei
h1. Objectif
2
3
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
5
* détection des interlocuteurs avec la caméra de Furhat
6
* détection visuelle avec YOLO (cartes vues de dessus par exemple) et une caméra externe
7
* gestion du regard/orientation de la tête vers les interlocuteurs ou les cartes détectées par YOLO
8 4 Frederic Elisei
9
10 10 Frederic Elisei
On découpera l'application sous forme d'un automate réactif, composé d'états simples. 
11 9 Frederic Elisei
L'environnement d'exécution s'appuie aussi sur 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
h1. Squelette minimal :
14
15 4 Frederic Elisei
On va s'appuyer sur lib_yarp_APP.py (et indirectement sur my_yarp.py) pour créer nos états et programmer les actions réactives et les transitions entre états.
16 1 Frederic Elisei
17
<pre><code class="python">
18
#! /usr/bin/env python3
19
20
# import app, furhat, State, AnyKeywords, yolo_center, yolo_target
21
from lib_yarp_APP import *
22
23
# create and run initial state, through its name
24
State("main")
25
app.run("main")
26
</code></pre>
27
28 10 Frederic Elisei
h1. Gestion des évènements et perception :
29 1 Frederic Elisei
30
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.*
31 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 :
32 1 Frederic Elisei
33 10 Frederic Elisei
| <code class="python">do_in(self)</code>
34
| appelée lorsqu'on rentre nouvellement dans l'état. Par exemple pour dire un message de bienvenue.|
35
#
36
| <code class="python">do_out(self)</code>
37
| appelée lorsqu'on quitte l'état.|
38
#
39
| <code class="python">do_reco(self, key, msg)</code>
40
| *msg* contient la chaîne de parole reconnue par le robot.
41 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{}"
42
...
43 10 Frederic Elisei
  "Oui{}" "je suis d'accord"
44
  "Oui{}" "oui"
45
  "Oui{}" "ok"
46
  "unknown{}" "C'est complètement l'idée que je me faisais de la chose."
47 1 Frederic Elisei
|
48
#
49
| <code class="python">do_user_in(self, user)</code> 
50
| un nouvel utilisateur a été détecté par le robot en face de lui.
51
...
52
l'objet de type User a plusieurs champs:
53 10 Frederic Elisei
*user.id* qui peut être utilisé avec *app.track()* ou *app.glance()*
54
*user.location* une chaine (str), directement utilisable avec *app.look3D()*
55
*user.visible* vaudra *True*
56 1 Frederic Elisei
|
57
#
58
| <code class="python">do_user_out(self, user)</code> 
59 10 Frederic Elisei
| l'utilisateur précédemment détecté comme *user.id* n'est plus visible. 
60
*user.visible* qui vaudra *False*
61
On ne peut bien sûr plus localiser ou regarder cet utilisateur !
62
|
63
#
64
| <code class="python">do_detect(self,yins,ydel,detections)</code>
65
| 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 positions. 
66
*yins* et *ydel* sont des sets python (itérables mais non indexables comme une liste ou un tableau !), possiblement vides.
67
Par exemple *{}* ou *{0,4}* ou <code class="python">yins.pop()</code> si *yins* n'est pas vide. 
68 8 Frederic Elisei
...
69 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"...). 
70
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 *detections* contiendra les références multiples, ainsi que les coordonnées.
71
...
72
La chaîne (str) *detections* contient 0, 1 ou plusieurs détections, toutes concaténées avec "+".
73
*""* ou *"id1 confidence x y w h"* ou *"id1 confidence x y w h+id2 conf2 x2 y2 w h"* etc
74
...
75
<code class="python">detections.split("+")</code> isole donc chaque détection, et
76
<code class="python">len(data.split("+"))</code> vous donne le nombre d'objets détectés (peut-être 0...)
77
pour retrouver s'il y a une/des mains : <code class="python">(d for d in detections.split("+") if d.startswith("0 ") )</code>|
78
|
79 1 Frederic Elisei
80 10 Frederic Elisei
Pour doter un état d'une de ces méthodes, on peut attacher une méthode directement à un objet état ou à des états différents :
81 2 Frederic Elisei
<pre><code class="python">
82 10 Frederic Elisei
State("main").set_behaviour(State.do_reco,catch_default_msg)
83
State("waiting").set_behaviour(State.do_reco,catch_default_msg)
84
</code></pre>
85
Plus classiquement, on peut sous-classer State (ou ses sous-classes), comme ici:
86
<pre><code class="python">
87 1 Frederic Elisei
class StateParrot(State):
88
89
  def do_reco(self,key,msg):
90
      app.say(msg)
91 2 Frederic Elisei
92
StateParrot("repeat_as_parrot")
93 1 Frederic Elisei
app.run("repeat_as_parrot")
94
</code></pre>
95 10 Frederic Elisei
Si on a besoin de plusieurs méthodes dans le même état, ou beaucoup d'états différents, il est plus probablement plus simple/lisible d'utiliser des sous-classes.
96
Si une méthode est utilisée dans plusieurs états, l'attacher aux instances est peut-être plus lisible, ou permet des modifications dynamiques.
97 1 Frederic Elisei
98 10 Frederic Elisei
h1. Génération d'actions
99 1 Frederic Elisei
100
Dans un état, au moment où on y entre ou à réception d'un évènement, il est possible de générer certaines actions. 
101
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 éviter de se retrouver avec beaucoup d'évènements en retard non traités.
102
103
Voici les actions possibles :
104
105
| <code class="python">app.switch("etat2"</code>) 
106 10 Frederic Elisei
| prépare la transition vers un autre état. Elle ne sera pas instantanée, laissant 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.
107
|
108 1 Frederic Elisei
#
109
| <code class="python">app.sayNB("texte à prononcer")</code>
110 10 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)_
111
|
112 1 Frederic Elisei
#
113
| <code class="python">app.say("texte à prononcer")</code>
114 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...
115
|
116 1 Frederic Elisei
#
117 10 Frederic Elisei
| <code class="python">app.track(target)</code>
118
| suit du regard la cible *target*
119
Les  valeurs possible sont *"All",* *"Nobody"* ou le *user-id* reçu lors d'un évènement USERIN
120
|
121
#
122
| <code class="python">app.glance(target)</code>
123
| comme précédemment, mais seulement en jetant un coup d’œil
124
|
125
#
126 1 Frederic Elisei
| <code class="python">app.look("x y z",duration=Xms)</code>
127
| demande au robot de regarder aux coordonnées correspondant à la *chaine (str)* en paramètre. 
128 3 Frederic Elisei
Celle-ci peut être obtenue par yolo ou construite à partir d'un triplet de flottants: "%d %d %d"%(1,2,3), en mètres (x-axis to the robot's left, the y-axis up, and z-axis to the front). Le repère est centré sur les yeux au repos.  
129 10 Frederic Elisei
Si *duration* est spécifié (en millisecondes), le regard reviendra sur sa cible initiale une fois ce délai écoulé. Sans ce paramètre, le regard du robot sera changé de façon plus définitive.
130
|
131
132
h1. Méthodes auxiliaires
133
134
| <code class="python">AnyKeyword(**strings).isin(message)</code>
135
| pour tester la présence de l'un des mots clefs (synonymes) dans un message reçu par le robot.
136
...
137
par exemple : if AnyKeyword('rouge','vert','bleu')isin(msg):
138
|
139
#
140
| <code class="python">x,y = yolo_center(yolo_data)</code>
141
| à partir d'une des lignes détectées par yolo, retourne la position detectée *(x,y)*
142
Utile pour comparer des positions de cartes (droite, gauche, proches d'une autre ou de la main...), choisir 
143
|
144
#
145
| <code class="python">id = yolo_target(yolo_data,duration=Xms)</code>
146
| à partir d'une des lignes détectées par yolo, oriente tête et regard du robot vers la carte (ou main) correspondante,
147
soit définitivement, soit temporairement si *duration* est passé en paramètre (durée en millisecondes)
148
*id* retourné permet d'avoir le label correspondant via *app.yolo_classes[id]*
149
|