https://d226lax1qjow5r.cloudfront.net/blog/blogposts/snapchat-filters-opentok-tracking-js-dr/Blog_Snapchat-Style-Filters_1200x600.png

Filtres de style Snapchat avec Tracking.js et Vonage

Publié le November 16, 2020

Temps de lecture : 20 minutes

Soyez Batman ou faites peur à certaines personnes. Dans ce guide, vous allez ajouter le suivi du visage à l'aide de Tracking.js et créer des filtres sympas à la Snapchat pour un flux vidéo. Cool !

Video API de Vonage

Video API de Vonage produit une plateforme basée sur WebRTC pour ajouter de la vidéo, de la voix et de la messagerie en direct aux applications web, mobiles et de bureau. Nous utiliserons l'API Video de Vonage pour nous connecter depuis notre application à une session de streaming vidéo. Lorsque vous travaillez avec l'API Video de Vonage, vous pouvez voir le nom OpenTok, son ancien nom, utilisé dans le code ou la documentation.

Tracking.js

Tracking.js est une bibliothèque légère et puissante de détection de visages et de suivi des couleurs en temps réel qui intègre différents algorithmes et techniques dans l'environnement du navigateur. Nous utiliserons tracking.js pour suivre les visages afin d'appliquer des masques.

Pré-requis

Voici ce dont vous aurez besoin pour suivre ce tutoriel.

Node et NPM

Pour commencer, vous aurez besoin d'installer node et le gestionnaire de paquets node (npm). Vérifions s'ils sont installés en exécutant :

node --version
npm --version

Si vous n'avez pas installé node et npm, consultez le site nodejs.org et installez la bonne version pour votre système d'exploitation. J'ai écrit cet article avec Node 11.8 et NPM 6.8.

Account Vonage

Rendez-vous sur le site de Vonage pour vous inscrire à un essai gratuit.

Git

Vérifiez si vous avez installé Git.

git --version

Si ce n'est pas le cas, Atlassian propose un excellent guide sur la manière d'installer Git. installer Git. J'ai écrit cet article avec Git 2.17 installé.

Commencer

Comme base, j'ai préparé une version de l'application, à partir de échantillons web OpenTok pour les filtres de flux, que vous pouvez modifier.

Jetez un coup d'œil à ma version des échantillons du filtre web OpenTok ici en exécutant la commande suivante dans l'outil de ligne de commande de votre choix, ou en utilisant votre interface Git pour consulter cette URL : https://github.com/nexmo-community/snapchat-filters-with-opentok-and-face-tracking.git

git clone git@github.com:nexmo-community/snapchat-filters-with-opentok-and-face-tracking.git

Note : La branche master se trouve au point de départ, mais si vous êtes impatient (et que vous aimez sauter à la fin comme moi) vous pouvez git checkout demo-end et exécuter l'application à partir de là.

Configurer l'Applications

Ainsi, avant de commencer à coder, vous devez configurer l'application telle qu'elle est et vous faire une idée du point de départ. Pour ce faire, vous aurez besoin du Account Vonage mentionné dans les pré-requis. Si vous n'en avez pas, rendez-vous sur le site de Vonage pour vous inscrire maintenant.

Créer un projet

Une fois que vous avez confirmé votre adresse électronique et que vous vous êtes connecté, vous pouvez cliquer sur Créer un projet à partir de votre vue d'ensemble de votre Account. Créez un nouveau projet Video API de Vonage en cliquant sur Créer un projet personnalisé. Donnez un nom à votre projet et cliquez sur Créer.

Maintenant, notez votre CLÉ DE L'API et SECRET pour l'étape de configuration.

Créer une session

Après avoir créé votre projet, vous serez redirigé vers le tableau de bord du projet. Faites défiler vers le bas jusqu'à Outils de projet et cliquez sur Créer un identifiant de session. Vous pouvez également choisir entre Relayé et Acheminé. En principe, Relayé tente d'établir une connexion d'égal à égal, mais utilise des serveurs relais lorsque les pare-feux bloquent l'établissement d'une connexion d'égal à égal. Routed est l'option par défaut qui permet aux flux d'être acheminés vers le routeur de médias. Vous pouvez en savoir plus sur les sessions dans la documentation.

Notez l ID SESSION qui est créé.

Générer un jeton

Avant de configurer l'application, nous devons générer un jeton. Sur le tableau de bord de votre projet, sous Outils du projet (la même section où vous avez créé votre identifiant de session), entrez l'identifiant de session ID DE SESSION que vous venez de générer et cliquez sur Générer un jeton.

Notez le JETON GÉNÉRÉ pour l'étape suivante.

Créer un fichier de configuration

Faites une copie de js/config.js.example et sauvegardez-la sous js/config.js (dans le répertoire js) en veillant à modifier les valeurs pour qu'elles correspondent à celles de votre Account Vonage.

CLÉ DE L'API, ID DE SESSIONet le TOKEN sont tous créés ci-dessus. Si vous exécutez l'application localement en utilisant npm startvous aurez besoin d'une URL DE BASE de http://127.0.0.1:8080. Pour quelque chose comme Heroku, c'est plus comme https://app-name.herokuapp.com.

Petite astuce : Si vous exécutez l'application après l'avoir lancée npm start vous pouvez l'arrêter à nouveau avec CTRL+C.

Votre js/config.js finira par ressembler à quelque chose comme ceci.

// js/config.js
(function closure(exports) {
  exports.BASE_URL = 'base url';
  exports.API_KEY = 'api key';
  exports.SESSION_ID = 'session id';
  exports.TOKEN = 'token';
})(exports);

Installer les dépendances

Utilisez maintenant npm pour installer vos dépendances. Exécutez ceci depuis le répertoire de votre projet.

npm install

Exécuter l'application

Vous pouvez démarrer l'application en utilisant npm start. Il s'agit d'une petite fonction intelligente. Si vous ne configurez pas de script de démarrage personnalisé dans package.jsoncette fonction supposera que vous voulez dire node index.jsindex.js est le fichier principal configuré dans package.json. Dans notre cas, il va exécuter ./node_modules/bin/http-server pour démarrer notre serveur web basé sur un nœud.

npm start

Une fois la session lancée, vous pouvez la visualiser localement en ouvrant localhost:8080 dans votre navigateur préféré. Cela devrait ressembler à ceci, mais avec vous à la place de moi.

Basic Vonage Video example up and runningBasic Vonage Video example up and running

Maintenant, chargez le même URL dans une autre fenêtre du navigateur pour pouvoir vous parler à vous-même !

Note importante : Je n'ai pas toujours l'air aussi débraillé :)

Basic Vonage Video conversation occuringBasic Vonage Video conversation occuring

Présentation de la technologie

La plateforme In-App Video API de Vonage facilite l'intégration de vidéos interactives en temps réel et de haute qualité, de la messagerie, du partage d'écran et plus encore dans les applications Web et mobiles. Dans ce cas précis, nous utiliserons les SDK client JavaScript.

SDK Video de Vonage

C'est le Client SDK JavaScript qui fait le gros du travail. Il va utiliser des informations d'identification codées en dur pour se connecter à une session à l'aide d'un jeton que nous générons dans le portail des développeurs. Il va ensuite s'abonner à des flux audio-vidéo et les publier dans notre navigateur.

Tracking.js

Tracking.js est une bibliothèque JavaScript légère permettant de suivre les positions du visage à l'intérieur d'images et de vidéos. Nous allons récupérer les coordonnées sur le canevas.

L'application de démonstration

L'application que vous avez clonée et démarrée est déjà équipée de quelques filtres. Ces filtres fonctionnent en utilisant les données d'image de l'API Canvas API et en les modifiant. Les données de l'image se trouvent dans un objet contenant des données de largeur, de hauteur et de pixel. Les données des pixels sont contenues dans un tableau. Les 4 éléments représentent respectivement les pixels R, G, B et A.

L'application d'effets d'image peut donc se faire en modifiant les données RGBA.

Voici un exemple bidon. Le code est expliqué à l'aide de commentaires, mais il ne sera pas exécuté.

// The existing image data.
const imgData = new ImageData(existingData, width, height);

// An empty typed data array for our new modified image data
const dataArray = new Uint8ClampedArray(imgData.data.length);

// Loop over the data, skipping to every 4th item.
for (let i = 0; i < imgData.data.length; i += 4) {
  // i is R / red
  // i+1 is G / green
  // i+2 is B / blue
  // i+3 is alpha - which specifies the opacity of the color.

  // here, we're addoing 80 to the relative channel value, effectively lighting this channel
  dataArray[i] = imgData.data[i] + 80;
  dataArray[i + 1] = imgData.data[i + 1] + 80;
  dataArray[i + 2] = imgData.data[i + 2] + 80;
  // lightening every channel will result in a whitewashed/brightened effect

  dataArray[i + 3] = imgData.data[i + 3];
}

return new ImageData(dataArray, imgData.width, imgData.height);

Vous pouvez voir les filtres existants qui modifient les valeurs RVB dans le fichier js/filters.js.

Création de nouveaux filtres

Vous trouverez ci-dessous les étapes à suivre pour modifier l'application afin de créer deux nouveaux filtres, pour ajuster la luminosité et le seuillage notre flux.

Luminosité

L'ajustement de la luminosité d'une image est l'une des opérations de traitement d'image les plus simples à réaliser. Comme le montre l'exemple de code précédent, il suffit d'ajouter le changement de luminosité souhaité à chacun des canaux rouge, vert et bleu.

Ouvrez js/filters.js et trouver la ligne avec le commentaire // Brighten et remplacez le commentaire par une nouvelle fonction pour notre filtre.

Conseil rapide : Les commentaires tels que // ... et <!-- // ... --> indiquent qu'il existe du code supplémentaire, mais qu'il a été supprimé, par souci de clarté, ici dans le blog.

// js/filters.js
(function closure(exports) {
  var Filters = {

    // ...

    brighten: function brighten(imgData) {
      // New data

      // Loop 

      // Return
    }
  };

  // ...
})(exports);

Les données de l'image se présentent sous la forme d'un tableau non signé de 8 bits. Cela signifie qu'il a une longueur fixe et que les valeurs sont limitées à des valeurs non signées comprises entre 0 et 255. C'est parfait pour les valeurs RVB. Nous devons donc créer un nouveau tableau 8 bits vide dans lequel nous stockerons nos données d'image modifiées. Remplacer // New data comme indiqué ici, où vous définissez la longueur fixe avec la longueur des données existantes.

// js/filters.js
    // ...

    brighten: function brighten(imgData) {
      const res = new Uint8ClampedArray(imgData.data.length);

      // Loop 

      // Return
    }

    // ...

Passez maintenant en boucle sur les données existantes et modifiez les valeurs pour appliquer un effet d'éclaircissement. Remplacer // Loop comme indiqué ici. Et, comme expliqué plus haut dans l'article, vous bouclez sur les données, en passant à chaque 4ème index du tableau.

// js/filters.js
    // ...

    brighten: function brighten(imgData) {
      const res = new Uint8ClampedArray(imgData.data.length);

      for (let i = 0; i < imgData.data.length; i += 4) {
        var inputRed = imgData.data[i];
        var inputGreen = imgData.data[i + 1];
        var inputBlue = imgData.data[i + 2];
        res[i] = inputRed + 80;
        res[i + 1] = inputGreen + 80;
        res[i + 2] = inputBlue + 80;
        res[i + 3] = imgData.data[i + 3];
      }

      // Return
    }

    // ...

Enfin, pour cette fonction, vous renverrez le nouveau tableau de données. Remplacer // Return comme indiqué.

// js/filters.js
    // ...

    brighten: function brighten(imgData) {
      const res = new Uint8ClampedArray(imgData.data.length);

      for (let i = 0; i < imgData.data.length; i += 4) {
        var inputRed = imgData.data[i];
        var inputGreen = imgData.data[i + 1];
        var inputBlue = imgData.data[i + 2];
        res[i] = inputRed + 80;
        res[i + 1] = inputGreen + 80;
        res[i + 2] = inputBlue + 80;
        res[i + 3] = imgData.data[i + 3];
      }

      return new ImageData(res, imgData.width, imgData.height);
    }

    // ...

Une fois votre fonction configurée, vous devez maintenant pouvoir la sélectionner. Ouvrez donc index.html et remplacez <!-- Brighten --> par une option permettant de sélectionner notre nouvelle fonction.

<!-- index.html -->
<!-- // ... -->
          <select id="filter">
            <option value="none">Filter: None</option>
            <option value="grayscale">Filter: Grayscale</option>
            <option value="sepia">Filter: Sepia</option>
            <option value="invert">Filter: Invert</option>
            <option value="brighten">Filter: Brightening</option>
            <!-- Threshold -->
          </select>
<!-- // ... -->

Maintenant, relancez l'application en ouvrant localhost:8080 et vérifiez le nouveau filtre d'éclaircissement. La valeur option est identique au nom de la fonction.

npm start

Vonage Video stream with a brightening filterVonage Video stream with a brightening filter

Seuil

L'étape suivante est le seuillage de l'image.

Ouvrez js/filters.js et trouver la ligne avec le commentaire // Threshold et remplacez-la par la fonction suivante. Cette fois, vous avez la fonction complète.

Le fonctionnement du seuillage consiste à remplacer la couleur par du blanc ou du noir sur la base d'un algorithme impliquant la somme des valeurs des canaux rouge, vert et bleu. Une somme calculée supérieure à 100 (&gt;= 100), représentant les couleurs les plus vives, est rendue blanche. Tout le reste est rendu noir.

// js/filters.js
(function closure(exports) {
  var Filters = {

    // ...

    threshold: function threshold(imgData) {
      const res = new Uint8ClampedArray(imgData.data.length);
      for (let i = 0; i < imgData.data.length; i += 4) {
        var inputRed = imgData.data[i];
        var inputGreen = imgData.data[i+1];
        var inputBlue = imgData.data[i+2];
        var v = (0.2126 * inputRed + 0.7152 * inputGreen + 0.0722 * inputBlue >= 100) ? 255 : 0;
        res[i] = res[i+1] = res[i+2] = v;
        res[i + 3] = imgData.data[i + 3];
      }

      return new ImageData(res, imgData.width, imgData.height);
    }

  };

  // ...
})(exports);

Il faut maintenant ajouter une option pour sélectionner le seuillage. Ouvrez donc index.html et remplacez <!-- Threshold --> par une option permettant de sélectionner notre nouvelle fonction.

<!-- index.html -->
<!-- // ... -->
          <select id="filter">
            <option value="none">Filter: None</option>
            <option value="grayscale">Filter: Grayscale</option>
            <option value="sepia">Filter: Sepia</option>
            <option value="invert">Filter: Invert</option>
            <option value="brighten">Filter: Brightening</option>
            <option value="threshold">Filter: Thresholding</option>
          </select>
<!-- // ... -->

Maintenant, relancez l'application en ouvrant localhost:8080 et vérifiez le nouveau filtre de seuillage.

npm start

Vonage Video stream with a thresholding filter!Vonage Video stream with a thresholding filter!

Suivi du visage avec Tracking.js

Ensuite, vous ajouterez Tracking.js et rendrez une image sur votre visage comme un masque.

Installer Tracking.js

En utilisant npm, installez Tracking.js.

npm i tracking

Ajoutez la bibliothèque à votre application en utilisant l'approche traditionnelle. Modifiez index.html et trouvez le commentaire où il est dit <!-- Scripts -->. Remplacez-le par les nouvelles balises de script présentées ci-dessous. Le premier fichier tracking-min.js est la bibliothèque Tracking.js minifiée. Le second fichier face-min.js est constitué de données utilisées par Tracking.js pour le suivi des visages dans les médias. D'autres ensembles de données tels que les caractéristiques faciales, y compris les yeux, le nez et la bouche, sont également disponibles.

<!-- index.html -->
<!-- // ... -->
    
      var exports = {};
    
    <a href="http://node_modules/tracking/build/tracking-min.js">http://node_modules/tracking/build/tracking-min.js</a>
    <a href="http://node_modules/tracking/build/data/face-min.js">http://node_modules/tracking/build/data/face-min.js</a>
    <a href="http://js/config.js">http://js/config.js</a>
<!-- // ... -->

Comme les filtres et les masques fonctionnent de manière légèrement différente, je les ai séparés. Cela est dû aux objets disponibles dans le fichier établi js/filters.js établi.

Créez un nouveau fichier js/masks.js et donnez-lui le contenu suivant pour commencer.

// js/masks.js
(function closure(exports) {
  var Masks = {
    none: function none(tracker, canvas, ctx) {}

    // Guy Fawkes

    // Batman
  };

  // Set the initial mask to none
  Masks.selectedMask = Masks.none;

  // When the mask selector changes we update the selectedMask
  var maskSelector = document.querySelector('#mask');
  maskSelector.addEventListener('change', function change() {
    Masks.selectedMask = Masks[maskSelector.value];
  });

  exports.Masks = Masks;
})(exports);

Ouvrez et modifiez js/publish.js et trouver le // Tracker.js commentaire. Remplacez-le, comme indiqué ici.

C'est ce traqueur d'objets qui lira le support et reconnaîtra les visages. Le suivi fonctionne en général en faisant passer une "forme" autour de votre média jusqu'à ce qu'il trouve une correspondance proche. La configuration permet de définir l'échelle de départ de la "forme" et la taille du pas. Ces paramètres permettent d'affiner le réglage (et d'améliorer la vitesse) du traqueur.

Remarque : Avec mes webcams intégrées et 720p, cette configuration a fonctionné correctement.

// js/publish.js

    // ...
    var reqId;

    // Draw a box around face
    var tracker = new tracking.ObjectTracker('face');
    tracker.setInitialScale(10);
    tracker.setStepSize(2);
    tracker.setEdgesDensity(0.01);

    // ...

Et dans le même fichier, trouvez le commentaire // apply Mask et remplacez-le par ceci.

// js/publish.js

      // ...
      ctx.putImageData(imgData, 0, 0);

      exports.Masks.selectedMask(tracker, canvas, ctx);

      // ...

Modifier index.html et trouver les deux <!-- Masks --> commentaires.

Pour séparer les masques, vous créerez également une boîte de sélection distincte. Pour l'instant, nous laisserons les sélections vides.

Vous devez également inclure le nouveau fichier js/masks.js ici.

<!-- index.html -->
<!-- // ... -->
    <div id="videos">
        <div id="subscriber"></div>
        <div id="publisher">
          <!-- // ... -->
          
            Mask: None
            <!-- Guy Fawkes -->
            <!-- Batman -->
          
        </div>
    </div>
    <!-- // ... -->
    <a href="http://js/filters.js">http://js/filters.js</a>
    <a href="http://js/masks.js">http://js/masks.js</a>
<!-- // ... -->

Avant de l'exécuter, il faut appliquer un certain style à la boîte de sélection des masques. Modifiez css/app.css et trouvez le commentaire /* Masks menu css */ et remplacez-le par le CSS suivant.

/* css/app.css */
/* // ... */
#mask {
  position: absolute;
  bottom: 0;
  left: 0;
  z-index: 102;
}

Vous n'avez pas encore créé de masques, mais relancez l'application en ouvrant localhost:8080 pour vérifier que vous n'avez pas fait d'erreur. Si le flux fonctionne, vous êtes sur la bonne voie.

npm start

Le masque de Guy Fawkes

Le premier masque sera Guy. Le masque de Guy Fawkes est devenu un symbole d'anonymat, ce qui est l'intérêt de porter un masque !

Ouvrez js/masks.js et trouver le commentaire // Guy Fawkes. Remplacez le commentaire par la nouvelle fonction.

// js/masks.js
// ...
(function closure(exports) {
  var Masks = {
    none: function none(tracker, canvas, ctx) {},

    guy: function guy(tracker, canvas, ctx) {
      // Load an image

      // Start tracking

      // Apply our image as a mask
    }

    // Batman
  };
// ...

Chargez l'image que vous souhaitez placer sur les visages trouvés par le traqueur, en remplaçant le commentaire // Load an image comme indiqué ici. ../images/guy.png devrait être disponible dans le référentiel.

// js/masks.js

    // ...
    guy: function guy(tracker, canvas, ctx) {
      var mask = document.createElement("img");
      mask.src = '../images/guy.png';  

      // Start tracking

      // Apply our image as a mask
    }
    // ...

Maintenant, activons le suivi de notre média. Le média que vous utilisez est le flux de toile. Vous devez activer la caméra et l'audio ici. Remplacez le commentaire // Start tracking comme suit.

// js/masks.js

    // ...
    guy: function guy(tracker, canvas, ctx) {
      var mask = document.createElement("img");
      mask.src = '../images/guy.png';

      tracking.track(canvas, tracker, { camera: true, audio: true });

      // Apply our image as a mask
    }
    // ...

Vous devez enregistrer un écouteur d'événement contre l'événement track du suiveur afin que, lorsqu'il trouve un visage, il puisse dessiner le masque sur le visage détecté.

// js/masks.js

    // ...
    guy: function guy(tracker, canvas, ctx) {
      var mask = document.createElement("img");
      mask.src = '../images/guy.png';  

      tracking.track(canvas, tracker, { camera: true, audio: true });

      tracker.on('track', function(event) {
        event.data.forEach(function(rect) {
          ctx.drawImage(mask, rect.x - 100, rect.y - 100, rect.width * 1.5, rect.height * 1.5);
        });
      });
    }
    // ...

Vous devez maintenant ajouter l'option du masque de Guy à l'application. Modifier index.htmlCherchez le commentaire <!-- Guy Fawkes --> et ajoutez l'option comme indiqué ci-dessous.

<!-- index.html -->
<!-- // ... -->
    <div id="videos">
        <div id="subscriber"></div>
        <div id="publisher">
          <!-- // ... -->
          
            Mask: None
            Mask: Guy Fawkes
            <!-- Batman -->
          
        </div>
    </div>
<!-- // ... -->

Croisez les doigts, ça marche ! Relancez l'application en ouvrant localhost:8080. Sélectionnez votre premier masque et vérifiez-le.

Vonage Video stream with a Guy Fawkes mask!Vonage Video stream with a Guy Fawkes mask!

Être Batman

Le masque final et la modification de notre code seront Batman. Ouvrez js/masks.js et trouver le commentaire // Batman. Remplacez le commentaire par cette nouvelle fonction.

// js/masks.js

    // ...

    batman: function batman(tracker, canvas, ctx) {
      var mask = document.createElement("img");
      mask.src = '../images/batman.png';  

      tracking.track(canvas, tracker, { camera: true, audio: true });
      tracker.on('track', function(event) {
        event.data.forEach(function(rect) {
          ctx.drawImage(mask, rect.x - 60, rect.y - 170, rect.width * 1.4, rect.height * 1.5);
        });
      });
    }
    // ...

Ajoutez maintenant l'option du masque Batman à l'application. Modifier index.htmlCherchez le commentaire <!-- Batman --> et ajoutez l'option comme indiqué ci-dessous.

<!-- index.html -->
<!-- // ... -->
    <div id="videos">
        <div id="subscriber"></div>
        <div id="publisher">
          <!-- // ... -->
          
            Mask: None
            Mask: Guy Fawkes
            Mask: Batman
          
        </div>
    </div>
<!-- // ... -->

Puis-je être Batman ? Relancez l'application en ouvrant localhost:8080. Sélectionnez le masque et essayez-le.

Vonage Video stream with a Batman maskVonage Video stream with a Batman mask

En résumé

L'écriture de filtres Snapchat et le port de masques dans les flux vidéo sont facilités par les SDK de Vonage Video. Vonage Video vous permet également d'envoyer vos flux vers des plateformes telles que YouTube Live, Twitch et Facebook en même temps. Ainsi, en plus de pouvoir créer votre propre site de diffusion en direct, vous pouvez toujours atteindre des personnes sur d'autres services.

Partager:

https://a.storyblok.com/f/270183/250x250/451101b4f0/lukeoliff.png
Luke OliffAnciens de Vonage

Éducateur technique sympathique, père de famille, défenseur de la diversité, il discute probablement un peu trop. Anciennement ingénieur backend. Parlez-moi de JavaScript (frontend ou backend), de l'incroyable Vue.js, de DevOps, de DevSecOps, de tout ce qui concerne JamStack. Rédacteur sur DEV.to