Confinement Jour 15 : Codons une connerie avec de l’IA Javascript (TensorFlow.JS)

En dehors du titre, le générique masculin est utilisé sans aucune discrimination et uniquement dans le but d'alléger le texte.

Confinement jour 15 : Codons une connerie avec de l’IA Javascript (TensorFlow.JS)

Confinement jour 15, je pète des plombs. Alors du coup je me suis dis tiens, on va faire du machine learning avec ma tronche. Une idée de génie qui s’est avérée m’aider psychologiquement. Aujourd’hui on va jouer avec des APIs, des librairies et ma gueule. Ça va être drôle.



Confinement jour 15

Alors je te le dis tout de suite, c’est pas un article comme les autres. J’ai beaucoup de temps et je le passe à faire des trucs bizarres. J’ai retrouvé une vieille webcam dans un tiroir de mon bureau et j’ai commencé à l’utiliser pendant le confinement. Mes collègues sont obligés de supporter ma tronche tous les matins en meeting Teams. C’est horrible pour tout le monde.

Bref, on s’en fout. Avec ce confinement j’ai du temps comme je te disais. J’ai toujours eu envie de m’amuser à faire des conneries avec TensorFlowJS. Je savais qu’il y avait moyen de rigoler, mais j’ai jamais eu le temps d’essayer. Le temps c’est pas ça qui manque en ce moment. Du coup, j’ai commencé à mettre mon nez dans les démos un peu partout. Hé ouais, c’est un truc de maboules !



tensorflow


Alors TensorFlow, ça vient d’où cette affaire encore ? Comme souvent, tout commence dans les bureaux de Google. Les bougres sont pas mauvais. Ils ont évidemment un projet concernant l’intelligence artificielle. ¨Ça s’appelle Google Brain et ça existe depuis 2011.

TensorFlow est sorti en 2017 et c’est en fait la version 2 du premier système de machine learning de Google Brain. Enfin, en 2018, cette affaire est jetée sur la table de tous les développeurs Javascript avec Tensorflow.JS.



C’est quoi TensorFlow.JS ?

Concrètement TensorFlow.JS est une librairie open source qui permet de définir, former et/ou utiliser des modèles de machine learning directement en Javascript dans un navigateur. Toute la partie hyper complexe de l’enfer du machine learning est abstraite. Toi tu utilises juste une API haut niveau. Et ça, c’est la vie.

Alors si t’es pas sûr de comprendre ce qu’est l’IA, le machine learning ou les modèles : je vais pas t’en parler aujourd’hui. Non, parce qu’on en a déjà discuté dans le passé. Mais si rappelle-toi quand on comprenait l’intelligence artificielle en seulement 10 minutes ! Article incroyable, je te le conseille fortement, l’auteur est fabuleux.

Bref, j’ai continué à tester tous les modèles officiels Google existants. En fait, j’ai passé pas mal de temp juste à faire le con avec. Ne me juge pas.



tensorflow


Et EURÊKA ! J’ai eu une idée de connerie ! Il me fallait un modèle qui puisse reconnaître les expressions du visage. Au lieu de refaire la roue, j’ai décidé de chercher si quelqu’un avait déjà entraîné un modèle similaire. Bingo !



La partie fun !

J’avais tout ce qu’il me fallait pour faire ma connerie. Restait plus qu’à mettre mes grosses mains dedans et coder tout ça. D’abord, il fallait que je me familiarise avec la librairie et son API. J’ai donc décidé de commencer par explorer tout ce qu’elle me proposait. Et en le mettant en place moi-même, j’ai pas été déçu.



confinement


Maintenant que j’avais cette base, je pouvais mettre en oeuvre ma super connerie. Cette idée m’est venue à force de passer du temps en conférence call à plusieurs durant ce confinement. Que ça soit pro ou perso, je passe mon temps en call vidéo avec plein de personnes et l’écran splitté, pour voir les tronches de tout le monde.

L’idée est aussi simple que débile. Je vais créer un faux call vidéo. Faire surveiller mes émotions par l’intelligence artificielle via la webcam, et faire changer les autres spots de la room avec des Gifs qui match mon émotion. Concrètement si je souris, boom que des Gifs qui sourient. Si je suis étonné, boom que des Gifs étonnés. Etc etc.. Ça sert à rien, mais c’est très drôle à faire. Et en quelques heures, c’était fait !



confinement


Fais voir le code

Tout fonctionne dans le browser. N’importe quel browser. Je l’ai fait responsive aussi donc c’est utilisable avec n’importe quels appareils avec une caméra. Ton téléphone, ton iPad, ton PC, ça va fonctionner. Ha ouais, il te faut une caméra du coup sinon il va rien se passer. Et ta tronche aussi. Il faut utiliser ta tronche, sinon pareil ça marchera pas. Si tu veux essayer tout de suite, c’est hosté sur une page statique. Allez, je te mets le lien vers la codesandbox aussi pour que tu puisses mettre tes mains dans le code.



let expressionGifs = {};
const expressionSpots = {
  topRight: null,
  bottomRight: null,
  bottomLeft: null
};

const video = document.getElementById("video");
video.addEventListener("play", refreshState);

/**
 * Launch the whole process following thoses steps
 * - Preload models from faceapi
 * - Fetch the data JSON for the gifs
 * - Preload all the gifs
 * - Ask user for the video stream and setup the video
 * @async
 */
async function launch() {
  await faceapi.nets.tinyFaceDetector.loadFromUri(
    "https://1rz6q.sse.codesandbox.io/dist/models"
  );
  await faceapi.nets.faceExpressionNet.loadFromUri(
    "https://1rz6q.sse.codesandbox.io/dist/models"
  );

  expressionGifs = await faceapi.fetchJson(
    "https://1rz6q.sse.codesandbox.io/dist/data/expressions.json"
  );

  preloadImages();
  setupVideo();
}

/**
 * Iterate on all the gifs fetched before and reload all the gifs
 * using Image constructor.
 */
function preloadImages() {
  let fullListOfGifs = [];
  const images = [];
  const expressionGifsKeys = Object.keys(expressionGifs);

  for (const expressionGifsKey of expressionGifsKeys) {
    fullListOfGifs = fullListOfGifs.concat(expressionGifs[expressionGifsKey]);
  }

  for (let i = 0; i < fullListOfGifs.length; i++) {
    images[i] = new Image();
    images[i].src = fullListOfGifs[i];
  }
}

/**
 * Setup the video stream for the user.
 * On success, the stream of the video is set to the source of the HTML5 video.
 * On error, the error is logged and the process continue.
 */
function setupVideo() {
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: false })
    .then(stream => {
      video.srcObject = stream;
    })
    .catch(err => console.error("can't found your camera :(", err));
}

/**
 * Get the most likely current expression using the facepi detection object.
 * Build a array to iterate on each possibility and pick the most likely.
 * @param {Object} expressions object of expressions
 * @return {String}
 */
function getCurrentExpression(expressions) {
  const maxValue = Math.max(
    ...Object.values(expressions).filter(value => value <= 1)
  );
  const expressionsKeys = Object.keys(expressions);
  const mostLikely = expressionsKeys.filter(
    expression => expressions[expression] === maxValue
  );

  return mostLikely[0] ? mostLikely[0] : "Neutral";
}

/**
 * Set the backgound emotion gif on each div related to the current expression.
 * @param {String} expression current expression
 */
function spreadByExpression(expression) {
  const expressionSpotsKeys = Object.keys(expressionSpots);
  const randomGifsByExpression = getRandomGifsByExpression(expression);

  for (const expressionSpotKey of expressionSpotsKeys) {
    if (expressionSpots[expressionSpotKey] !== expression) {
      expressionSpots[expressionSpotKey] = expression;

      const randomGif = randomGifsByExpression.shift();
      document.getElementById(
        expressionSpotKey
      ).style.backgroundImage = `url('${randomGif}')`;
    }
  }
}

/**
 * Get three random gifs from the JSON data by related expression and return an array
 * @param {String} expression current expression
 * @return {Array}
 */
function getRandomGifsByExpression(expression) {
  const randomGifs = [];
  const poolOfGifs = JSON.parse(JSON.stringify(expressionGifs[expression]));

  for (let i = 0; i <= 2; i++) {
    const randomNumber = Math.floor(Math.random() * poolOfGifs.length);
    const randomGif = poolOfGifs.splice(randomNumber, 1);

    randomGifs.push(randomGif);
  }

  return randomGifs;
}

/**
 * Set an refresh interval where the faceapi will scan the face of the subject
 * and return an object of the most likely expressions.
 * Use this detection data to pick an expression and spread background gifs on divs.
 * @async
 */
async function refreshState() {
  setInterval(async () => {
    const detections = await faceapi
      .detectAllFaces(video, new faceapi.TinyFaceDetectorOptions())
      .withFaceExpressions();

    if (detections && detections[0] && detections[0].expressions) {
      spreadByExpression(getCurrentExpression(detections[0].expressions));
    }
  }, 500);
}

launch();


Bon je vais pas passer sur tout le code pour expliquer. J’ai décrété que t’allais le faire tout seul. Ça va, j’ai fait un effort : j’ai mit une JSDoc. Allez deux/trois explications high level quand même, on est pas des bêtes.

D’abord on va preload les modèles de machine learning et ensuite on setup la vidéo. Sans ça, on fait quedal. Ensuite, en utilisant l’API de faceapi, on va scanner le visage de l’utilisateur deux fois par seconde. Faceapi va gentiment nous renvoyer un objet d’expressions avec une probabilité pour chacun.

On va itérer sur chaque émotion et choisir la plus probable. Celle qui se rapproche le plus de 1, sans la dépasser, c’est la bonne. Une fois qu’on a la bonne émotion, on va la spread partout. On va prendre trois gifs au hasard et les distribuer sur chacune des divs. À chaque fois que l’utilisateur change d’émotion, on l’imite !



confinement


Je te remets le lien de la codesanbox pour que tu check tout le reste des fichiers. Notamment la data en JSON, le petit serveur node qui fait tourner tout ça et la page HTML qui fait le split des écrans. Je vais également te foutre tout ça sur GitHub bientôt !



Épilogue

Ça m’a bien occupé la tête. Si le sujet IA en Javascript t’intéresse, n’hésite pas à me le dire dans les commentaires. On pourrait entraîner un modèle nous-mêmes la prochaine fois ! Et si me voir coder des conneries t’a plu, tu vas être content, il se pourrait que je recommence assez rapidement. C’est pas comme si j’avais autre chose à foutre.

Qui me parle ?

jesuisundev
Je suis un dev. En ce moment je suis Backend Développeur / DevOps à Montréal. Le dev est l'une de mes passions et j'écris comme je parle. Je continue à te parler quotidiennement sur mon Twitter. Tu peux m'insulter à cet e-mail ou le faire directement dans les commentaires juste en dessous. Y'a même une newsletter !

6 commentaires sur “Confinement jour 15 : Codons une connerie avec de l’IA Javascript (TensorFlow.JS)”

  1. J’adore l’idée, et je trouve pas ça si inutile ! Il m’arrive de chercher un gif correspondant à une expression faciale sans le trouver…avec cette idée, ça peut faciliter la “vie” 😜🤣

  2. Vraiment cool, j’ai envie de voir si ya moyen d’interfacer ce genre de chose avec twitter / facebook / messenger pour pouvoir répondre des gifs rapidement

T'en penses quoi ?

Your email address will not be published. Required fields are marked *