https://a.storyblok.com/f/270183/24097/211d3113e4/blog_sentiment-analysis_azure_1200x600.png

Análise de sentimento com a API Face do Azure e a Vonage

Publicado em April 27, 2021

Tempo de leitura: 6 minutos

Você conhece esse tipo de pessoa. Pode ser seu parceiro, um filho, um colega de trabalho ou um amigo. Aquela pessoa que diz uma coisa, mas dá para perceber pela expressão do rosto que ela quer dizer algo completamente diferente. Provavelmente você acabou de imaginá-la na sua cabeça. Talvez se lembre exatamente da conversa. Talvez tenha sido mais ou menos assim:

Você: Tudo bem?

Eles: Tudo bem.

Alerta de spoiler: Não deu tudo certo.

Não seria ótimo se você pudesse saber o sentimento por trás do que eles estão dizendo? Com a Video API da Vonage e a API de Reconhecimento Facial do Azure, isso é possível!

Neste tutorial, vamos criar uma videoconferência com vários participantes que nos permita analisar o sentimento de cada um com base em sua expressão facial. Em seguida, exibiremos esse sentimento na forma de um emoji sobre o vídeo de cada participante.

Pré-requisitos

Antes de começar, você vai precisar de algumas coisas:

Quer pular direto para o final? Você pode encontrar todo o código-fonte deste tutorial no GitHub.

Introdução

Vamos usar JavaScript para fazer o trabalho pesado, então vamos resolver primeiro o HTML e o CSS.

mkdir video-sentiment
cd video-sentiment

Na raiz da pasta “video-sentiment”, crie um index.html arquivo e copie o seguinte nele.

<!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>

Em seguida, crie um css diretório e adicione um app.css arquivo nele. Copie o CSS abaixo nesse arquivo.

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;
}

Vamos configurar tudo

Ótimo! Agora podemos fazer com que esse belo HTML e CSS façam alguma coisa. Crie uma js pasta e adicione um config.js arquivo.

O config.js arquivo contém parâmetros de configuração que obteremos de nossas contas da Video API do Vonage e do Azure. Copie o seguinte para o config.js arquivo.

// 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 = '';

Configurações do OpenTok

Vamos pegar o OPENTOK_API_KEY, OPENTOK_SESSION_ID e OPENTOK_TOKEN da nossa conta da Video API do Vonage.

Na sua Account da Video API da Vonage, clique no menu “Projetos” e em “Criar novo projeto”. Em seguida, clique no botão “Criar projeto personalizado”. Dê um nome ao seu novo projeto e clique no botão “Criar”. Você pode deixar o codec preferencial como “VP8”.

![Captura de tela da caixa de diálogo “projeto criado” em um Account da Video API do Vonage.](/content/blog/sentiment-analysis-with-azure-face-api-and-vonage/tb-project-created.png “Captura de tela da caixa de diálogo ‘projeto criado’ em um Account da Video API da Vonage.”)

Em seguida, você pode copiar sua chave de API e colá-la como o valor da OPENTOK_API_KEY configuração.

Em seguida, clique em “Visualizar projeto”. Na parte inferior da página de detalhes do projeto, você encontrará as Ferramentas do Projeto, onde poderá criar um ID de sessão e um token. Escolha “Routed” como modo de mídia da sua sessão e clique no botão “Criar ID de sessão”. Em seguida, copie o ID de sessão gerado e cole-o como valor da OPENTOK_SESSION_ID configuração.

Por fim, cole o ID de sessão gerado no campo “ID de sessão” do formulário “Gerar token” e clique no botão “Gerar token”. Copie o token gerado como o valor do OPENTOK_TOKEN configuração.

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.

Configurações da API do Azure Face

Faça login no seu Account do Azure e crie um novo Serviço Cognitivo Face API. Após a criação, clique no serviço e acesse a aba “Início rápido”. Lá você encontrará seu Key e Endpoint. Copie esses dois valores para o AZURE_FACE_API_SUBSCRIPTION_KEY e AZURE_FACE_API_ENDPOINT , respectivamente.

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.

Eu me sinto reconhecido

Com nossa configuração pronta, vamos adicionar um pouco de JavaScript para nos conectarmos a uma sessão do OpenTok. Adicione um app.js arquivo na pasta js e copie o seguinte nele.

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 = [];

Há quatro coisas acontecendo aqui:

  1. Carregamos as variáveis com base naquelas que especificamos no config.js arquivo

  2. Criamos um handleError método que usaremos sempre que ocorrer um erro

  3. Adicionamos um dataURItoBlob método que usaremos para converter uma imagem em base64/URLEncoded em um blob para envio à API do Azure Face

  4. Adicionamos dois arrays chamados streams e emotions

A streams matriz conterá todos os fluxos de participantes ativos para que possamos acessá-los a fim de capturar imagens para enviar à API do Azure Face.

A emotions matriz conterá strings que representam quaisquer emoções retornadas pela API do Azure Face. Isso será usado para exibir dinamicamente uma legenda de emojis para o usuário.

Inicializando a sessão do OpenTok

Adicione o initializeSession método abaixo no final do app.js arquivo.

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);
    }
  });
}

O initializeSession método inicializa nosso cliente da Video API do Vonage com a sessão que especificamos por meio do ID da sessão. Em seguida, ele adiciona manipuladores de eventos para o streamCreated e streamDestroyed para gerenciar a adição e a remoção de fluxos da nossa streams matriz. Por fim, ele se conecta à sessão usando o token que definimos em nosso config.js arquivo.

Agora você pode abrir a index.html no Chrome ou no Firefox. Ao carregar a página, talvez seja necessário permitir que o navegador acesse sua webcam e seu microfone. Depois disso, você deverá ver uma transmissão de vídeo sua (ou do que quer que sua webcam esteja captando) sendo exibida na página.

Se isso funcionar, desative o som e, em seguida, abra outra aba (mantendo a original aberta) e carregue o mesmo arquivo. Agora você deve conseguir ver um segundo Video.

Dica para solução de problemas: Se nenhum Video estiver sendo exibido na página, abra a aba “console” nas ferramentas do seu navegador (command+option+i no Mac, CTRL+i no Windows) e verifique se há erros. O problema mais provável é que sua chave da Video API da Vonage, seu ID de sessão ou seu token não estejam configurados corretamente. Como você codificou suas credenciais diretamente no código, também é possível que seu token tenha expirado.

Eu conheço esse olhar

Agora podemos ver e ouvir os participantes, mas o que o rosto deles está nos dizendo que a boca não diz? Vamos adicionar um botão que nos permita analisar cada participante.

No index.html arquivo, substitua o comentário que diz <!-- Footer will go here --> pelo seguinte:

<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>

Isso adiciona um rodapé na parte inferior da página com um botão “Analisar” e uma lista não ordenada que usaremos como legenda entre os emojis e os sentimentos.

Agora vamos adicionar o JavaScript para realizar nossa análise de sentimento. Adicione o seguinte ao final do app.js arquivo.

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);
  }
}

Vamos rever o que esse código faz.

O assignEmoji método recebe uma classe CSS associada à emoção de um stream de Video específico e o índice desse stream em nossa interface do usuário. Ele faz o seguinte:

  1. Adiciona a classe fornecida à nossa emotions matriz

  2. Adiciona um elemento `div` sobre o painel de Video correspondente com a classe necessária para que o emoji seja exibido

  3. Adiciona uma used classe ao li em nosso rodapé para esse emoji, de modo que ele seja exibido na legenda

O processEmotion método recebe a carga útil dos dados faciais da API do Azure Face e identifica a emoção com a classificação mais alta. Em seguida, ele chama assignEmoji a classe CSS apropriada para essa emoção e o índice do Video que está sendo processado.

O sendToAzure método recebe um elemento de vídeo HTML e o índice desse objeto de vídeo em nossa página. Ele obtém o fluxo associado a esse elemento de vídeo e, em seguida, cria uma tela HTML com as mesmas dimensões do fluxo. Em seguida, ele desenha uma captura do fluxo na nova tela e envia uma solicitação XMLHttpRequest para a API do Azure Face com a imagem criada. A API do Azure Face retornará um objeto JSON que, então, enviaremos ao processEmotion método.

Por fim, o processImages método limpa todos os emojis existentes da interface do usuário, obtém todas as tags de Video HTML no DOM e as envia para o sendToAzure método para serem processadas. Esse método é chamado pelo nosso botão “Analisar” no rodapé.

O que você está realmente pensando?

Agora, quando abrimos a index.html página em nossos navegadores, podemos clicar no botão “Analisar” para ver qual emoção a API de Rosto do Azure identificou. Existem algumas limitações no momento. Por exemplo, se a API de Rosto do Azure reconhecer dois rostos no quadro, ela retornará dados para ambos, mas nosso código, atualmente, adiciona um emoji apenas para o primeiro.

Além disso, não tenho certeza, mas talvez isso não funcione com adolescentes. Fiz minha filha adolescente testar isso dezenas de vezes, mas o resultado sempre foi apenas “nojo” e “desprezo” como emoções. Talvez isso não tenha sido uma ideia tão boa assim. Talvez seja melhor não saber o que eles realmente pensam. 😂

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

Leitura complementar

Quer saber mais sobre como usar a análise de sentimento com o Nexmo? Confira as seguintes postagens do blog:

Compartilhar:

https://a.storyblok.com/f/270183/225x225/b0360f94ad/michaeljolley.png
Michael JolleyEx-funcionários da Vonage

Michael é o construtor careca e barbudo. Com seus 20 anos de experiência em desenvolvimento de software e DevOps, esse desenvolvedor com pouco cabelo dedica seus dias a ajudar os outros a terem sucesso.