Reconnaissance faciale – TP1 : La vidéo en python

Optimisation de notre ligne de performance

La théorie

Ok, posons déjà un peu le schéma de ce qu’on veut faire. Ce sera plus simple ensuite pour faire le code.

A chaque boucle, lorsque l’on va appeler notre méthode textPerf, on va calculer le temps écoulé, ajouter 1 à notre compteur de boucle/image. Ensuite, on vérifie si on a atteint 1 seconde au total écoulé. Si c’est le cas, on peut alors calculer le temps écoulé moyen pour le nombre de boucle effectuée. Dans l’image ci-dessus, il y a eu 3 boucles pendant 1,1 seconde. Donc, si on fait 1,1 seconde divisé par 3 boucles, ça nous donne une moyenne de 0,36 seconde par boucle. Pour calculer la fréquence moyenne, on divise 1 par 0,36 donc 2,72 boucles par seconde donc par extrapolation 2,72 images par seconde.

Le code python

Mettons tout ça sous forme de code. On va reprendre notre classe PerfImage et on va l’améliorer un peu.

Au niveau des déclarations, ça ne change pas

## On importe les librairies dont on va avoir besoin
import psutil
import time

Dans la déclaration de notre classe, on va ajouter un argument. tempsEntre2perf. En fait cet argument va permettre de définir tous les combien de temps nous allons faire nos calculs et mettre à jour notre ligne de perf.

class PerfImage:
    
    def __init__(self,tempsEntre2perf):

Ensuite on déclare nos attributs. on connait déjà __tempsPrecedent

        self.__tempsPrecedent = 0

Comme il nous faut calculer le temps écoulé pendant plusieurs boucles, nous avons besoin d’un attribut qui va stocker au fur et à mesure le temps total écoulé

        self.__tempsEcoule = 0

De la même façon, il nous faut un attribut qui va stocker au fur et à mesure le nombre de boucle effectuée

        self.__compteurImage = 0

On crée aussi un attribut qui va contenir le temps entre 2 performances

        self.__tempsEntre2perf = tempsEntre2perf

Lorsque le temps écoulé n’a pas dépassé le temps que nous avons défini entre 2 performances, il faut quand même renvoyer une ligne de performance. Pour ça, nous allons utiliser un attribut. A chaque fois qu’on fera le calcul on mettra à jour cet attribut. Et le reste du temps, on ne renverra que la ligne précédente… en attendant le nouveau calcul 🙂

        self.__texte = 'FPS: {:2.1f} - CPU: {:2.0f}% - MEM: {:2.2f}Go'.format(0,0,0)

Pour la partie qui suit, on ajoute notre compteur qui s’incrémente à chaque boucle

    def textePerf(self):
        
        ## On ajoute 1 à notre compteur d'image affichée
        self.__compteurImage = self.__compteurImage + 1

        ## On prend le temps exact à l'instant t et on le met dans notre variable
        tempsActuel = time.time()
        

Désormais, il faut qu’on ajoute le temps écoulé depuis la dernière boucle au temps total écoulé.

        ## Petite sécurité histoire d'éviter la division par 0 lors du premier
        ## appel
        if self.__tempsPrecedent != 0:

            ## On récupère le temps écoulé entre le temps actuel t et le temps lors
            ## du précédent appel
            self.__tempsEcoule = self.__tempsEcoule + (tempsActuel - self.__tempsPrecedent)


        else:
            fps = 0

Est-ce qu’on a dépassé le temps entre 2 performances ?

        if self.__tempsEcoule >= self.__tempsEntre2perf:

Si on a dépassé, on lance les calculs et on met à jour notre ligne de performance

            ## On calcule notre fréquence moyenne
            fps = 1 / (self.__tempsEcoule / self.__compteurImage)

            ## on récupère le pourcentage d'utilisation du cpu
            cpu = psutil.cpu_percent()
            
            ## On récupère la quantité de mémoire total utilisée
            mem = (psutil.virtual_memory().used)/1024/1024/1024
            
            ## On prépare notre chaine de caractère contenant les infos
            self.__texte = 'FPS: {:2.1f} - CPU: {:2.0f}% - MEM: {:2.2f}Go'.format(fps,cpu,mem)

On remet l’affiche du temps écoulé et du nombre de FPS dans la console

            ## Tu peux décommenter la ligne ci dessous si tu souhaites voir
            ## le temps écoulé entre 2 appels
            print("Temps écoulé : {:2.2f} - FPS : {:2.0f}".format(self.__tempsEcoule, fps))        

Il ne faut pas oublier de remettre à 0 le temps total écoulé et notre compteur de boucle

            ## On réinitialise nos attributs qui stockent le nombre d'image
            ## et le temps écoulé pour recommercer le process à la prochaine
            ## boucle
            self.__compteurImage = 0
            self.__tempsEcoule = 0

Afin de pouvoir calculer le temps écoulé entre 2 boucles, on affecte le temps actuel au temps précédent pour la prochaine boucle. Attention : Cette ligne ne doit pas être intégrée à notre condition IF au-dessus. Vérifie bien ton indentation.

        ## On en profite pour affecter le temps actuel à notre attribut 
        ## __tempsPrecedent. Comme ça, ce sera prêt pour la prochaine
        ## exécution         
        self.__tempsPrecedent = tempsActuel

Et on renvoie notre ligne

        ## On retourne notre chaine de caractère là où ça a été demandé
        return self.__texte

On sauvegarde parce que ce serait bête de devoir tout se retaper !

Dans notre script principal, il nous faut modifier l’instanciation de notre objet pour y ajouter notre argument tempsEntre2perf.

## On instancie notre objet depuis la classe PerfiImage
monPerfImage = perfImage.PerfImage(1)

Le 1 correspond au fait qu’on veut que la fréquence moyenne soit calculée toutes les une seconde.

Si on exécute notre script, désormais l’affichage des FPS est plus stable et il y a déjà moins de variations de FPS. Voici la sortie de ma console

Temps écoulé : 1.00 - FPS : 29
Temps écoulé : 1.05 - FPS : 27
Temps écoulé : 1.00 - FPS : 29
Temps écoulé : 1.01 - FPS : 29
Temps écoulé : 1.05 - FPS : 30
Temps écoulé : 1.01 - FPS : 31
Temps écoulé : 1.00 - FPS : 30

Néanmoins, on sait que si on calcule la fréquence à chaque boucle, nous avons des variations. Un coup c’est 19 FPS et un autre 91 FPS. Ça veut dire qu’il y a quelque chose dans notre boucle qui prend beaucoup de temps quand on est à 19 FPS et très peu de temps quand on est à 91 FPS. D’après toi ?

Alors, histoire de savoir quelle partie du code, j’ai ajouté de l’affichage du temps écoulé entre chaque étape :

while True:
    
    tempsActuel = time.time()
    tempsPrecedent = tempsActuel
    print ("Top départ")
    ##on récupère la dernière image de la vidéo 
    valeurRetour, imageWebcam = videoWebcam.read()

    tempsPrecedent = tempsActuel
    tempsActuel = time.time()
    print ("récupération de l'image : {:2.4f}".format(tempsActuel-tempsPrecedent))

    ## on s'assurer qu'aucune erreur n'a été rencontrée
    if valeurRetour:
        
        ## On récupère notre texte avec les performances
        textePerf = monPerfImage.textePerf()

        tempsPrecedent = tempsActuel
        tempsActuel = time.time()
        print ("récupération de la ligne de perf : {:2.4f}".format(tempsActuel-tempsPrecedent))
        
        ## On ajoute notre ligne à l'image
        cv2.putText(imageWebcam, textePerf, (10, 15),
                cv2.FONT_HERSHEY_SIMPLEX , 0.5, (0, 0, 0), thickness=2, lineType=1)
        
        tempsPrecedent = tempsActuel
        tempsActuel = time.time()
        print ("Ajout de la ligne de perf à l'image : {:2.4f}".format(tempsActuel-tempsPrecedent))
        
        ## On affiche l'image 
        cv2.imshow('Image de la webcam', imageWebcam)        

        tempsPrecedent = tempsActuel
        tempsActuel = time.time()
        print ("Affichage de l'image : {:2.4f}".format(tempsActuel-tempsPrecedent))
    
    ## Comme c'est une boucle infinie, il faut bien se prévoir une sortie
    ## Dans notre cas, ce sera l'appui sur la touche Q
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

Et voici le résultat :

Top départ
récupération de l'image : 0.0658
récupération de la ligne de perf : 0.0000
Ajout de la ligne de perf à l'image : 0.0000
Affichage de l'image : 0.0000
Top départ
récupération de l'image : 0.0080
récupération de la ligne de perf : 0.0000
Ajout de la ligne de perf à l'image : 0.0000
Affichage de l'image : 0.0000
Top départ
récupération de l'image : 0.0499
récupération de la ligne de perf : 0.0000
Ajout de la ligne de perf à l'image : 0.0000
Affichage de l'image : 0.0000
Top départ
récupération de l'image : 0.0080
récupération de la ligne de perf : 0.0000
Ajout de la ligne de perf à l'image : 0.0000
Affichage de l'image : 0.0000

Il est clair désormais que c’est la récupération de l’image de la webcam qui ralentie notre boucle. C’est à dire cette ligne

    valeurRetour, imageWebcam = videoWebcam.read()

Et c’est vraiment pénalisant car cela va fausser toutes nos prochaines comparaisons quand on jouera avec la détection et la reconnaissance faciale.

On comprend donc que la méthode read() bloque l’exécution de notre script jusqu’à la récupération de l’image. Et cette récupération de l’image prend environ 50ms dans mon cas. Par contre, si jamais l’image n’a pas changé depuis la dernière boucle, alors là, la récupération de l’image se fait en 8 ms. N’oublie pas, ma webcam envoie 30 images par seconde. Mais la boucle s’exécute beaucoup plus vite. Donc elle va appeler à plusieurs reprises la méthode read() alors que l’image de la webcam n’a pas changé !

Alors comment résoudre ce souci ?

2 Comments

  1. Bonjour, j’ai essayé de rentrer le code mais lorsque je dois installer le module outils, je tape pip install outils, je redémarre le kernel mais après, spyder me dit:”No module named ‘outils'”. Quelqu’un saurait ce que je devrais faire?

    • Bonjour Jeremy,

      Effectivement, je ne vois pas la trace du fichier outils.py dans l’article.
      Essaye de remplacer outils.perfImage as perfImage par perfImage
      Cela dépend de la où tu as mis la classe perfImage.

      Ced

Poster un Commentaire

Votre adresse de messagerie ne sera pas publiée.


*


Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.