Köpingkodar upphör snart och flyttar till skolprogrammering.se Välkommen dit! Skolprogrammering.se

Facemesh för filter

Facemesh för filter

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 in 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 en bild på en av dessa punkter. Facemesh fungerar bäst när ansiktet som visas tar upp en stor andel av videoytan.

Vi använder en enkel .png 2D-bild med transparent bakgrund. Bilden har storleken 46 * 24 pixlar:

drawing of eyelashes

När filter skapas i olika appar kan man förfina med 3D-bilder och få det att se realistiskt ut. Här vill vi visa hur man hittar punkterna att placera filter-bilden på ansiktet. Bilden måste importeras till editorn genom att klicka på pilen vid SketchFiles och välja Upload file.

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>

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 = [];
let img;

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. img är den bild som vi vill använda.

Bilden laddas in i programmet genom p5.js fördefinierade funktion preload.

function preload(){
  img = loadImage('eyeLash.png')
}

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!");
}

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.

I objektet annotations finns positionen på punkter indelade i arrayer med namn vilket gör det lättare att hitta var man vill placera bilder/filter. Här kommer vi att använda leftEyeUpper0 vilken består av 7 punkter.

Nedan en lista på alla namngivna arrayer i den här modellen.

  • (1) [Object]
    1. ▶0: Object
      • faceInViewConfidence: 0.9999986886978149
      • ▶boundingBox: Object
      • ▶scaledMesh: Array(468)
      • ▶mesh: Array(468)
      • ▶annotations: Object
        1. ▶silhouette: Array(36)
        2. ▶lipsUpperOuter: Array(11)
        3. ▶lipsLowerOuter: Array(10)
        4. ▶lipsUpperInner: Array(11)
        5. ▶lipsLowerInner: Array(11)
        6. ▶rightEyeUpper0: Array(7)
        7. ▶rightEyeLower0: Array(9)
        8. ▶rightEyeUpper1: Array(7)
        9. ▶rightEyeLower1: Array(9)
        10. ▶rightEyeUpper2: Array(7)
        11. ▶rightEyeLower2: Array(9)
        12. ▶rightEyeLower3: Array(9)
        13. ▶rightEyebrowUpper: Array(8)
        14. ▶rightEyebrowLower: Array(6)
        15. ▶leftEyeUpper0: Array(7)
        16. ▶leftEyeLower0: Array(9)
        17. ▶leftEyeUpper1: Array(7)
        18. ▶leftEyeLower1: Array(9)
        19. ▶leftEyeUpper2: Array(7)
        20. ▶leftEyeLower2: Array(9)
        21. ▶leftEyeLower3: Array(9)
        22. ▶leftEyebrowUpper: Array(8)
        23. ▶leftEyebrowLower: Array(6)
        24. ▶midwayBetweenEyes: Array(1)
        25. ▶noseTip: Array(1)
        26. ▶noseBottom: Array(1)
        27. ▶noseRightCorner: Array(1)
        28. ▶noseLeftCorner: Array(1)
        29. ▶rightCheek: Array(1)
        30. ▶leftCheek: Array(1)

I funktionen draw kan vi kontrollera om det finns en predictions genom en if-sats. Om ( if ) det finns några predictions skapar vi tre variabler och ritar ut bilden på rätt ställe.

let face = predictions[0]; En variabel face skapas för att lagra det första objektet i predictions.

let eye = face.annotations.leftEyeUpper0; En variabel eye skapas för att lagra positionen av sju punkter som finns lagrade under namnet leftEyeUpper0

let eyePoint= eye[3]; En variabel med namnet eyePoint skapas för att lagra positionen av den fjärde punkten i eye. Fjärde för att i programmering börjar man ränkna på 0. Man kan testa med andra punkter genom att ändra siffran 3 till något mellan 0 och 6.

image(img, eyePoint[0]-23, eyePoint[1]-12); Bilden ritas ut på x-position(eyePoint[0]) och y-position (eyePoint[1]). Eftersom en bild ritas ut från övre vänstra hörnet behöver bilden flyttas för att hamna rätt. Bilden här är 46 pixlar bred och 24 pixlar hög. Bilden ska flyttas halva värdet av bredden och halva värdet av höjden för att hamna mitt på den punkt vi valt.

    if(predictions.length > 0){
    let face = predictions[0];
    let eye = face.annotations.leftEyeUpper0;
    let eyePoint= eye[3];
    image(img, eyePoint[0]-23, eyePoint[1]-12);
  }

Hela koden för funktionen draw:

function draw() {
  image(video, 0, 0, width, height);
    if(predictions.length > 0){
    let face = predictions[0]
    let eye = face.annotations.leftEyeUpper0
    let eyePoint= eye[3]
    image(img, eyePoint[0]-23, eyePoint[1]-12)
  }
}

Hela koden för programmet kan du se här.