Interaction Design WikiComputer Vision

Image Processing

In diesem Tutorial werden wir mit Bildern zu arbeiten – dabei benutzen wir zunächst statische Bilder. Wir werden Schritt für Schritt unsere eigenen Filter schreiben und einfache Histogramm-Aufgaben bearbeiten.

PImage

Image ist eine Klasse, welche uns in Processing erlaubt, Bilder darzustellen und zu bearbeiten. Grundsätzlich können die Formate .gif.jpg.tga und .png gelesen werden. Die einfachste Art und Weise mit einem Bild zu arbeiten ist es auf den Screen zu zeichnen. Dazu wird zunächst ein Objekt der Klasse PImage deklariert:

PImage myImage;

Danach können wir ein beliebiges Bild laden und darstellen:

myImage = loadImage("Bild.png");

image(myImage, 0, 0, width, height);

Folgend das komplette Processing-Sketch:

Beispiel Expand source
PImage myImage;

void setup() {
  size(200,200);
  myImage = loadImage("EinBild.png");
}

void draw() {
  image(myImage, 0, 0, width, height);
}

Tint

Mit der Funktion tint() könne einfache Bildfilter erstellt werden. Dabei werden die Werte, welche mit tint() angegeben werden immer zum Bild hinzu gemischt. Hier ein Paar Beispiele:

tint(255);Originale Darstellung des Bildes

tint(100);Etwas mehr Schwarz-Anteile

tint(255, 127);Transparenz auf 50%

tint(0, 200, 127);Grünlich einfärben

tint(255, 0, 0, 200);Rot einfärben und Transparenz auf ca. 75%

 

Folgend ein komplettes Processing-Sketch:

Beispiel Expand source
PImage myImage;

void setup() {
  size(200,200);
  myImage = loadImage("EinBild.png");
}

void draw() {
  background(255);
  tint(100);
  image(myImage, 0, 0, width, height);
}

Array von Bildern

Es lassen sich natürlich auch mehrere Bilder anzeigen. Dazu deklarieren wir ein Array von PImage Objekten und initialisieren es:

PImage [] images;

images = new PImage[numOfImages];

Mit einer for Schlaufe könne wir dann numerierte Bilder laden:

for(int i=0; i<10; i++) {
  images[i] = loadImage("Bild_"+i+".png");
}

Für die Darstellung der Bilder geben wir dann den gewünschten Index an:

image(images[3], 0, 0);

Folgend das komplette Processing-Sketch:

Beispiel Expand source
PImage [] images;
int numOfImages = 6;

int whichImage = 0;

void setup() {
  size(200,200);
  images = new PImage[numOfImages];
  
  for(int i=0; i<numOfImages; i++) {
    images[i] = loadImage("Bilder"+i+".png");
  }
}

void draw() {
  image(images[whichImage], 0, 0, width, height);
}

void mousePressed() {
  whichImage = (int)random(0, numOfImages);
}

Aufgabe

Erweitert das Beispiel so dass durch drücken des Rechts- bzw. Linkspfeiles durch die Bilder gegangen wird.

Pixel schreiben

Jedes Bild besteht aus einem Array an Farbinformationen (siehe Wikipedia). Diese Informationen werden in einem Raster (Breite, Höhe) dargestellt und ergeben so das ganze Bild. Abgelegt werden diese Informationen jedoch in einem simplen fortlaufenden Array [0][1][2][3]...[n]. Wir müssen also nun wissen, wie wir von einer Position aus dem Array (Index) auf eine Position auf dem Screen (x, y) schliessen können. Zur Veranschaulichung kann folgende Grafik herangezogen werden.

Wenn wir nur mit der Processing Zeichenfläche arbeiten und keine externen Bilder hinzu laden, können wir die Farben auf der Zeichenfläche direkt aus dem pixels[]-Array auslesen. Dazu laden wir uns die Farbwerte in den Speicher loadPixels() gehen mit zwei verschachtelten for-Schleifen durch x und y Positionen und setzen an der entsprechenden Stelle einen neuen Farbwert pixels[loc] = color(255);. Zum Abschluss ist es wichtig updatePixels() auf zu rufen um die neuen Werte zu aktualisieren:

Beispiel Expand source
void setup() {
  size(600, 400);
}

void draw() {
  loadPixels();

  // loop through all rows
  for (int y=0; y<height; y++) {
    // loop through all columns per row
    for (int x=0; x<width; x++) {
      // calculate pixel location in list
      int loc = y*width + x;

      // set color depending on if color is dividable by two (even, odd)
      if (x%10 == 0) {
        pixels[loc] = color(255);
      } else {
        pixels[loc] = color(0);
      }
    }
  }

  updatePixels();
}

Wenn wir das für jeden zenten x-Wert machen erhalten wir beispielsweise folgendes Bild:

Aufgaben

Pixel Farben auslesen

Auf die gleiche Art und Weise, wie wir die Farbwerte im pixels[]-Array setzen können, lassen sich diese auch auslesen. Dies lässt sich zum einen als color(r,g,b)-Variable machen, als auch Komponentenweise durch red(pixel[loc])green(pixel[loc]) und blue(pixel[loc]).

Bei diesem Beispiel machen wir das gleiche, was wir ganz zu Beginn getan haben, um ein Bild zu laden und auf der Zeichenfläche anzuzeigen. Dieses Mal verwenden wir aber statt der vor definierten Funktion image() eine eigene Funktion, welche durch alle Pixel des Arrays des Bildes geht und die darin enthaltenen Farbinformationen extrahiert:

float r = red(myImage.pixels[loc]);
float g = green(myImage.pixels[loc]);
float b = blue(myImage.pixels[loc]);

Dann kopieren wir diese Informationen in das Pixel Array der Zeichenfläche:

pixels[loc] = color(r,g,b);

Folgend das komplette Processing-Sketch:

Beispiel Expand source
PImage myImage;

void setup() {
  size(200, 200);
  myImage = loadImage("EinBild.png");
}

void draw() {
  loadPixels();
  myImage.loadPixels();
  
  for(int x=0; x<width; x++) {
    for(int y=0; y<height; y++) {
      int loc = x+y*width;
      
      float r = red(myImage.pixels[loc]);
      float g = green(myImage.pixels[loc]);
      float b = blue(myImage.pixels[loc]);
      
      pixels[loc] = color(r,g,b);
    }
  }
  
  updatePixels();
}

Pixel lesen, verändern und schreiben

Die Methode zum setzen und auslesen von Bildinfromationen (Farben) lässt sich sehr gut nutzen um damit Bilder zu manipulieren oder – noch wichtiger – zu analysieren.

Pixelate

Im folgenden Beispiel verwenden wir die gelernten Funktionen um ein Bild gröber aufzulösen:

Beispiel Expand source
PImage myImage;

void setup() {
  size(600, 400);
  myImage = loadImage("EinBild.png");
  myImage.loadPixels();
}

void draw() {
  int size = mouseX/10+1;
  
  for(int x=0; x<width; x+=size) {
    for(int y=0; y<height; y+=size) {
      int loc = x+y*width;
      
      color c = myImage.pixels[loc];
      
      fill(c);
      noStroke();
      rect(x, y, size, size);
    }
  }
}

Flashlight

Im folgenden Beispiel verändern wir die Helligkeit der Pixel durch die Position der Maus:

Beispiel Expand source
PImage myImage;

void setup() {
  size(600, 400);
  myImage = loadImage("EinBild.png");
}

void draw() {
  loadPixels();
  myImage.loadPixels();

  PVector mousePos = new PVector(mouseX, mouseY);
  PVector currPixel = new PVector(0, 0);

  for (int x=0; x<width; x++) {
    for (int y=0; y<height; y++) {
      int loc = x+y*width;

      currPixel.x = x;
      currPixel.y = y;

      float r = red(myImage.pixels[loc]);
      float g = green(myImage.pixels[loc]);
      float b = blue(myImage.pixels[loc]);

      float distance = mousePos.dist(currPixel);
      float brightness = (100-distance)/100;

      r*=brightness;
      g*=brightness;
      b*=brightness;

      r = constrain(r, 0, 255);
      g = constrain(g, 0, 255);
      b = constrain(b, 0, 255);

      color c = color(r, g, b);

      pixels[loc] = c;
    }
  }

  updatePixels();
}

Threshold

Im folgenden Beispiel zeichnen wir ein Schatz-Weiss Bild welches auf bestimmte Grenzwerte abgestimmt ist. Ausserdem zeichnen wir in diesem Beispiel nicht auf die Zeichenfläche direkt, sondern initialisieren ein neues “leeres” Bild auf dem wir dann zeichen:

destImage = createImage(width, height, RGB);
destImage.pixels[loc] = color(255);

Folgend das komplette Processing-Sketch:

Beispiel Expand source
PImage myImage;
PImage destImage;
int threshold = 100;

void setup() {
  size(200, 200);
  myImage = loadImage("EinBild.png");
  destImage = createImage(width, height, RGB);
}

void draw() {
  myImage.loadPixels();
  destImage.loadPixels();
  
  threshold = mouseX;
  
  for(int x=0; x<width; x++) {
    for(int y=0; y<height; y++) {
      int loc = x+y*width;
      
      if(brightness(myImage.pixels[loc])<threshold) {
        destImage.pixels[loc] = color(255);
      } else {
        destImage.pixels[loc] = color(0);
      }
    }
  }

  destImage.updatePixels();
  image(destImage, 0, 0);
}

Simple Sobel

Im letzten Beispiel verwenden wir einen Filter um Kanten zu erkennen. Dazu vergleichen wir immer den aktuellen Pixel mit seinen Nachbarn:

Beispiel Expand source
PImage myImage;
PImage destImage;

void setup() {
  size(200, 200);
  myImage = loadImage("EinBild.png");
  destImage = createImage(width, height, RGB);
}

void draw() {
  myImage.loadPixels();
  destImage.loadPixels();
  
  for(int x=1; x<width; x++) {
    for(int y=0; y<height; y++) {
      int currLoc = x+y*width;
      color currColor = myImage.pixels[currLoc];
      
      int prevLoc = (x-1)+y*width;
      color prevColor = myImage.pixels[prevLoc];
      
      float difference = abs(brightness(currColor)-brightness(prevColor));
      destImage.pixels[currLoc] = color(difference);
    }
  }

  destImage.updatePixels();
  image(destImage, 0, 0);
}

Weitere Beispiele

Line Art

Sketch Expand source
PImage myImage;

void setup() {
  size(1920, 1080);
  myImage = loadImage("Gradient_3.png");
  smooth();
}

void draw() {
  myImage.loadPixels();
  background(0);

  // image(myImage,0,0);
  
  int steps = mouseX + 4;
  for (int x=0; x<width; x+=steps) {
    for (int y=0; y<height; y+=steps) {
      int loc = x+y*width;

      float bright = brightness(myImage.pixels[loc]);
      color c = myImage.pixels[loc];
      pushMatrix();
      translate(x, y);
      rotate(radians((bright/255)*360));
      stroke(c);
      strokeWeight(2);
      line((bright/255)*-50, 0, (bright/255)*50, 0);
      popMatrix();
    }
  }
  
  updatePixels();
}

ASCII Art

Sketch Expand source
PImage myImage;

void setup() {
  size(900, 900);
  myImage = loadImage("Marilyn.jpg");
  smooth();
}

void draw() {
  myImage.loadPixels();
  background(0);

  // image(myImage,0,0);
  
  int steps = mouseX+10;
  for (int x=0; x<width; x+=steps) {
    for (int y=0; y<height; y+=steps) {
      int loc = x+y*width;

      float bright = brightness(myImage.pixels[loc]);
      color c = myImage.pixels[loc];
      fill(c);
      textSize(map(bright,0,255,0,50));
      String ascii = Character.toString((char) int(bright));
      text(ascii,x,y);
    }
  }
  
  updatePixels();
}

Aufgaben

Weiteres