Sentiment Analysis su volti con tensorflow.js – quale è la mia emozione?
Analisi delle immagini e Face Emotion
In questo articolo proverò ad illustrarvi una procedura su come usare tensorflow.js applicato al riconoscimento delle emozioni facciali. Lo scopo è quello di fornire una procredura in JavaScript per usare dei modelli preaddestrati utili allo scopo. In questo esempio utilizzeremo una libreria molto interessante face-api.js con la quale è possibile gestire il riconoscimento di volti all’interno di una foto o di un frame video.
Ai fini dell’articolo e per facilitare qualche vostro esperimento vi lascio l’esempio completo ed il codice sorgente qui, mentre di seguito potrete verificare il risultato direttamente online. State tranquilli non verrà memorizzata nessuna informazione potete dare l’ok all’accesso della webcam, gira tutto sul vostro client… ed in ogni caso evitate di fare brutte facce 🙂
Come già anticipato per questo progetto useremo Tensorflow; per chi non lo conoscesse TensorFlow è una Libreria software open source realizzata da google specializzata in ambito machine learning/deep learning. Devo dire che la potenzialità di tensoflow sono ancora più apprezzabili grazie ai vari modelli rilasciati che ci consentono facilemente di creare sofisticati sistemi “intelligenti” in poco tempo. Nel caso specifico utilizzeremo un modello molto interessante la MobilnetV1.
Rispetto ad altri modelli simili, la MobileNet funziona bene sia in termini di latenza che di precisione. Le Mobilnet,infatti, sono una famiglia di modelli di Computer Vision mobile-first , progettate per massimizzare efficacemente tali paramentri , perchè tengono conto delle risorse limitate per un’applicazione su dispositivi mobile o web.
Le MobileNet possono essere usate per operazioni di classificazione, rilevamento, segmentazione di immagini.
Il mio stato d'animo: Attendi un attimo
Per aiutare un pò la comprensione del codice vi lascio qualche nota. La prima in ordine è : quando sviluppate con tensorflow.js dovete fare molta attenzione alle versioni usate. Molti degli errori che potreste ottenere deriva , infatti, da una errata scelta delle versioni.
Bene iniziamo a descrivere un pò il codice.
Nel primo blocco non faccio altro che creare il container web per la gestione della webcam e per importare le prime librerie necessarie al nostro scopo. Non mi sofferemo, pertanto, perchè si tratta di HTML base.
<html> <head> <!-- Load TensorFlow.js. This is required to use MobileNet. --> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.2.1"/> <!-- Load the MobileNet model. --> <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet@1.0.0"/> <script src="https://code.jquery.com/jquery-3.4.0.min.js" integrity="sha256-BJeo0qm959uMBGb65z40ejJYGSgR7REI4+CW1fNKwOg=" crossorigin="anonymous"> </script> </head> <body> <div id="emotionContainer"> <video autoplay="true" width="640" height="480" id="videoElement"></video> <p>Il mio stato d'animo: <span id ="emotion">Attendi un attimo</span></p> </div> </body> </html> <script> . . .
In questo blocco ho configurato un pò di parametri utili; quelli importanti sono le classi di uscita, ovvero le emotions_labels, ed il valore di soglia per attivare la classificazione delle emozioni: scoreThreshold.
let scoreThreshold = 0.5 var EmotionModel; var emotion_labels = ["angry", "disgust", "fear", "happy", "sad", "surprise", "neutral"]; var emotion_colors = ["#ff0000", "#00a800", "#ff4fc1", "#ffe100", "#306eff", "#ff9d00", "#7c7c7c"]; var offset_x = 27; var offset_y = 20;
Anche il blocco seguente è abbastanza standard. Nel caso specifico ho utilizzato i MediaDevices per attivare la webcam ed ottenere l’oggetto video che potremo utiizzare in seguito per elabarore i singoli frames.
// START VIDEO startVideo(); function startVideo() { // use MediaDevices API // docs: // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia var video = document.querySelector('video'),canvas; webcamElement=video; console.log("navigator " + navigator.mediaDevices) if (navigator.mediaDevices) { // access the web cam navigator.mediaDevices.getUserMedia({ video: true }) // permission granted: .then(function(stream) { //video.src = window.URL.createObjectURL(stream); video.srcObject = stream; loadFaceApi(); }) // permission denied: .catch(function(error) { document.body.textContent = 'Could not access the camera.Error:' + error.name; }); } }
Il blocco seguente non fa altro che importare dinamicamente le librerie face-api.js alla conclusione dell’attivazione della webcam (vedi blocco precedente). Il fatto che lal ibreiria venga caricata dinamicamente è una mia scelta, mentre diventa importante caricare il modello preaddestrato per il riconoscimento delle emozioni basato sulla mobilnet solo a valle del caricamento delle face-api.
// LOAD FACE-API PER IL DETECT DEI VOLTI function loadFaceApi(){ jQuery.getScript('/js/FaceEmotion/face-api.js', function(){ console.log('loaded FaceApi'); setTimeout(function(){ loadTrainedModelEmotion(); },4000); }); }
Il caricamento del modello facciale viene effettuato invocando una funzione asincrona quindi, come dicevo, è importante gestire bene l’ordine di caricamento. Alla fine del caricamento del modello avvierò il riconoscimento dei volti su cui lavoreremo per catturare le eomozioni.
// AVVIA IL LOADING DEL MODELLO PER LA VERIFICA DELLE EMOZIONI
async function loadTrainedModelEmotion(){
path='/js/FaceEmotion/models/mobilenetv1_models/model.json';
EmotionModel = await tf.loadLayersModel(path)
console.log(EmotionModel);
load_modello_di_riconoscimento_facciale().then(function(){
var video = document.querySelector('video'),canvas;
start_riconoscimento(video);
});
}
La seguente funzione non merita molti commenti; tieniamo solo presente che si tratta di una funzione asincrona , motivo per cui abbiamo invocato la stessa utilizzando il metodo “then” che ci consente di attivare il riconoscimento solo nel momento il cui il modello viene caricato.
async function load_modello_di_riconoscimento_facciale() { //const Model_url = '/js/FaceEmotion/models/tiny_face_detector/tiny_face_detector_model-weights_manifest.json' const Model_url = '/js/FaceEmotion/models/tiny_face_detector/' await faceapi.loadTinyFaceDetectorModel(Model_url); console.log("FaceEmotion modelLoaded"); }
La seguente funzione ci servirà per verificare se nei frame è presente o meno un volto attraverso detectAllFaces. Nel caso in cui viene individuato un volto viene invocata la funzione EmotionModel.predict che ci restituisce in definiva l’indice della label che identifica lo stato emozionale tra quelli gestiti mappati e definiti in emotion_labels.
async function start_riconoscimento(videoEl) {
const {
width,
height
} = faceapi.getMediaDimensions(videoEl)
var canvas = canvas || document.createElement('canvas');
canvas.width = width
canvas.height = height
const forwardParams = {
inputSize: parseInt(160),
scoreThreshold
}
const result = await faceapi.detectAllFaces(videoEl, new faceapi.TinyFaceDetectorOptions(forwardParams))
// SE TROVO DEI VOLTI ESEGUO IL CONTROLLO DELL'EMOZIONE
if (result.length != 0) {
const context = canvas.getContext('2d')
context.drawImage(videoEl, 0, 0, width, height)
let ctx = context;
for (var i = 0; i < result.length; i++) {
ctx.beginPath();
var item = result[i].box;
let s_x = Math.floor(item._x+offset_x);
if (item.y<offset_y){
var s_y = Math.floor(item._y);
}
else{
var s_y = Math.floor(item._y-offset_y);
}
let s_w = Math.floor(item._width-offset_x);
let s_h = Math.floor(item._height);
let cT = ctx.getImageData(s_x, s_y, s_w, s_h);
cT = preprocess(cT);
z = EmotionModel.predict(cT)
if(z != "undefined" && z != null)
{
let index = z.argMax(1).dataSync()[0]
let label = emotion_labels[index];
var emotionEl = document.getElementById('emotion');
emotionEl.innerHTML = label;
console.log("risultato: "+label);
}
}
await sleep(500);
}
async function loadNetWeights(uri) {
return new Float32Array(await (await fetch(uri)).arrayBuffer())
}
La funzione prepropress trasforma l’immagine acquisita da frame in un tensore compatibile con la funzione di predict.
function preprocess(imgData) { return tf.tidy(() => { let tensor = tf.browser.fromPixels(imgData).toFloat(); tensor = tensor.resizeBilinear([100, 100]) tensor = tf.cast(tensor, 'float32') const offset = tf.scalar(255.0); // Normalize the image const normalized = tensor.div(offset); //We add a dimension to get a batch shape const batched = normalized.expandDims(0) return batched }) }
. . . function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } . . . </script> <style> #emotionContainer{ font-size: 2em; padding:20px; text-align: center; } #emotion{ color: brown; } </style>
Sono amante della tecnologia e delle tante sfumature del mondo IT, ho partecipato, sin dai primi anni di università ad importanti progetti in ambito Internet proseguendo, negli anni, allo startup, sviluppo e direzione di diverse aziende; Nei primi anni di carriera ho lavorato come consulente nel mondo dell’IT italiano, partecipando attivamente a progetti nazionali ed internazionali per realtà quali Ericsson, Telecom, Tin.it, Accenture, Tiscali, CNR. Dal 2010 mi occupo di startup mediante una delle mie società techintouch S.r.l che grazie alla collaborazione con la Digital Magics SpA, di cui sono Partner la Campania, mi occupo di supportare ed accelerare aziende del territorio .
Attualmente ricopro le cariche di :
– CTO MareGroup
– CTO Innoida
– Co-CEO in Techintouch s.r.l.
– Board member in StepFund GP SA
Manager ed imprenditore dal 2000 sono stato,
CEO e founder di Eclettica S.r.l. , Società specializzata in sviluppo software e System Integration
Partner per la Campania di Digital Magics S.p.A.
CTO e co-founder di Nexsoft S.p.A, società specializzata nella Consulenza di Servizi in ambito Informatico e sviluppo di soluzioni di System Integration, CTO della ITsys S.r.l. Società specializzata nella gestione di sistemi IT per la quale ho partecipato attivamente alla fase di startup.
Sognatore da sempre, curioso di novità ed alla ricerca di “nuovi mondi da esplorare“.
Comments