2013.01.21.
16:15

Írta: harsanyireka

Képek és pixelek

A digitális képek nem mások mint adatok, R G B értékek által meghatározott részek egy pixelekből álló rácshálón. Kreatív gondolkodással és a kód alapú pixel manipuláció szeretetével ezeket az információkat számtalan módon megjeleníthetjük. Ezt a leckét a formák rajzolásából való kitörésnek szenteljük, képeket használunk fel processing-grafikák létrehozására.. 

    

Kezdő lépések

Remélhetőleg már ismered az egyszerű adat típusokat és tudod is használni őket, pl. a floatot és az integert, amik a számítógép memóriájában eltárolt értékek. Az objektum és osztály használatot is ismerheted egy korábbi leckéből.

A Processingben már előre defíniált osztályok is vannak így ezekehez már nem kell kódot írnunk, az egyik ilyen a PImage, ami kép betöltésére és megjelenítésére alkalmas. 

// PImage típusú változó deklarálása:
PImage img;	

void setup() {
  size(320,240);
  // új PImage objektumba kép fájl betöltése:
  img = loadImage("mysummervacation.jpg");
}

void draw() {
  background(0);
  // Kép kirajzolása 0,0 koordinátára
  image(img,0,0);
}

A PImage használata megegyezik a magadáltal írt osztályok használatával. A változója img néven már deklarálva van, ezért ezt kell használnod. Másodszor, új hivatkozást a loadImage() metódussal hozhatsz létre, aminek egy argumentuma van, egy stringel/szöveggel megjelelölt fájl név - ezt a képet betölti majd a memóriába. A képet amit használsz a sketch mappán belül található data nevű mappába kell tenni. Ezt megteheted úgy is, hogy a Sketc/Add file menüt használod vagy a Sketch/Show sketch folder menüvel hagyományos úton másolod be a képedet ide.

Ezeket a kiterjesztéseket tudod használni: GIF, JPG, TGA, PNG.

    

A fönti példában nem használunk konstruktort (new PImage() stb.) ennek ellenére a legtöbb objektum-alapú példában konstruktor szükséges az objektum létrehozásához. Két példaezekre: 

Spaceship ss = new Spaceship();
Flower flr = new Flower(25);

és

PImage img = loadImage("file.jpg");

    

Valójában a loadImage() függvény végzi el a konstruktor munkáját. egy új hivatkozást küld vissza a PImage-en belül meghatározott fájlnévvel.

Üres kép létrenozásáraa createImage() függvényt használhatjuk. Pl egy 200x200 pixel méretű RGB képet így adhatunk meg: 

PImage img = createImage(200,200,RGB);

A képbetöltés elég lassú feladat, de mivel a setup() csak egyszer futle a legelején, elég egyszer kivárni ezt. Ha draw()-ba tesszük "Out of Memory" hibaüzenetet kaphatunk.

Ha betöltöttük a képünket, akkor image() függvénnyel tudjuk megjeleníteni, aminek 3 argumnetuma van: a kép, valamint az x és az y pozíciója. Opcionálisan további két paraméter is megadhatóhaát akarjuk méretezni a képünket. 

image(img,10,20,90,60);

        

Képszűrők

 Ezek hasznosak ha pl meg akarod változtatni a képed szín értékeit, sötétségét vagy átlátszóságát, stb. Ehhez a tint() függvényt használhatod ami megegyezik a formáknál használt fill() függvénnyel. A tint() paraméterének azt a színértéket kell megadnod amit a képed minden egyespixelére alkalmazni akarsz.

A következő példában két képet maniplálunk, a kutya lesz a háttérbenés ez fog majd előtűnni. Első lépés a képek betöltése:

PImage sunflower = loadImage("sunflower.jpg");
PImage dog = loadImage("dog.jpg");
background(dog);

Ha a tint()paraméterének egy számot adunk meg akkor az a világosságára vonatkozik majd. 

// Eredeti állapotot látjuk, mivel 255 a max érték:
tint(255);  
image(sunflower,0,0);

      

//Sötétebb lett a képünk:
tint(100);  
image(sunflower,0,0);

     

A második paraméter az alfa csatornáját állítja a képünknek:

// 50%-ben átlátszó a képünk:
tint(255,127);  
image(sunflower,0,0);

    

Három paraméter esetén az RGB színértékekra vonatkoznak a számok:

// Nincs benne piros(0), csak kék(255) és zöld(200) szín:
tint(0,200,255);
image(sunflower,0,0);

    

Ha ehhez hozzáteszünk még egy negyedik paramétert az az alfa csatornát állítja, pont mint a második példánál. colorMode() függvénnyel szabályozhatjuk a tint() értékeit ugyanúgy mint a fill()-nél, egy előző leckében.

// Pirosra szineztük a képet és átlátszóvá tettük:
tint(255,0,0,100);
image(sunflower,0,0);

     

Pixel, pixel és még több pixel

Mint láthatadd, a rajzolás vagy képelőállítás alapfeltétele hogy meghívsz egy (mások által már megírt) függvényt, vonalat rajzolsz, formákat színezel ki, vagy bitmap képet manipulálsz. Néha azonban a megjelenő pixelekkel közvetlenül akarunk foglalkozni. Ezeknek szintén x és y koordinátán meghatározható pozíciója van, valamint szín értéket tárolnak lineáris sorrendben.

 

Nézzünk meg egy konkrét példát. Rajzoljunk ki pixelenként egy képet, ahol a pixelek színértékei a szürke skálán random változnak. A pixelek számát egy tömbben tároljuk el és egy for ciklussal haladunk végig sorban rajtuk:

size(200, 200);
//Mielőtt a pixelekkel foglalkozunk:
loadPixels();  
// az egyes pixeleket ciklussal számoljuk:
for (int i = 0; i < pixels.length; i++) {
  // 0-255 közötti random értéket rendelünk rand változónkhoz:
  float rand = random(255);
  // A random értékből szürkeárnyalatos színt kapunk:
  color c = color(rand);
  // ezt hozzárendeljük a pixelünkhöz:
  pixels[i] = c;
}
// Miután befejeztük a pixellel való munkát
updatePixels(); 

pixel1.jpg

Mint láthatod két függvényt hívtunk meg a példában, az egyik jelzi a Processingnek hogy most pixeleketfogunk manipulálni - loadPixels();

A másikkat a folyamat végén hívjuk meg amikor befejeztük a pixel manipulálást és meg akarjuk jeleníteni - updatePixels();

Mivel a fenti példában random értéket használtunk, nem fontos hogy x és y koordinátákat használjunk a pixelek meghívásánál, de néha ez is szükségeslehet. Vegyünk egy konkrét példát, állítsuk be minden páros oszlopot színezzünk fehérre és minden páratlant feketére! Hogyan tudjuk ezt egy egydimenziós tömbbel létrehozni? Honnan tudjuk hogy melyik pixel hol helyezkedik el?

  1. Létrehozhatunk egy képet általunk megadott szélességgel és magassággal (pixelben adjuk meg).
  2. Így már tudjuk hogy a pixeleket tartalmazó tömbbünk lemeinek száma a szélesség szorozva a magassággal. 
  3. Egy adott pixel koordinátáit is ki tudjuk számolni: x + (y*szélesség):

 

Ismét for ciklust használunk, azzal a különbséggel hogy most az oszlopokon belül számláljuk a sorokat:

size(200, 200);
loadPixels();  
// oszlopokat számláló ciklus:
for (int x = 0; x < width; x++) {
  // sorokat számláló ciklus:
  for (int y = 0; y < height; y++) {
    // 1-dimenziós helyét (y) megkeressüka pixelünknek:
    int loc = x + y * width;
    if (x % 2 == 0) { 
// ha párososzlopban vagyunk: pixels[loc] = color(255); } else {
// ha páratlan oszlopban vagyunk: pixels[loc] = color(0); } } } updatePixels();

pixel2.jpg

Bevezetés a képfeldolgozásba

Az előzőekben megnéztünk pár példát hogyan állítsunk be pixel értékeket tetszőlegesen. Most megnézzük ugyanezt PImage() használatán keresztül is. Pszeudokódal leírva ezt fogjuk csinálni:

  1. PImage objektumba betöltjük a képet
  2. Kiolvassuk belőle minden egyes pixel színét és ezt megjelenítjük

A PImange osztály a következő adatokat tárolja a képeel kapcsolatban: szélesség, magasság, színkezelés. Éppúgy mint egyéb felhasználó-definiált osztályoknál pont-szintaxis használatávalérhetjük ezeket el. 

PImage img = createImage(320,240,RGB);  
println(img.width);  // 320-at ad vissza
println(img.height); // 240-et ad vissza
img.pixels[0] = color(255,0,0); 
// a kép első pixelét pirosra állítja

Mivel ezekehez az adatokhoz hozzáférünk pixelenként, az értékek szerint ezeket a pixeleket meg is tudjuk jeleníteni:

pixel3.jpg

void setup() {
  size(200, 200);
  img = loadImage("sunflower.jpg");
}

void draw() {
  loadPixels(); 
  // Mivel el akarjuk érni a képünk pixeleit:  
  img.loadPixels(); 
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      int loc = x + y*width;
      
      // A red(), green(), blue() fügvényekkel 
// olvassuk ki a 3 színértéket: float r = red(img.pixels[loc]); float g = green(img.pixels[loc]); float b = blue(img.pixels[loc]); // Képfeldolgozás // Ha az RGB értéket akarjuk beállítani azt a
// megjelenítő ablakba küldés előtt kell
// megtennünk: // A kép pixelét beállítjuk megjelenítendő pixelnek: pixels[loc] = color(r,g,b); } } updatePixels(); }

        

Ha csak megjeleníteni akarunk egy képet akkor a kódot egyszerűsíthetjünk, mert ennél a pixelek RGB értékeihez is hozzáfértünk. Most az x y koordináta meghatározására használtunk algoritmust, de egyebeket is felfedezhetünk. Mielőtt tovább lépünk, hangsúlyozom hogy a fenti példa azért működik mert a kijelző ablak mérete megegyezik a kép méretével. Ha nem így lenne, akkor két helymeghatározás kalkulációt kellene elvégezni,egyet a forrás képen a másikat a megjelenített képen:

int imageLoc = x + y*img.width; 
int displayLoc = x + y*width;

Második kép szűrő, saját "színárnyalat" létrehozása

A tint() függvényt már használtuk korábban színezésre és alfa állítására. A pixelről pixelre módszer lehetővé teszi algoritmusok felfedezését képek átszínezésére. Példánkban a világosság értékkel kísérletezünk, ezt változtatjuk, dinamikusan növeljük vagy csökkentjük az egér vízszintes mozgatásával. Ehhez for ciklust használunk:

for (int x = 0; x < img.width; x++) {
  for (int y = 0; y < img.height; y++ ) {
    // 1D pixel helyzetének meghatározása
    int loc = x + y*img.width;
    // az R,G,B érték kiolvasása a képből
    float r = red   (img.pixels[loc]);
    float g = green (img.pixels[loc]);
    float b = blue  (img.pixels[loc]);
    // világosság változása egér koordináta alapján
    float adjustBrightness = ((float) mouseX / width)*8.0;
    r *= adjustBrightness;
    g *= adjustBrightness;
    b *= adjustBrightness;
    // RGB érték bekényszerítéseconstrain-nel 0-255 közé
    r = constrain(r,0,255);
    g = constrain(g,0,255);
    b = constrain(b,0,255);
    //új szín definiálása és kijelzése az ablakban
    color c = color(r,g,b);
    pixels[loc] = c;
  }
}

pixel4.jpg

Ebben a példában minden pixelértéket egyszerre változtatunk, de megtehetjök pl. hogy az egértől való távolságuk szerint (x,y) manipuláljuk őket:

for (int x = 0; x < img.width; x++) {
  for (int y = 0; y < img.height; y++ ) {
    // 1D pixel helyzetének meghatározása
    int loc = x + y*img.width;
    // R,G,B érték kiolvasásaa képből
    float r = red   (img.pixels[loc]);
    float g = green (img.pixels[loc]);
    float b = blue  (img.pixels[loc]);
    // Avilágosságot változtató érték meghatározása, 
    // az egértől való távolsághoz képest
    float distance = dist(x,y,mouseX,mouseY);
    float adjustBrightness = (50-distance)/50;  
    r *= adjustBrightness;
    g *= adjustBrightness;
    b *= adjustBrightness;
    // RGB érték bekényszerítése 0-255 közé
    r = constrain(r,0,255);
    g = constrain(g,0,255);
    b = constrain(b,0,255);
    // új szín definiálása és megjelenítése az ablakban
    color c = color(r,g,b);
    pixels[loc] = c;
  }
}

pixel5.jpg

Egyéb PImage objektumok írása

Eddigi képfeldolgozópéldáink kiolvasták a pixelek információit és a képet is pixelenként jelenítették meg közvetlenül az ablakban. Mindazonáltal néha kényelmesebb új pixelt írni a cél képbe amit aztán image() függvénnyel megjeleníthetünk. Határérték (Threshold) szűrő segítségével demonstrálom az utóbbit. 

A határérték szűrő minden pixelt két állapotban jelenít meg vagy fekete vagy fehér, attól függően hogy a megadott küszöbértékhez képest milyen értéke van. Ha a pixel világosság értéke nagyobb a küszöbértéknél akkor fehér pixel lesz megjelenítve, ha alacsonyabb akkor fekete. A kódban ez az érték100 lesz most:

pixel6.jpg

PImage source;       // forrás kép
PImage destination;  // cél kép

void setup() {
  size(200, 200);
  source = loadImage("sunflower.jpg");  
  // a célképet üres képként definiáljuk aminek mérete 
// megegyezik a forrás kép méretével: destination=createImage(source.width,source.height,RGB); } void draw() { float threshold = 127; // mindkét képnek megnézzük a pixeleit: source.loadPixels(); destination.loadPixels(); for (int x = 0; x < source.width; x++) { for (int y = 0; y < source.height; y++ ) { int loc = x + y*source.width; // világosság érték megvizsgálása: if (brightness(source.pixels[loc]) > threshold) { destination.pixels[loc] = color(255); // fehét } else { destination.pixels[loc] = color(0); // fekete } } } // a cél kép pixelét megváltoztatjuk: destination.updatePixels(); // és megjelenítjük: image(destination,0,0); }

De pixel programozás nélkül is létrehozhatjuk a fenti effektet a Processing filter() nevű függvényével. Ez azonban saját filter írásánálnem használható, csak ilyen (threshold) előre defíniált függvényeknél.

// Kép rajzolása:
image(img,0,0);
// határérték szűrőt alkalmazzuk az ablakon
// 0.5 küszöbérték 50% világosság értéket jelent
filter(THRESHOLD,0.5);

2. szint, pixel-csoport

Kezdjük azzal hogy két pixelt kiveszünk a forrás képből, a második az első mellett balra helyezkedik el:

Első pixel koordinátái x,y:

int loc = x + y*img.width;
color pix = img.pixels[loc];

A bal oldali szomszédja x-1,y koordinátával rendelkezik:

int leftLoc = (x-1) + y*img.width;
color leftPix = img.pixels[leftLoc];

Ezután a két pixel közötti különbséget beállítjuk egy új színnek:

float diff = abs(brightness(pix) - brightness(leftPix));
pixels[loc] = color(diff);

       

TELJES ALGORITMUS:

// Mivel a baloldali szomszédja kell a pixelnek
// az első oszlopot átugorjuk
for (int x = 1; x < width; x++) {
  for (int y = 0; y < height; y++ ) {
    // pixel helye és színe
    int loc = x + y*img.width;
    color pix = img.pixels[loc];

    // baloldali-pixel helye és színe
    int leftLoc = (x-1) + y*img.width;
    color leftPix = img.pixels[leftLoc];

    //új szín, értéke a két pixel különbsége:
    float diff = abs(brightness(pix) - brightness(leftPix));
    pixels[loc] = color(diff);
  }
}

pixel7.jpg  

Ez egy egyszer perem-érzékelő algoritmus. Ha a szomszédos pixel értékhez képest a különbség nagy, akkor valószínűleg az egy alakzat széle, ezt fehérrel rajzolja ki, atöbbitfeketével, agy jön létre ez a táblaképre krétával rajzolt hatás. 

Jóval kifinomultabb algoritmust is létrehozhatunk ha egyszerre több pixelt vizsgálunk. Minden pixelnek 8 szomszédja van:

Ezt az algoritmust gyakran "térbeli spirálnak" nevezik. A folyamat a bemeneti és a szomszédos pixel súlyozott középértékét (számtani közepét) használja a kimeneti pixel kiszámítására. Más szóval, az új pixel egy adott terület pixeleinek a függvénye. Különbözőszámú szomszédos pixeleket használhatunk, pl. 3x3, 5x5 mátrixot, stb.

Különböző súlyozású pixelek különféle effekteket eredményeznek. Pl.kiélseíthetünk egy képet ha a szomszédos pixel értékéből kivonunk és növeljük a középső pixel értékét. Blurt úgy hozhatunk lére hogy a szomszédos pixelek átlagértékét vesszük. (Aspirális mátrixban az érték 1-et adhat ki). (példa: http://celebrate.digitalbrain.com/celebrate/accounts/banhegyesi/web/stat2/205/)

Példa:

Élesítés:
-1   -1   -1
-1    9   -1
-1   -1   -1

Blur:
1/9  1/9  1/9
1/9  1/9  1/9
1/9  1/9  1/9

          

A következő példa 2D tömbb segítségével egy spirált hoz létre eltárolja a pixel súlyozott átlagát egy 3x3 mátrixban:

pixel8.jpg  

PImage img;
int w = 80;

// Spirál létrehozása különböző
// különböző mátrixokkal lehetséges 

float[][] matrix = { { -1, -1, -1 },
                     { -1,  9, -1 },
                     { -1, -1, -1 } }; 

void setup() {
  size(200, 200);
  frameRate(30);
  img = loadImage("sunflower.jpg");
}

void draw() {
  // Mivel csak a kép egy részével dolgozunk,
  // először az egész képet a háttérbe küldjük
  image(img,0,0);
  // A kis négyzet amin belül feldolgozzuk a képet:
  int xstart = constrain(mouseX-w/2,0,img.width);
  int ystart = constrain(mouseY-w/2,0,img.height);
  int xend = constrain(mouseX+w/2,0,img.width);
  int yend = constrain(mouseY+w/2,0,img.height);
  int matrixsize = 3;
  loadPixels();
  // Ciklus amivel a pixeleken végigmegyünk:
  for (int x = xstart; x < xend; x++) {
    for (int y = ystart; y < yend; y++ ) {
      // convolution()-be töltjük a pixelek helyét (x,y) 
      // ami egy új színértéket ad vissza,ezt megjelenítjük: 
      color c = convolution(x,y,matrix,matrixsize,img);
      int loc = x + y*img.width;
      pixels[loc] = c;
    }
  }
  updatePixels();

  stroke(0);
  noFill();
  rect(xstart,ystart,w,w);
}

color convolution(int x,int y,float[][] matrix,int matrixsize,PImage img){
  float rtotal = 0.0;
  float gtotal = 0.0;
  float btotal = 0.0;
  int offset = matrixsize / 2;
  // ciklus a spirális mátrixhoz:
  for (int i = 0; i < matrixsize; i++){
    for (int j= 0; j < matrixsize; j++){
      // amelyik pixelt teszteljük:
      int xloc = x+i-offset;
      int yloc = y+j-offset;
      int loc = xloc + img.width*yloc;
      // hogy ne lépjünk ki a pixel tömbből bekényszerítjük: 
      loc = constrain(loc,0,img.pixels.length-1);
      // Spirál kiszámítása
      // a szomszédos pixeleket összeadjuk és megszorozzuk 
// spirális mátrix értékekkel: rtotal += (red(img.pixels[loc]) * matrix[i][j]); gtotal += (green(img.pixels[loc]) * matrix[i][j]); btotal += (blue(img.pixels[loc]) * matrix[i][j]); } } // RGB érték bekényszerítése: rtotal = constrain(rtotal,0,255); gtotal = constrain(gtotal,0,255); btotal = constrain(btotal,0,255); // az eredményként kapott színt visszaküldjük: return color(rtotal,gtotal,btotal); }


Kép megjelenítése

Gondolhatnád hogy ezeket a képeket Photoshopban is csinálhatnád. Ami a Processing előnye hogy valós időben hozhatsz vele létre generált vagy interaktív képeket.

A következő két példában forma kirajzoló algoritmus fogunk használni. Egy PImage objektum kép pixeleibőlkiválasztunk színeket, a képet magát nem jelenítjük meg, csak adatbázisként szolgál, így számtalan kreatív képet hozhatunk létre.

Az első példában random köröket rajzolunk ki amiket az adott helyen lévő kép-pixel színével töltünk ki. Ez egyfajta pointilista stílust eredményez.

pixel9.jpg

PImage img;
int pointillize = 16;

void setup() {
  size(200,200);
  img = loadImage("sunflower.jpg");
  background(0);
  smooth();
}

void draw() {
  // pixel kiválasztása random:
  int x = int(random(img.width));
  int y = int(random(img.height));
  int loc = x + y*img.width;
  
  // RGB érték megnézése a képben:
  loadPixels();
  float r = red(img.pixels[loc]);
  float g = green(img.pixels[loc]);
  float b = blue(img.pixels[loc]);
  noStroke();
  
  // elipszis kirajzolása adott helyen adott színnel:
  fill(r,g,b,100);
  ellipse(x,y,pointillize,pointillize);
}

A következő példában egy két dimenziós képből nyerünk adatot és ennek felhasználásával négyzeteket rakunk ki egy három dimenziós térben. A négyzetek pozícióját egér koordinátákhoz rendeljük.

 

pixel10.jpg



PImage img; // forrás kép int cellsize = 2; // rács celláinak kiterjedése int cols, rows; // oszlopok és sorok száma a rendszerünkben void setup() { size(200, 200, P3D); img = loadImage("sunflower.jpg"); // kép betöltése cols = width/cellsize; // oszlopok kiszámítása rows = height/cellsize; // sorok kiszámítása } void draw() { background(0); loadPixels(); // for ciklus oszlopokhoz for ( int i = 0; i < cols;i++) { // for ciklus sorokhoz for ( int j = 0; j < rows;j++) { int x = i*cellsize + cellsize/2; // x érték int y = j*cellsize + cellsize/2; // y érték int loc = x + y*width; // pixel tömb x,y értéke color c = img.pixels[loc];
// a hozzá tartozó szín kiolvasása // egér x tengelyéhez rendeljük a négyzetek z értékét
// és a világosság értéket: float z=(mouseX/(float)width)*brightness(img.pixels[loc])-100.0; // elhelyezés az adott koordinátára, kitöltés és
// körvonalszín beállítása, és négyzetek kirajzolása: pushMatrix(); translate(x,y,z); fill(c); noStroke(); rectMode(CENTER); rect(0,0,cellsize,cellsize); popMatrix(); } } }

 

           

forrás: http://processing.org/learning/pixels/ 

Szólj hozzá!

A bejegyzés trackback címe:

https://processing.blog.hu/api/trackback/id/tr95006212

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása