https://d226lax1qjow5r.cloudfront.net/blog/blogposts/sentiment-analysis-with-opentok-and-azure-face-api-dr/Blog_Sentiment-Analysis_Azure_1200x600.png

Analyse de sentiments avec Azure Face API et Vonage

Publié le April 27, 2021

Temps de lecture : 7 minutes

Vous connaissez cette personne. Il peut s'agir de votre moitié, d'un enfant, d'un collègue ou d'un ami. La personne qui dit une chose, mais dont vous pouvez voir sur son visage qu'elle veut dire quelque chose de complètement différent. Vous l'avez probablement imaginée dans votre tête. Vous vous souvenez peut-être de la conversation exacte. Elle s'est peut-être déroulée comme suit :

Vous : D'accord ?

Eux : Très bien.

Alerte au spoiler : Ce n'était pas bien.

Ne serait-ce pas formidable si vous pouviez connaître le sentiment qui se cache derrière ce qu'ils disent ? Avec l'API Video de Vonage et l'API Visage d'Azure, c'est possible !

Dans ce tutoriel, nous allons construire une conférence vidéo multipartite qui nous permet d'analyser le sentiment de chaque participant en fonction de son expression faciale. Ensuite, nous afficherons ce sentiment sous forme d'emoji par-dessus leur vidéo.

Conditions préalables

Avant de commencer, vous aurez besoin de quelques éléments :

Vous voulez sauter à la fin ? Vous pouvez trouver tout le code source de ce tutoriel sur GitHub.

Pour commencer

Nous utiliserons JavaScript pour faire le gros du travail, et nous allons donc nous débarrasser du HTML et du CSS.

mkdir video-sentiment
cd video-sentiment

À la racine du dossier Video-sentiment, créez un fichier index.html et copiez-y ce qui suit.

<!DOCTYPE html>
<html>
  <head>
    <title>Vonage Video API Sentiment Analysis</title>

    <link href="https://emoji-css.afeld.me/emoji.css" rel="stylesheet" type="text/css" />
    <link href="css/app.css" rel="stylesheet" type="text/css" />

    <a href="https://static.opentok.com/v2/js/opentok.min.js">https://static.opentok.com/v2/js/opentok.min.js</a>

    <!-- Polyfill for fetch API so that we can fetch the sessionId and token in IE11 -->
    <a href="https://cdn.jsdelivr.net/npm/promise-polyfill@7/dist/polyfill.min.js">https://cdn.jsdelivr.net/npm/promise-polyfill@7/dist/polyfill.min.js</a>
    <a href="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js">https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js</a>
  </head>
  <body>

    <div id="videos">
      <div id="subscriber"></div>
      <div id="publisher"></div>
    </div>

    <!-- Footer will go here -->

    <a href="http://js/config.js">http://js/config.js</a>
    <a href="http://js/app.js">http://js/app.js</a>
  </body>
</html>

Ensuite, créez un répertoire css et y ajouter un fichier app.css dans ce répertoire. Copiez le CSS ci-dessous dans ce fichier.

body,
html {
  height: 100%;
  background-color: black;
  margin: 0;
  padding: 0;
  font-family: Arial, Helvetica, sans-serif;
}

#videos {
  width: 100%;
  height: 50%;
  margin-left: auto;
  margin-right: auto;
}

#subscriber {
  width: 100%;
  height: 100%;
}

#publisher {
  position: absolute;
  bottom: 50px;
  right: 0px;
  z-index: 100;
}

.OT_subscriber {
  width: 300px !important;
  height: 200px !important;
  float: left;
  margin: 5px !important;
}

.OT_widget-container {
  padding: 6px 0 0 6px !important;
  background-color: #70B7FD !important;
}

#publisher .OT_widget-container {
  padding: 6px 0 0 6px !important;
  background-color: hotpink !important;
}

.sentiment {
  position: absolute;
  z-index: 9000;
  height: 100px;
  width: 100px;
  font-size: 48px;
}

footer {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 30px;
  padding: 10px;
  background-color: gray;
}

button {
  font-size: 16px;
  padding: 5px 10px;
  display: inline;
}

ul {
  float: right;
  display: inline;
  list-style: none;
  padding: 5px 10px;
  margin: 0;
}

li {
  display: inline;
  background-color: lightgrey;
  padding: 10px;
  display: none;
}
li.used {
  display: inline;
}

Configurons le système

C'est génial ! Maintenant, nous pouvons faire en sorte que ces jolis HTML et CSS fassent quelque chose. Créez un dossier js et ajoutez un fichier config.js fichier.

Le fichier config.js contient les paramètres de configuration que nous obtiendrons de nos comptes Vonage Video API et Azure. Copiez les éléments suivants dans le fichier config.js dans le fichier.

// Replace these values with those generated in your Vonage Video API and Azure Accounts
const OPENTOK_API_KEY = '';
const OPENTOK_SESSION_ID = '';
const OPENTOK_TOKEN = '';
const AZURE_FACE_API_SUBSCRIPTION_KEY = '';
const AZURE_FACE_API_ENDPOINT = '';

Paramètres OpenTok

Nous obtiendrons le OPENTOK_API_KEY, OPENTOK_SESSION_ID et OPENTOK_TOKEN de notre Video API Account de Vonage.

Dans votre Video API Account de Vonage, cliquez sur le menu " Projets " et " Créer un nouveau projet ", puis sur le bouton " Créer un projet personnalisé ". Donnez un nom à votre nouveau projet et appuyez sur le bouton "Create". Vous pouvez laisser le codec préféré à "VP8".

(/content/blog/sentiment-analysis-with-azure-face-api-and-vonage/tb-project-created.png "Screenshot of the \"project created\" dialog within a Vonage Video API account.")

Vous pouvez ensuite copier votre clé API et la coller comme valeur pour le paramètre OPENTOK_API_KEY paramètre.

Ensuite, cliquez sur "Voir le projet". Au bas de la page de détail du projet, vous trouverez les outils de projet où vous pouvez créer un identifiant de session et un jeton. Choisissez "Routed" pour le mode média de votre session et cliquez sur le bouton "Create Session ID". Copiez ensuite l'identifiant de session généré et collez-le comme valeur du paramètre OPENTOK_SESSION_ID paramètre.

Enfin, collez l'ID de session généré dans le champ ID de session du formulaire Générer un jeton et appuyez sur le bouton "Générer un jeton". Copiez le jeton généré comme valeur du paramètre OPENTOK_TOKEN paramètre.

Project Tools area of a specific project in a Vonage Video API account.Project Tools area of a specific project in a Vonage Video API account.

Paramètres de l'API Azure Face

Connectez-vous à votre Account Azure et créez un nouveau service cognitif API visage. Une fois créé, cliquez sur le service et accédez à la lame "Quick start". Vous y trouverez votre Key et Endpoint. Copiez ces deux valeurs dans les champs AZURE_FACE_API_SUBSCRIPTION_KEY et AZURE_FACE_API_ENDPOINT respectivement.

Screenshot of the Quick start blade within Azure for the Face API service.Screenshot of the Quick start blade within Azure for the Face API service.

Je me sens vu

Notre configuration étant prête, ajoutons du JavaScript pour nous connecter à une session OpenTok. Ajoutez un fichier app.js dans le dossier js et copiez-y ce qui suit.

var opentok_api_key;
var opentok_session_id;
var opentok_token;
var azure_face_api_subscription_key;
var azure_face_api_endpoint;

// See the config.js file.
if (OPENTOK_API_KEY &&
  OPENTOK_SESSION_ID &&
  OPENTOK_TOKEN &&
  AZURE_FACE_API_SUBSCRIPTION_KEY &&
  AZURE_FACE_API_ENDPOINT) {

  opentok_api_key = OPENTOK_API_KEY;
  opentok_session_id = OPENTOK_SESSION_ID;
  opentok_token = OPENTOK_TOKEN;
  azure_face_api_subscription_key = AZURE_FACE_API_SUBSCRIPTION_KEY;
  azure_face_api_endpoint = AZURE_FACE_API_ENDPOINT;

  initializeSession();

} else {

  alert('Failed to get configuration variables. Make sure you have updated the config.js file.');

}

// Handling all of our errors here by logging them to the console
function handleError(error) {
  if (error) {
    console.log(error.message);
  }
}

function dataURItoBlob(dataURI) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  var byteString;
  if (dataURI.split(",")[0].indexOf("base64") >= 0)
    byteString = atob(dataURI.split(",")[1]);
  else byteString = unescape(dataURI.split(",")[1]);

  // separate out the mime component
  var mimeString = dataURI
    .split(",")[0]
    .split(":")[1]
    .split(";")[0];

  // write the bytes of the string to a typed array
  var ia = new Uint8Array(byteString.length);
  for (var i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i);
  }

  return new Blob([ia], { type: mimeString });
}

var streams = [];
var emotions = [];

Il y a quatre choses qui se passent ici :

  1. Nous chargeons les variables en fonction de celles que nous avons spécifiées dans le config.js fichier

  2. Nous créons une méthode handleError que nous utiliserons partout lorsqu'une erreur se produira

  3. Nous ajoutons une méthode dataURItoBlob que nous utiliserons pour convertir une image codée en base64/URLEncoded en un blob à envoyer à Azure Face API

  4. Nous avons ajouté deux tableaux nommés streams et emotions

Le tableau streams contiendra tous les flux de participants actifs afin que nous puissions y accéder pour capturer des images à envoyer à l'API Azure Face.

Le tableau emotions contiendra des chaînes de caractères représentant les émotions renvoyées par Azure Face API. Il sera utilisé pour afficher dynamiquement une légende d'emojis à l'utilisateur.

Initialisation de la session OpenTok

Ajoutez la méthode initializeSession ci-dessous au bas du fichier app.js au bas du fichier.

function initializeSession() {
  var session = OT.initSession(opentok_api_key, opentok_session_id);

  // Subscribe to a newly created streams and add
  // them to our collection of active streams.
  session.on("streamCreated", function (event) {
    streams.push(event.stream);
    session.subscribe(
      event.stream,
      "subscriber",
      {
        insertMode: "append"
      },
      handleError
    );
  });

  // Remove streams from our array when they are destroyed.
  session.on("streamDestroyed", function (event) {
    streams = streams.filter(f => f.id !== event.stream.id);
  });

  // Create a publisher
  var publisher = OT.initPublisher(
    "publisher",
    {
      insertMode: "append"
    },
    handleError
  );

  // Connect to the session
  session.connect(opentok_token, function (error) {
    // If the connection is successful, initialize a publisher and publish to the session
    if (error) {
      handleError(error);
    } else {
      session.publish(publisher, handleError);
    }
  });
}

La méthode initializeSession initialise notre client Video API de Vonage avec la session que nous avons spécifiée avec l'ID de session. Elle ajoute ensuite des gestionnaires d'événements pour les éléments streamCreated et streamDestroyed pour gérer l'ajout et la suppression de flux dans notre tableau streams de notre tableau. Enfin, elle se connecte à la session à l'aide du jeton que nous avons défini dans notre fichier config.js dans notre fichier

Vous pouvez maintenant ouvrir le index.html dans Chrome ou Firefox. Lorsque vous chargez la page, vous devrez peut-être autoriser le navigateur à accéder à votre webcam et à votre microphone. Après cela, vous devriez voir un flux vidéo de vous-même (ou de ce que votre webcam regarde) s'afficher sur la page.

Si cela a fonctionné, coupez le son, ouvrez un autre onglet (en gardant l'original ouvert) et chargez le même fichier. Vous devriez maintenant pouvoir voir une deuxième Video.

Conseil de dépannage : Si aucune vidéo ne s'affiche sur la page, ouvrez l'onglet " console " dans les outils de votre navigateur (commande+option+i sur Mac, CTRL+i sur Windows) et vérifiez s'il y a des erreurs. Le problème le plus probable est que la clé de l'API Video de Vonage, l'ID de session ou le jeton n'est pas correctement configuré. Puisque vous avez codé en dur vos informations d'identification, il est également possible que votre jeton ait expiré.

Je connais ce regard

Nous pouvons maintenant voir et entendre les participants, mais qu'est-ce que leur visage nous dit que leur bouche ne nous dit pas ? Ajoutons un bouton qui nous permettra d'analyser chaque participant.

Dans le fichier index.html remplacez le commentaire qui dit <!-- Footer will go here --> par ce qui suit :

<footer>
    <button id="analyze" type="button" onclick="processImages();">Analyze</button>

    <ul>
        <li name="em-angry"><i class="em em-angry"></i> Angry</li>
        <li name="em-frowning"><i class="em em-frowning"></i> Contempt</li>
        <li name="em-face_vomiting"><i class="em em-face_vomiting"></i> Disgust</li>
        <li name="em-fearful"><i class="em em-fearful"></i> Fear</li>
        <li name="em-grin"><i class="em em-grin"></i> Happiness</li>
        <li name="em-neutral_face"><i class="em em-neutral_face"></i> Neutral</li>
        <li name="em-cry"><i class="em em-cry"></i> Sadness</li>
        <li name="em-astonished"><i class="em em-astonished"></i> Surprise</li>
    </ul>
</footer>

Cela ajoute un pied de page au bas de la page avec un bouton "Analyser" et une liste non ordonnée que nous utiliserons comme légende entre les emojis et les sentiments.

Ajoutons maintenant le JavaScript pour gérer notre analyse des sentiments. Ajoutez ce qui suit à la fin du fichier app.js au bas du fichier.

function assignEmoji(emojiClass, index) {
  var widgets = document.getElementsByClassName('OT_widget-container');
  emotions.push(emojiClass);

  var sentimentDiv = document.createElement("div");
  sentimentDiv.classList.add("sentiment");
  sentimentDiv.classList.add("em");
  sentimentDiv.classList.add(emojiClass);

  widgets[index].appendChild(sentimentDiv);

  const legendEl = document.getElementsByName(emojiClass);
  legendEl[0].classList.add('used');
}

function processEmotion(faces, index) {
  // for each face identified in the result
  for (i = 0; i 
      memo[1] > value ? memo : [key, value]
    );

    let emojiClass = 'em-neutral_face';
    switch (maxEmotion[0]) {
      case 'angry':
        emojiClass = 'em-angry';
        break;
      case 'contempt':
        emojiClass = 'em-frowning';
        break;
      case 'disgust':
        emojiClass = 'em-face_vomiting';
        break;
      case 'fear':
        emojiClass = 'em-fearful';
        break;
      case 'happiness':
        emojiClass = 'em-grin';
        break;
      case 'sadness':
        emojiClass = 'em-cry';
        break;
      case 'surprise':
        emojiClass = 'em-astonished';
        break;
      default:
        break;
    }
    assignEmoji(emojiClass, index);
  }
}

// Gets a <video> element and draws it to a new
// canvas object. Then creates a jpeg blob from that
// canvas and sends to Azure Face API to get emotion
// data.
function sendToAzure(video, index) {
  // Get the stream object associated with this
  // <video> element.
  var stream = streams[index];

  var canvas = document.createElement("canvas");
  canvas.height = stream.videoDimensions.height;
  canvas.width = stream.videoDimensions.width;

  var ctx = canvas.getContext("2d");
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

  var dataURL = canvas.toDataURL("image/jpeg", 0.8);
  var blob = dataURItoBlob(dataURL);
  var fd = new FormData(document.forms[0]);
  fd.append("canvasImage", blob);

  // Perform the REST API call.
  var uriBase = `${azure_face_api_endpoint}/face/v1.0/detect`;

  // Request parameters.
  var params = 'returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=emotion';

  const xhr = new XMLHttpRequest();
  xhr.open('POST', `${uriBase}?${params}`);
  xhr.responseType = 'json';
  xhr.setRequestHeader('Content-Type', 'application/octet-stream');
  xhr.setRequestHeader("Ocp-Apim-Subscription-Key", azure_face_api_subscription_key);

  xhr.send(blob);

  xhr.onload = () => {

    if (xhr.status == 200) {
      processEmotion(xhr.response, index);
    } else {
      var errorString = `(${xhr.status}) ${xhr.statusText}`;
      alert(errorString);
    }
  }
}

// Reset emojis and loop through all <video> elements and call
// sendToAzure
function processImages() {
  emotions = [];
  var sentiments = document.getElementsByClassName('sentiment');
  var usedListItems = document.getElementsByClassName('used');
  var videos = document.getElementsByTagName('video');

  // Remove any existing sentiment & emotion objects
  if (sentiments.length > 0) {
    for (s = 0; s  0) {
    for (l = 0; l < usedListItems.length; l++) {
      usedListItems[l].classList.remove('used');
    }
  }

  for (v = 0; v < (videos.length - 1); v++) {
    sendToAzure(videos[v], v);
  }
}

Voyons ce que fait ce code.

La méthode assignEmoji prend en compte une classe CSS associée à l'émotion pour un flux vidéo spécifique et l'index de ce flux dans notre interface utilisateur. Elle effectue les opérations suivantes :

  1. Ajoute la classe fournie à notre emotions tableau

  2. Ajoute une div au-dessus du panneau vidéo approprié avec la classe de l'emoji à afficher

  3. Ajoute une used à la classe li dans notre pied de page pour cet emoji afin qu'il s'affiche dans la légende

La méthode processEmotion reçoit les données sur les visages de l'API Azure Face et identifie l'émotion la mieux classée. Elle appelle ensuite assignEmoji avec la classe CSS appropriée pour cette émotion et l'index de la vidéo qu'elle traite.

La méthode sendToAzure reçoit un élément vidéo HTML et l'index de cet objet vidéo sur notre page. Elle obtient le flux associé à cet élément vidéo, puis crée un canevas HTML aux mêmes dimensions que le flux. Ensuite, elle réalise une capture du flux sur le nouveau canevas et envoie une demande XMLHttpRequest à l'API Azure Face avec l'image qu'elle a créée. L'API Azure Face renverra un objet JSON que nous enverrons ensuite à la méthode processEmotion méthode.

Enfin, la méthode processImages efface tous les emojis existants de l'interface utilisateur et récupère toutes les balises HTML Video dans le DOM et les envoie à la méthode sendToAzure pour qu'elles soient traitées. Cette méthode est appelée par notre bouton "Analyser" dans le pied de page.

Que pensez-vous vraiment ?

Maintenant, lorsque nous ouvrons la page index.html dans nos navigateurs, nous pouvons appuyer sur le bouton "Analyser" pour voir quelle émotion l'API visage d'Azure a identifiée. Il y a quelques limitations pour le moment. Par exemple, si Azure Face API reconnaît deux visages dans le cadre, il renverra des données pour les deux, mais notre code n'ajoute actuellement un emoji que pour le premier.

De plus, je n'en suis pas certain, mais il se peut qu'il ne fonctionne pas pour les adolescents. J'ai demandé à ma fille adolescente de le tester des dizaines de fois, mais il n'a retourné que "dégoût" et "mépris" comme émotions. Ce n'était peut-être pas une bonne idée. Peut-être vaut-il mieux ne pas savoir ce qu'ils pensent vraiment. 😂

Four video frames of Michael showing different sentiments returned from Azures Face APIFour video frames of Michael showing different sentiments returned from Azure's Face API

Pour en savoir plus

Vous souhaitez en savoir plus sur l'utilisation de l'analyse des sentiments avec Nexmo ? Consultez les articles de blog suivants :

Partager:

https://a.storyblok.com/f/270183/225x225/b0360f94ad/michaeljolley.png
Michael JolleyAnciens de Vonage

Michael est le bâtisseur chauve et barbu. Fort de ses 20 ans d'expérience dans le développement de logiciels et DevOps, ce développeur aux prises avec des difficultés folliculaires passe ses journées à aider les autres à réussir.