Video in ProcessingEinführung in den Umgang mit Video-Daten in Processing

Diese Lesson führt den Umgang mit Live-Video in Processing ein. Nach einer grundlegenden Erläuterung zur Verwendung von Kamera-Videomaterial wird der kreative Umgang mit Videostreams über die Programmierung eigener Filter und einfacher Analyse-Methodiken vermittelt. Dabei spielen sowohl visuelle Effekte als auch das Verfolgen markanter Bildbereiche und das Bestimmen von aktiven Zonen im Kamerabild eine Rolle.

Kamera Input

  • Capture ist der Datentyp zum Ablegen von Bewegtbildern die von angeschlossenen Aufnahmegeräten, wie z.B. Webcams, kommen. Mit new Capture (this, BREITE, HÖHE); kann auf diese zugegriffen werden. Optional kann der Gerätename und die Anzahl der Einzelbilder von der Kamera angegeben werden. Wenn kein Gerät namentlich aufgeführt wird, selektiert Processing das letzte aus der mit Capture.list() erfragbaren Liste mit verfügbaren Geräten. Die Anzahl der Einzelbilder pro Sekunde kann im weiteren Verlauf mit der Funktion frameRate() festgelegt werden. siehe Referenz
    // Aufnahmeabmaße 640x480 Pixel
    Capture cam1 = new Capture (this, 640, 480);
    // 20 Einzelbilder pro Sekunde (fps)
    Capture cam2 = new Capture (this, 320, 240, 20);
    // Kamera 'My Webcam', 20fps
    Capture cam3 = new Capture (this, 800, 600, "My Webcam", 20);

Verfügbarkeit von Geräten ermitteln

  • list() gibt eine Liste (String Array) aller in Processing verfügbaren Kameras zurück. Diese Geräte können beim erzeugen einer Capture Instanz mit Namen angegeben werden. siehe Referenz
    String[] list = Capture.list ();
    for (int i=0; i < list.length; i++) {
      println ('cam ' + i + ':' + list[i]);
    }
  • available() gibt true zurück, wenn ein Einzelbild fertig aufgenommen wurde. Nach dem Einlesen mit read() kann es in Processing verarbeitet werden. Wenn das Resultat false lautet, ist die Kamera mit hoher Wahrscheinlichkeit nicht betriebsbereit. siehe Referenz
    Capture cam = new Capture (this, 320, 240);
    if (cam.available ()) {
      println ("Cam is ready");
    }else{
      println ("Cam is not ready");
    }

Bilder auslesen und darstellen

  • crop(x, y, width, height) stellt die von der Kamera kommenden Bilder frei. Die Maske wird durch die beim Zeichnen eines Rechtecks bekannten Angaben festgelegt. siehe Referenz
    Capture cam = new Capture (this, 320, 240);
    cam.crop (10, 10, 300, 220);
  • read() liest das aktuelle Einzelbild 'aus der Kamera' und macht es in Processing verfügbar. Der Test ob ein Kamerabild verfügbar ist, siehe available(), sollte vorher durchgeführt werden. siehe Referenz
    Capture cam = new Capture (320, 240);
    if (cam.available ()) {
      cam.read ();
    }
  • image() genau wie bei Bildern wird image() zum Darstellen von Videoframes genutzt. Als ersten Parameter gibt man dabei nicht die PImage sondern die Capture Variable an. siehe Referenz
    Capture cam = new Capture (320, 240);
    if (cam.available ()) {
      cam.read ();
    }
    image (cam, 0, 0);
Bsp.: Kamerabild auslesen
Das erste Beispielt erläutert das Auslesen und Abbilden eines Kamera-Inputs. Mit new Capture (this, with, height, 25); wird die Verbindung zur angeschlossenen Kamera in Processing aufgebaut. Dabei wählt Processing automatisch das letzt der verfügbaren Geräte, siehe list(), aus. Mit 25 Bildern pro Sekunde - jeweils 320x240 Pixel groß - versorgt uns von nun an die Kamera, welche wir mit dem Variablennamen videocam ansprechen. Da die frameRate des Sketches ohne unser Zutun bei 60 liegt, ist nicht bei jedem Sketch-Einzelbild ein neues Kamerabild verfügbar. Um Fehler zu vermeiden prüfen wir deshalb in jedem draw()-Durchlauf mit videocam.available() ob wir das aktuelle Bild der Kamera auslesen können. Ist dies der Fall, geschieht dies durch durch den Aufruf von videocam.read(). Final findet das Abbilden mit dem image() Befehl statt. Positioniert in der oberen-linken Ecke füllt es die gesamte Zeichenfläche aus.
import processing.video.*;

// Variable für das Ansprechen 
// der Videokamera in P5
Capture videocam;

void setup () {
  size (320, 240);
  // Größe und Bildanzahl pro Sekunde für 
  // den Kamara-Input festlegen
  videocam = new Capture (this, width, height, 24);
}

void draw () {
  // wenn Kamera-Input verfügbar
  if (videocam.available ()) {
    // liest momentanes Kamerabild aus
    videocam.read ();
  }
  // Abbilden des mit 'read()' ausgelesenen Bildes 
  // an der Position x=0, y=0 im Sketch
  image (videocam, 0, 0);
}
Kamerabild verzögert abspielen in Processing

Das simpelsten Beispiel stellt das aktuelle Kamerabild in 'nahezu' Echtzeit dar. Ein Ablegen des Bildes bzw. Modifizieren der Farbwerte einzelner Pixel findet nicht statt. Im nächsten Schritt wird das verzögerte Abspielen des Videoinputs vorgestellt.

Den Kern stellt ein PImage Array namens buffer. Zu Beginn wird im setup() dessen Größe berechnet, um später die gewünschte Anzahl an Einzelbildern zu speichern. Je nach Länge der Verzögerung wird demnach beim Start kein Videobild im Sketch angezeigt. Die Abspiel- und Speicherposition, in welchem Feld das aktuelle Bild abgelegt/abgerufen werden soll, definiert die Variable index. Dabei wird das Array nicht von Anfang bis ins Endlose mit Bilder versorgt, sondern beim Erreichen des letzten Feldes auf das erste verwiesen. Die Variable index verursacht demnach einen Loop durch den Bildspeicher buffer. Bilder die älter als die Angegebene lagSeconds sind wurden abgespielt und werden überschrieben.

import processing.video.*;

// Schnittstelle zur Kamera
Capture cam;

// Speicher für die Einzelbilder aus 
// der 'Vergangenheit'
PImage[] buffer;

// Anzahl der Einzelbilder pro 
// Sekunde für Sketch und Aufnahme 
int fps = 20;

// Zeitverzögerung in Sekunden
int lagSeconds = 2;

// Abspielposition und damit Schlüssel
// für das Einfügen/Auslesen aus dem Buffer
int index = 0;

// Status ob der Bildspeicher schon 
// einmalig komplett gefüllt wurde
boolean filled = false;

void setup () {
  size (320, 240);
  frameRate (fps);
  cam    = new Capture (this, width, height, 20);
  buffer = new PImage[lagSeconds * fps];
}

void draw () {
  // Wenn der Bildspeicher einmal 
  // komplett gefüllt wurde...
  if (filled == true) {
    // Stelle das Bild im Sketch dar
    image (buffer[index], 0, 0);
  }
}

void captureEvent (Capture theCam) {
  // Lies das aktuelle Kamerabild aus
  theCam.read ();
  // Speicher eine Kopie im Buffer an 
  // der Position des Bildes welches 
  // gerade ausgelesen wurde
  buffer[index] = theCam.get ();
  // Erhöhe die Position für das Auslesen
  // um eins, auf das älteste Bild im Buffer
  index++;
  
  // Wenn am Ende des Buffers angekommen,
  // springe wieder an den Anfang.
  if (index >= buffer.length) {
    index = 0;
    if (!filled) {
      filled = true;
    }
  }
}
Kamerabild-Verlauf im Raster abbilden in Processing

Auf Grundlage des vorherigen Beispiels stellt dieser Sketch das von der Kamera aufgenommene Bild nicht 1:1 dar, sondern zeichnet den gesamten 'Bildspeicher' in die Zeichenfläche. Um keinen Fehler zu verursuchen überprüft die if-Bedingung, mit der Abfrage if (buffer[i] != null) { ob bereits ein Bild vom Typ PImage im Array-Feld vorhanden ist. Ist das der Fall, wird anhand der Zählvariable i die Position des Bildes in der Matrix berechnet.

import processing.video.*;

// Schnittstelle zur Kamera
Capture cam;

// Speicher für die Einzelbilder aus 
// der 'Vergangenheit'
PImage[] buffer;

// Anzahl der Einzelbilder pro 
// Sekunde für Sketch und Aufnahme 
int fps = 21;

// Zeitverzögerung in Sekunden
int lagSeconds = 2;

// Abspielposition und damit Schlüssel
// für das Einfügen/Auslesen aus dem Buffer
int index = 0;

// Status ob der Bildspeicher schon 
// einmalig komplett gefüllt wurde
boolean filled = false;

// Anzahl der Spalten im Sketchfenster
int cols = 6;

void setup () {
  size (318, 278);
  frameRate (fps);
  cam    = new Capture (this, 320, 240, 20);
  buffer = new PImage[lagSeconds * fps];
}

void draw () {   
  // Bildgröße für Raster ermitteln
  float imgWidth = width / cols;
  float aspect = imgWidth / cam.width;
  float imgHeight = cam.height * aspect;

  // Für jedes Feld im 'Bildspeicher'
  for (int i=0; i < buffer.length; i++) {
    // Wenn ein Bild existiert
    if (buffer[i] != null) {
      // Berechne die Position im Raster
      float x = (i % cols) * imgWidth;
      float y = floor (i / cols) * imgHeight;

      image (buffer[i], x, y, imgWidth, imgHeight);
    }
  }
}

void captureEvent (Capture theCam) {
  // Lies das aktuelle Kamerabild aus
  theCam.read ();
  // Speicher eine Kopie im Buffer an 
  // der Position des Bildes welches 
  // gerade ausgelesen wurde
  buffer[index] = theCam.get ();
  // Erhöhe die Position für das Auslesen
  // um eins, auf das älteste Bild im Buffer
  index++;

  // Wenn am Ende des Buffers angekommen,
  // springe wieder an den Anfang.
  if (index >= buffer.length) {
    index = 0;
    if (!filled) {
      filled = true;
    }
  }
}
Kamerabild verzerren in Processing

Jegliches Auslesen und Modifizieren von Bildinformationen kann wie im Kapitel Bilder auch auf Einzelbilder von Videos angewendet werden. Zur Demonstration verzerrt dieses Beispiel ein Videobild in der Vertikalen (siehe Bilder Scratch).
Im draw() testet eine Bedingung mit videocam.available() ob bereits eine neue Aufnahme der angeschlossenen Kamera zum Abrufen bereit steht und liest diese mit read() ein. Teil zwei besteht aus einer for-Schleife die von 0 bis 319 (Sketchbreite - 1) läuft. Mittels dieser Zählvariable können die Farbwerte der ersten Pixelreihe des Kamerabildes ausgelesen werden. Nach der Selektion des Farbwertes wird eine Linie von der Oberkante zur Unterkante der Zeichenfläche für jeden Pixel in der angesprochenen Reihe gezeichnet. Die Strichfarbe kommt vom ausgelesenen Pixel im Kamerabild. Im Gegensatz zum vorigen Beispiel wird das eigentliche Kamerabild nicht abgebildet.

import processing.video.*;

// Variable für das Ansprechen 
// der Videokamera in P5
Capture videocam;

void setup () {
  size (320, 240);
  // Größe und Bildanzahl pro Sekunde für 
  // den Kamara-Input festlegen
  videocam = new Capture (this, width, height, 24);
}

void draw () {
  // wenn Kamera-Input verfügbar
  if (videocam.available ()) {
    // liest momentanes Kamerabild aus
    videocam.read ();
  }
  
  // für jeden Pixel in Breite des Sketches
  for (int i=0; i < width; i++) {
    // lies den Farbwert für jeden Pixel in
    // der obersten Spalte des Kamerabildes 
    // aus (von links nach rechts).
    color c = videocam.get (i, 0);
    // nutze den Farbwert für die Strichfarbe
    stroke (c);
    // Zeichne eine vertikale Linie für jeden 
    // Pixel von oben nach unten
    line (i, 0, i, height);
  }
}
Einfacher Kamerafarbfilter in Processing

Im folgenden Beispiel testen wir ob der Rot-Wert eines jeden Pixels über der Marke von 170 liegt. Ist es der Fall, wird es an seiner Position im Sketchfenster abgebildet. Mittels zweier ineinander verschachtelter for-Schleifen erreichen wir alle Bildpunkte der Kameraaufnahme. Nach der Farbselektion mit get(x, y) entscheidet die Bedingung if (red (c) > 170) {… ob der Pixel gezeichnet wird. Da wir mit point() arbeiten, wird die Einfärbung durch stroke() bestimmt.

import processing.video.*;

// Variable für das Ansprechen 
// der Videokamera in P5
Capture videocam;

void setup () {
  size (320, 240);
  // Größe und Bildanzahl pro Sekunde für 
  // den Kamara-Input festlegen
  videocam = new Capture (this, width, height, 24);
}

void draw () {
  background (255);
  
  // wenn Kamera-Input verfügbar
  if (videocam.available ()) {
    // liest momentanes Kamerabild aus
    videocam.read ();
  }

  // für jeden Pixel in Breite des Sketches
  for (int i=0; i < width; i++) {
    // für jeden Pixel in der Höhe des Sketches
    for (int j=0; j < height; j++) {
      // lies den Farbwert für jeden Pixel aus
      color c = videocam.get (i, j);
      
      // wenn der Rotanteil groß genug ist
      // -> bilde das Pixel an seiner Position ab
      if (red (c) > 170) {
        stroke (c);
        point (i, j);
      }
    }
  }
}
Abstrahierte Bilddarstellung in Processing

In der folgenden Version wird das pixels Array, welches alle Bildpunkte des Kamerabildes enthält, in großen Schritten durchlaufen und durch eingefärbe Kreise in der Zeichenfläche repräsentiert. In der Breite wird dabei jedes achte Pixel interpretiert und in der Höhe jede vierte Zeile. Bei der Positionsabfrage auf der y-Achse und der Ermittlung der x-Koordinate wird von Modulo (%) gebrauch gemacht. Modulo gibt den Rest einer Division zurück und ermöglicht dadurch unteranderem die Frage ob ein Wert gerade ist oder nicht. Da das Programm nicht mit dem get() und set() Befehlen zum Auslesen und Ändern von Farbwerten innerhalb des bildes Arbeit, sonder dieser Bereich über das pixels Array abgewickelt wird, schafft Processing eine größere Anzahl von Bildern pro Sekunde abzuarbeiten.

import processing.video.*;

Capture cam;

void setup () {
  size (320, 240);
  cam = new Capture (this, width, height, 24);
  noStroke ();
}

void draw () {
  background (0);
  // wenn Kamera-Input verfügbar
  if (cam.available ()) {
    // liest momentanes Kamerabild aus
    cam.read ();
  }
  // für jeden Pixel im Videobild
  for (int i=0; i < cam.pixels.length; i += 8) {
    
    // Wenn 'i' an dem erste Pixel einer Zeile 
    // angekommen ist (Rest aus i/Bildbreite = 0)
    if (i % cam.width == 0) {
      // Wenn die Zeile einen geraden Index
      // besitzt (Rest aus i/2 = 0)
      if (i%2 == 0) {
        // Verschiebe i um 4 Zeilen
        i += cam.width * 4;
      }
    }
    // Berechne die Position des Pixels an der 
    // Stelle 'i' in der Bildfläche.
    int x = i % cam.width;
    int y = (int) floor (i / cam.width);
    
    // Setze die Füllfarbe und zeichne eine 
    // Ellipse an der Position des Pixels
    fill (cam.pixels[i]);
    ellipse (x, y, 10, 10);
  }
}
Simpler Green-Screen in Processing

Video Hintergrund entfernen
Unter einem green- bzw. bluescreen versteht man die Art und Weise Videoaufnahmen vor einer einfarbigen grünen oder blauen Fläche aufzunehmen, welche es ermöglicht Objekte vor der Fläche freizustellen. Dabei wird jeder in diesem Ton eingefärbte Bildpunkt des Videos als transparent angesehen. Die Grundlage für eine Umsetzung des Prinzips in Processing haben wir schon im Beispiel simpler Farbfilter gelegt. Jedoch benötigen wir den speziellen Hintergrund für die Aufnahme.

In diesem Beispiel wird ein Weg vorgestellt diesen Effekt mit jeglichem Hintergrund zu erzielen. Dabei nutzen wir nicht eine standardisierte Farbe als Hintergrund, sondern bedienen uns eines vorher aufgenommenen Bildes. Das Bild steht in diesem Fall für den zu entfernenden Hintergrund. Nach dem Starten des Processing Programms wird es durch das Drücken der Taste 'b' aufgenommen und mit der Funktion get() in der Variable back abgelegt. Wie in den vorherigen Beispielen durchlaufen wir im draw-Block jedes einzelne Pixel des Kamerabildes und vergleichen dabei den Farbwert mit dem Hintergrundbild back. Wenn eine Änderung üben den Schwellwert threshold vorliegt wird dieses Pixel im Sketchfenster abgebildet. Anderenfalls ist an dieser Stelle nur der graue Hintergrund sichtbar. Bei der Ermittlung der Farbwerte bilden wird die Summe aus allen drei Farbkanälen und vergleichen diese. Eine präzisere Lösung wäre jeden Kanal einzeln abzufragen und zu vergleichen. Grundsätzlich gilt: Je besser die Szene ausgeläuchtet ist, umso genauer die Freistellung.

import processing.video.*;

// Video-Aufnahme-Auflösung
final int VIDEO_WIDTH  = 320;
final int VIDEO_HEIGHT = 240;

// Variable zum Speichern des Hintergrundbildes.
PImage back = null;

// Wert um den sich die Summe der RGB Kanäle mind. ändern 
// muss, damit das Pixel als 'aktiv' erkannt wird. 
float threshold = 50;

// Objekt zum Zugriff auf die angeschlossene Kamera.
Capture cam = null;

void setup () {
  size (VIDEO_WIDTH, VIDEO_HEIGHT);
  // Initialisiert die Verbindung zur Kamera und 
  // gleicht die frame rate von Kamera und Sketch an.
  cam = new Capture (this, VIDEO_WIDTH, VIDEO_HEIGHT, 15);
  frameRate (15);
}

void draw () {

  // Wenn die Videokamrea verfügbar ist
  if (cam.available ()) {
    
    // Auslesen des Kamerabildes und
    // füllen des Hintergrunds mit grau
    cam.read ();
    background (40);

    // Alle Pixel vom Sketchfenster für die 
    // spätere Bearbeitung laden.
    loadPixels ();

    // Wenn ein Hintergrundbild aufgenommen wurde.
    if (back != null) {

      // Für jedes Pixel in der Zeichenfläche
      for (int i=0; i < cam.pixels.length; i++) {

        // Farbe an der Position 'i' im Kamerabild
        // und im Hintergrundbild auslesen
        color c1 = cam.pixels[i];
        color c2 = back.pixels[i];

        // Die Summe aus allen drei Farbkanälen bilden
        float s1 = red (c1) + green (c1) + blue (c1);
        float s2 = red (c2) + green (c2) + blue (c2);

        // Unterschied zwischen Hintergrundbild und Kamerabild
        // errechnen für dieses Pixel.
        float deltaPixel = s1 - s2;

        // Hat sich dieses Pixel mind. um unseren Schwellenwert 
        // geändert? Wenn ja – zeichne das Pixel aus dem 
        // Kamerabild an diese Position.
        if (deltaPixel > threshold || deltaPixel < -threshold) {
          pixels[i] = cam.pixels[i];
        }
      }
    }

    // Aktualisiere das Sketchfenster mit dem Bild 
    // aus dem von uns modifizierten 'pixels' Array.
    updatePixels ();
  }
}

void keyReleased () {
  // Beim Drücken der Taste 'b' wird das Hintergrundbild 
  // im Speicher durch die Variable 'back' abgelegt. Dieses 
  // gilt als Referenz für unseren "blue screen".
  if (key == 'b' || key == 'B') {
    back = cam.get ();
  }
}
Hellsten Punkt im Videobild tracken in Processing

Tracking wird als das Lokalisiern von Bildpunkten -abschnitten bezeichnet. Dieser Sketch durchsucht das Kamerabild nach dem hellsten Pixel und zeichnet ein Rechteck um seine Position. In der Variable maxCol speichert Processing den höchsten brightness()-Wert und die dazugehörige Position im Bild (xpos und ypos). Pixel für Pixel wird das aktuelle Maxima mit den verbleibenden Pixeln vergleichen - und wenn überschritten auf die neue Position gesetzt.

import processing.video.*;

Capture videocam;
// tracking Position
int xpos = 0;
int ypos = 0;

void setup () {
  size (320, 240);
  videocam = new Capture (this, width, height, 24);
  noFill ();
  strokeWeight (2);
  stroke (255, 0, 102);
}

void draw () {
  background (255);
  
  float maxCol = 0;
  if (videocam.available ()) {
    videocam.read ();
  }

  // für jeden Pixel in Breite des Sketches
  for (int i=0; i < width; i++) {
    for (int j=0; j < height; j++) {
      // lies den Farbwert für jeden Pixel aus
      color c = videocam.get (i, j);
      // Wenn heller als aktuelles Maximum
      // -> ersetze Tracking-Koordinaten
      if (brightness (c) > maxCol) {
        maxCol = brightness (c);
        xpos = i;
        ypos = j;
      }
    }
  }
  // Zeichne Bild und TrackingBox
  image (videocam, 0, 0);
  rect (xpos-2, ypos-2, 5, 5);
}
Aktivität im Bild feststellen in Processing

Video Aktivität erkennen
Um Bewegung in einer Bildreihe zu erkennen, müssen diese nach Veränderungen durchsucht werden. D.h. man vergleicht das aktuell von der Kamera gelieferte Bild mit den vorher aufgenommenen. Wenn sich Pixel bzw. Abschnitt merklich in ihrem Farbton verändert haben, liegt eine Aktivität vor der Kamera vor. Wie in vorherigen Beispielen testen wir diese Form der Änderung indem wir die Summen der RGB Kanäle vom aktuellen und vergangenen Bildern ermitteln und vergleichen. Die in der Vergangenheit aufgenommenen Bilder speichern wir daher nicht in Form eines PImage, sondern als float array (beinhaltet die Summe der RGB-Farbwerte pro Pixel). Betitelt mit den Namen buffer1, buffer2 und buffer3 werden die letzten drei Bilder abgelegt. Der Mittelwert dieser geht in den Vergleich mit dem aktuellen ein, als Fehlerkorrektur bei rauschenden Kameras. Stärke der Änderung in Graustufen
Video Aktivität erkennen
Eine kleine Veränderung im Programm macht die Stärke der Änderung sichtbar. Je weiter entfernt die beiden verglichenen Farbwerte sind, umso großer bzw. kleiner ist der Wert von deltaPixel. Dieser in der color() Funktion verwendet macht die Aktivität in Graustufen sichtbar. Alle Änderungen mit einem negativen Wert werden dabei schwarz repräsentiert. Um diese Abschnitte ebenfalls in feinen Abstufungen darzustellen bedarf es einer if-Abfrage. Innerhalb wird das Vorzeichen umkehrt wenn deltaPixel kleiner als 0 ist(im Code ausgeklammert).
if (deltaPixel > threshold || deltaPixel < -threshold) {
        
  /* Wenn die Differenz kleiner als 0 ist, 
    * kehre das Vorzeichen um. Dadurch werden 
    * die schwarzen Stellen vermieden.
    */
  //if (deltaPixel < 0) {
  // deltaPixel = deltaPixel * -1;
  //}

  pixels[i] = color (deltaPixel);
}

import processing.video.*;

// Video-Capture-Auflösung
final int VIDEO_WIDTH  = 320;
final int VIDEO_HEIGHT = 240;

// Bildergedächtnis zum Ablegen der letzen 
// drei Videobilder. Diese bilden die Grundlage 
// für das Erkennen der Aktivität vor der Kamera.
float[] buffer1 = new float[VIDEO_WIDTH * VIDEO_HEIGHT];
float[] buffer2 = new float[buffer1.length];
float[] buffer3 = new float[buffer1.length];

// Wert um den sich die Summe der RGB Kanäle mind. ändern 
// muss, damit das Pixel als 'aktiv' erkannt wird. 
float threshold = 30;

// Schnittstelle zur Kamera
Capture cam;

void setup () {
  size (VIDEO_WIDTH, VIDEO_HEIGHT);
  // Da die Videokamera keine 60 Bilder pro Sekunde liefert,
  // kann die Framerate des sketches auf 15 angepasst werden.
  // Framerate von Kamera und Sketch sind damit gleich.
  cam = new Capture (this, VIDEO_WIDTH, VIDEO_HEIGHT, 15);
  frameRate (15);
}

void draw () {
  
  // Wenn die Videokamrea verfügbar ist
  if (cam.available ()) {
    
    // Auslesen des Kamerabildes und
    // füllen des Hintergrunds mit grau
    cam.read ();
    background (40);
    
    // Alle Pixel vom Sketchfenster für die 
    // spätere Bearbeitung laden.
    loadPixels ();
    
    // Gehe durch jedes Pixel im Bild und Vergleiche
    // den Farbwert an dieser Position mit den Farbwerten 
    // in der Vergangenheit (siehe buffer 1-3).
    for (int i=0; i < cam.pixels.length; i++) {

      // Farbe an der Position 'i' im Kamerabild
      color col = cam.pixels[i];
      // Die Summe aus allen drei Farbkanälen bilden
      float sum = red (col) + green (col) + blue (col);

      // Die mögliche Bewegung an dieser Position im Kamerabild 
      // wird durch das Vergleichen der Farbwerte praktiziert. 
      // Dabei werden die letzten drei Bilder und das aktuelle 
      // einbezogen.
      float deltaPixel = (buffer1[i] + buffer2[i] + buffer3[i]) / 3 - sum;

      // Falls sich der Farbwert über die festgelegte Grenze 
      // geändert hat, färbe das Pixel weiß ein. Anderefalls 
      // zeichne es nicht (bleibt als Hintergrund eingefärbt).
      if (deltaPixel > threshold || deltaPixel < -threshold) {
        pixels[i] = color (255);
      }

      // Verschiebe das 'Bildgedächnis' um einen Rutsch nach hinten.
      // Überschreibe das älteste Bild (buffer3) und rücke die  beiden 
      // anderen um eine Stelle nach hinten. Das aktuelle Bild wird in 
      // 'buffer1' abgelegt. (Dies passiert Pixel für Pixel!)
      buffer3[i] = buffer2[i];
      buffer2[i] = buffer1[i];
      buffer1[i] = sum;
    }
    
    updatePixels ();
  }
}
Aktivität in Bildbereichen feststellen in Processing

Video Aktivität erkennen
Speziell für das Interagieren mit der Kamera ist die oben vorgestellte Methode meist zu genau in ihrer Funktionsweise. Unsere Möglichkeiten der Bewegung vor der Kamera erlauben uns nur schwer spezielle Pixel zu beeinflussen. Um dieses Problem zu lösen nutzt dieses Beispiel Abstraktion als Grundlage für das Erfassen von Dynamik. Dabei wird das Bildraster in eine gröbere Repräsentanz unterteilt(VIDEO_COLS Spaltenanzahl, VIDEO_ROWS Zeilenanzahl). Für jedes dieser Segmente wird ein Durchschnittswert ermittelt, welcher die Aktivität repräsentiert. Die Anzahl der Spalten (VIDEO_COLS) und Zeilen (VIDEO_ROWS) des abstrahierten Rasters muss abhängig von der Auflösung des Videos gewählt werden. Bei der Teilung der Bildbreite durch die Spaltenzahl darf kein Rest übrig bleiben (ebenfalls bei der Bildhöhe). Anderenfalls wird Processing den Sketch wegen eines Fehlers nicht starten können. Im unteren Bild sind sechs Beispiele für mögliche Rastergrößen aufgeführt.
Video Aktivität erkennen Auflösung
Wie bereits erwähnt liegt der Unterschied dieses Beispiels im Vergleich zu Aktivität im Bild feststellen in der Abstraktion des Videobildes. Unterteilt in ein Raster wird die Aktivität (Pixelfarbwertänderungen) in Segmente zusammengefasst. Eine for-Schleife durchläuft alle Bildpunkte wenn ein neues Bild von der Kamera gelesen wurde. Jedes Pixel wird dabei mit Pixeln an der selben Position in älteren Bildern verglichen. Das Resultat dieses Vergleichens findet wie im vorherigen Beispiel statt. Im Anschluss legen wir den Wert im Array activity ab. Dieses beinhaltet ein Feld für jedes Rastersegment. Bei 4×3 Feldern besitzt es beispielsweise eine Länge von 12. Pro Pixel muss also die Frage nach der Segmentzugehörigkeit gestellt werden. Dies geschieht mit den folgenden zwei Zeilen aus dem Sketchcode:
int x = (int) ((i % cam.width) / pxPerCol);
int y = (int) ((i / cam.width) / pxPerRow);
Die Variablen x und y präsentieren dabei nicht die genau Position des Pixels im Videobild, sondern die Koordinate des Quadranten. Da das activity Array nach dem gleichen Prinzip wie das pixels Array bei Bildern angelegt ist, können wir im nächsten Schritt den Index herausfinden:
index = y * VIDEO_COLS + x;
Nach dem Auslesen des Videobildes kommt es zum Durchlaufen des activity Arrays. Dabei teilen wir die dort gesammelten Gesamtwerte durch die Anzahl der Pixel pro Segment um die Mittelwerte pro Rasterabschnitt zu erhalten. Mit dem nächsten draw-Durchlauf werden alle Felder das activity Array wieder auf 0 zurückgesetzt.

import processing.video.*;

final int VIDEO_WIDTH  = 320;
final int VIDEO_HEIGHT = 240;
// Anzahl der Quadranten in der Breite
final int VIDEO_COLS   = 8;
// Anzahl der Quadranten in der Höhe
final int VIDEO_ROWS   = 6;

float[] activity = new float[VIDEO_COLS * VIDEO_ROWS];
float[] buffer1 = new float[VIDEO_WIDTH * VIDEO_HEIGHT];
float[] buffer2 = new float[buffer1.length];
float[] buffer3 = new float[buffer1.length];

Capture cam = null;

void setup () {
  size (VIDEO_WIDTH, VIDEO_HEIGHT);
  cam = new Capture (this, VIDEO_WIDTH, VIDEO_HEIGHT, 15);
  frameRate (15);
}

void draw () {
  
  if (cam.available ()) {
    
    cam.read ();
    
    int index;
    int pxPerCol = VIDEO_WIDTH / VIDEO_COLS;
    int pxPerRow = VIDEO_HEIGHT / VIDEO_ROWS;

    image (cam, 0, 0);
    
    // 'activity' array auf 0 setzen
    for (int i=0; i < activity.length; i++) {
      activity[i] = 0;
    }

    // Für jedes Pixel im Videoraster
    for (int i=0; i < cam.pixels.length; i++) {
      
      // x und y Position des Quadranten berechnen
      int x = (int) ((i % cam.width) / pxPerCol);
      int y = (int) ((i / cam.width) / pxPerRow);
      
      // Quadranten-Zugehörigkeit des Pixels herausfinden.
      // Später wichtig für das 'activity' array.
      index = y * VIDEO_COLS + x;

      // Farbe an der Position 'i' im Kamerabild
      color col = cam.pixels[i];
      // Die Summe aus allen drei Farbkanälen bilden
      float sum = red (col) + green (col) + blue (col);

      // Farbwertänderung des Pixels bezogen auf alle Bilder errechnen
      float deltaPixel = (buffer1[i] + buffer2[i] + buffer3[i]) / 3 - sum;

      if (deltaPixel < 0) {
        deltaPixel *= -1;
      }

      // Die Änderung auf den Gesamtwert des Quadranten addieren
      activity[index] += deltaPixel;

      // Verschiebe das 'Bildgedächnis' um einen Rutsch nach hinten.
      buffer3[i] = buffer2[i];
      buffer2[i] = buffer1[i];
      buffer1[i] = sum;
    }

    // Für jeden Quadranten
    for (int i=0; i < activity.length; i++) {
      
      // Durchschnittliche Farbwertänderung berechnen.
      // Mit dem Teilen der Summe durch die Anzahl der Pixel
      activity[i] /= pxPerCol* pxPerRow;
      
      // Quadrant in das Sketchfenster zeichnen
      stroke (255, 20);
      fill (0, 255, 230, activity[i]);
      rect ((i % VIDEO_COLS) * pxPerCol, (i / VIDEO_COLS) * pxPerRow, pxPerCol, pxPerRow);
    }
  }
}