Image classification

Image classification

Programmet kommer att klassificera bilder som användaren kan dra från sin dator och släppa i canvasen. Det bibliotek som används kommer från ml5.js och och p5.js. Modellen som används heter MobileNetV2. Modellen är tränad på fler än en miljon bilder. Modellen klassificerar bilder baserat på vad den tidigare lärt sig och baserat på de kategorier som den tränats på.

Bilderna är från databasen ImageNet vilken har ett dataset med 1000 olika kategorier. ImageNet startades 2009 och har samlat in data från olika sökmtorer på internet. Idag underhålls databasen av personer på olika universitet i USA.
De olika kategorierna som används i den modell vi ska använda kan man se på ml5s GitHub. När du testar ditt program sedan är det bra att ha bilder som är så kvadratiska som möjligt. Canvasen är 400 * 400 och bilden som släpps kommer att ”squeezas” för att fylla kvadraten vilket gör att den kan bli helt olik ursprungsbilden.

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, text som talar om vad användaren ska göra, en canvas som är 400 * 400 pixlar stor och under den text som talar om vad modellen tror att bilden visar.
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.

Om du behöver uppdatera dig på variabler kan du titta här

let img;
let classifier;
let label;
let confidence;

Den modell vi ska använda laddas i den i p5.js fördefinierade funktionen preload() så att modellen är laddad och klar när funktionen setup() startas.

function preload() {
  classifier = ml5.imageClassifier('MobileNet'); 
}

I funktionen setup() skapar vi en paragraf, dvs text där det står instruktion för vad användaren ska göra.
Vi skapar en canvas (en grå rektangel) och ger den ett namn ”canvas”. Här använder vi inte let utan det andra alternativet som JavaScript ger: const.
const används när man skapar ett namngivet objekt som inte kan ändras.

Vi skapar också två div vilka tilldelas till våra globala variabler label och confidence.

Bakgrundsfärgen på canvasen bestäms till en grå färg skriven som hexadecimal färgkod.

Därefter bestäms vad som ska ske när användaren släpper något i canvasen. Det görs med en funktion som finns i p5.js som heter drop(). Som parameter i drop anges en annan funktion som visar vad som ska göras när något släppts i canvasen. Den funktionen är inte skriven än men vi namnger den gotFile.

Hela koden för funktionen setup:

function setup(){
  createP "Drop your image into the gray rectangle ");
  const canvas = createCanvas(400, 400);
  label = createDiv('Label: ');
  confidence = createDiv('Confidence: ')
  background('#eeeeee');
  canvas.drop(gotFile);
}

För att ange vilken teckenstorlek det ska vara på texten som skapats som p-element och div-element får vi skriva det i filen style.css. Storleken på texten ska vara 24 pixlar.

div, p{
  font-size: 24px;
}

Åter till script.js
När man släpper en bild i canvasen och därefter släpper en bild till så läggs de över varandra. Om bilden är en .png med transparent bakgrund så kommer bilden som är bakom också att synas. För att undvika det ska vi skapa en funktion som heter changeBG() som vi sen ska använda för att rita om bakgrunden på canvasen innan nästa bild släpps. Färgen som vi anger i changeBG är samma färg som vi angett på background i funktionen setup.
Namnet changeBG är inte ett namn man måste ha såsom setup(), preload() och andra fördefinierade funktioner i p5.js utan det är ett namn programmeraren själv bestämmer. De fördefinierade funktionerna visas i editorn med bold text. De som inte har bold text kan man alltså namnge hur man vill. Bra är att tänka på att namn på funktion bör visa vad den är till för.

function changeBG() {
  background('#eeeeee');
}

Nu kan vi skapa funktionen gotFile() vilken ska visa vad som ska ske då användaren släpper en fil i canvasen. gotFile ska ha en parameter, file dvs den fil som släppts.

Först i funktionen kallar vi på changeBG(). Därefter kontrollerar vi att filen som släppts verkligen är en bild, image. Det görs med en if-sats. Om du behöver uppdatera dig på if-satser kan du titta här.

function gotFile(file){
  changeBG()
  if (file.type == 'image') {  
   Gör något
  }
  else   {
   gör något annat
  }

Om filen som släppts är en image så skapar vi en bild av den. Bildens information finns i file.data.

När en bild skapas med createImg i p5.js så placeras den som ett eget element på sidan efter det som redan finns där. Eftersom vi inte vill visa den där kan vi använda en funktion som heter hide().
Om det är en image som släppts ska modellen påbörja klassificeringen. I classifier.classify() finns två parametrar. Den första anger vad som ska klassificeras och den andra anger vad som ska ske sen. img är den bild som precis släppts i canvasen och gotResult() är en funktion som talar om vad som ska ske när modellen har fått ett resultat. Den funktionen finns inte än men vi ska skapa den snart.
Om det inte är en image file vill vi bara skriva det  i konsolen. Det läggs in i else.

if (file.type == ’image’) {
img = createImg(file.data).hide();
classifier.classify(img, gotResult);
}
else {
console.log(’Not an image file!’);
}

Hela koden för funktionen gotFile:

function gotFile(file){
  changeBG()
  if (file.type == 'image') {  
    img = createImg(file.data).hide(); 
    classifier.classify(img, gotResult);
  }
  else   {
     console.log('Not an image file!');
  }      
}

Skapa funktionen gotResults

gotResults måste ha två parametrar. En för vad som händer när det blir något fel och en för vad som händer när man fått en förutsägelse (predictions) för vad det är på bilden.

Med en if-sats kontrolleras om något gått fel

if (error) {
    console.error(error);
  }

Om allt gått bra fortsätter programmet till nästa del av funktionen.
Det man får tillbaka som results är en array av de 3 predictions (förutsägelser) som har högst värde. Vi väljer att visa dem i konsolen. Den bild som släppts ritas ut på canvasen.

console.log(results);
image(img, 0, 0, 400, 400);

På skärmen kommer det visas vilken label den gissning som har högst värde har.
Värdet på den gissningen kommer att anges bredvid confidence. Tidigare var texten på label och confidence bara orden ”Label:” och ”Confidence: ”. För att ändra det använder vi:

label.html('Label: ' + results[0].label)
confidence.html('Confidence: ' + nf(results[0].confidence, 0, 2))

.html betyder att vi vill ange vad som ska stå som text där. ’Label: ’ + results[0].label betyder att det ska stå Label (som text) och att det ska adderas med det första resultatet (results[0].label) i arrayen results.

För confidence står det:

+ nf(results[0].confidence, 0, 2))

nf() är en funktion för att formatera ett tal till text. Här vill vi formatera den confidence som results har. Sifftan 0 säger att vi vi ha 0 värdesiffror till vänster om kommatecknet (här är det punkt då de amerikanska tecknen gäller). Siffran 2 säger att vi vill ha två decimaler. Vi kommer alltså att få confidence i decimalform, vilket vi själva snabbt gör om till procent i huvudet 😊.

Hela koden för funktionen gotResults:

function gotResult(error, results) {
  if (error) {
    console.error(error);
  }
  console.log(results);
  image(img, 0, 0, 400, 400);
  label.html('Label: ' + results[0].label)
  confidence.html('Confidence: ' + nf(results[0].confidence, 0, 2))
}

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