
Compartir:
Michael es el constructor calvo y barbudo. Aprovechando sus 20 años de experiencia en desarrollo de software y DevOps, este desarrollador con problemas de folclore pasa sus días centrado en ayudar a los demás a tener éxito.
Análisis de sentimientos con Azure Face API y Vonage
Tiempo de lectura: 6 minutos
Usted conoce a esa persona. Puede ser tu pareja, un hijo, un compañero de trabajo o un amigo. La persona que dice una cosa pero, por su cara, se nota que quiere decir otra completamente distinta. Probablemente te la imaginaste en tu cabeza. Tal vez recuerde la conversación exacta. Tal vez fue así:
A ti: ¿De acuerdo?
A ellos: Bien.
Alerta de Spoiler: No estuvo bien.
¿No sería genial si pudieras conocer el sentimiento detrás de lo que están diciendo? Con Video API de Vonage y Face API de Azure, ¡puedes hacerlo!
En este tutorial, construiremos una videoconferencia multipartita que nos permita analizar el sentimiento de cada participante basándonos en su expresión facial. A continuación, mostraremos ese sentimiento como un emoji sobre su vídeo.
Requisitos previos
Antes de empezar, necesitarás algunas cosas:
Una cuenta de Video API de Vonage - crea una gratis si aún no la tienes
Una Azure Account gratuita con Face API Cognitive Service
Opcional: Ngrok para el despliegue de prueba
¿Quieres saltar hasta el final? Puedes encontrar todo el código fuente de este tutorial en GitHub.
Primeros pasos
Vamos a utilizar JavaScript para hacer el trabajo pesado, así que vamos a obtener el HTML y CSS fuera del camino.
mkdir video-sentiment
cd video-sentimentEn la raíz de la carpeta video-sentiment, cree un archivo index.html y copia en él lo siguiente
<!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>
A continuación, cree un directorio css y añádale un archivo app.css en él. Copia el CSS de abajo en ese archivo.
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 a configurarlo
Genial. Ahora podemos hacer que ese bonito HTML y CSS hagan algo. Crea una carpeta js y añade un archivo config.js archivo.
El archivo config.js contiene parámetros de configuración que obtendremos de nuestras cuentas de Vonage Video API y Azure. Copia lo siguiente en el archivo config.js archivo.
// 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 = ''; Configuración de OpenTok
Conseguiremos el OPENTOK_API_KEY, OPENTOK_SESSION_ID y OPENTOK_TOKEN de nuestra cuenta de Video API de Vonage.
En tu cuenta de Video API de Vonage, haz clic en el menú "Proyectos" y "Crear nuevo proyecto". Luego, haz clic en el botón "Crear proyecto personalizado". Dale un nombre a tu nuevo proyecto y presiona el botón 'Crear'. Puedes dejar el códec preferido como 'VP8'.
.
A continuación, puede copiar su clave de API y pegarla como valor para el ajuste OPENTOK_API_KEY configuración.
A continuación, haga clic en "Ver proyecto". En la parte inferior de la página de detalles del proyecto, encontrará las Herramientas del proyecto, donde podrá crear un ID de sesión y un token. Elija "Routed" para el modo multimedia de su sesión y pulse el botón "Create Session ID". A continuación, copie el ID de sesión generado y péguelo como valor del OPENTOK_SESSION_ID de la configuración.
Por último, pegue el identificador de sesión generado en el campo Identificador de sesión del formulario Generar token y pulse el botón "Generar token". Copie el Token generado como valor de la OPENTOK_TOKEN configuración.
Project Tools area of a specific project in a Vonage Video API account.
Configuración de la API Azure Face
Accede a tu Account Azure y crea un nuevo Face API Cognitive Service. Una vez creado, haz clic en el servicio y ve a la hoja "Inicio rápido". Allí encontrarás tu Key y Endpoint. Copia estos dos valores en el campo AZURE_FACE_API_SUBSCRIPTION_KEY y AZURE_FACE_API_ENDPOINT respectivamente.
Screenshot of the Quick start blade within Azure for the Face API service.
Me siento visto
Con nuestra configuración lista, agreguemos algo de JavaScript para conectarnos a una sesión de OpenTok. Añade un archivo app.js en la carpeta js y copia lo siguiente en él.
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 = [];
Aquí están pasando cuatro cosas:
Cargamos las variables en función de las que hayamos especificado en el
config.jsarchivoCreamos un método
handleErrorque utilizaremos cuando se produzca un errorAñadimos un método
dataURItoBlobque utilizaremos para convertir una imagen codificada en base64/URLEncoded a un blob para enviarlo a Azure Face APIAñadimos dos matrices denominadas
streamsyemotions
La matriz streams contendrá todos los flujos de participantes activos para que podamos acceder a ellos para capturar imágenes y enviarlas a la API Azure Face.
La matriz emotions contendrá cadenas que representan cualquier emoción devuelta por la API Azure Face. Se utilizará para mostrar una leyenda de emojis al usuario de forma dinámica.
Inicializar la sesión de OpenTok
Añade el método initializeSession al final del archivo app.js archivo.
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);
}
});
}
El método initializeSession inicializa nuestro cliente de la Video API de Vonage con la sesión que especificamos con el ID de sesión. Luego agrega controladores de eventos para los botones streamCreated y streamDestroyed para administrar la adición y eliminación de secuencias de nuestra matriz streams matriz. Por último, se conecta a la sesión utilizando el token que establecimos en nuestro archivo config.js archivo.
Ahora puede abrir el index.html en Chrome o Firefox. Cuando cargues la página, es posible que tengas que permitir que el navegador acceda a tu cámara web y a tu micrófono. Una vez hecho esto, deberías ver una secuencia de vídeo de ti mismo (o de lo que esté mirando tu webcam) en la página.
Si ha funcionado, silencia el audio, abre otra pestaña (manteniendo abierta la original) y carga el mismo archivo. Ahora deberías poder ver un segundo Video.
Consejo para solucionar problemas: Si no aparece ningún video en la página, abre la pestaña "consola" en las herramientas de tu navegador (command+option+i en Mac, CTRL+i en Windows) y busca errores. El problema más probable es que tu clave, ID de sesión o token de Video API de Vonage no estén configurados correctamente. Como codificaste tus credenciales, también es posible que tu token haya caducado.
Conozco esa mirada
Ahora podemos ver y oír a los participantes, pero ¿qué nos dice su cara que no nos diga su boca? Añadamos un botón que nos permita analizar a cada participante.
En el archivo index.html sustituya el comentario que dice <!-- Footer will go here --> por lo siguiente:
<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>Esto añade un pie de página en la parte inferior de la página con un botón "Analizar" y una lista desordenada que utilizaremos como leyenda entre emojis y sentimientos.
Ahora vamos a añadir el JavaScript para manejar nuestro análisis de sentimiento. Añada lo siguiente al final del archivo app.js archivo.
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);
}
}Repasemos lo que hace este código.
El método assignEmoji toma una clase CSS asociada con la emoción para un flujo de vídeo específico y el índice de ese flujo en nuestra interfaz de usuario. Hace lo siguiente
Añade la clase proporcionada a nuestro
emotionsmatrizAñade un div sobre el panel de Video apropiado con la clase para el emoji a mostrar
Añade una
useda la claselien nuestro pie de página para que ese emoji se muestre en la leyenda
El método processEmotion recibe la carga útil de datos faciales de la API Azure Face e identifica la emoción con la clasificación más alta. A continuación, llama a assignEmoji con la clase CSS apropiada para esa emoción y el índice del vídeo que está procesando.
El método sendToAzure recibe un elemento HTML Video y el índice de ese objeto Video en nuestra página. Obtiene la secuencia asociada a ese elemento de vídeo y, a continuación, crea un lienzo HTML con las mismas dimensiones que la secuencia. A continuación, dibuja una captura del flujo en el nuevo lienzo y envía una XMLHttpRequest a la Azure Face API con la imagen creada. La API Azure Face devolverá un objeto JSON que enviaremos al método processEmotion método
Por último, el método processImages borra cualquier emoji existente en la interfaz de usuario y obtiene todas las etiquetas HTML de vídeo del DOM y las envía al método sendToAzure para ser procesadas. Este método es llamado por nuestro botón "Analizar" en el pie de página.
¿Qué piensas realmente?
Ahora, cuando abramos la página index.html en nuestros navegadores podemos pulsar el botón "Analizar" para ver qué emoción ha identificado la Face API de Azure. Por el momento existen algunas limitaciones. Por ejemplo, si la Face API de Azure reconoce dos caras en el fotograma devolverá datos para ambas, pero nuestro código actualmente solo añade un emoji para la primera.
Además, no estoy seguro, pero puede que no funcione con adolescentes. Hice que mi hija adolescente lo probara docenas de veces, pero sólo devolvía "asco" y "desprecio" como emociones. Tal vez no fuera tan buena idea. Tal vez sea mejor no saber lo que realmente piensan. 😂
Four video frames of Michael showing different sentiments returned from Azure's Face API
Lecturas complementarias
¿Desea obtener más información sobre el uso del análisis de opiniones con Nexmo? Consulte las siguientes publicaciones del blog: