Kuvankäsittely

Kuva tiedostosta

Aloitetaan valitsemalla kuva, jota haluamme käsitellä ohjelmassamme. Jos sinulla ei ole itse kuvaamaasi kuvaa käytössäsi, voit googlata sopivan. Käytä hakutyökaluja ja suodata käyttöoikeudet-kohdasta vain sellaiset kuvat, joita saa uudelleenkäyttää ja muokata vapaasti. Esimerkiksi tällainen kuva kissanpennusta sopii tarkoitukseen oikein hyvin:

Kuva: Mathias Erhart

Kuva: Mathias Erhart

Luo uusi sketch ja tallenna se. Tallenna nyt käyttämäsi kuva tämän sketchin kansioon. Kaikki sketchisi löytyvät sketchbookista, jonka sijainnin voit tarkistaa File > Preferences > Sketchbook location.

// Processingissa kuvia käsitellään PImage-luokan olioina
PImage img;                       

void setup(){
  size(350, 240);   // ruudun koko vastaa suunnilleen kuvan mittasuhteita

// ladataan loadImage-metodilla kitten.jpg-tiedostosta 
// ja asetetaan kuva olioon img
  img = loadImage("kitten.jpg");  
}

void draw(){

// kuva piirretään ruudulle kutsumalla image-metodia 
// ja antamalla sille parametriksi 
// img-olio, kuvan sijoituspaikka, leveys ja korkeus
  image(img, 0, 0, width, height); 
}

Kuvankäsittelyn metodeja

Processingissa on muutamia näppäriä metodeja kuvankäsittelyyn:

Kuvan värittäminen ja läpinäkyvyys.

Kahden kuvan yhdistäminen, erilaisia moodeja. Voit myös yhdistää kuvan ja esimerkiksi taustalla olevan punaisen neliön, jolloin saat ikään kuin tint-metodin ja useamman moodin käyttöösi.

Erilaisia filttereitä kuville. Tällaisia voi myös tehdä itse! Ohjeet Images and pixels -tutoriaalissa.

Muista että 2D-transformaatioita voi käyttää aina, myös kuvien kanssa.


Taulukot

Kuvaa voidaan myös käsitellä pikseli kerrallaan. Kuva on tallennettu taulukkoon, jossa jokainen taulukon elementti vastaa yhtä pikseliä. Tutustutaan nyt hieman taulukoihin - ne ovat näppäriä muutoinkin kuin kuvankäsittelyssä. Voit vaikkapa ensiksi tallettaa sata eri hiiren sijaintia ja sitten toistaa niiden muodostamaa kuviota kutsuttaessa. Kannattaa lukea myös Processing.orgin tutoriaali taulukoista.

// luodaan uusi int-tyyppisiä alkioita sisältävä taulukko
// taulukolle on annettava jokin koko: tässä se on 6
int[] taulukko = new int[6];  

Nyt meillä on kuuden alkion kokoinen, tyhjä taulukko. Käytämme esimerkkinä int-taulukkoa, johon voi säilöä vain kokonaislukuja. Myös muun tyyppisiä taulukoita voi tehdä (String, boolean, float..).

Ylläolevasta kuvasta huomaamme, että taulukon numerointi alkaa ykkösen sijaan nollasta. Kuusialkioisen taulukon viimeinen luku on siis kohdassa [5].

Lisätään taulukkoon alkioita seuraavasti:

taulukko[0] = 20;
taulukko[1] = 100;
taulukko[4] = -2

Alla muutama esimerkki siitä, miten taulukosta voi lukea alkioita.

int muuttuja = taulukko[0];                // tallentaa muuttujaan arvon 20
ellipse(taulukko[1], taulukko[1], 20, 20); // piirtää ellipsin kohtaan (100, 100)
taulukko[4] = taulukko[4]*taulukko[0];     // asettaa arvon -2*20 = -40 
                                           // taulukon kohtaan [4]

Huomaa, että jos yrität hakea lukua, jota ei ole määritelty, ohjelma kaatuu. Samoin tapahtuu, jos yrität viitata taulukon alkioon, joka on taulukon ulkopuolella.

int muuttuja = taulukko[2]; // NullPointer exception: viitattu arvo ei määritelty
taulukko[7] = 100;          // ArrayIndexOutOfBoundsException: 
                            // viitattu alkio taulukon ulkopuolella

ArrayList

Kuvankäsittelyssä tukeudumme taulukoihin, mutta monissa tapauksissa on kätevämpää käyttää hieman hienostuneempaa tietorakennetta nimeltä ArrayList. Taulukon ja ArrayListin erona on se, että ArrayList kasvaa automaattisesti, kun sinne lisätään alkioita. Indekseistä (missä lokeroissa on lukuja ja missä ei, mikä on taulukon pituus..) ei siis tarvitse huolehtia.

Kuvankäsittelyssä ja muissa sellaisissa tapauksissa, jossa käsiteltäviä alkioita on hyvin paljon, käytetään mieluummin taulukkoa siksi että ArrayList on hieman hitaampi. ArrayListissa käytetään metodeja, kuten esimerkiksi add, get ja remove alkioiden käsittelyyn. Metodien kuvaukset löytyvät täältä.

ArrayListista lisää myös kurssimateriaalissa.


Kuva pikseleinä

Kuvat koostuvat pikseleistä. Processingissa ruudun pikselit löytyvät taulukosta pixels[]. Tämä taulukko vastaa kaikkia ruudulla olevia pikseleitä kullakin draw-kierroksella. Taulukko ei siis välitä, koostuuko kuva ympyröistä, valokuvasta tai siitä, miten se on tuotettu. Jokaisella pikselillä on väriarvo, josta pystymme värin lisäksi päättelemään esimerkiksi pikselin kirkkauden ja vertaamaan sitä muihin pikseleihin.

Ylläoleva kuva havainnollistaa, miten taulukosta löytyvät pikselit sijoittuvat ruudulle. Asia on selitetty tarkemmin Images and pixels -tutoriaalissa. Kannattaa kuitenkin huomata jo tässä vaiheessa, että kohdasta (x, y) löytyvä pikseli löytyy taulukosta kohdasta x + (y * width). Eli esimerkiksi pikseli kohdassa (3, 2) sijaitsee taulukossa kohdassa 3 + (2 * 4) = [11]. Muista, että indeksointi alkaa nollasta.

void setup(){
  size(500, 500);
}

void draw(){

// aluksi kutsutaan metodia loadPixels, jotta voimme käsitellä kuvaa pikseleinä 
  loadPixels();  
  for (int i = 0; i < pixels.length; i++){  // käydään läpi kaikki kuvan pikselit
// täällä voimme tehdä kuvan pikselille mitä tahansa!
    pixels[i] = color(random(255));
  }

  updatePixels();   // lopuksi päivitetään muutetut pikselit ruudulle
}

Esimerkki: Kohina ja jakojäännös

Alla olevassa esimerkissä vain joka kolmas pikseli arvotaan. Jos siis jakolaskun i/3 tulos on kokonaisluku (eli jakojäännös i%3 on nolla), arvotaan pikselille harman sävy väliltä 0-255.


Pointillismi

Pointillismi eli pistemaalaus on taidesuuntaus, jossa kuva luodaan siveltimen vetojen sijaan pienistä pisteistä, jotka yhdessä luovat illuusion yhtenäisestä kuvasta - vähän kuin tietokoneen ruudun pikselit!

Paul Signac: Antibes - aamu, 1914

Paul Signac: Antibes - aamu, 1914

Georges Seurat: Eiffelin torni, n. 1889

Georges Seurat: Eiffelin torni, n. 1889

Toteutetaan nyt pointillistinen työ hyödyntäen aiemmin käyttämäämme valokuvaa.


Esimerkki: Valokuvasta pointillismiin

Aloitetaan lataamalla haluamamme kuva muuttujaan img. Emme tällä kertaa käytä image-metodia kuvan näyttämiseen, vaan käsittelemme kuvan (ei siis tällä kertaa ruudun!) pixels-taulukkoa ja muodostamme kuvan uudella tavalla.

PImage img;                       

void setup(){
  size(692, 461);
  img = loadImage("kitten.jpg");  
}

void draw(){

}

Aloitetaan sillä, että piirrämme joka draw-kierroksella esiin yhden kuvan pikseleistä. Tämä tapahtuu arpomalla kuvasta x- ja y-koordinaatit, laskemalla pikselin paikka taulukossa ja hakemalla tieto kyseisen pisteen punaisen, vihreän ja sinisen määrästä kuvan pixels-taulukosta.

PImage img;                       

void setup(){
  size(692, 461);
  img = loadImage("kitten.jpg");  
}

void draw(){
// random-metodi palauttaa float-tyyppisen luvun
// muutetaan se int-metodilla kokonaisluvuksi
  int x = int(random(img.width));
  int y = int(random(img.height));
// lasketaan pikselin (x, y) sijainti pixels-taulukossa
  int index = x+(y*img.width);

// haetaan kuvan pixels-taulukosta pikselin väriarvot yksitellen
// käyttämällä red-, green- ja blue-metodeja saamme asetettua
// float-tyyppisiin muuttujiin kunkin värin määrän
  float red = red(img.pixels[index]);
  float green = green(img.pixels[index]);
  float blue = blue(img.pixels[index]);

// asetetaan taulukosta luetut värit täyttöväriksi
  fill(red, green, blue);
// piirretään ellipsi kohtaan (x, y)
  ellipse(x, y, 20, 20);
}

Pointillismin idea alkaa välittyä! Tehdään kuitenkin vielä muutamia hienosäätöjä, jotta teoksemme näyttäisi paremmalta.

PImage img;                       

void setup(){
  size(692, 461);
  img = loadImage("kitten.jpg"); 
  noStroke();   // poistetaan ellipseistä reunat   
}

void draw(){

// asetetaan kaikki koodi for-loopin sisälle
// nyt joka draw-kierroksella arvotaan 100 ellipsiä

  for (int i = 0; i < 100; i++){
    int x = int(random(img.width));
    int y = int(random(img.height));
    int index = x+(y*img.width);


    float red = red(img.pixels[index]);
    float green = green(img.pixels[index]);
    float blue = blue(img.pixels[index]);

    float size = random(15);   // arvotaan ellipsin koko  
    fill(red, green, blue, 100);  // asetetaan väriin läpinäkyvyyttä
    ellipse(x, y, size, size);
  }
}

Nyt työmme näyttää hiotummalta! Voit kokeilla muuttaa parametreja ja muutakin koodia. Miltä kuva näyttää, jos ellipsin sijaan piiretään vaikkapa viivoja, rasteja tai neliöitä?