Facemesh

Facemesh

Facemesh är en maskininlärningsmodell som möjliggör detektering av landmärken ( landmarks ) i ansiktet i webbläsaren. Den visar 486 3D punkter på ansiktet.

Image of landmarks on a facemesh

För att se vilket nummer varje landmark har kan du titta på denna sida.

I det här programmet kommer vi att använda webkameran för att detektera ett ansikte och rita ut punkter på alla landmarks. Facemesh fungerar bäst när ansiktet som visas tar upp en stor andel av videoytan.

De bibliotek vi använder är ml5.js och p5.js.

Kod som står i de ljusgröna fälten nedan är den kod som ska skrivas i editorn. De grå fälten är där kod visas för en närmare beskrivning av delar av koden.

Starta en ny sketch via p5.js editor.

I index.html måste vi lägga till några bibliotek för att få tillgång till ml5.js.
Efter de script som finns lägg till:

<script src=”https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.dom.min.js”></script>
<script src=”https://unpkg.com/ml5@latest/dist/ml5.min.js” type=”text/javascript”></script>

Programmet ska ha en rubrik och en canvas som är 640 * 480 pixlar stor.
I body skapas rubriken:

<h1>Image classification using MobileNet and p5.js</h1>

I script.js skapas programmet med JavaScript, p5.js.

Det finns några variabler som vi ska använda på lite olika platser i koden. För att kunna göra det behöver variablerna vara globala, dvs skapade utanför alla funktioner.
Vi skapar dem med let och ger dem inget initialt värde. Variabeln predictions initierar vi som en tom array.

let facemesh;
let video;
let predictions = [];

facemesh är själva modellen. video är den video vi vill visa på canvasen och predictions är en array som fylls med information om det ansikte som modellen upptäcker.

I funktionen setup() skapas en canvas som har bredden 640 pixlar och höjden 480 pixlar. Video skapas med p5.js funktion createCapture(VIDEO) och storleken är samma som canvasens bredd och höjd. video.hide() används för att videon inte ska visas, som ett eget element under canvasen.

Med  facemesh = ml5.facemesh(video, modelReady); initieras handpose modellen med video och en funktion, modelReady, som ska kallas på när modellen är laddad. Den funktionen måste skapas efter setup().

  facemesh.on(”predict”, (results) => {
    predictions = results;
  });
säger att när modellen upptäcker en hand ska data för den handen lagras i variabeln predictions som skapades globalt.

Hela koden för funktionen setup:

function setup() {
  createCanvas(640, 480);
  video = createCapture(VIDEO);
  video.size(width, height);
  video.hide();
  facemesh = ml5.facemesh(video, modelReady);
  facemesh.on("predict", (results) => {
    predictions = results;
  });
}

Efter setup skapas funktionen modelReady vilken skriver ut Model Ready! i konsolen när modellen är laddad.

function modelReady() {
  console.log("Model ready!");
}

För att rita ut alla landmarks skapas en funktion som här kallas drawKeypoints. Innan vi skriver den behöver vi lite information om vad modellen returnerar.

Om man gör en console.log() på predictions får man se all information den innehåller. console.log(predictions) kan skrivas i funktionen setup direkt efter koden predictions = results;
Om du gör det stoppa programmet direkt när du fått ett resultat. Det blir enormt många loggar och det finns risk för att datorn låser sig.

Det som lagras i predictions är ett objekt som ser ut så här:

  1. (1) [Object]
    1. ▶0: Object
      1. faceInViewConfidence: 1
      2. ▶boundingBox: Object
      3. ▶mesh: Array(468)
      4. ▶scaledMesh: Array(468)
      5. ▶annotations: Object

faceInViewConfidence: talar om med vilken säkerhet modellen tror at det är ett ansikte i bild

boundingBox: innehåller information om var i videon modellen anser att ansiktet befinner sig

mesh: är en array av alla de 468 landmarks som modellen identifierar. Visar position för en kvadratisk bild på 192 * 192 pixlar.

scaledMesh: är en array av alla de 468 landmarks som modellen identifierar. Position skalad för att anpassas till det format vi har på videon.

annotations: är ett objekt som innehåller information om punkter i ansiktet indelade i grupp såsom rightEyeLower0 , lipsUpperOuter eller noseTip.

Det är de olika punkterna scaledMesh som vi här behöver identifiera för att kunna rita ut cirklar på dem. I varje scaledMesh finns information om dess x, y och z position. Vi kommer inte att använda z-positionen då canvasen bara visar 2 dimensioner.

Vi behöver alltså loopa igenom hela predictions och därefter alla landmarks. Detta görs med två for-loopar, en yttre loop där varje predictions sparas namnet keypoints och en inre loop. där varje landmark sparas som en array med x och y värde för varje keypoints. Om du behöver uppdatera dig på for-loop kan du titta här.

Hela koden för funktionen drawKeypoints:

function drawKeypoints() {
  for (let i = 0; i < predictions.length; i += 1) {
    const keypoints = predictions[i].scaledMesh;
    for (let j = 0; j < keypoints.length; j += 1) {
      const [x, y] = keypoints[j];
      fill(0, 255, 0);
      ellipse(x, y, 5, 5);
    }
  }
}

I funktionen draw() ritar vi ut videon på canvasen och kallar på drawKeypoints.

function draw() {
  image(video, 0, 0, width, height);
  drawKeypoints();
}

Hela koden för programmet finns att se här.