Reconnaissance faciale

Transcription

Reconnaissance faciale
Reconnaissance faciale
Thibault Suzanne, Ralph Maloudi
Table des matières
Introduction
3
1 La reconnaissance faciale
1.1 Les faces propres : la théorie . . . . . . . . . . . . . . . . . . . . .
1.2 Les faces propres : l’implantation pratique . . . . . . . . . . . . .
4
4
9
2 L’interface graphique
15
2.1 Fonctionnement général . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 Implantation python . . . . . . . . . . . . . . . . . . . . . . . . . 16
Conclusion
20
1
Table des figures
1.1
1.2
1.3
1.4
Illustration de l’ACP . . . . . . . . . . . . . . . .
Approximation d’un créneau par des sinusoïdales
Quelques faces propres . . . . . . . . . . . . . . .
Illustration de la distance de Mahalanobis . . . .
.
.
.
.
5
5
7
9
2.1
Capture d’écran du logiciel (Arch Linux, gestionnaire de fenêtre
Awesome) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Introduction
Ce projet a pour objectif la réalisation d’un logiciel de reconnaissance faciale. Il est découpé en deux parties liées mais distinctes : le codage d’un outil
de reconnaissance faciale à l’aide de l’algorithme eigenfaces, et la réalisation
d’une interface graphique permettant d’utiliser n’importe quel programme de
reconnaissance pourvu qu’il respecte un certain format. En particulier, cette
interface servira à l’utilisation de notre premier code.
Le programme de reconnaissance faciale sera codé en Python. Il utilisera
les bibliothèques OpenCV et Numpy. Son rôle sera d’identifier, parmi une base
de visage connu, duquel se rapproche le plus un visage passé en paramètre. Il
devra également indiquer à quel point il s’en rapproche, afin de pouvoir valider
ou infirmer l’identification. Il utilisera la technique dite des « faces-propres »,
qui bien que n’offrant pas les meilleures performances à l’heure actuelle, est
relativement simple à mettre en œuvre et reste efficace.
L’interface graphique sera également réalisée en Python, à l’aide de la plateforme PySide, qui est un portage de la librairie Qt pour le langage Python.
Elle utilisera également OpenCV pour pouvoir prendre des photos à l’aide de
la webcam. Elle devra prendre une photo à l’aide de la webcam et permettre
le choix d’un module de reconnaissance parmi ceux qu’elle connait (qui seront
dans une liste facilement extensible). Elle devra ensuite appeler ce module afin
qu’il reconnaisse ou non le visage en question. Elle affichera dans une fenêtre le
visage reconnu par le module, ainsi que diverses informations que ledit module
aura pu lui donner.
3
Chapitre 1
La reconnaissance faciale
La reconnaissance faciale s’inscrit dans le domaine plus vaste de la vision par
ordinateur, qui part du constat que le sens le plus utilisé par l’homme est la vue.
Dès lors, il peut s’avérer très utile de donner des « yeux » à son ordinateur :
ainsi, il peut devenir capable de remplacer ceux de l’homme pour des tâches
répétitives telles que la reconnaissance de nombreux visages, ou demandant
des calculs conséquents comme le lissage d’une image ou l’augmentation de sa
netteté.
Le principe de la reconnaissance faciale est très simple : comme son nom
l’indique, il s’agit d’identifier une face donnée. Selon les algorithmes et les buts
recherchés, cette identification peut prendre plusieurs aspects : il peut s’agir de
déterminer à qui appartient un visage, de décider si oui ou non ce visage est
reconnu, ou même dans certains cas de déterminer s’il s’agit bien d’un visage.
1.1
Les faces propres : la théorie
Nous cherchons à effectuer de la reconnaissance faciale, en employant des
systèmes simples comme une webcam. Le sujet se plaçant alors face à la caméra, nous partons donc de la remarque, qui nous simplifie grandement notre
démarche, selon laquelle nous pratiquons de la reconnaissance à deux dimensions.
La méthode de reconnaissance faciale Eigenfaces emploie la technique de
l’analyse en composante principale, qui marque une différence notable avec les
méthodes plus classiques, appelées méthodes géométriques ou locales, qui se
basent sur les particularités du visage analysé, et dont les défauts résident dans
son manque de précision, ainsi que sa sensibilité aux informations qui ne sont
pas pertinentes.
La méthode que nous avons utilisé est qualifiée de globale, puisque l’ensemble
du visage est alors analysé.
Notre technique de reconnaissance va donc utiliser la méthode d’analyse en
composantes principales (également dite ACP).De manière simple, nous visons
la diminution de la dimension de l’espace dans lequel nous allons travailler, et
nous pourrons alors simplifier les données à notre disposition et leur interprétation.
4
Ainsi nous pourrons prendre en compte les informations importantes qui nous
permettrons de reconnaître un visage parmi d’autres avec un bon taux de réussite.(Figure 1.1)
Figure 1.1 – Illustration de l’ACP
Faisons un simple parallèle entre ma méthode des faces propres et les séries
de Fourier. Les séries de Fourier nous permettent généralement de représenter
un signal périodique à l’aide de sommes de cosinus et de sinus.(Figure 1.2)
Figure 1.2 – Approximation d’un créneau par des sinusoïdales
Dans le cas de notre projet, notre approche consiste à représenter un visage comme étant la combinaison linéaire d’un ensemble d’images, ces dernières
formant une base de référence. Mathématiquement, cela revient à parvenir à
l’équation :
Φi =
n
X
pi di
i=1
où di représente le visage propre, et pi le coefficient associé.
Nous allons chercher à trouver les visages propres ; tout d’abord, nous devons prendre un nombre M de visages d’apprentissage. Chacune de ces images,
qui sont en pratique des matrices N × N sont alors transformées en un unique
vecteur colonne de longueur N 2 .
Matrice N × N initiale :

α11
 α21

 ...
αN 1
α12
α22
...
αN 2
α1i
α2i
...
αN i
5

α1N
α2N 

... 
αN N
transformée en :

α11
 .. 
 . 


 αN 1 


 .. 
 . 


 α1N 


 . 
 .. 
αN N

Nous devons par la suite déterminer le visage moyen, déduit des M visages
d’apprentissages.
Ψ=
M
1 X
Γi
M i=1
Ce visage moyen va servir dans l’analyse d’images, on soustrait en effet ce visage moyen aux visages d’apprentissages, ce qui nous laisse alors les informations
propres à ce visage, nous récupérons alors dans Φi uniquement les informations
qui sont particulières à ce visage d’apprentissage.
Φi = Γi − Ψ
Où Φi représente le ieme visage auquel on a soustrait le visage moyen.
A présent nous devons calculer la matrice de covariance D. Elle correspond à
D = QQT ,
avec
Q = [Φ1 , Φ2 , ....., ΦM ]
Nous devrions calculer les vecteurs propres di de la matrice D. Mais cela
représente pour nous N 2 vecteurs propres de dimension N 2 chacun.
C’est à présent que nous allons réduire l’information en limitant les composantes avec lesquelles nous travaillerons, en accord avec le principe de l’analyse
en composantes principales. Nous allons donc considérer la matrice E = QT Q,
dont nous trouverons les vecteurs propres ei .
Cette matrice est de taille M × M ce qui nous simplifiera donc les choses
étant donné que nous aurons M vecteurs propres de taille M chacun.
Le passage de la matrice D à la matrice E n’est pas anodin, nous utilisons
en effet le fait que les vecteurs propres de ces deux matrices sont liés de manière
assez proche.
En effet, nous avons comme relation,
Eei = QT Qei = λi ei
avec λi la valeur propre associée au vecteur propre ei .
6
En multipliant cette équation par la matrice Q, il vient
QEei = QQT Qei
Nous voyons alors apparaître la matrice D
QEei = DQei = λi Qei
Nous en déduisons donc qu’avec ei vecteur propre de la matrice E associé à
la valeur propre λi , nous avons par conséquent Qei est un vecteur propre de la
matrice D associé à la même valeur propre λi .
Ainsi, nous avons di vecteur propre de D, avec
di = Qei
Ce sont les valeurs propres qui leur sont associées qui nous permet ensuite
de classer les vecteurs propres en fonction de leur capacité à caractériser les
variations entre les images.
Lorsque l’on les visualise (ces vecteurs sont à l’origine des matrices de taille
N × N ), les faces propres sont ce que l’on pourrait appeler des images aux airs
fantomatiques. Mais gardons à l’esprit que sont les vecteurs propres de la matrice de covariance des images d’apprentissage des visages. (Figure 1.3)
Figure 1.3 – Quelques faces propres
Les M vecteurs propres que nous avons alors obtenus nous permettrons donc
d’approximer au mieux les visages d’apprentissage en utilisant les visages propres
de plus grande importance.
L’avantage de réduire le nombre de visages propres est d’une part de nécessiter de moins d’espace mémoire, mais aussi de réduire les calculs, leur temps
d’exécution ; cependant nous perdons sans aucun doute de l’information et donc
l’information moins précise, mais les résultats ne s’en verront pas vraiment modifiés, étant donné que nous ne nous donnons qu’une mission d’identification.
Nous ne cherchons pas à reconstruire le visage du sujet à partir de nos visages
propres, mais seulement à le reconnaître.
Parmi les M vecteurs propres trouvés, nous allons seulement conserver un
nombre L, qui seront les plus significatifs.
Nous allons trouver maintenant le poids associé à chacun des visages propres.
Les images servant à l’apprentissage, auquel on a enlevé l’image moyenne, sont
en fait combinaison linéaire des visages propres.
7
Φi =
L
X
pi di
i=1
Pour trouver le poids associé, nous faisons pour chacune des coordonnées
correspondant à un visage d’apprentissage
pi = dTi Φi
Ce qui nous permet d’obtenir pour chacun des M visages d’apprentissages
un vecteur Πi , où i représente le ieme visage, et qui nous informe sur le coefficientage appliqué à chacun des visages propres.



Πi = 


p1
p2
..
.




pL
Passons à présent au travail à effectuer pour la reconnaissance d’un visage
d’un sujet.
Une fois l’image prise, l’image (vecteur colonne Γ) obtenue est soustraite à
l’image moyenne Ψ :
Φ=Γ−Ψ
Puis nous trouvons les coordonnées de cette image dans l’espace réduit des
faces propres
pi = dTi Φi
Ce qui nous donne au final :



Π=

p1
p2
..
.





pL
Il nous faut maintenant interpréter la projection de l’image à analyser pour
identifier le sujet. Pour cela nous allons utiliser une mesure de distance particulière, la distance de Mahalanobis. L’intérêt de cette distance réside dans le
fait qu’elle va accorder un poids moins important aux composantes bruitées,
et qu’elle permet séparer efficacement les axes pour lesquels l’information peut
être mieux classifiée. (Figure 1.4)
8
Figure 1.4 – Illustration de la distance de Mahalanobis
Elle est définie par :
d(a, b) =
q
(a − b)T Q−1 (a − b)
avec Q covariance des variables.
Nous cherchons donc :
m = min kΠ − Πi k
Puis, nous comparons la valeur de m trouvée à une valeur seuil ∆, qui aurait
du être déterminée à partir d’essais sur des images choisies aléatoirement, qui
peuvent aussi bien représenter des visages qu’autres choses, puis en comparant
ces valeurs aux valeurs obtenues avec des visages d’apprentissage, et décider du
seuil que nous avons choisi. Mais le choix de ce seuil dépend de trop nombreuses
conditions ( prise de vue des images, niveau de précision souhaité pour la reconnaissance, etc..) c’est pour cela qu’en pratique nous avons décidé de ne pas
prendre de seuil.
L’utilisation d’un seuil nous aurait permis de déterminer si oui ou non l’image
analysée correspond à un visage présent dans la base de données. Si alors la
valeur de m était inférieure à celle du seuil, l’image correspond au visage qui a
donné cette valeur la plus basse.
Dans notre cas, le fait de ne pas choisir de seuil à eu pour conséquence que
lorsqu’un visage non présent dans la base de donnée était testé, il était tout de
même reconnu par le programme.
1.2
Les faces propres : l’implantation pratique
Voici donc le code de l’implantation en Python, commenté :
1
2
3
4
5
6
# !/ usr / bin / env python2
# -* - coding : utf -8 -* import cv
import math
import numbers
9
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import
import
import
import
numpy as np
os
sys
time
def list_de_iplimage ( image ):
" Renvoie ␣ la ␣ forme ␣ liste ␣ d ’ une ␣ image ␣ OpenCV "
return [[ cv . Get2D ( image , i , j )[0] for j in xrange ( image . width )]
for i in xrange ( image . height )]
class Tableau :
def __add__ ( self , autre ):
" Additionne ␣ avec ␣ un ␣ Tableau ␣ ou ␣ un ␣ scalaire "
if isinstance ( autre , Tableau ):
return Tableau ( self . tab + autre . tab )
elif isinstance ( autre , numbers . Number ):
return Tableau ( self . tab + autre )
else :
raise TypeError ( " Tableau . __add__ ␣ : ␣ Tableau ␣ ou ␣ scalaire ␣ attendu "
def __div__ ( self , autre ):
" Divise ␣ par ␣ un ␣ scalaire "
return Tableau ( self . tab / autre )
def __getitem__ ( self , index ):
" Renvoie ␣ un ␣ coefficient ␣ du ␣ Tableau , ␣ pour ␣ pouvoir ␣ utiliser ␣ tableau [ i
if isinstance ( index , int ) and self . largeur != 1:
return Tableau ( self . tab [ index ])
else :
return self . tab [ index ]
def __init__ ( self , source , nom = str ( time . time ())):
""" Constructeur d ’ un objet Tableau
On peut lui passer :
* une c h a n e de c a r a c t r e , qui sera le chemin de l ’ image
charg
* une image au format OpenCV
passer en Tableau
* un array numpy
passer en Tableau
"""
if isinstance ( source , str ):
self . tab = np . array ( list_de_iplimage ( cv . LoadImage ( source , cv . C V
elif isinstance ( source , cv . iplimage ):
self . tab = nb . array ( list_de_iplimage ( source ))
elif isinstance ( source , ( np . ndarray , list )):
self . tab = np . array ( source )
else :
raise TypeError ( " Tableau . __init__ ␣ : ␣ % s ␣ inattendu " % type ( source
self . nom = nom
self . calc_dimensions ()
10
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
def __len__ ( self ):
" Pour ␣ pouvoir ␣ utiliser ␣ len ( tableau ) ␣ au ␣ cas ␣ o
return len ( self . tab )
"
def __mul__ ( self , autre , el e me n t _p a r_ e le m e nt = False ):
""" Multiplie : multiplication de matrice , ou par un scalaire
Le flag e l em e nt _ p ar _ el e me n t indique , s ’ il est vrai , qu ’ on n ’ utilise
la multiplication de l ’ a l g b r e des matrices , mais une multiplicati
lment
par
lment
de deux matrices de m m e s dimensions """
if isinstance ( autre , Tableau ):
if el e me n t_ p a r_ e le m e nt :
return Tableau ( self . tab * autre . tab )
else :
resultat = np . dot ( self . tab , autre . tab )
if isinstance ( resultat , numbers . Number ):
return resultat
else :
return Tableau ( resultat )
elif isinstance ( autre , numbers . Number ):
return Tableau ( self * autre )
else :
raise TypeError ( " Tableau . __mul__ ␣ : ␣ Tableau ␣ ou ␣ scalaire ␣ attendu "
def __setitem__ ( self , index , valeur ):
""" Modifie un coefficient du Tableau :
i m p l m e n t e tableau [ index ] = valeur """
self . tab [ index ] = valeur
def __sub__ ( self , autre ):
" Additionne ␣ avec ␣ un ␣ Tableau ␣ ou ␣ un ␣ scalaire "
if isinstance ( autre , Tableau ):
return Tableau ( self . tab - autre . tab )
elif isinstance ( autre , numbers . Number ):
return Tableau ( self . tab - autre )
else :
raise TypeError ( " Tableau . __sub__ ␣ : ␣ Tableau ␣ ou ␣ scalaire ␣ attendu "
def afficher ( self , nom = None ):
""" Affiche le Tableau , comme une image , dans une f e n t r e
Utilise OpenCV pour cela """
if nom is None :
nom = self . nom
cv . ShowImage ( nom , self . vers_iplimage ())
def calc_dimensions ( self ):
" Calcule ␣ les ␣ dimensions ␣ du ␣ Tableau "
11
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
self . hauteur = len ( self . tab )
try :
self . largeur = len ( self . tab [0])
except TypeError :
self . largeur = 1
def c a l c _ e l e m e n t s _ p r o p r e s ( self ):
" Calcule ␣ valeurs ␣ propres ␣ et ␣ vecteurs ␣ propres ␣ du ␣ tableau "
valeurs , vecteurs = np . linalg . eig ( self . tab )
self . val_propres = list ( valeurs )
self . vect_propres = [ Tableau ( vect ) for vect in vecteurs ]
def covariance ( self ):
" Renvoie ␣ le ␣ Tableau ␣ de ␣ covariance ␣ du ␣ Tableau "
return self * self . transposee ()
def dimensions ( self ):
" Renvoie ␣ les ␣ dimensions ␣ -␣ hauteur ␣ * ␣ largeur ␣ -␣ du ␣ tableau "
return self . hauteur , self . largeur
def en_colonne ( self ):
" Renvoie ␣ le ␣ Tableau ␣ comme ␣ un ␣ vecteur ␣ colonne "
return self . redimensionnee ( -1)
def inverse ( self ):
" Renvoie ␣ l ’ inverse ␣ du ␣ Tableau ␣ dans ␣ l ’ a g b r e ␣ des ␣ matrices "
return Tableau ( np . linalg . inv ( self . tab ))
def mahalanobis ( self , autre , cov ):
" Calcule ␣ la ␣ distance ␣ de ␣ Mahalanobis ␣ entre ␣ deux ␣ Tableaux "
a = ( self - autre ) * cov . inverse () * ( self - autre )
return math . sqrt ( a )
def met tre_en_c olonne ( self ):
" Met ␣ le ␣ Tableau ␣ en ␣ forme ␣ de ␣ vecteur ␣ colonne "
self . redimensionner ( -1)
def redimensionnee ( self , nouvelle_forme ):
""" Renvoie le Tableau sous une nouvelle forme
Le nombre total d ’
lments
doit rester le m m e """
tab = Tableau ( np . reshape ( self . tab , nouvelle_forme ))
tab . calc_dimensions ()
return tab
def redimensionner ( self , nouvelle_forme ):
""" Change la forme du Tableau
Le nombre total d ’
lments
reste le m m e """
self . tab = np . reshape ( self . tab , nouvelle_forme )
self . calc_dimensions ()
12
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def transposee ( self ):
" Renvoie ␣ la ␣ t r a n s p o s e ␣ du ␣ Tableau "
return Tableau ( np . transpose ( self . tab ))
def transposer ( self ):
" Transpose ␣ le ␣ Tableau "
self . tab = np . transpose ( self . tab )
def vers_iplimage ( self , profondeur = cv . IPL_DEPTH_8U ):
" Renvoie ␣ l ’ iplimage ␣ quivalente ␣ ( format ␣ OpenCV ) "
temp = cv . CreateImage (( self . largeur , self . hauteur ) ,
cv . IPL_DEPTH_64F , 1)
for (i , ligne ) in enumerate ( self . tab ):
for (j , valeur ) in enumerate ( ligne ):
cv . Set2D ( temp , i , j , valeur )
img = cv . CreateImage (( self . largeur , self . hauteur ) , profondeur , 1)
cv . Convert ( temp , img )
return img
class Collection :
def __init__ ( self , chemin = " faces / " ):
" Constructeur ␣ d ’ un ␣ objet ␣ Collection "
self . liste_faces = [ Tableau ( chemin + fichier )
for fichier in os . listdir ( chemin )]
self . nb_faces = len ( self . liste_faces )
# On p r p a r e la Collection pour la reconnaissance "
self . calc_moyenne ()
self . calc_ecarts ()
self . calc_covariance ()
self . c alc _f ac es_ pr op re s ()
self . calc_coeff_faces ()
def calc_covariance ( self ):
" Calcule ␣ la ␣ covariance ␣ des ␣ faces ␣ n o r m a l i s e s ␣ de ␣ la ␣ Collection "
self . covariance = self . ecarts . transposee (). covariance ()
def calc_moyenne ( self ):
" Calcule ␣ la ␣ moyenne ␣ des ␣ faces ␣ de ␣ la ␣ Collection "
self . moyenne = reduce ( Tableau . __add__ , self . liste_faces ) / self . nb_
def calc_coeff_faces ( self ):
""" Calcule les coefficients des faces de la collection ,
dans la base des faces propres """
self . coeff_faces = Tableau ([ self . coeff ( face )
for face in self . ecarts . transposee ()])
13
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
self . covar_coeff = self . coeff_faces . transposee (). covariance ()
def calc_ecarts ( self ):
" Calcule ␣ le ␣ Tableau ␣ des ␣ carts ␣ ␣ la ␣ moyenne "
self . ecarts = Tableau ([( face - self . moyenne ). en_colonne (). tab
for face in self . liste_faces ]). transposee ()
def c al c_ fac es _p rop re s ( self ):
" Calcule ␣ les ␣ faces ␣ propres ␣ de ␣ la ␣ collection "
self . covariance . c a l c _ e l e m e n t s _ p r o p r e s ()
self . faces_propres = [ self . ecarts * vect
for vect in self . covariance . vect_propres ]
# Pour un affichage , redimensionner en (100 , 80)
def coeff ( self , tableau ):
" Renvoie ␣ les ␣ coefficients ␣ d ’ un ␣ tableau ␣ sur ␣ la ␣ base ␣ des ␣ faces ␣ propre
return [ face * tableau . en_colonne () for face in self . faces_propres ]
def reconnaitre ( self , image ):
""" Reconnait ou non une image .
Sauvegarde la face connue la plus proche dans le fichier resultat . b
et renvoie la distance entre le visage
r e c o n n a t r e et cette fac
coeff = Tableau ( self . coeff ( image - self . moyenne ))
distances = [ coeff . mahalanobis ( coeff_face , self . covar_coeff )
for coeff_face in self . coeff_faces ]
distance_min = min ( distances )
i = distances . index ( distance_min )
cv . SaveImage ( " resultat . bmp " , self . liste_faces [ i ]. vers_iplimage ())
return distance_min
if __name__ == " __main__ " :
collection = Collection ()
# On passe en argument le visage
reconna tre
visage = Tableau ( sys . argv [1])
# On utilise la m t h o d e reconnaissance et on affiche le r s u l t a t en s
# L ’ interface s ’ occupe du reste
print collection . reconnaitre ( visage )
14
Chapitre 2
L’interface graphique
Une fois le moteur réalisé, il nous faut trouver un moyen pratique pour
l’utilisateur afin de s’en servir. Si la ligne de commande peut s’avérer très utile
et pratique, elle est a priori moins accessible pour quelqu’un qui ne connait pas
bien le fonctionnement du programme.
Nous avons donc cherché à réaliser une interface graphique. En plus de fournir un moyen commode d’utiliser le moteur codé précédemment, elle sera utilisable avec n’importe quel moteur de reconnaissance faciale utilisant un format
commun. Ainsi, elle permettra de tester un algorithme de reconnaissance de
façon ergonomique en quelques clics, sans que le programme l’implémentant
n’ait besoin d’effectuer les manipulations communes de lecture à la webcam et
d’affichage du résultat.
Elle permettra également de choisir quel moteur utiliser, et il sera possible
d’en ajouter de nouveaux à l’aide d’un fichier de configuration simple (qui aurait
du être également généré par une interface graphique, ce que nous n’avons pas
pu faire en raison d’un manque de temps).
2.1
Fonctionnement général
Afin de pouvoir utiliser le module de reconnaissance voulu, le logiciel doit
respecter une certaine architecture. L’interface graphique doit être correctement
séparée du moteur, et ils doivent respecter un certain format de communication.
Ainsi, on pourra utiliser n’importe quel moteur respectant ce format avec notre
interface.
De plus, notre logiciel doit pouvoir proposer plusieurs modules, et on doit
pouvoir en rajouter dans le temps. Ils ne seront donc pas implantés en dur
mais il les chargera dynamiquement à partir d’une liste facilement modifiable et
extensible.
Le format choisi pour l’appel des modules est relativement simple tout en
restant flexible. Ils doivent être appelables à l’aide d’un appel système, qui doit
accepter parmi ses arguments le chemin d’une image qui contient le visage à reconnaître. Cet argument peut être placé n’importe où dans la ligne de commande
à lancer. Il devra lire cette image, et écrire une autre image, qui devra contenir
le visage de la personne a priori reconnue. Elle pourra également afficher un
15
texte sur la sortie standard, qui contiendra des informations supplémentaires,
comme par exemple le degré de confiance, le nom de la personne...
Ces modules seront listés dans un fichier au format JSON, qui contiendra
leur nom, la commande à lancer pour les exécuter (formatée avec le nom de
l’image à lire) et une description du module. L’interface graphique commencera
par afficher une image correspondant à la capture de la webcam. Une touche
permettra de prendre la photo. Ensuite, une fenêtre sera affichée avec divers
composants. Une liste déroulante permettra de sélectionner le module souhaité,
et un bouton permettra de rafraîchir cette liste si on a modifié le fichier JSON
depuis le début de l’affichage. La description du module sera également affichée
en dessous. Enfin, il y aura un bouton permettant d’appeler le module choisi
pour effectuer la reconnaissance.
Il était normalement prévu d’avoir la capture webcam affichée en même
temps que la fenêtre principale, mais nous n’avons pas réussi à comprendre
comment afficher cette capture dans PySide. Nous deviosn également fournir
une interface pour ajouter simplement des modules sans éditer à la main le
fichier json, mais nous n’avons pas eu le temps de réaliser ces fonctions.
2.2
Implantation python
Cette interface graphique utilise la librairie PySide, un portage de Qt pour
le langage Python. En PySide, chaque élément graphique est un objet dont la
classe hérite de QWidget (un WInDow gadGET). Il existe de nombreux widgets
prédéfinis. Pour personnaliser son widget, on crée une classe héritant de la classe
correspondante. Par exemple, nous voulons que le pointeur de la souris devienne
la « main » lorsqu’il passe au dessus d’un bouton cliquable. Nous allons donc
redéfinir notre propre classe de boutons cliquables :
1
2
3
4
5
6
class Bouton ( QPushButton ):
""" Classe utilisee pour changer le pointeur
quand on passe la souris sur un bouton """
def __init__ ( self , texte , parent = None ):
super ( Bouton , self ). __init__ ( texte , parent )
self . setCursor ( Qt . Poi nt in gHa nd Cu rso r )
Ici, on ne change qu’un comportement défini une seule fois à l’initialisation du
bouton. On ne doit donc réécrire que le constructeur. On appelle le constructeur
parent, et on rajoute dans ce constructeur le paramétrage souhaité du pointeur.
Ainsi, chaque bouton que l’on créera en utilisant cette classe Bouton profitera
de ce réglage.
La classe Fenêtre, elle, dispose de plus de réglages. On y spécifiera en particulier les différents widgets qu’elle contient, disposés à l’aide d’un QGridLayout,
qui permet de donner leurs positions (dans des coordonnées entières) dans une
grille qui couvre la fenêtre.
Afin de gérer les évènements, on utilise le mécanisme de signaux et de slots
de Qt. Un évènement (par exemple, le clic sur un bouton) correspond à un
« signal », que l’on connecte ) un « slot », c’est à dire à une fonction qui sera
exécutée lors de son déclenchement. Les signaux sont ici des méthodes de la
classe Fenetre.
16
Le code final de l’interface graphique est donc le suivant, suivi par une capture d’écran :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# !/ usr / bin / env python2
# -* - coding : utf -8 -* import cv
import json
import os
import sys
from PySide . QtCore import *
from PySide . QtGui import *
class Bouton ( QPushButton ):
""" Classe u t i l i s e pour changer le pointeur
quand on passe la souris sur un bouton """
def __init__ ( self , texte , parent = None ):
super ( Bouton , self ). __init__ ( texte , parent )
self . setCursor ( Qt . Po int in gH and Cu rs or )
class Fenetre ( QWidget ):
def __init__ ( self , parent = None ):
super ( Fenetre , self ). __init__ ( parent )
# C r a t i o n des
lments
self . setWindowTitle ( " Thira " )
self . liste_modules = QComboBox () # liste d r o u l a n t e
self . recha rger_mod ules = Bouton ( " Recharger ␣ les ␣ modules " )
self . description = QLabel ()
self . action = Bouton ( " Action ␣ ! " )
# Placement des
lments
layout = QGridLayout ()
layout . addWidget ( self . liste_modules , 0 , 0)
layout . addWidget ( self . recharger_modules , 0 , 1)
layout . addWidget ( self . description , 1 , 0 , 1 , 2)
layout . addWidget ( self . action , 2 , 0 , 1 , 2)
self . setLayout ( layout )
# C r a t i o n des slots
self . liste_modules . c ur r en t I nd e xC h a ng e d . connect ( self . changer_module )
self . recha rger_mod ules . clicked . connect ( self . charger_modules )
self . action . clicked . connect ( self . reconnaitre )
# Autoriser le passage
la ligne dans la description
# Sinon elle est en une ligne et on ne peut pas r d u i r e la taille
self . description . setWordWrap ( True )
# On charge les modules disponibles
self . charger_modules ()
17
la c r a t i o n de la f e n t r e
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def reconnaitre ( self ):
" U t i l i s e ␣ lorsqu ’ on ␣ demande ␣ ␣ r e c o n n a t r e ␣ le ␣ visage ␣ pris ␣ en ␣ phot
commande = self . modules [ self . liste_modules . currentIndex ()][ " command
# La commande est sous la forme :
# n o m _ d u _ p r o g r a m m e _ a _ e x e c u t e r {0}
# on a " c {0} d ". format (" bla ") == " cblad "
# Le moteur r p o n d sur la sortie standard
# Actuellement on se contente de le laisser l ’ afficher ,
# on pourrait le rediriger vers une variable pour l ’ exploiter
# par exemple en l ’ affichant dans une f e n t r e
os . system ( commande . format ( " visage . bmp " ))
def changer_module ( self , texte ):
""" Lorsqu ’ on change de module
l ’ aide de la liste d r o u l a n t e ,
on actualise l ’ affichage de la description """
self . description . setText (
self . modules [ self . liste_modules . currentIndex ()][ " description " ])
def charger_modules ( self ):
""" Pour recharger la liste des modules si on a m o d i f i le fichier
modules . js qui contient leur description sous format json """
self . liste_modules . clear ()
self . modules = json . loads ( open ( " modules . js " ). read ())
for module in self . modules :
self . liste_modules . addItem ( module [ " nom " ])
if __name__ == " __main__ " :
# Ce code ne s ’ e x c u t e que si on e x c u t e directement le module ( pas e
webcam = cv . CaptureFromCAM (0)
print " Appuyez ␣ sur ␣ S ␣ pour ␣ prendre ␣ la ␣ photo "
while True :
image = cv . GetSubRect ( cv . QueryFrame ( webcam ) , (281 , 191 , 80 , 100))
cv . ShowImage ( " webcam " , image )
key = cv . WaitKey (1)
# La touche pour prendre la photo est la touche s
# Sous le linux u t i l i s pour tester , la valeur correspondante
#
tait
1048691.
# Sous Windows , la valeur
tait
ord (" s ") , soit 115.
# Sous certaines machine , cette valeur peut
tre " s ".
if key in [1048691 , ord ( " s " ) , " s " ]:
cv . SaveImage ( " visage . bmp " , image )
break
# thira = THIbault RAlph
# sys . argv = les arguments de la commande e x c u t e ,
# par exemple [" interface . py " , " - o3 " , " visage . bmp "]
# Inutile pour nous mais d e m a n d dans le constructeur de QApplication
thira = QApplication ( sys . argv )
18
98
99
100
fenetre = Fenetre ()
fenetre . show ()
sys . exit ( thira . exec_ ())
Figure 2.1 – Capture d’écran du logiciel (Arch Linux, gestionnaire de fenêtre
Awesome)
19
Conclusion
Ce projet nous a permis de découvrir plus profondément plusieurs aspects
du développement d’un logiciel complexe. Il nous a fallu d’abord nous renseigner
sur le côté algorithmique de la reconnaissance de visage, et plus généralement de
la vision par ordinateur, qui est un domaine en pleine expansion. Cette recherche
nous a donc mené à la réalisation d’un moteur de reconnaissance faciale « brut ».
Il nous a fallu résoudre plusieurs problèmes algorithmiques ayant plus ou moins
de rapport avec les mathématiques, discipline importante dans le traitement
d’images en général.
Ensuite, nous avons du concevoir une interface graphique. Il nous a fallu pour
cela comprendre les mécanismes de fonctionnement de ces outils particuliers,
qui ne suivent pas une structure impérative linéaire mais plutôt un modèle
déclaratif, où la bibliothèque utilisée se charge de tout gérer une fois qu’on a
décrit le comportement attendu. Dans la continuité du cours de Génie Logiciel
et dans une démarche d’ingénieur, nous avons également du organiser notre
logiciel final de façon à le rendre modulaire et extensible par la suite.
Ainsi, la réalisation de ce projet nous a permis de comprendre plus en profondeur le développement d’un logiciel, de la conception à la réalisation en passant
par la réflexion logique et algorithmique. Si nous avions plus de temps, nous
aurions aimé développer un peu plus l’interface graphique afin de compléter son
rôle (il ne se serait toutefois agit principalement que de réutiliser des concepts
déjà présents dans notre code final). Il aurait été également intéressant d’affiner
le moteur de reconnaissance, en trouvant un moyen de prendre des images correctement cadrées et en déterminant les seuils de façon rigoureuse. Si d’autres
projets se font sur ce thème, il serait sans doute intéressant de leur fournir
l’interface graphique afin de tester une utilisation par des utilisateurs externes.
20