Comment utiliser Computer Vision pour créer un filtre pour chiens basé sur les émotions dans Python 3

L’auteur a sélectionné Girls Who Code pour recevoir un don dans le cadre du programme Write for DOnations .

introduction

La vision par ordinateur est un sous-domaine de la science informatique qui vise à extraire une compréhension d’ordre supérieur à partir d’images et de vidéos. Ce champ comprend des tâches telles que la détection d’objet, la restauration d’image (complétion de matrice) et le flux optique. La vision par ordinateur alimente des technologies telles que les prototypes de voitures autonomes, les épiceries sans employés, les filtres amusants Snapchat et l’authentificateur de visage de votre appareil mobile.

Dans ce didacticiel, vous allez explorer la vision par ordinateur en utilisant des modèles pré-formés pour créer un filtre pour chiens de type Snapchat. Pour ceux qui ne connaissent pas Snapchat, ce filtre détectera votre visage et superposera ensuite un masque de chien. Vous allez ensuite former un classifieur visage-émotion afin que le filtre puisse sélectionner des masques de chien basés sur des émotions, tels qu’un corgi pour un joyeux ou un carlin pour un triste. En cours de route, vous explorerez également les concepts associés dans les moindres carrés ordinaires et en vision par ordinateur, ce qui vous exposera aux principes fondamentaux de l’apprentissage automatique.

image: https: //assets.digitalocean.com/articles/python3_dogfilter/Hf5RDn3.gif [Un filtre de chien de travail]

Au cours de ce didacticiel, vous utiliserez + OpenCV +, une bibliothèque de vision par ordinateur, + numpy + pour les utilitaires d’algèbre linéaire et + matplotlib + pour le traçage. Vous appliquerez également les concepts suivants lors de la création d’une application de vision par ordinateur:

  • Moindres carrés ordinaires comme technique de régression et de classification.

  • Les bases des réseaux de neurones à gradient stochastique.

Bien qu’il ne soit pas nécessaire de suivre ce didacticiel, il vous sera plus facile de comprendre certaines des explications plus détaillées si vous connaissez bien ces concepts mathématiques:

  • Concepts fondamentaux d’algèbre linéaire: scalaires, vecteurs et matrices.

  • Calcul fondamental: comment prendre un dérivé.

Vous pouvez trouver le code complet de ce tutoriel à l’adresse https://github.com/do-community/emotion-based-dog-filter.

Commençons.

Conditions préalables

Pour compléter ce tutoriel, vous aurez besoin des éléments suivants:

Étape 1 - Création du projet et installation des dépendances

Créons un espace de travail pour ce projet et installons les dépendances dont nous aurons besoin. Nous appellerons notre espace de travail + DogFilter +:

mkdir ~/DogFilter

Accédez au répertoire + DogFilter +:

cd ~/DogFilter

Créez ensuite un nouvel environnement virtuel Python pour le projet:

python3 -m venv

Activez votre environnement.

source /bin/activate

L’invite change, indiquant que l’environnement est actif. Maintenant, installez PyTorch, un framework d’apprentissage en profondeur pour Python que nous utiliserons dans ce tutoriel. Le processus d’installation dépend du système d’exploitation que vous utilisez.

Sur macOS, installez Pytorch avec la commande suivante:

python -m pip install torch== torchvision==

Sous Linux, utilisez les commandes suivantes:

pip install http://download.pytorch.org/whl/cpu/torch--cp35-cp35m-.whl
pip install torchvision

Et pour Windows, installez Pytorch avec ces commandes:

pip install http://download.pytorch.org/whl/cpu/torch--cp35-cp35m-.whl
pip install torchvision

Maintenant, installez les fichiers binaires pré-emballés pour + OpenCV + et + numpy +, qui sont des bibliothèques de vision par ordinateur et d’algèbre linéaire, respectivement. Le premier propose des utilitaires tels que les rotations d’image, et le second propose des utilitaires d’algèbre linéaire tels qu’une inversion de matrice.

python -m pip install opencv-python== numpy==

Enfin, créez un répertoire pour nos ressources, qui contiendra les images que nous utiliserons dans ce tutoriel:

mkdir assets

Avec les dépendances installées, construisons la première version de notre filtre: un détecteur de visage.

Étape 2 - Construire un détecteur de visage

Notre premier objectif est de détecter tous les visages d’une image. Nous allons créer un script qui accepte une seule image et génère une image annotée avec les faces délimitées par des cadres.

Heureusement, au lieu d’écrire notre propre logique de détection de visage, nous pouvons utiliser des modèles pré-formés. Nous allons configurer un modèle, puis charger des paramètres pré-formés. OpenCV facilite cette tâche en fournissant les deux.

OpenCV fournit les paramètres du modèle dans son code source. mais nous avons besoin du chemin absolu de notre OpenCV installé localement pour utiliser ces paramètres. Ce chemin absolu pouvant varier, nous allons télécharger notre propre copie à la place et la placer dans le dossier + assets +:

wget -O assets/haarcascade_frontalface_default.xml https://github.com/opencv/opencv/raw/master/data/haarcascades/haarcascade_frontalface_default.xml

L’option + -O + spécifie la destination sous la forme + assets / haarcascade_frontalface_default.xml. Le deuxième argument est l’URL source.

Nous allons détecter tous les visages dans l’image suivante à partir de https://pexels.com [Pexels] (CC0, link to original image).

image: https: //assets.digitalocean.com/articles/python3_dogfilter/CfoBWbF.png [Image d’enfants]

Tout d’abord, téléchargez l’image. La commande suivante enregistre l’image téléchargée sous le nom + children.png + dans le dossier + assets:

wget -O assets/children.png https://assets.digitalocean.com/articles/python3_dogfilter/CfoBWbF.png

Pour vérifier que l’algorithme de détection fonctionne, nous l’exécuterons sur une image individuelle et enregistrerons l’image annotée résultante sur le disque. Créez un dossier + sorties + pour ces résultats annotés.

mkdir outputs

Créez maintenant un script Python pour le détecteur de visage. Créez le fichier + step_1_face_detect + en utilisant + nano + ou votre éditeur de texte préféré:

nano step_2_face_detect.py

Ajoutez le code suivant au fichier. Ce code importe OpenCV, qui contient les utilitaires d’image et le classificateur de faces. Le reste du code est typique du programme Python.

step_2_face_detect.py

"""Test for face detection"""

import cv2


def main():
   pass

if __name__ == '__main__':
   main()

Maintenant, remplacez + pass + dans la fonction + main + par ce code qui initialise un classificateur de visage à l’aide des paramètres OpenCV que vous avez téléchargés dans votre dossier + assets +:

step_2_face_detect.py

def main():
   # initialize front face classifier
   cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")

Ensuite, ajoutez cette ligne pour charger l’image + children.png +.

step_2_face_detect.py

   frame = cv2.imread('assets/children.png')

Ajoutez ensuite ce code pour convertir l’image en noir et blanc, car le classificateur a été entraîné sur les images en noir et blanc. Pour ce faire, nous convertissons en niveaux de gris, puis discrétisons l’histogramme:

step_2_face_detect.py

   # Convert to black-and-white
   gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
   blackwhite = cv2.equalizeHist(gray)

Utilisez ensuite la fonction https://docs.opencv.org/2.4/modules/objdetect/doc/cascade_classification.html#cascadeclassifier-detectmultiscale [+ detectMultiScale +] de OpenCV pour détecter tous les visages de l’image.

step_2_face_detect.py

   rects = cascade.detectMultiScale(
       blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
       flags=cv2.CASCADE_SCALE_IMAGE)
  • + scaleFactor + spécifie combien l’image est réduite le long de chaque dimension.

  • + minNeothers + indique le nombre de rectangles voisins qu’un rectangle candidat doit être conservé.

  • + minSize + est la taille minimale autorisée de l’objet détecté. Les objets plus petits que celui-ci sont rejetés.

Le type de retour est une liste de tuples, où chaque tuple a quatre nombres désignant le minimum x, le minimum y, la largeur, et la hauteur du rectangle dans cet ordre.

Parcourez tous les objets détectés et tracez-les sur l’image en vert à l’aide de https://docs.opencv.org/2.4/modules/core/doc/drawing_functions.html#rectangle [+ cv2.rectangle +]:

step_2_face_detect.py

   for x, y, w, h in rects:
       cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
  • Les deuxième et troisième arguments sont des angles opposés du rectangle.

  • Le quatrième argument est la couleur à utiliser. + (0, 255, 0) + correspond au vert pour notre espace colorimétrique RVB.

  • Le dernier argument dénote la largeur de notre ligne.

Enfin, écrivez l’image avec les cadres de sélection dans un nouveau fichier dans + output / children_detected.png +:

step_2_face_detect.py

   cv2.imwrite('outputs/children_detected.png', frame)

Votre script complété devrait ressembler à ceci:

step_2_face_detect.py

"""Tests face detection for a static image."""

import cv2


def main():

   # initialize front face classifier
   cascade = cv2.CascadeClassifier(
       "assets/haarcascade_frontalface_default.xml")

   frame = cv2.imread('assets/children.png')

   # Convert to black-and-white
   gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
   blackwhite = cv2.equalizeHist(gray)

   rects = cascade.detectMultiScale(
       blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
   flags=cv2.CASCADE_SCALE_IMAGE)

   for x, y, w, h in rects:
       cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

   cv2.imwrite('outputs/children_detected.png', frame)

if __name__ == '__main__':
   main()

Enregistrez le fichier et quittez votre éditeur. Puis lancez le script:

python step_2_face_detect.py

Ouvrez + output / children_detected.png +. L’image suivante montre les faces délimitées par des zones:

image: https: //assets.digitalocean.com/articles/python3_dogfilter/x0fUqyk.png [Image d’enfants avec des cadres de sélection]

À ce stade, vous avez un détecteur de visage fonctionnel. Il accepte une image en tant qu’entrée et dessine des cadres de délimitation autour de toutes les faces de l’image, produisant l’image annotée. Appliquons maintenant cette même détection à un flux de caméra en direct.

Étape 3 - Lier le flux de caméra

L’objectif suivant est de relier la caméra de l’ordinateur au détecteur de visage. Au lieu de détecter les visages dans une image statique, vous détecterez tous les visages à partir de la caméra de votre ordinateur. Vous collecterez les entrées de la caméra, détecterez et annoterez tous les visages, puis afficherez l’image annotée à l’utilisateur. Vous allez continuer à partir du script de l’étape 2, commencez donc par dupliquer ce script:

cp step_2_face_detect.py step_3_camera_face_detect.py

Puis ouvrez le nouveau script dans votre éditeur:

nano step_3_camera_face_detect.py

Vous mettrez à jour la fonction + main + en utilisant certains éléments de cette https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_gui/py_video_display/py_video_display.html#capture-video-from-cameraarthtest script] de la documentation officielle OpenCV. Commencez par initialiser un objet + VideoCapture + configuré pour capturer l’affichage en direct de la caméra de votre ordinateur. Placez ceci au début de la fonction + main +, avant l’autre code de la fonction:

step_3_camera_face_detect.py

def main():

   ...

En commençant par la ligne définissant + frame +, indiquez tout votre code existant, en plaçant tout le code dans une boucle + while +.

step_3_camera_face_detect.py

       frame = cv2.imread('assets/children.png')
       ...
       for x, y, w, h in rects:
           cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

       cv2.imwrite('outputs/children_detected.png', frame)

Remplacez la ligne définissant + frame au début de la boucle` + while`. Au lieu de lire une image sur le disque, vous lisez maintenant de la caméra:

step_3_camera_face_detect.py

Remplacez la ligne + cv2.imwrite (…​) + à la fin de la boucle + while +. Au lieu d’écrire une image sur le disque, vous afficherez l’image annotée sur l’écran de l’utilisateur:

step_3_camera_face_detect.py

Ajoutez également du code pour surveiller les entrées au clavier afin d’arrêter le programme. Vérifiez si l’utilisateur frappe le caractère + q + et, si c’est le cas, quittez l’application. Juste après + cv2.imshow (…​) +, ajoutez ce qui suit:

step_3_camera_face_detect.py

...
       cv2.imshow('frame', frame)


...

La ligne + cv2.waitkey (1) + arrête le programme pendant 1 milliseconde pour que l’image capturée puisse être affichée à nouveau à l’utilisateur.

Enfin, relâchez la capture et fermez toutes les fenêtres. Placez-le en dehors de la boucle while while pour mettre fin à la fonction` + main`.

step_3_camera_face_detect.py

...

   while True:
   ...

Votre script devrait ressembler à ceci:

step_3_camera_face_detect.py

"""Test for face detection on video camera.

Move your face around and a green box will identify your face.
With the test frame in focus, hit `q` to exit.
Note that typing `q` into your terminal will do nothing.
"""

import cv2


def main():
   cap = cv2.VideoCapture(0)

   # initialize front face classifier
   cascade = cv2.CascadeClassifier(
       "assets/haarcascade_frontalface_default.xml")

   while True:
       # Capture frame-by-frame
       ret, frame = cap.read()

       # Convert to black-and-white
       gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
       blackwhite = cv2.equalizeHist(gray)

       # Detect faces
       rects = cascade.detectMultiScale(
           blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
           flags=cv2.CASCADE_SCALE_IMAGE)

       # Add all bounding boxes to the image
       for x, y, w, h in rects:
           cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

       # Display the resulting frame
       cv2.imshow('frame', frame)
       if cv2.waitKey(1) & 0xFF == ord('q'):
           break

   # When everything done, release the capture
   cap.release()
   cv2.destroyAllWindows()


if __name__ == '__main__':
   main()

Enregistrez le fichier et quittez votre éditeur.

Maintenant, lancez le script de test.

python step_3_camera_face_detect.py

Ceci active votre caméra et ouvre une fenêtre affichant le flux de votre caméra. Votre visage sera encadré par un carré vert en temps réel:

image: https: //assets.digitalocean.com/articles/python3_dogfilter/a7lyf7q.gif [Détecteur de visage de travail]

Notre prochain objectif est de prendre les visages détectés et de superposer des masques de chien sur chacun d’eux.

Étape 4 - Construire le filtre chien

Avant de construire le filtre lui-même, voyons comment les images sont représentées numériquement. Cela vous donnera le fond nécessaire pour modifier les images et finalement appliquer un filtre chien.

Regardons un exemple. Nous pouvons construire une image en noir et blanc en utilisant des nombres, où + 0 + correspond au noir et + 1 + correspond au blanc.

Concentrez-vous sur la ligne de division entre 1 et 0. Quelle forme voyez-vous?

0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 1 1 1 0 0 0
0 0 1 1 1 1 1 0 0
0 0 0 1 1 1 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0

L’image est un diamant. Si vous sauvegardez cette matrix de valeurs sous forme d’image. Cela nous donne l’image suivante:

image: https: //assets.digitalocean.com/articles/python3_dogfilter/QPontyM.png [Le diamant en image]

Nous pouvons utiliser n’importe quelle valeur comprise entre 0 et 1, telle que 0,1, 0,26 ou 0,74391. Les nombres plus proches de 0 sont plus sombres et les nombres plus proches de 1 sont plus clairs. Cela nous permet de représenter le blanc, le noir et toutes les nuances de gris. C’est une excellente nouvelle pour nous, car nous pouvons désormais construire n’importe quelle image en niveaux de gris en utilisant 0, 1 et toute valeur intermédiaire. Considérez ce qui suit, par exemple. Pouvez-vous dire ce que c’est? Encore une fois, chaque nombre correspond à la couleur d’un pixel.

1  1  1  1  1  1  1  1  1  1  1  1
1  1  1  1  0  0  0  0  1  1  1  1
1  1  0  0 .4 .4 .4 .4  0  0  1  1
1  0 .4 .4 .5 .4 .4 .4 .4 .4  0  1
1  0 .4 .5 .5 .5 .4 .4 .4 .4  0  1
0 .4 .4 .4 .5 .4 .4 .4 .4 .4 .4  0
0 .4 .4 .4 .4  0  0 .4 .4 .4 .4  0
0  0 .4 .4  0  1 .7  0 .4 .4  0  0
0  1  0  0  0 .7 .7  0  0  0  1  0
1  0  1  1  1  0  0 .7 .7 .4  0  1
1  0 .7  1  1  1 .7 .7 .7 .7  0  1
1  1  0  0 .7 .7 .7 .7  0  0  1  1
1  1  1  1  0  0  0  0  1  1  1  1
1  1  1  1  1  1  1  1  1  1  1  1

Re-rendu sous forme d’image, vous pouvez maintenant dire qu’il s’agit en fait d’un Poké Ball:

image: https: //assets.digitalocean.com/articles/python3_dogfilter/RwAXIGE.png [Pokeball comme image]

Vous avez maintenant vu comment les images en noir et blanc et en niveaux de gris sont représentées numériquement. Pour introduire la couleur, nous avons besoin d’un moyen de coder plus d’informations. Une image a sa hauteur et sa largeur exprimées sous la forme + h x w +.

image: https: //assets.digitalocean.com/articles/python3_dogfilter/sctg0YN.png [Image]

Dans la représentation en niveaux de gris actuelle, chaque pixel a une valeur comprise entre 0 et 1. Nous pouvons dire de manière équivalente que notre image a les dimensions + h x w x 1 +. En d’autres termes, chaque position «+ (x, y) +» dans notre image n’a qu’une valeur.

image: https: //assets.digitalocean.com/articles/python3_dogfilter/58GGRPe.png [image en niveaux de gris]

Pour une représentation des couleurs, nous représentons la couleur de chaque pixel en utilisant trois valeurs comprises entre 0 et 1. Un chiffre correspond au «degré de rouge», un au «degré de vert» et le dernier au «degré de bleu». Nous appelons cela l’espace colorimétrique RVB. Cela signifie que pour chaque position "+ (x, y) " de notre image, nous avons trois valeurs ` (r, g, b) `. En conséquence, notre image est maintenant ` h x w x 3 +`:

image: https: //assets.digitalocean.com/articles/python3_dogfilter/kXL8Mms.png [Image en couleur]

Ici, chaque nombre va de 0 à 255 au lieu de 0 à 1, mais l’idée est la même. Différentes combinaisons de chiffres correspondent à différentes couleurs, telles que le violet foncé + (102, 0, 204) + ou l’orange lumineux + (255, 153, 51) +. Les plats à emporter sont les suivants:

  1. Chaque image sera représentée par une boîte de chiffres comportant trois dimensions: hauteur, largeur et canaux de couleur. Manipuler directement cette boîte de nombres équivaut à manipuler l’image.

  2. Nous pouvons aussi aplatir cette case pour ne devenir qu’une liste de nombres. De cette manière, notre image devient un vector. Nous ferons référence plus tard aux images en tant que vecteurs.

Maintenant que vous comprenez comment les images sont représentées numériquement, vous êtes bien équipé pour commencer à appliquer des masques de chien sur les visages. Pour appliquer un masque de chien, vous allez remplacer les valeurs de l’image enfant par des pixels de masque de chien non blanc. Pour commencer, vous travaillerez avec une seule image. Téléchargez ce recadrage de visage à partir de l’image que vous avez utilisée à l’étape 2.

wget -O assets/child.png https://assets.digitalocean.com/articles/python3_dogfilter/alXjNK1.png

image: https: //assets.digitalocean.com/articles/python3_dogfilter/alXjNK1.png [Face tronquée]

De plus, téléchargez le masque de chien suivant. Les masques de chien utilisés dans ce tutoriel sont mes propres dessins, qui sont maintenant publiés dans le domaine public sous une licence CC0.

image: https: //assets.digitalocean.com/articles/python3_dogfilter/ED32BCs.png [Masque de chien]

Téléchargez ceci avec + wget +:

wget -O assets/dog.png https://assets.digitalocean.com/articles/python3_dogfilter/ED32BCs.png

Créez un nouveau fichier nommé + step_4_dog_mask_simple.py + qui contiendra le code du script qui applique le masque de chien aux visages:

nano step_4_dog_mask_simple.py

Ajoutez le passe-partout suivant pour le script Python et importez les bibliothèques OpenCV et + numpy +:

step_4_dog_mask_simple.py

"""Test for adding dog mask"""

import cv2
import numpy as np


def main():
   pass

if __name__ == '__main__':
   main()

Remplacez + pass + dans la fonction + main + par ces deux lignes qui chargent en mémoire l’image d’origine et le masque de chien.

step_4_dog_mask_simple.py

...
def main():
   face = cv2.imread('assets/child.png')
   mask = cv2.imread('assets/dog.png')

Ensuite, adaptez le masque de chien à l’enfant. La logique est plus compliquée que ce que nous avons fait précédemment, nous allons donc créer une nouvelle fonction appelée + apply_mask + pour modulariser notre code. Directement après les deux lignes qui chargent les images, ajoutez cette ligne qui appelle la fonction + apply_mask +:

step_4_dog_mask_simple.py

...
   face_with_mask = apply_mask(face, mask)

Créez une nouvelle fonction appelée + apply_mask + et placez-la au-dessus de la fonction + main +:

step_4_dog_mask_simple.py

...
def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   pass

def main():
...

À ce stade, votre fichier devrait ressembler à ceci:

step_4_dog_mask_simple.py

"""Test for adding dog mask"""

import cv2
import numpy as np


def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   pass


def main():
   face = cv2.imread('assets/child.png')
   mask = cv2.imread('assets/dog.png')
   face_with_mask = apply_mask(face, mask)

if __name__ == '__main__':
   main()

Construisons la fonction + apply_mask +. Notre objectif est d’appliquer le masque sur le visage de l’enfant. Cependant, nous devons conserver les proportions de notre masque de chien. Pour ce faire, nous devons calculer explicitement les dimensions finales de notre masque de chien. Dans la fonction + apply_mask +, remplacez + + pass + par ces deux lignes qui extraient la hauteur et la largeur des deux images:

step_4_dog_mask_simple.py

...
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

Ensuite, déterminez quelle dimension doit être «réduite davantage». Pour être précis, nous avons besoin du resserrement des deux contraintes. Ajoutez cette ligne à la fonction + apply_mask +:

step_4_dog_mask_simple.py

...

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)

Puis calculez la nouvelle forme en ajoutant ce code à la fonction:

step_4_dog_mask_simple.py

...
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)

Ici, nous convertissons les nombres en nombres entiers, car la fonction + redimensionner + a besoin de dimensions intégrales.

Ajoutez maintenant ce code pour redimensionner le masque de chien à la nouvelle forme:

step_4_dog_mask_simple.py

...

   # Add mask to face - ensure mask is centered
   resized_mask = cv2.resize(mask, new_mask_shape)

Enfin, écrivez l’image sur le disque pour vérifier que votre masque de chien redimensionné est correct après l’exécution du script:

step_4_dog_mask_simple.py

   cv2.imwrite('outputs/resized_dog.png', resized_mask)

Le script terminé devrait ressembler à ceci:

step_4_dog_mask_simple.py

"""Test for adding dog mask"""
import cv2
import numpy as np

def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)

   # Add mask to face - ensure mask is centered
   resized_mask = cv2.resize(mask, new_mask_shape)
   cv2.imwrite('outputs/resized_dog.png', resized_mask)


def main():
   face = cv2.imread('assets/child.png')
   mask = cv2.imread('assets/dog.png')
   face_with_mask = apply_mask(face, mask)

if __name__ == '__main__':
   main()

Enregistrez le fichier et quittez votre éditeur. Exécutez le nouveau script:

python step_4_dog_mask_simple.py

Ouvrez l’image dans + output / resized_dog.png + pour vérifier que le masque a été redimensionné correctement. Il correspondra au masque de chien présenté plus haut dans cette section.

Maintenant, ajoutez le masque de chien à l’enfant. Ouvrez à nouveau le fichier + step_4_dog_mask_simple.py + et retournez à la fonction + apply_mask +:

nano step_4_dog_mask_simple.py

Premièrement, supprimez la ligne de code qui écrit le masque redimensionné de la fonction + apply_mask + puisque vous n’en avez plus besoin:

   ...

À la place, appliquez vos connaissances en matière de représentation d’images depuis le début de cette section pour modifier l’image. Commencez par créer une copie de l’image de l’enfant. Ajoutez cette ligne à la fonction + apply_mask +:

step_4_dog_mask_simple.py

...
   face_with_mask = face.copy()

Ensuite, trouvez toutes les positions où le masque de chien n’est ni blanc ni presque blanc. Pour ce faire, vérifiez si la valeur de pixel est inférieure à 250 sur tous les canaux de couleur, car nous nous attendions à ce qu’un pixel presque blanc soit proche de + [255, 255, 255] +. Ajoutez ce code:

step_4_dog_mask_simple.py

...
   non_white_pixels = (resized_mask < 250).all(axis=2)

À ce stade, l’image du chien est tout au plus aussi grande que celle de l’enfant. Nous voulons centrer l’image du chien sur le visage, alors calculez le décalage nécessaire pour centrer l’image du chien en ajoutant ce code à + ​​apply_mask +:

step_4_dog_mask_simple.py

...
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)

Copiez tous les pixels non blancs de l’image du chien dans l’image de l’enfant. Étant donné que l’image de l’enfant peut être plus grande que celle du chien, nous devons prendre un sous-ensemble de l’image de l’enfant:

step_4_dog_mask_simple.py

   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
           resized_mask[non_white_pixels]

Puis retournez le résultat:

step_4_dog_mask_simple.py

   return face_with_mask

Dans la fonction + main +, ajoutez ce code pour écrire le résultat de la fonction + apply_mask + dans une image de sortie afin de pouvoir vérifier manuellement le résultat:

step_4_dog_mask_simple.py

...
   face_with_mask = apply_mask(face, mask)

Votre script terminé ressemblera à ceci:

step_4_dog_mask_simple.py

"""Test for adding dog mask"""

import cv2
import numpy as np


def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)
   resized_mask = cv2.resize(mask, new_mask_shape)

   # Add mask to face - ensure mask is centered
   face_with_mask = face.copy()
   non_white_pixels = (resized_mask < 250).all(axis=2)
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)
   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
        resized_mask[non_white_pixels]

   return face_with_mask

def main():
   face = cv2.imread('assets/child.png')
   mask = cv2.imread('assets/dog.png')
   face_with_mask = apply_mask(face, mask)
   cv2.imwrite('outputs/child_with_dog_mask.png', face_with_mask)

if __name__ == '__main__':
   main()

Enregistrez le script et exécutez-le:

python step_4_dog_mask_simple.py

Vous aurez l’image suivante d’un enfant avec un masque de chien dans + output / child_with_dog_mask.png +:

image: https: //assets.digitalocean.com/articles/python3_dogfilter/ZEn0RsJ.png [Image d’un enfant avec un masque de chien]

Vous avez maintenant un utilitaire qui applique des masques de chien aux visages. Maintenant, utilisons ce que vous avez construit pour ajouter le masque de chien en temps réel.

Nous reprendrons là où nous en étions à l’étape 3. Copiez + step_3_camera_face_detect.py + dans + step_4_dog_mask.py +.

cp step_3_camera_face_detect.py step_4_dog_mask.py

Ouvrez votre nouveau script.

nano step_4_dog_mask.py

Commencez par importer la bibliothèque NumPy en haut du script:

step_4_dog_mask.py

import numpy as np
...

Ajoutez ensuite la fonction + apply_mask + de votre travail précédent dans ce nouveau fichier au-dessus de la fonction + main +:

step_4_dog_mask.py

def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)
   resized_mask = cv2.resize(mask, new_mask_shape)

   # Add mask to face - ensure mask is centered
   face_with_mask = face.copy()
   non_white_pixels = (resized_mask < 250).all(axis=2)
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)
   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
        resized_mask[non_white_pixels]

   return face_with_mask
...

Deuxièmement, localisez cette ligne dans la fonction + main +:

step_4_dog_mask.py

   cap = cv2.VideoCapture(0)

Ajoutez ce code après cette ligne pour charger le masque de chien:

step_4_dog_mask.py

   cap = cv2.VideoCapture(0)



   ...

Ensuite, dans la boucle + while +, localisez cette ligne:

step_4_dog_mask.py

       ret, frame = cap.read()

Ajoutez cette ligne après pour extraire la hauteur et la largeur de l’image:

step_4_dog_mask.py

       ret, frame = cap.read()

       ...

Ensuite, supprimez la ligne dans + main + qui dessine des cadres de délimitation. Vous trouverez cette ligne dans la boucle + pour + qui itère sur les visages détectés:

step_4_dog_mask.py

       for x, y, w, h in rects:
       ...

       ...

A sa place, ajoutez ce code qui recadre le cadre. Pour des raisons esthétiques, nous découpons une zone légèrement plus grande que le visage.

step_4_dog_mask.py

       for x, y, w, h in rects:
           # crop a frame slightly larger than the face
           y0, y1 = int(y - 0.25*h), int(y + 0.75*h)
           x0, x1 = x, x + w

Introduisez une vérification au cas où le visage détecté serait trop proche du bord.

step_4_dog_mask.py

           # give up if the cropped frame would be out-of-bounds
           if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h:
               continue

Enfin, insérez le visage masqué dans l’image.

step_4_dog_mask.py

           # apply mask
           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

Vérifiez que votre script ressemble à ceci:

step_4_dog_mask.py

"""Real-time dog filter

Move your face around and a dog filter will be applied to your face if it is not out-of-bounds. With the test frame in focus, hit `q` to exit. Note that typing `q` into your terminal will do nothing.
"""

import numpy as np
import cv2


def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)
   resized_mask = cv2.resize(mask, new_mask_shape)

   # Add mask to face - ensure mask is centered
   face_with_mask = face.copy()
   non_white_pixels = (resized_mask < 250).all(axis=2)
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)
   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
        resized_mask[non_white_pixels]

   return face_with_mask

def main():
   cap = cv2.VideoCapture(0)

   # load mask
   mask = cv2.imread('assets/dog.png')

   # initialize front face classifier
   cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")

   while(True):
       # Capture frame-by-frame
       ret, frame = cap.read()
       frame_h, frame_w, _ = frame.shape

       # Convert to black-and-white
       gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
       blackwhite = cv2.equalizeHist(gray)

       # Detect faces
       rects = cascade.detectMultiScale(
           blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
           flags=cv2.CASCADE_SCALE_IMAGE)

       # Add mask to faces
       for x, y, w, h in rects:
           # crop a frame slightly larger than the face
           y0, y1 = int(y - 0.25*h), int(y + 0.75*h)
           x0, x1 = x, x + w

           # give up if the cropped frame would be out-of-bounds
           if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h:
               continue

           # apply mask
           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

       # Display the resulting frame
       cv2.imshow('frame', frame)
       if cv2.waitKey(1) & 0xFF == ord('q'):
           break

   # When everything done, release the capture
   cap.release()
   cv2.destroyAllWindows()


if __name__ == '__main__':
   main()

Enregistrez le fichier et quittez votre éditeur. Ensuite, lancez le script.

python step_4_dog_mask.py

Vous avez maintenant un filtre chien en temps réel en cours d’exécution. Le script fonctionnera également avec plusieurs visages sur la photo afin que vous puissiez réunir vos amis pour une vérification automatique.

image: https: //assets.digitalocean.com/articles/python3_dogfilter/g9CiUD1.gif [GIF pour le filtre du chien de travail]

Ceci conclut notre premier objectif principal dans ce didacticiel, qui consiste à créer un filtre pour chiens de type Snapchat. Utilisons maintenant l’expression du visage pour déterminer le masque de chien appliqué sur un visage.

Étape 5 - Construire un classifieur de base d’émotions pour le visage en utilisant les moindres carrés

Dans cette section, vous allez créer un classificateur d’émotions pour appliquer différents masques en fonction des émotions affichées. Si vous souriez, le filtre appliquera un masque de corgi. Si vous froncez les sourcils, il appliquera un masque de carlin. En cours de route, vous explorerez le cadre des moindres carrés, qui est fondamental pour comprendre et discuter des concepts d’apprentissage automatique.

Pour comprendre comment traiter nos données et produire des prévisions, nous allons d’abord explorer brièvement les modèles d’apprentissage automatique.

Nous devons poser deux questions pour chaque modèle considéré. Pour le moment, ces deux questions suffiront à différencier les modèles:

  1. Entrée: Quelle information est le modèle donné?

  2. Résultat: Qu’est-ce que le modèle tente de prédire?

À haut niveau, l’objectif est de développer un modèle de classification des émotions. Le modèle est:

  1. Entrée: images données de visages.

  2. Sortie: prédit l’émotion correspondante.

model: face -> emotion

L’approche que nous allons utiliser est la plus petite des carrés; nous prenons un ensemble de points et nous trouvons une ligne de meilleur ajustement. La ligne de meilleur ajustement, illustrée dans l’image suivante, correspond à notre modèle.

image: https: //assets.digitalocean.com/articles/python3_dogfilter/lYQDlWs.png [Moindres carrés]

Considérez l’entrée et la sortie pour notre ligne:

  1. Entrée: étant donné les coordonnées + x +.

  2. Sortie: prédit la coordonnée $ y $ correspondante.

least squares line: x -> y

Notre entrée + x + doit représenter les visages et notre sortie + y + doit représenter l’émotion, afin que nous puissions utiliser les moindres carrés pour la classification des émotions:

  • + x → face +: Au lieu d’utiliser one number pour + x +, nous utiliserons un vector de valeurs pour + x +. Ainsi, + x + peut représenter des images de visages. L’article Moins carrés moindres explique pourquoi vous pouvez utiliser un vecteur de valeurs pour + x +.

  • + y → émotion +: chaque émotion correspondra à un nombre. Par exemple, «fâché» est 0, «triste» est 1 et «heureux» est 2. De cette façon, + y + peut représenter des émotions. Cependant, notre ligne n’est pas contrainte de produire les valeurs + y + 0, 1 et 2. Il y a un nombre infini de valeurs y possibles - il pourrait être 1.2, 3.5 ou 10003.42. Comment traduire ces valeurs + y + en entiers correspondant à des classes? Voir l’article One-Hot Encoding pour plus de détails et d’explications.

Forts de cette connaissance de base, vous allez créer un classificateur simple des moindres carrés à l’aide d’images vectorisées et d’étiquettes codées à une étape. Vous y arriverez en trois étapes:

  1. Prétraitez les données: comme expliqué au début de cette section, nos échantillons sont des vecteurs où chaque vecteur code une image d’un visage. Nos étiquettes sont des entiers correspondant à une émotion et nous appliquerons un encodage à chaud à ces étiquettes.

  2. Spécifiez et entraînez le modèle: utilisez la solution des moindres carrés sous forme fermée, + w ^ * +.

  3. Exécutez une prédiction en utilisant le modèle: Prenez l’argmax de + Xw ^ * + pour obtenir les émotions prédites.

Commençons.

Commencez par configurer un répertoire contenant les données:

mkdir data

Ensuite, téléchargez les données, organisées par Pierre-Luc Carrier et Aaron Courville, d’une classification de Face Emotion 2013 competition. sur Kaggle.

wget -O data/fer2013.tar https://bitbucket.org/alvinwan/adversarial-examples-in-computer-vision-building-then-fooling/raw/babfe4651f89a398c4b3fdbdd6d7a697c5104cff/fer2013.tar

Accédez au répertoire + data + et décompressez les données.

cd data
tar -xzf fer2013.tar

Nous allons maintenant créer un script pour exécuter le modèle des moindres carrés. Accédez à la racine de votre projet:

cd ~/DogFilter

Créez un nouveau fichier pour le script:

nano step_5_ls_simple.py

Ajoutez le standard Python et importez les packages dont vous aurez besoin:

step_5_ls_simple.py

"""Train emotion classifier using least squares."""

import numpy as np

def main():
   pass

if __name__ == '__main__':
   main()

Ensuite, chargez les données en mémoire. Remplacez + pass + dans votre fonction + main + par le code suivant:

step_5_ls_simple.py

   # load data
   with np.load('data/fer2013_train.npz') as data:
       X_train, Y_train = data['X'], data['Y']

   with np.load('data/fer2013_test.npz') as data:
       X_test, Y_test = data['X'], data['Y']

Maintenant, encodez les étiquettes à chaud. Pour ce faire, construisez la matrice d’identité avec + numpy + puis indexez-vous dans cette matrice en utilisant notre liste d’étiquettes:

step_5_ls_simple.py

   # one-hot labels
   I = np.eye(6)
   Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]

Ici, nous utilisons le fait que la ligne «+ i » de la matrice d’identité est zéro, à l’exception de l’entrée « i ». Ainsi, la i-ème ligne est l'encodage one-hot pour l'étiquette de la classe ` i `. De plus, nous utilisons l'indexation avancée ` numpy `, où ` [a, b, c, d] [[1, 3]] = = b, d] +`.

Le calcul + (X ^ TX) ^ {- 1} + prendrait trop de temps sur du matériel standard, car + X ^ TX + est une matrice + 2304x2304 + avec plus de quatre millions de valeurs, nous allons donc réduire ce temps en sélectionnant uniquement les 100 premières fonctionnalités. Ajoutez ce code:

step_5_ls_simple.py

...
   # select first 100 dimensions
   A_train, A_test = X_train[:, :100], X_test[:, :100]

Ensuite, ajoutez ce code pour évaluer la solution des moindres carrés en forme fermée:

step_5_ls_simple.py

...
   # train model
   w = np.linalg.inv(A_train.T.dot(A_train)).dot(A_train.T.dot(Y_oh_train))

Définissez ensuite une fonction d’évaluation pour les ensembles de formation et de validation. Placez ceci avant votre fonction + main +:

step_5_ls_simple.py

def evaluate(A, Y, w):
   Yhat = np.argmax(A.dot(w), axis=1)
   return np.sum(Yhat == Y) / Y.shape[0]

Pour estimer les étiquettes, prenons le produit intérieur avec chaque échantillon et obtenons les indices des valeurs maximales en utilisant + np.argmax +. Ensuite, nous calculons le nombre moyen de classifications correctes. Ce nombre final est votre précision.

Enfin, ajoutez ce code à la fin de la fonction + main + pour calculer l’exactitude de la formation et de la validation à l’aide de la fonction + évaluer + que vous venez d’écrire:

step_5_ls_simple.py

   # evaluate model
   ols_train_accuracy = evaluate(A_train, Y_train, w)
   print('(ols) Train Accuracy:', ols_train_accuracy)
   ols_test_accuracy = evaluate(A_test, Y_test, w)
   print('(ols) Test Accuracy:', ols_test_accuracy)

Vérifiez que votre script correspond aux éléments suivants:

step_5_ls_simple.py

"""Train emotion classifier using least squares."""

import numpy as np


def evaluate(A, Y, w):
   Yhat = np.argmax(A.dot(w), axis=1)
   return np.sum(Yhat == Y) / Y.shape[0]

def main():

   # load data
   with np.load('data/fer2013_train.npz') as data:
       X_train, Y_train = data['X'], data['Y']

   with np.load('data/fer2013_test.npz') as data:
       X_test, Y_test = data['X'], data['Y']

   # one-hot labels
   I = np.eye(6)
   Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]

   # select first 100 dimensions
   A_train, A_test = X_train[:, :100], X_test[:, :100]

   # train model
   w = np.linalg.inv(A_train.T.dot(A_train)).dot(A_train.T.dot(Y_oh_train))

   # evaluate model
   ols_train_accuracy = evaluate(A_train, Y_train, w)
   print('(ols) Train Accuracy:', ols_train_accuracy)
   ols_test_accuracy = evaluate(A_test, Y_test, w)
   print('(ols) Test Accuracy:', ols_test_accuracy)


if __name__ == '__main__':
   main()

Enregistrez votre fichier, quittez votre éditeur et exécutez le script Python.

python step_5_ls_simple.py

Vous verrez le résultat suivant:

Output(ols) Train Accuracy: 0.4748918316507146
(ols) Test Accuracy: 0.45280545359202934

Notre modèle offre une précision de train de 47,5%. Nous répétons cela sur le jeu de validation pour obtenir une précision de 45,3%. Pour un problème de classification à trois voies, 45,3% est raisonnablement supérieur à deviner, ce qui correspond à 33 \%. C’est notre classificateur de départ pour la détection des émotions et, à la prochaine étape, vous allez vous baser sur ce modèle des moindres carrés pour améliorer la précision. Plus la précision est élevée, plus le filtre pour chiens basé sur les émotions est fiable et permet de trouver le filtre pour chiens approprié pour chaque émotion détectée.

Étape 6 - Améliorer la précision en mettant en valeur les entrées

Nous pouvons utiliser un modèle plus expressif pour améliorer la précision. Pour ce faire, nous traitons nos entrées.

L’image d’origine nous indique que la position (+0, 0 +) est en rouge, (+1, 0 +) est en marron, etc. Une image personnalisée peut nous indiquer qu’il y a un chien en haut à gauche de l’image, une personne au milieu, etc. La fonctionnalité est puissante, mais sa définition précise dépasse le cadre de ce tutoriel.

Nous allons utiliser un approximation pour le noyau de la fonction de base radiale (RBF), en utilisant une matrice gaussienne aléatoire. Nous n’entrerons pas dans les détails dans ce tutoriel. Au lieu de cela, nous traiterons cela comme une boîte noire qui calcule des caractéristiques d’ordre supérieur pour nous.

Nous allons continuer là où nous nous étions arrêtés à l’étape précédente. Copiez le script précédent pour avoir un bon point de départ:

cp step_5_ls_simple.py step_6_ls_simple.py

Ouvrez le nouveau fichier dans votre éditeur:

nano step_6_ls_simple.py

Nous allons commencer par créer la matrice aléatoire. Encore une fois, nous n’utiliserons que 100 fonctionnalités dans notre nouvel espace de fonctionnalités.

Localisez la ligne suivante en définissant + A_train + et + A_test +:

step_6_ls_simple.py

   # select first 100 dimensions
   A_train, A_test = X_train[:, :100], X_test[:, :100]

Directement au-dessus de cette définition de + A_train + et + A_test +, ajoutez une matrice de caractéristiques aléatoire:

step_6_ls_simple.py

   # select first 100 dimensions
   A_train, A_test = X_train[:, :100], X_test[:, :100]  ...

Remplacez ensuite les définitions de + A_train + et + A_test +. Nous redéfinissons nos matrices, appelées matrices design, en utilisant cette fonction aléatoire.

step_6_ls_simple.py

   A_train, A_test = X_train.dot(W), X_test.dot(W)

Enregistrez votre fichier et exécutez le script.

python step_6_ls_simple.py

Vous verrez le résultat suivant:

Output(ols) Train Accuracy: 0.584174642717
(ols) Test Accuracy: 0.584425799685

Cette fonctionnalité offre désormais une précision de train de 58,4% et une précision de validation de 58,4%, soit une amélioration de 13,1% des résultats de validation. Nous avons ajusté la matrice X à +100 x 100 +, mais le choix de 100 était arbitraire. Nous pourrions aussi ajuster la matrice + X + à +1000 x 1000 + ou +50 x 50 +. Disons que la dimension de + x + est + d x d +. Nous pouvons tester plus de valeurs de + d + en redimensionnant X pour qu’il soit + d x d + et en recalculant un nouveau modèle.

En essayant plus de valeurs de «+ d », nous trouvons une amélioration supplémentaire de 4,3% de la précision du test à 61,7%. Dans la figure suivante, nous considérons les performances de notre nouveau classificateur lorsque nous varions ` d `. Intuitivement, à mesure que ` d +` augmente, la précision devrait également augmenter, car nous utilisons de plus en plus de nos données d’origine. Plutôt que de brosser un tableau rose, le graphique présente une tendance négative:

image: https: //assets.digitalocean.com/articles/python3_dogfilter/cfKxdJ9.png [Performances des moindres carrés ordinaires]

Au fur et à mesure que nous conservons davantage de données, l’écart entre l’exactitude de la formation et celle de la validation augmente également. Ceci est une preuve évidente de la sur-adaptation, où notre modèle apprend des représentations qui ne sont plus généralisables à toutes les données. Pour lutter contre la suralimentation, nous allons normaliser notre modèle en pénalisant des modèles complexes.

Nous modifions notre fonction objectif des moindres carrés ordinaires par un terme de régularisation, ce qui nous donne un nouvel objectif. Notre nouvelle fonction objectif s’appelle ridge regression et ressemble à ceci:

min_w |Aw- y|^2 + lambda |w|^2

Dans cette équation, + lambda + est un hyperparamètre ajustable. Branchez + lambda = 0 + dans l’équation et la régression de crête devient des moindres carrés. Branchez + lambda = infinity + dans l’équation, et vous constaterez que le meilleur + w + doit maintenant être égal à zéro, car tout non-nul + w + entraîne une perte infinie. En fin de compte, cet objectif donne également une solution fermée:

w^* = (A^TA + lambda I)^{-1}A^Ty

Toujours en utilisant les échantillons personnalisés, reconditionnez et réévaluez le modèle une fois de plus.

Ouvrez à nouveau + step_6_ls_simple.py + dans votre éditeur:

nano step_6_ls_simple.py

Cette fois, augmentez la dimensionnalité du nouvel espace de fonctions à + ​​d = 1000 +. Modifiez la valeur de + d + de + 100 + en + 1000 + comme indiqué dans le bloc de code suivant:

step_6_ls_simple.py

...
   d =
   W = np.random.normal(size=(X_train.shape[1], d))
...

Puis appliquez la régression de crête en utilisant une régularisation de + lambda = 10 ^ {10} +. Remplacez la ligne définissant + w + par les deux lignes suivantes:

step_6_ls_simple.py

...
   # train model

   1e10 * I

Puis localisez ce bloc:

step_6_ls_simple.py

...
 ols_train_accuracy = evaluate(A_train, Y_train, w)
 print('(ols) Train Accuracy:', ols_train_accuracy)
 ols_test_accuracy = evaluate(A_test, Y_test, w)
 print('(ols) Test Accuracy:', ols_test_accuracy)

Remplacez-le par ce qui suit:

step_6_ls_simple.py

...

 print('(ridge) Train Accuracy:', evaluate(A_train, Y_train, w))
 print('(ridge) Test Accuracy:', evaluate(A_test, Y_test, w))

Le script terminé devrait ressembler à ceci:

step_6_ls_simple.py

"""Train emotion classifier using least squares."""

import numpy as np

def evaluate(A, Y, w):
   Yhat = np.argmax(A.dot(w), axis=1)
   return np.sum(Yhat == Y) / Y.shape[0]

def main():
   # load data
   with np.load('data/fer2013_train.npz') as data:
       X_train, Y_train = data['X'], data['Y']

   with np.load('data/fer2013_test.npz') as data:
       X_test, Y_test = data['X'], data['Y']

   # one-hot labels
   I = np.eye(6)
   Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]
   d = 1000
   W = np.random.normal(size=(X_train.shape[1], d))
   # select first 100 dimensions
   A_train, A_test = X_train.dot(W), X_test.dot(W)

   # train model
   I = np.eye(A_train.shape[1])
   w = np.linalg.inv(A_train.T.dot(A_train) + 1e10 * I).dot(A_train.T.dot(Y_oh_train))

   # evaluate model
   print('(ridge) Train Accuracy:', evaluate(A_train, Y_train, w))
   print('(ridge) Test Accuracy:', evaluate(A_test, Y_test, w))

if __name__ == '__main__':
   main()

Enregistrez le fichier, quittez votre éditeur et exécutez le script:

python step_6_ls_simple.py

Vous verrez le résultat suivant:

Output(ridge) Train Accuracy: 0.651173462698
(ridge) Test Accuracy: 0.622181436812

Une amélioration supplémentaire de 0,4% de la précision de validation à 62,2%, la précision du train tombant à 65,1%. Une fois de plus en réévaluation sur un certain nombre de «+ d +» différents, nous constatons un écart réduit entre la précision de la formation et celle de la validation pour la régression de crête. En d’autres termes, la régression de crête était sujette à moins de surajustement.

image: https: //assets.digitalocean.com/articles/python3_dogfilter/gzGBSGo.png [Performances de la régression des ols et des crêtes]

Les performances de base pour les moindres carrés, avec ces améliorations supplémentaires, fonctionnent assez bien. Les temps d’entraînement et d’inférence, tous ensemble, ne prennent pas plus de 20 secondes pour obtenir les meilleurs résultats. Dans la section suivante, vous explorerez des modèles encore plus complexes.

Étape 7 - Construire le classifieur visage-émotion à l’aide d’un réseau de neurones de convolution dans PyTorch

Dans cette section, vous allez créer un deuxième classifieur d’émotions en utilisant des réseaux de neurones au lieu des moindres carrés. Encore une fois, notre objectif est de produire un modèle qui accepte les visages en tant qu’entrée et génère une émotion. Finalement, ce classificateur déterminera quel masque de chien appliquer.

Pour une brève visualisation et introduction du réseau de neurones, voir l’article Comprendre les réseaux de neurones. Ici, nous allons utiliser une bibliothèque d’apprentissage en profondeur appelée PyTorch. Un grand nombre de bibliothèques d’apprentissage en profondeur sont largement utilisées et chacune a ses avantages et ses inconvénients. PyTorch est un bon point de départ. Pour implémenter ce classificateur de réseau neuronal, nous procédons à nouveau en trois étapes, comme nous l’avons fait avec le classificateur des moindres carrés:

  1. Prétraitez les données: appliquez un codage à chaud, puis appliquez les abstractions PyTorch.

  2. Spécifiez et entraînez le modèle: Configurez un réseau de neurones à l’aide de couches PyTorch. Définissez les hyperparamètres d’optimisation et lancez la descente de gradient stochastique.

  3. Exécutez une prédiction à l’aide du modèle: Évaluez le réseau de neurones.

Créez un nouveau fichier, nommé + step_7_fer_simple.py +

nano step_7_fer_simple.py

Importez les utilitaires nécessaires et créez une class contenant vos données. Pour le traitement des données ici, vous allez créer les ensembles de données train et test. Pour ce faire, implémentez l’interface + Dataset + de PyTorch, qui vous permet de charger et d’utiliser le pipeline de données intégré de PyTorch pour le jeu de données de reconnaissance face à des émotions:

step_7_fer_simple.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.

   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.

   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """
   pass

Supprimez l’espace réservé + pass dans la classe` + Fer2013 Dataset`. A sa place, ajoutez une fonction qui initialisera notre détenteur de données:

step_7_fer_simple.py

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()
...

Cette fonction commence par charger les échantillons et les étiquettes. Ensuite, il encapsule les données dans des structures de données PyTorch.

Directement après la fonction + init +, ajoutez une fonction + len +, car cela est nécessaire pour implémenter l’interface + Dataset + `PyTorch attend:

step_7_fer_simple.py

...
   def __len__(self):
       return len(self._labels)

Enfin, ajoutez une méthode + getitem +, qui renvoie un dictionary contenant l’échantillon et le libellé:

step_7_fer_simple.py

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}

Vérifiez que votre fichier ressemble à ce qui suit:

step_7_fer_simple.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.
   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.
   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}

Ensuite, chargez le jeu de données + Fer2013Dataset +. Ajoutez le code suivant à la fin de votre fichier après la classe + Fer2013Dataset +:

step_7_fer_simple.py

trainset = Fer2013Dataset('data/fer2013_train.npz')
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

testset = Fer2013Dataset('data/fer2013_test.npz')
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)

Ce code initialise l’ensemble de données à l’aide de la classe + Fer2013Dataset + que vous avez créée. Ensuite, pour les ensembles train et validation, il encapsule l’ensemble de données dans un + DataLoader +. Cela traduit le jeu de données en un itérable à utiliser ultérieurement.

Pour vérifier l’intégrité, vérifiez que les utilitaires de jeu de données fonctionnent. Créez un exemple de chargeur de données en utilisant + DataLoader + et imprimez le premier élément de ce chargeur. Ajoutez ce qui suit à la fin de votre fichier:

step_7_fer_simple.py

if __name__ == '__main__':
   loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False)
   print(next(iter(loader)))

Vérifiez que votre script terminé ressemble à ceci:

step_7_fer_simple.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.
   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.
   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}

trainset = Fer2013Dataset('data/fer2013_train.npz')
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

testset = Fer2013Dataset('data/fer2013_test.npz')
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)

if __name__ == '__main__':
   loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False)
   print(next(iter(loader)))

Quittez votre éditeur et exécutez le script.

python step_7_fer_simple.py

Cela génère la paire suivante de tensors. Notre pipeline de données produit deux échantillons et deux étiquettes. Cela indique que notre pipeline de données est opérationnel et prêt à fonctionner:

Output{'image':
(0 ,0 ,.,.) =
  24   32   36  ...   173  172  173
  25   34   29  ...   173  172  173
  26   29   25  ...   172  172  174
     ...         ⋱        ...
 159  185  157  ...   157  156  153
 136  157  187  ...   152  152  150
 145  130  161  ...   142  143  142
    ⋮

(1 ,0 ,.,.) =
  20   17   19  ...   187  176  162
  22   17   17  ...   195  180  171
  17   17   18  ...   203  193  175
     ...         ⋱        ...
   1    1    1  ...   106  115  119
   2    2    1  ...   103  111  119
   2    2    2  ...    99  107  118
[torch.LongTensor of size 2x1x48x48]
, 'label':
1
1
[torch.LongTensor of size 2]
}

Maintenant que vous avez vérifié que le pipeline de données fonctionne, retournez à + ​​step_7_fer_simple.py + pour ajouter le réseau de neurones et l’optimiseur. Ouvrez + step_7_fer_simple.py +.

nano step_7_fer_simple.py

Tout d’abord, supprimez les trois dernières lignes que vous avez ajoutées lors de la dernière itération:

step_7_fer_simple.py

# Delete all three lines

A leur place, définissez un réseau de neurones PyTorch comprenant trois couches de convolution, suivies de trois couches entièrement connectées. Ajoutez ceci à la fin de votre script existant:

step_7_fer_simple.py

class Net(nn.Module):
   def __init__(self):
       super(Net, self).__init__()
       self.conv1 = nn.Conv2d(1, 6, 5)
       self.pool = nn.MaxPool2d(2, 2)
       self.conv2 = nn.Conv2d(6, 6, 3)
       self.conv3 = nn.Conv2d(6, 16, 3)
       self.fc1 = nn.Linear(16 * 4 * 4, 120)
       self.fc2 = nn.Linear(120, 48)
       self.fc3 = nn.Linear(48, 3)

   def forward(self, x):
       x = self.pool(F.relu(self.conv1(x)))
       x = self.pool(F.relu(self.conv2(x)))
       x = self.pool(F.relu(self.conv3(x)))
       x = x.view(-1, 16 * 4 * 4)
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x

Initialisez maintenant le réseau de neurones, définissez une fonction de perte et définissez des hyperparamètres d’optimisation en ajoutant le code suivant à la fin du script:

step_7_fer_simple.py

net = Net().float()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

Nous allons nous entraîner pendant deux heures. Pour le moment, nous définissons une epoch_ comme une itération de formation où chaque échantillon de formation a été utilisé exactement une fois.

Tout d’abord, extrayez + image et` + label` du chargeur de jeu de données, puis enveloppez-les dans un PyTorch + Variable A. Deuxièmement, exécutez la passe avant, puis propulsez en arrière à travers la perte et le réseau neuronal. Ajoutez le code suivant à la fin de votre script pour le faire:

step_7_fer_simple.py

for epoch in range(2):  # loop over the dataset multiple times

   running_loss = 0.0
   for i, data in enumerate(trainloader, 0):
       inputs = Variable(data['image'].float())
       labels = Variable(data['label'].long())
       optimizer.zero_grad()

       # forward + backward + optimize
       outputs = net(inputs)
       loss = criterion(outputs, labels)
       loss.backward()
       optimizer.step()

       # print statistics
       running_loss += loss.data[0]
       if i % 100 == 0:
           print('[%d, %5d] loss: %.3f' % (epoch, i, running_loss / (i + 1)))

Votre script devrait maintenant ressembler à ceci:

step_7_fer_simple.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.

   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.

   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """
   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)


   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}


trainset = Fer2013Dataset('data/fer2013_train.npz')
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

testset = Fer2013Dataset('data/fer2013_test.npz')
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)


class Net(nn.Module):
   def __init__(self):
       super(Net, self).__init__()
       self.conv1 = nn.Conv2d(1, 6, 5)
       self.pool = nn.MaxPool2d(2, 2)
       self.conv2 = nn.Conv2d(6, 6, 3)
       self.conv3 = nn.Conv2d(6, 16, 3)
       self.fc1 = nn.Linear(16 * 4 * 4, 120)
       self.fc2 = nn.Linear(120, 48)
       self.fc3 = nn.Linear(48, 3)

   def forward(self, x):
       x = self.pool(F.relu(self.conv1(x)))
       x = self.pool(F.relu(self.conv2(x)))
       x = self.pool(F.relu(self.conv3(x)))
       x = x.view(-1, 16 * 4 * 4)
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x

net = Net().float()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)


for epoch in range(2):  # loop over the dataset multiple times

   running_loss = 0.0
   for i, data in enumerate(trainloader, 0):
       inputs = Variable(data['image'].float())
       labels = Variable(data['label'].long())
       optimizer.zero_grad()

       # forward + backward + optimize
       outputs = net(inputs)
       loss = criterion(outputs, labels)
       loss.backward()
       optimizer.step()

       # print statistics
       running_loss += loss.data[0]
       if i % 100 == 0:
           print('[%d, %5d] loss: %.3f' % (epoch, i, running_loss / (i + 1)))

Enregistrez le fichier et quittez l’éditeur une fois que vous avez vérifié votre code. Ensuite, lancez cette formation de validation:

python step_7_fer_simple.py

Vous verrez une sortie semblable au suivant lorsque le réseau de neurones se forme:

Output[0,     0] loss: 1.094
[0,   100] loss: 1.049
[0,   200] loss: 1.009
[0,   300] loss: 0.963
[0,   400] loss: 0.935
[1,     0] loss: 0.760
[1,   100] loss: 0.768
[1,   200] loss: 0.775
[1,   300] loss: 0.776
[1,   400] loss: 0.767

Vous pouvez ensuite augmenter ce script en utilisant un certain nombre d’autres utilitaires PyTorch pour enregistrer et charger des modèles, produire des précisions sur la formation et la validation, affiner un programme de cadence d’apprentissage, etc. Après un entraînement de 20 époques avec un taux d’apprentissage de 0,01 et une impulsion de 0,9, notre réseau de neurones atteint une précision de train de 87,9% et une précision de validation de 75,5%, soit une amélioration supplémentaire de 6,8% par rapport à la méthode des moindres carrés la plus performante à 66,6%. . Nous allons inclure ces options supplémentaires dans un nouveau script.

Créez un nouveau fichier contenant le détecteur d’émotion du visage final utilisé par votre flux de caméra en direct. Ce script contient le code ci-dessus ainsi qu’une interface de ligne de commande et une version facile à importer de notre code qui sera utilisée ultérieurement. De plus, il contient les hyperparamètres réglés à l’avance, pour un modèle plus précis.

nano step_7_fer.py

Commencez avec les importations suivantes. Cela correspond à notre fichier précédent mais inclut en plus OpenCV comme + import cv2. +

step_7_fer.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse

Directement sous ces importations, réutilisez votre code de + step_7_fer_simple.py + pour définir le réseau de neurones:

step_7_fer.py

class Net(nn.Module):
   def __init__(self):
       super(Net, self).__init__()
       self.conv1 = nn.Conv2d(1, 6, 5)
       self.pool = nn.MaxPool2d(2, 2)
       self.conv2 = nn.Conv2d(6, 6, 3)
       self.conv3 = nn.Conv2d(6, 16, 3)
       self.fc1 = nn.Linear(16 * 4 * 4, 120)
       self.fc2 = nn.Linear(120, 48)
       self.fc3 = nn.Linear(48, 3)

   def forward(self, x):
       x = self.pool(F.relu(self.conv1(x)))
       x = self.pool(F.relu(self.conv2(x)))
       x = self.pool(F.relu(self.conv3(x)))
       x = x.view(-1, 16 * 4 * 4)
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x

Encore une fois, réutilisez le code pour le jeu de données Face Emotion Recognition de + step_7_fer_simple.py + et ajoutez-le à ce fichier:

step_7_fer.py

class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.
   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.
   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}

Ensuite, définissez quelques utilitaires pour évaluer les performances du réseau de neurones. Tout d’abord, ajoutez une fonction + évaluer + qui compare l’émotion prédite du réseau de neurones à l’émotion vraie pour une seule image:

step_7_fer.py

def evaluate(outputs: Variable, labels: Variable, normalized: bool=True) -> float:
   """Evaluate neural network outputs against non-one-hotted labels."""
   Y = labels.data.numpy()
   Yhat = np.argmax(outputs.data.numpy(), axis=1)
   denom = Y.shape[0] if normalized else 1
   return float(np.sum(Yhat == Y) / denom)

Ajoutez ensuite une fonction appelée + batch_evaluate + qui applique la première fonction à toutes les images:

step_7_fer.py

def batch_evaluate(net: Net, dataset: Dataset, batch_size: int=500) -> float:
   """Evaluate neural network in batches, if dataset is too large."""
   score = 0.0
   n = dataset.X.shape[0]
   for i in range(0, n, batch_size):
       x = dataset.X[i: i + batch_size]
       y = dataset.Y[i: i + batch_size]
       score += evaluate(net(x), y, False)
   return score / n

Maintenant, définissez une fonction appelée + get_image_to_emotion_predictor + qui prend une image et émet une émotion prédite, en utilisant un modèle pré-entraîné:

step_7_fer.py

def get_image_to_emotion_predictor(model_path='assets/model_best.pth'):
   """Returns predictor, from image to emotion index."""
   net = Net().float()
   pretrained_model = torch.load(model_path)
   net.load_state_dict(pretrained_model['state_dict'])

   def predictor(image: np.array):
       """Translates images into emotion indices."""
       if image.shape[2] > 1:
           image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
       frame = cv2.resize(image, (48, 48)).reshape((1, 1, 48, 48))
       X = Variable(torch.from_numpy(frame)).float()
       return np.argmax(net(X).data.numpy(), axis=1)[0]
   return predictor

Enfin, ajoutez le code suivant pour définir la fonction + main + afin de tirer parti des autres utilitaires:

step_7_fer.py

def main():
   trainset = Fer2013Dataset('data/fer2013_train.npz')
   testset = Fer2013Dataset('data/fer2013_test.npz')
   net = Net().float()

   pretrained_model = torch.load("assets/model_best.pth")
   net.load_state_dict(pretrained_model['state_dict'])

   train_acc = batch_evaluate(net, trainset, batch_size=500)
   print('Training accuracy: %.3f' % train_acc)
   test_acc = batch_evaluate(net, testset, batch_size=500)
   print('Validation accuracy: %.3f' % test_acc)


if __name__ == '__main__':
   main()

Cela charge un réseau de neurones pré-entraîné et évalue ses performances sur l’ensemble de données fourni avec Face Emotion Recognition. Plus précisément, le script génère une précision sur les images que nous avons utilisées pour la formation, ainsi qu’un ensemble d’images séparé que nous avons mis de côté à des fins de test.

Vérifiez que votre fichier correspond aux éléments suivants:

step_7_fer.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse

class Net(nn.Module):
   def __init__(self):
       super(Net, self).__init__()
       self.conv1 = nn.Conv2d(1, 6, 5)
       self.pool = nn.MaxPool2d(2, 2)
       self.conv2 = nn.Conv2d(6, 6, 3)
       self.conv3 = nn.Conv2d(6, 16, 3)
       self.fc1 = nn.Linear(16 * 4 * 4, 120)
       self.fc2 = nn.Linear(120, 48)
       self.fc3 = nn.Linear(48, 3)

   def forward(self, x):
       x = self.pool(F.relu(self.conv1(x)))
       x = self.pool(F.relu(self.conv2(x)))
       x = self.pool(F.relu(self.conv3(x)))
       x = x.view(-1, 16 * 4 * 4)
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.
   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.
   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}


def evaluate(outputs: Variable, labels: Variable, normalized: bool=True) -> float:
   """Evaluate neural network outputs against non-one-hotted labels."""
   Y = labels.data.numpy()
   Yhat = np.argmax(outputs.data.numpy(), axis=1)
   denom = Y.shape[0] if normalized else 1
   return float(np.sum(Yhat == Y) / denom)


def batch_evaluate(net: Net, dataset: Dataset, batch_size: int=500) -> float:
   """Evaluate neural network in batches, if dataset is too large."""
   score = 0.0
   n = dataset.X.shape[0]
   for i in range(0, n, batch_size):
       x = dataset.X[i: i + batch_size]
       y = dataset.Y[i: i + batch_size]
       score += evaluate(net(x), y, False)
   return score / n


def get_image_to_emotion_predictor(model_path='assets/model_best.pth'):
   """Returns predictor, from image to emotion index."""
   net = Net().float()
   pretrained_model = torch.load(model_path)
   net.load_state_dict(pretrained_model['state_dict'])

   def predictor(image: np.array):
       """Translates images into emotion indices."""
       if image.shape[2] > 1:
           image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
       frame = cv2.resize(image, (48, 48)).reshape((1, 1, 48, 48))
       X = Variable(torch.from_numpy(frame)).float()
       return np.argmax(net(X).data.numpy(), axis=1)[0]
   return predictor


def main():
   trainset = Fer2013Dataset('data/fer2013_train.npz')
   testset = Fer2013Dataset('data/fer2013_test.npz')
   net = Net().float()

   pretrained_model = torch.load("assets/model_best.pth")
   net.load_state_dict(pretrained_model['state_dict'])

   train_acc = batch_evaluate(net, trainset, batch_size=500)
   print('Training accuracy: %.3f' % train_acc)
   test_acc = batch_evaluate(net, testset, batch_size=500)
   print('Validation accuracy: %.3f' % test_acc)


if __name__ == '__main__':
   main(

Enregistrez le fichier et quittez votre éditeur.

Comme auparavant, avec le détecteur de visage, téléchargez les paramètres de modèle pré-formés et enregistrez-les dans votre dossier + assets + à l’aide de la commande suivante:

wget -O assets/model_best.pth https://github.com/alvinwan/emotion-based-dog-filter/raw/master/src/assets/model_best.pth

Exécutez le script pour utiliser et évaluer le modèle pré-formé:

python step_7_fer.py

Cela produira les éléments suivants:

OutputTraining accuracy: 0.879
Validation accuracy: 0.755

À ce stade, vous avez créé un classificateur visage-émotion assez précis. En substance, notre modèle peut correctement distinguer les visages gais, tristes et surpris huit fois sur dix. Ceci est un bon modèle, vous pouvez donc maintenant utiliser ce classifieur visage-émotion pour déterminer le masque de chien à appliquer aux visages.

Étape 8 - Terminer le filtre pour chien basé sur les émotions

Avant d’intégrer notre tout nouveau classificateur visage-émotion, nous devons choisir des masques pour animaux. Nous allons utiliser un masque de Dalmation et un masque de chien de berger:

image: https: //assets.digitalocean.com/articles/python3_dogfilter/HveFdkg.png [masque de Dalmation] + image: https: //assets.digitalocean.com/articles/python3_dogfilter/E9ax7PI.png [masque de berger]

Exécutez ces commandes pour télécharger les deux masques dans votre dossier + assets:

wget -O assets/dalmation.png https://assets.digitalocean.com/articles/python3_dogfilter/E9ax7PI.png  # dalmation
wget -O assets/sheepdog.png https://assets.digitalocean.com/articles/python3_dogfilter/HveFdkg.png  # sheepdog

Maintenant, utilisons les masques de notre filtre. Commencez par dupliquer le fichier + step_4_dog_mask.py +:

cp step_4_dog_mask.py step_8_dog_emotion_mask.py

Ouvrez le nouveau script Python.

nano step_8_dog_emotion_mask.py

Insérez une nouvelle ligne en haut du script pour importer le prédicteur d’émotion:

step_8_dog_emotion_mask.py

from step_7_fer import get_image_to_emotion_predictor
...

Ensuite, dans la fonction + main () +, localisez cette ligne:

step_8_dog_emotion_mask.py

   mask = cv2.imread('assets/dog.png')

Remplacez-le par ce qui suit pour charger les nouveaux masques et regrouper tous les masques dans un tuple:

step_8_dog_emotion_mask.py

   mask0 = cv2.imread('assets/dog.png')
   mask1 = cv2.imread('assets/dalmation.png')
   mask2 = cv2.imread('assets/sheepdog.png')
   masks = (mask0, mask1, mask2)

Ajoutez un saut de ligne, puis ajoutez ce code pour créer le prédicteur d’émotion.

step_8_dog_emotion_mask.py

   # get emotion predictor
   predictor = get_image_to_emotion_predictor()

Votre fonction + main + devrait maintenant correspondre à:

step_8_dog_emotion_mask.py

def main():
   cap = cv2.VideoCapture(0)

   # load mask
   mask0 = cv2.imread('assets/dog.png')
   mask1 = cv2.imread('assets/dalmation.png')
   mask2 = cv2.imread('assets/sheepdog.png')
   masks = (mask0, mask1, mask2)

   # get emotion predictor
   predictor = get_image_to_emotion_predictor()

   # initialize front face classifier
   ...

Ensuite, localisez ces lignes:

step_8_dog_emotion_mask.py

           # apply mask
           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

Insérez la ligne suivante sous la ligne + # apply mask + pour sélectionner le masque approprié à l’aide du prédicteur:

step_8_dog_emotion_mask.py

           # apply mask

           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

Le fichier complet devrait ressembler à ceci:

step_8_dog_emotion_mask.py

"""Test for face detection"""

from step_7_fer import get_image_to_emotion_predictor
import numpy as np
import cv2

def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)
   resized_mask = cv2.resize(mask, new_mask_shape)

   # Add mask to face - ensure mask is centered
   face_with_mask = face.copy()
   non_white_pixels = (resized_mask < 250).all(axis=2)
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)
   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
        resized_mask[non_white_pixels]

   return face_with_mask

def main():

   cap = cv2.VideoCapture(0)
   # load mask
   mask0 = cv2.imread('assets/dog.png')
   mask1 = cv2.imread('assets/dalmation.png')
   mask2 = cv2.imread('assets/sheepdog.png')
   masks = (mask0, mask1, mask2)

   # get emotion predictor
   predictor = get_image_to_emotion_predictor()

   # initialize front face classifier
   cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")

   while True:
       # Capture frame-by-frame
       ret, frame = cap.read()
       frame_h, frame_w, _ = frame.shape

       # Convert to black-and-white
       gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
       blackwhite = cv2.equalizeHist(gray)

       rects = cascade.detectMultiScale(
           blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
           flags=cv2.CASCADE_SCALE_IMAGE)

       for x, y, w, h in rects:
           # crop a frame slightly larger than the face
           y0, y1 = int(y - 0.25*h), int(y + 0.75*h)
           x0, x1 = x, x + w
           # give up if the cropped frame would be out-of-bounds
           if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h:
               continue
           # apply mask
           mask = masks[predictor(frame[y:y+h, x: x+w])]
           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

       # Display the resulting frame
       cv2.imshow('frame', frame)
       if cv2.waitKey(1) & 0xFF == ord('q'):
           break

   cap.release()
   cv2.destroyAllWindows()

if __name__ == '__main__':
   main()

Enregistrez et quittez votre éditeur. Maintenant, lancez le script:

python step_8_dog_emotion_mask.py

Maintenant, essayez-le! Sourire va enregistrer comme "heureux" et montrer le chien original. Un visage neutre ou un froncement de sourcils sera considéré comme «triste» et donnera le dalmatien. Un visage de «surprise» avec une belle grosse goutte de mâchoire donnera le chien de berger.

image: https: //assets.digitalocean.com/articles/python3_dogfilter/JPavHJl.gif [GIF pour le filtre pour chien basé sur les émotions]

Ceci conclut notre filtre pour chiens basé sur les émotions et notre incursion dans la vision par ordinateur.

Conclusion

Dans ce didacticiel, vous avez créé un détecteur de visage et un filtre pour chiens à l’aide de la vision par ordinateur. Vous avez utilisé des modèles d’apprentissage automatique pour appliquer des masques basés sur les émotions détectées.

L’apprentissage automatique est largement applicable. Cependant, il appartient au praticien de prendre en compte les implications éthiques de chaque application lors de l’apprentissage automatique. L’application que vous avez construite dans ce didacticiel était un exercice amusant, mais souvenez-vous que vous vous êtes fondé sur OpenCV et sur un jeu de données existant pour identifier les faces, plutôt que de fournir vos propres données pour former les modèles. Les données et les modèles utilisés ont un impact significatif sur le fonctionnement d’un programme.

Par exemple, imaginez un moteur de recherche d’emploi où les modèles ont été formés avec des données sur les candidats. comme la race, le sexe, l’âge, la culture, la langue maternelle ou d’autres facteurs. Et peut-être que les développeurs ont formé un modèle qui applique la parcimonie, ce qui finit par réduire l’espace des fonctions à un sous-espace où le genre explique l’essentiel de la variance. En conséquence, le modèle influence les recherches d’emploi des candidats et même les processus de sélection des entreprises basés principalement sur le sexe. Examinons maintenant des situations plus complexes dans lesquelles le modèle est moins interprétable et vous ne savez pas à quoi correspond une fonctionnalité particulière. Vous pouvez en apprendre davantage à ce sujet dans Egalité des chances dans l’apprentissage automatique par le professeur Moritz Hardt de l’Université de Berkeley.

Il peut y avoir une incertitude considérable dans l’apprentissage automatique. Pour comprendre ce caractère aléatoire et complexe, vous devrez développer à la fois des intuitions mathématiques et des capacités de réflexion probabilistes. En tant que praticien, il vous appartient de creuser les fondements théoriques de l’apprentissage automatique.