Sentiment Analysis su volti con tensorflow.js – quale è la mia emozione?

AI, INFORMATICA, INTELLIGENZA ARTIFICIALE

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>

 

 

Se vuoi farmi qualche richiesta o contattarmi per un aiuto riempi il seguente form