
Compartilhar:
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.
Análise de sentimento com a API Face do Azure e a Vonage
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:
Um Account da Video API da Vonage — crie uma gratuitamente se ainda não tiver feito isso
Um Account gratuito do Azure com Serviço Cognitivo Face API
Opcional: Ngrok para implantação de teste
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-sentimentNa 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”.

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.
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.
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:
Carregamos as variáveis com base naquelas que especificamos no
config.jsarquivoCriamos um
handleErrormétodo que usaremos sempre que ocorrer um erroAdicionamos um
dataURItoBlobmétodo que usaremos para converter uma imagem em base64/URLEncoded em um blob para envio à API do Azure FaceAdicionamos dois arrays chamados
streamseemotions
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:
Adiciona a classe fornecida à nossa
emotionsmatrizAdiciona um elemento `div` sobre o painel de Video correspondente com a classe necessária para que o emoji seja exibido
Adiciona uma
usedclasse aoliem 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 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: