Point Cloud


Processing 2.0

Der Kinect Sensor bietet die einzigartige Möglichkeit die Distanz der einzelnen Bildpunkte zur Kamera zu messen. Wir nutzen diese Möglichkeit jetzt und zeichnen alle einzelnen Bildpunkte, die der Sensor liefert einfach im 3-dimensionalen Raum darzustellen. Das ganze bezeichnet man als Point Cloud.

141104_113108_52

/** Copyright 2014 Thomas Koberger
 */

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import SimpleOpenNI.*;
import java.util.Calendar;

SimpleOpenNI kinect;

void setup() {
  size(1200, 768, P3D);
  kinect = new SimpleOpenNI(this);
  kinect.enableDepth();
  stroke(255);
}

void draw() {
  background(0);
  kinect.update();

  // Verschiebe die Szene in den Mittelpunkt des Fensters
  translate(width/2, height/2, -500);
  // vertikal drehen, damit die Szene nicht auf dem Kopf steht
  rotateX(radians(180));
  // Rotationspunkt in die Mitte der Szene verschieben

  translate(0, 0, 1500);
  randomSeed(20);
  // Damit die Szene automatisch rotiert.
  rotateY((float)frameCount/50);
  translate(0, 0, -1500);

  // Einkommentieren, wenn man die Szene per Maus rotieren will!
  //  rotateY(map(mouseX, 0, width, -PI, PI));
  //  rotateX(map(mouseY, 0, width, -PI, PI));

  // Hier liefert die Kinect ein Array mit Vektoren
  PVector[] depthPoints = kinect.depthMapRealWorld();

  // Wir zeichnen nicht jeden Punkt, um die Sache zu beschleunigen
  for (int i = 0; i < depthPoints.length; i+= (int)random(2, 10)) {
    PVector currentPoint = depthPoints[i];
    stroke(map(currentPoint.z, 0, 7000, 255, 80));
    point(currentPoint.x, currentPoint.y, currentPoint.z);
  }
}

void keyReleased() {
  if (key == DELETE || key == BACKSPACE) background(360);
  if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
} 

//timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  println("Frame saved");
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Farbe

Nun können wir auch beide Kameras der Kinect nutzen und den Punkten ihre entsprechende Farbe zuweisen. Dabei kommen die Tiefeninformationen von der IR und die Farbinformationen von der RGB Kamera. Die dafür notwendigen mathematischen Operationen, nämlich das Auffinden der korrespondierenden Punkte der beiden Kameras, erledigt die Kinect.

141107_164425_59

</pre>
<pre>/** Copyright 2014 Thomas Koberger
 */

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import SimpleOpenNI.*;
import java.util.Calendar;

SimpleOpenNI kinect;

void setup() {
  size(1200, 768, P3D);
  kinect = new SimpleOpenNI(this);
  kinect.enableDepth();
  // Aktiviert die RGB Kamera der Kinect
  kinect.enableRGB();
  //Berechnet die korresponierenden Punkte der beiden Kameras
  //Dadurch bekommen die korrespondierenden Punkte der Tiefen- und der RGB Kamera
  //die gleichen Indizes
  kinect.alternativeViewPointDepthToImage();
}

void draw() {
  background(0);
  kinect.update();

  // Verschiebe die Szene in den Mittelpunkt des Fensters
  translate(width/2, height/2, -500);
  // vertikal drehen, damit die Szene nicht auf dem Kopf steht
  rotateX(radians(180));
  // Rotationspunkt in die Mitte der Szene verschieben

  translate(0, 0, 1500);
 
  // Damit die Szene automatisch rotiert.
  rotateY((float)frameCount/50);
  translate(0, 0, -1500);

  // Einkommentieren, wenn man die Szene per Maus rotieren will!
  //  rotateY(map(mouseX, 0, width, -PI, PI));
  //  rotateX(map(mouseY, 0, width, -PI, PI));

  // Hier liefert die Kinect ein Array mit Vektoren
  PVector[] depthPoints = kinect.depthMapRealWorld();
  PImage rgbImage = kinect.rgbImage();
 
  // Wir zeichnen nicht jeden Punkt, um die Sache zu beschleunigen
  for (int i = 0; i < depthPoints.length; i+= 10) {
    PVector currentPoint = depthPoints[i];
    
    // Hiermit färben wir die Punkte der Tiefeninformation mit der entsprechend
    // Farbe aus der RGB Kamera ein.
    stroke(rgbImage.pixels[i],map(currentPoint.z,0,7000,255,80));
    point(currentPoint.x, currentPoint.y, currentPoint.z);
  }
  println(frameRate);
}

void keyReleased() {
  if (key == DELETE || key == BACKSPACE) background(360);
  if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
} 

//timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  println("Frame saved");
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Linien

Nun kann man die einzelnen Punkte auch mit Linien verbinden.

 

/** Copyright 2014 Thomas Koberger
 */

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import SimpleOpenNI.*;
import java.util.Calendar;

SimpleOpenNI kinect;

void setup() {
  size(1200, 768, P3D);
  kinect = new SimpleOpenNI(this);
  kinect.enableDepth();
  // Aktiviert die RGB Kamera der Kinect
  kinect.enableRGB();
  //Berechnet die korresponierenden Punkte der beiden Kameras
  //Dadurch bekommen die korrespondierenden Punkte der Tiefen- und der RGB Kamera
  //die gleichen Indizes
  kinect.alternativeViewPointDepthToImage();
  noSmooth();
}

void draw() {
  background(0);
  kinect.update();

  // Verschiebe die Szene in den Mittelpunkt des Fensters
  translate(width/2, height/2, -500);
  // vertikal drehen, damit die Szene nicht auf dem Kopf steht
  rotateX(radians(180));
  // Rotationspunkt in die Mitte der Szene verschieben

  translate(0, 0, 1500);

  // Damit die Szene automatisch rotiert.
  rotateY((float)frameCount/50);
  translate(0, 0, -1500);

  // Einkommentieren, wenn man die Szene per Maus rotieren will!
  //  rotateY(map(mouseX, 0, width, -PI, PI));
  //  rotateX(map(mouseY, 0, width, -PI, PI));

  // Hier liefert die Kinect ein Array mit Vektoren
  PVector[] depthPoints = kinect.depthMapRealWorld();
  PImage rgbImage = kinect.rgbImage();
  strokeWeight(1);
  PVector prevPoint, currentPoint;
  prevPoint = new PVector(0, 0, 0);
  // Wir zeichnen nicht jeden Punkt, um die Sache zu beschleunigen
  for (int i = 20; i < depthPoints.length; i+= 3) {

    currentPoint = depthPoints[i];

    stroke(rgbImage.pixels[i]); 

    if (prevPoint.x!=0 && currentPoint.x!=0) {
      line(currentPoint.x, currentPoint.y, currentPoint.z, prevPoint.x, prevPoint.y, prevPoint.z);
    }
    
    // Wie speichern die Koordinaten des aktuellen Punkts als letzten Punkt
    prevPoint = currentPoint;
  }
  println(frameRate);
}

void keyReleased() {
  if (key == DELETE || key == BACKSPACE) background(360);
  if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
} 

//timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  println("Frame saved");
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Kinect und Processing


Habe heute den Kinect Sensor bekommen. In Java kann man ihn mit verschiedenen Bibliotheken nutzen. Z.B. mit der Simple-OpenNI Bibliothek, welche die vielfältigsten Möglichkeiten bietet. Die Simple OpenNI ist ein OpenNI- und NITE Wrapper für Processing. Die Installation unter Win7 laut dieser Anleitung ist sehr einfach. Dann kann man den ersten Processing Code laufen lassen.

141103_145804_933
/** Copyright 2012 Thomas Koberger
 */
// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import SimpleOpenNI.*;
import java.util.*;
SimpleOpenNI kinect;

void setup()
{
  size(640*2, 480);
  kinect = new SimpleOpenNI(this);
  kinect.enableDepth();
  kinect.enableIR();
}

void draw()
{
  kinect.update();
  image(kinect.depthImage(), 0, 0);
  image(kinect.irImage(), 640, 0);
  println(frameRate);
}

void keyReleased() {
  if (key == DELETE || key == BACKSPACE) background(360);
  if (key == 's' || key == 'S') saveFrame(timestamp()+&quot;_##.png&quot;);
}  


// timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  return String.format(&quot;%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS&quot;, now);
}


Was also kann der Kinect Sensor leisten?

Er liefert neben dem RGB Bild ein, durch einen IR-Emitter und einen IR-Sensor erzeugtes, Tiefenbild der Umgebung (eine DepthMap).  Das versetzt uns als Programmierer in die Lage, die Position von Objekten festzustellen und darauf zu reagieren. Der Kinect Sensor kann also die Lage von Objekten im Raum erkennen. Die Bilder haben eine Auflösung von 640 x 480 Pixel.

Die Technik hat allerdings noch ein paar Einschränkungen. Erstens funktioniert der Tiefensensor erst ab einer Distanz von ~50 cm. Zweitens werfen Objekte im Vordergrund natürlich einen Schatten. Für im Schatten liegende Objekte ist natürlich keine Tiefen-Information verfügbar.

Eine kleine Rolle in diesem Zusammenhang spielt bei diesen Überlegungen auch, dass die RGB- und Tiefenkamera in einem Abstand von einigen cm ihren Blick auf die Welt offenbaren.

Ubuntu 12.04 laut Anleitung installiert.

Die Geburt der Sonne


Processing 2.0

Hierbei handelt es sich um eine Kombination der Techniken Additive Blending und Flocking. Es werden Partikel erzeugt, die sich dann in der Mitte verdichten.

Der Sketch wäre vielleicht in Processing 2.x auch mit anderen Techniken zu realisieren. Ich habe ihn aber nur von der Version 1.5 portiert.

/** Copyright 2014 Thomas Koberger
 */
// based on a flocking algorithm by Daniel Shiffman
// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import processing.opengl.*;
import peasy.*;
import javax.media.opengl.*;
import javax.media.opengl.GL2;
import java.util.*;

// Flock
Flock flock;
Stars stars;

GL2 gl; 
PGraphicsOpenGL pgl;

PVector l[];
float str;

void setup() {
 size(1280, 720, OPENGL);

 // Create Flock
 flock = new Flock();
 stars = new Stars(400);

 for (int x = 0; x < 350; x+=1) {
 flock.addBoid(new Boid(new PVector(random(-500, 500), random(-500, 500)), random(5.0, 2), 0.5, 10000));
 Boid b = (Boid) flock.boids.get(flock.boids.size()-1);
 b.desiredseparation=random(3, 20);
 }

 for (int x = 0; x < 15; x+=1) {
 flock.addBoid(new Boid(new PVector(random(-500, 500), random(-500, 500)), random(29.0, 2), 0.5, 10000));
 Boid b = (Boid) flock.boids.get(flock.boids.size()-1);
 b.desiredseparation=random(20, 50);
 }
}

void draw() {
 hint(DISABLE_DEPTH_TEST);
 fill(0, 15);
 rect(-width, -height, width*2, height*2);
 translate(width/2, height/2, 300);

 pgl = (PGraphicsOpenGL) g; // g may change
 gl = ((PJOGL)beginPGL()).gl.getGL2();

 gl.glEnable( GL.GL_BLEND ); 
 gl.glEnable(GL.GL_LINE_SMOOTH); 

 // This fixes the overlap issue
 gl.glDisable(GL.GL_DEPTH_TEST);

 // Define the blend mode
 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);

 stars.drawStars(gl);
 flock.run(gl);

 gl.glDisable(GL.GL_BLEND);
 endPGL();
 if (frameCount%100==1) println("Rate: "+frameRate);
 //saveFrame("line-####.jpg");
}

void keyReleased() {
 //if (key == DELETE || key == BACKSPACE) background(360);
 if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.jpg");
} 

// timestamp
String timestamp() {
 Calendar now = Calendar.getInstance();
 println("Frame saved");
 return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}
// The Boid class

class Boid {

 PVector loc;
 PVector vel;
 PVector acc;

 float maxforce; // Maximum steering force
 float maxspeed; // Maximum speed
 float maxVertspeed; // Maximum speed vertical
 float maxVertforce; // Maximum speed vertical
 int lifeTime;

 float desiredseparation;// Distance to separate from neighbours
 float neighbordistAlgn; // Distance to align with neighbours
 float neighbordist; // Distance to stick to neighbours
 float desiredAvoidDist; // Distance to avoid Avoid Hunters

 float r, g, b, alpha;

 Boid(PVector l, float ms, float mf, int lt) {
 acc = new PVector(0, 0, 0 );
 vel = new PVector(0, 0, 0);
 loc = l.get();
 r = 2.0;
 maxspeed = ms;
 maxforce = mf;
 lifeTime= lt;
 maxVertspeed=ms*3;

 desiredseparation = 30.0;
 neighbordistAlgn = 15.0;
 neighbordist = 40.0;
 desiredAvoidDist = 300;
 }

 void run(ArrayList boids, GL2 gl) {
 flock(boids, gl);
 update();
 render(gl);
 lifeTime-=1;
 }

 // We accumulate a new acceleration each time based on three rules
 void flock(ArrayList boids, GL2 gl) {
 PVector sep = separate(boids); // Separation
 PVector ali = align(boids); // Alignment
 PVector coh = cohesion(boids, gl); // Cohesion
 PVector target = target(); // Food
 // Arbitrarily weight these forces
 sep.mult(2);
 ali.mult(0.5);
 coh.mult(1.0);
 target.mult(1.0);
 // Add the force vectors to acceleration
 acc.add(sep);
 acc.add(ali);
 acc.add(coh);
 acc.add(target);
 }

 // Method to update location
 void update() {
 // Update velocity
 vel.add(acc);
 // Limit speed
 vel.limit(maxspeed);
 loc.add(vel);
 // Reset accelertion to 0 each cycle
 acc.mult(0);
 }

 void render(GL2 gl) {
 PVector modelOrientation = new PVector(0, 0, 1); 
 PVector heading=new PVector(vel.x, vel.y, vel.z);
 heading.mult(2);

 if (PVector.dist(loc, new PVector(0, 0, 0))>70) {
 int lines=5;
 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);

 for (int i=lines;i>0;i-=4) {
 gl.glLineWidth(i);
 gl.glColor4f(float(1-i/lines), 0.5-i/lines, 0.2+i/lines, 
 alpha/i);
 gl.glBegin(GL.GL_LINES); 
 gl.glVertex2f(loc.x-heading.x, loc.y-heading.y);
 gl.glVertex2f(loc.x, loc.y);
 gl.glEnd();
 }
 }
 }

 void seek(PVector target) {
 acc.add(steer(target, false));
 }

 void arrive(PVector target) {
 acc.add(steer(target, true));
 }

 // A method that calculates a steering vector towards a target
 // Takes a second argument, if true, it slows down as it approaches the target
 PVector steer(PVector target, boolean slowdown) {
 PVector steer; // The steering vector
 PVector desired = target.sub(target, loc); // A vector pointing from the location to the target
 float d = desired.mag(); // Distance from the target is the magnitude of the vector
 // If the distance is greater than 0, calc steering (otherwise return zero vector)
 if (d > 0) {
 // Normalize desired
 desired.normalize();
 // Two options for desired vector magnitude (1 -- based on distance, 2 -- maxspeed)
 if ((slowdown) && (d < 100.0)) desired.mult(maxspeed*(d/100.0)); // This damping is somewhat arbitrary
 else desired.mult(maxspeed);
 // Steering = Desired minus Velocity
 steer = target.sub(desired, vel);
 steer.limit(maxforce); // Limit to maximum steering force
 } 
 else {
 steer = new PVector(0, 0, 0);
 }
 return steer;
 }

 // Separation
 // Method checks for nearby boids and steers away
 PVector separate (ArrayList boids) {
 PVector steer = new PVector(0, 0, 0);
 int count = 0;
 // For every boid in the system, check if it's too close
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = PVector.dist(loc, other.loc);

 //verändert, damit der Scwarm besser zusammenhält
 // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
 if ((d > 0) && (d < desiredseparation/3)) {
 // Calculate vector pointing away from neighbor
 PVector diff = PVector.sub(loc, other.loc);
 diff.normalize();
 diff.div(d*10); // Weight by distance
 steer.add(diff);
 count++; // Keep track of how many
 }

 if ((d > 0) && (d < desiredseparation)) {
 // Calculate vector pointing away from neighbor
 PVector diff = PVector.sub(loc, other.loc);
 diff.normalize();
 diff.div(d/16); // Weight by distance
 steer.add(diff);
 count++; // Keep track of how many
 }
 }
 // Average -- divide by how many
 if (count > 0) {
 steer.div((float)count);
 }

 // As long as the vector is greater than 0
 if (steer.mag() > 0) {
 // Implement Reynolds: Steering = Desired - Velocity
 steer.normalize();
 steer.mult(maxspeed);
 steer.sub(vel);
 steer.limit(maxforce);
 }
 return steer;
 }

 // Alignment
 // For every nearby boid in the system, calculate the average velocity
 PVector align (ArrayList boids) {
 PVector steer = new PVector(0, 0, 0);
 int count = 0;
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = PVector.dist(loc, other.loc);
 if ((d > 0) && (d < neighbordistAlgn)) {
 steer.add(other.vel);
 count++;
 }
 }
 if (count > 0) {
 steer.div((float)count);
 }
 // As long as the vector is greater than 0
 if (steer.mag() > 0) {
 // Implement Reynolds: Steering = Desired - Velocity
 steer.normalize();
 steer.mult(maxspeed);
 steer.sub(vel);
 steer.limit(maxforce);
 }
 return steer;
 }

 // Cohesion
 // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
 PVector cohesion (ArrayList boids, GL2 gl) {
 PVector sum = new PVector(0, 0, 0); // Start with empty vector to accumulate all locations
 int count = 0;

 gl.glEnable( GL.GL_BLEND ); 
 gl.glColor4f(0.95, 0.3, 0.2, 0.007);
 gl.glLineWidth(6);
 gl.glBegin(GL2.GL_POLYGON); 
 gl.glVertex2f(loc.x, loc.y);
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = loc.dist(other.loc); 

 if ((d > 0) && (d < neighbordist)) {
 sum.add(other.loc); // Add location
 count++;

 gl.glVertex2f(other.loc.x, other.loc.y);
 }
 }
 gl.glDisable( GL.GL_BLEND );
 gl.glEnd();

 // für Farbverdichtungen
 alpha = map(count, 0, 50, 0, 0.9);

 if (count > 0) {
 sum.div((float)count);
 return steer(sum, true); // Steer towards the location
 }
 return sum;
 }

 // Move Towards Target
 PVector target () {
 PVector sum = new PVector(0, 0, 0); // Start with empty vector to accumulate all locations
 //sum.limit(maxforce);
 return steer(sum, true); // Steer towards the location
 }

 boolean alive() {
 if (lifeTime<0) return false;
 else return true;
 }
}

// The Flock (a list of Boid objects)

class Flock {
 ArrayList boids; // An arraylist for all the boids

 Flock() {
 boids = new ArrayList(); // Initialize the arraylist
 }

 void run(ArrayList hunters, GL gl) {
 for (int i = 0; i < boids.size(); i++) {
 Boid b = (Boid) boids.get(i);
 }
 }
 void run(GL2 gl) {

 for (int i = 0; i < boids.size(); i++) {
 Boid b = (Boid) boids.get(i); 

 b.run(boids, gl); // Passing the entire list of boids to each boid individually

 if (!b.alive()) boids.remove(i);
 }
 }


 void addBoid(Boid b) {
 boids.add(b);
 }
}

// The Boid class

class Stars {

 PVector [] stars; 

 Stars(int amt) {

 stars = new PVector [amt];
 
 for (int i=0; i < amt; i++) {
 stars[i] = new PVector (random(-width/2, width/2), random(-height/2, height/2), 0);
 }
 }

 void drawStars(GL2 gl) {
 for (int i=0; i<stars.length;i++) {
 if(frameCount%5==0){
 gl.glColor4f(1, 1, 1, random(0.02, 0.2));
 float w=random(0.3, 1.2);
 gl.glLineWidth(w*2);
 gl.glBegin(GL.GL_LINES); 
 gl.glVertex3f(stars[i].x-w/2, stars[i].y, stars[i].z);
 gl.glVertex3f(stars[i].x+w/2, stars[i].y, stars[i].z);
 gl.glVertex3f(stars[i].x, stars[i].y-w/2, stars[i].z);
 gl.glVertex3f(stars[i].x, stars[i].y+w/2, stars[i].z);
 gl.glEnd();
 }
 }
 }
}


 

Polarlichter


Processing 2.0

Hierbei handelt es sich um eine Kombination der Techniken Additive Blending und Flocking. Es werden an zufälligen Koordinaten Partikelschwärme erzeugt, deren Partikel dann mit Linien verbunden werden. Diese werden dann additiv übereinander geblendet und ergeben farbliche Verdichtungen, die mich an Polarlichter erinnern. Außerdem werden im Hintergrund Sterne gezeichnet.

Der Sketch wäre vielleicht in Processing 2.x auch mit anderen Techniken zu realisieren. Ich habe ihn aber nur von der Version 1.5 portiert.

/** Copyright 2014 Thomas Koberger
 */
// based on a flocking algorithm by Daniel Shiffman
// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import processing.opengl.*;
import javax.media.opengl.*;
import javax.media.opengl.GL2;
import java.util.*;

Flock flock;
Stars stars;

//Über diese Objekte kann man auf OPENGL features zugreifen
GL2 gl;
PGraphicsOpenGL pgl;

PVector l[];
float str;

void setup() {
 size(1280, 720, OPENGL);

 //Sterne und Schwarm erzeugen
 flock = new Flock();
 stars = new Stars(350);
}

void draw() {
 //Verhindert, dass Objekte am Schirm, die von anderen verdeckt werden nicht gezeichnet werden
 hint(DISABLE_DEPTH_TEST);
 fill(0, 20);
 rect(-width, -height, width*2, height*2);
 translate (width/2, height/2, -200);

 // OpenGL Object Setup
 pgl = (PGraphicsOpenGL) g; // g may change
 gl = ((PJOGL)beginPGL()).gl.getGL2();
 gl.glEnable( GL.GL_BLEND );
 gl.glEnable(GL.GL_LINE_SMOOTH); 

 // This fixes the overlap issue
 gl.glDisable(GL.GL_DEPTH_TEST);

 // Define the blend mode
 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);

 //zeichne Sterne
 stars.drawStars(gl);

 if (frameCount%6==0) {
 //Add an initial set of boids into the system
 float x=random(-width/1.3, width/1.3);
 float y=random(-height/1.3, height/1.3);
 for (int i =(int) random(5,25); i > 0; i-=1) {
 flock.addBoid(new Boid(new PVector(x, y, 0), 10.0, 0.1, 300));
 }
 } 

 flock.run(gl);
 gl.glDisable(GL.GL_BLEND);
 endPGL();
 if (frameCount%100==1) println("Rate: "+frameRate);

 //einkommentieren, wenn man die Frames speichern will
 //saveFrame(timestamp()+"_##.jpg");
}

void mousePressed() {
 //fügt neue Boids hinzu
 if ( mouseButton==LEFT) {
 for (int x = 0; x < 30; x+=1) {
 flock.addBoid(new Boid(new PVector(mouseX-width*2/3, mouseY-height*2/3), 10.0, 0.1, 500));
 }
 }
}

void keyReleased() {
 //if (key == DELETE || key == BACKSPACE) background(360);
 if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.jpg");
} 

// timestamp
String timestamp() {
 Calendar now = Calendar.getInstance();
 println("Frame saved");
 return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

// The Boid class
// Original by Danial Shiffman modified by Thomas Koberger

class Boid {

 PVector loc;
 PVector vel;
 PVector acc;
 //float r;
 float maxforce; // Maximum steering force
 float maxspeed; // Maximum speed
 float maxVertspeed; // Maximum speed vertical
 float maxVertforce; // Maximum speed vertical
 int lifeTime;

 float desiredseparation;// Distance to separate from neighbours
 float neighbordistAlgn; // Distance to align with neighbours
 float neighbordist; // Distance to stick to neighbours
 float desiredAvoidDist; // Distance to avoid Avoid Hunters

 float r, g, b, alpha; 

 Boid(PVector l, float ms, float mf, int lt) {
 acc = new PVector(0, 0, 0 );
 vel = new PVector(0, 0, 0);
 loc = l.get();
 r = 2.0;
 maxspeed = ms;
 maxforce = mf;

 //definiert eine Lebensspanne für die einzelnen Boids
 lifeTime= lt;

 //definiert die Abstände, innerhalb derer sich die einzelnen Boids gegenseitig beeinflussen
 maxVertspeed=ms*3;
 desiredseparation = 8.0;
 neighbordistAlgn = 100.0;
 neighbordist = 60.0;
 }

 void run(ArrayList boids, GL2 gl) {
 flock(boids, gl);
 update();
 //borders();
 render(gl);
 lifeTime-=1;
 }

 // We accumulate a new acceleration each time based on three rules
 void flock(ArrayList boids, GL2 gl) {
 PVector sep = separate(boids); // Separation
 PVector ali = align(boids); // Alignment
 PVector coh = cohesion(boids, gl); // Cohesion
 PVector target = target(); // Food
 // Arbitrarily weight these forces
 sep.mult(2);
 ali.mult(1.0);
 coh.mult(0.1);
 target.mult(0.6);
 // Add the force vectors to acceleration
 acc.add(sep);
 acc.add(ali);
 acc.add(coh);
 acc.add(target);
 }

 // Method to update location
 void update() {
 // Update velocity
 vel.add(acc);
 // Limit speed
 vel.limit(maxspeed);
 loc.add(vel);
 // Reset accelertion to 0 each cycle
 acc.mult(0);
 }

 //Zeichne die Boids
 void render(GL2 gl) {
 PVector modelOrientation = new PVector(0, 0, 1);
 PVector heading=new PVector(vel.x, vel.y, vel.z);
 heading.mult(2);

 int lines=1;
 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
 for (int i=lines;i>0;i-=4) {
 gl.glLineWidth(i);
 gl.glColor4f(1-alpha*i/lines, 0.5-alpha*i/lines, alpha*i/lines,
 alpha/8);
 gl.glBegin(GL.GL_LINES);
 gl.glVertex2f(loc.x-heading.x, loc.y-heading.y);
 gl.glVertex2f(loc.x, loc.y);
 gl.glEnd();
 }
 }

 void seek(PVector target) {
 acc.add(steer(target, false));
 }

 void arrive(PVector target) {
 acc.add(steer(target, true));
 }

 // A method that calculates a steering vector towards a target
 // Takes a second argument, if true, it slows down as it approaches the target
 PVector steer(PVector target, boolean slowdown) {
 PVector steer; // The steering vector
 PVector desired = target.sub(target, loc); // A vector pointing from the location to the target
 float d = desired.mag(); // Distance from the target is the magnitude of the vector
 // If the distance is greater than 0, calc steering (otherwise return zero vector)
 if (d > 0) {
 // Normalize desired
 desired.normalize();
 // Two options for desired vector magnitude (1 -- based on distance, 2 -- maxspeed)
 if ((slowdown) && (d < 100.0)) desired.mult(maxspeed*(d/100.0)); // This damping is somewhat arbitrary
 else desired.mult(maxspeed);
 // Steering = Desired minus Velocity
 steer = target.sub(desired, vel);
 steer.limit(maxforce); // Limit to maximum steering force
 }
 else {
 steer = new PVector(0, 0, 0);
 }
 return steer;
 }

 // Separation
 // Method checks for nearby boids and steers away
 PVector separate (ArrayList boids) {
 PVector steer = new PVector(0, 0, 0);
 int count = 0;
 // For every boid in the system, check if it's too close
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = PVector.dist(loc, other.loc);

 //verändert, damit der Scwarm besser zusammenhält
 // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
 if ((d > 0) && (d < desiredseparation/3)) {
 // Calculate vector pointing away from neighbor
 PVector diff = PVector.sub(loc, other.loc);
 diff.normalize();
 diff.div(d*10); // Weight by distance
 steer.add(diff);
 count++; // Keep track of how many
 }

 if ((d > 0) && (d < desiredseparation)) {
 // Calculate vector pointing away from neighbor
 PVector diff = PVector.sub(loc, other.loc);
 diff.normalize();
 diff.div(d/16); // Weight by distance
 steer.add(diff);
 count++; // Keep track of how many
 }
 }
 // Average -- divide by how many
 if (count > 0) {
 steer.div((float)count);
 }

 // As long as the vector is greater than 0
 if (steer.mag() > 0) {
 // Implement Reynolds: Steering = Desired - Velocity
 steer.normalize();
 steer.mult(maxspeed);
 steer.sub(vel);
 steer.limit(maxforce);
 }
 return steer;
 }

 // Alignment
 // For every nearby boid in the system, calculate the average velocity
 PVector align (ArrayList boids) {
 PVector steer = new PVector(0, 0, 0);
 int count = 0;
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = PVector.dist(loc, other.loc);
 if ((d > 0) && (d < neighbordistAlgn)) {
 steer.add(other.vel);
 count++;
 }
 }
 if (count > 0) {
 steer.div((float)count);
 }
 //definiert den Rot Wert
 r=map(steer.mag(), 0, 10, 0, 0.9);
 // As long as the vector is greater than 0
 if (steer.mag() > 0) {
 // Implement Reynolds: Steering = Desired - Velocity
 steer.normalize();
 steer.mult(maxspeed);
 steer.sub(vel);
 steer.limit(maxforce);
 }
 return steer;
 }

 // Cohesion
 // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
 PVector cohesion (ArrayList boids, GL2 gl) {
 PVector sum = new PVector(0, 0, 0); // Start with empty vector to accumulate all locations
 int count = 0;

 // Define the blend mode
 gl.glEnable( GL.GL_BLEND );
 gl.glColor4f(r, g, b, 0.004);
 gl.glLineWidth(8);
 gl.glBegin(GL2.GL_POLYGON);
 gl.glVertex2f(loc.x, loc.y);
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = loc.dist(other.loc);

 //definiert den Blau und Grün Wert
 b=map(d, 0, neighbordist*12, 0, 0.9);
 g=map(vel.mag(), 0, 10, 0, 0.9);

 if ((d > 0) && (d < neighbordist)) {
 sum.add(other.loc); // Add location
 count++;

 gl.glVertex2f(other.loc.x, other.loc.y);
 }
 }
 gl.glEnd();

 // für Farbverdichtungen
 alpha = map(count, 0, 50, 0, 1);

 if (count > 0) {
 sum.div((float)count);
 return steer(sum, true); // Steer towards the location
 }
 return sum;
 }

 // Move Towards Target
 PVector target () {
 PVector sum = new PVector(0, 0, 0); // Start with empty vector to accumulate all locations
 return steer(sum, true); // Steer towards the location
 }

 boolean alive() {
 if (lifeTime<0) return false;
 else return true;
 }
}

// The Flock (a list of Boid objects)

class Flock {
 ArrayList boids; // An arraylist for all the boids

 Flock() {
 boids = new ArrayList(); // Initialize the arraylist
 }

 void run(ArrayList hunters) {
 for (int i = 0; i < boids.size(); i++) {
 Boid b = (Boid) boids.get(i);
 }
 }
 void run(GL2 gl) {
 for (int i = 0; i < boids.size(); i++) {
 Boid b = (Boid) boids.get(i);
 b.run(boids, gl); // Passing the entire list of boids to each boid individually

 //Elimiert Boids, wenn sie den sichbaren bereich verlassen
 if (!b.alive() || b.loc.x>1200 || b.loc.x<-1200 || b.loc.y>1200 || b.loc.y<-1200) {
 boids.remove(i);
 }
 }
 }

 void addBoid(Boid b) {
 boids.add(b);
 }
}

// The Boid class

class Stars {

 PVector [] stars; 

 Stars(int amt) {

 stars = new PVector [amt];

 for (int i=0; i < amt; i++) {
 stars[i] = new PVector (random(-width, width), random(-height, height), 0);
 }
 }

 void drawStars(GL2 gl) {
 for (int i=0; i<stars.length;i++) {
 if(frameCount%5==0){
 gl.glColor4f(1, 1, 1, random(0.02, 0.2));
 float w=random(0.3, 1.2);
 gl.glLineWidth(w*2);
 gl.glBegin(GL.GL_LINES);
 gl.glVertex3f(stars[i].x-w/2, stars[i].y, stars[i].z);
 gl.glVertex3f(stars[i].x+w/2, stars[i].y, stars[i].z);
 gl.glVertex3f(stars[i].x, stars[i].y-w/2, stars[i].z);
 gl.glVertex3f(stars[i].x, stars[i].y+w/2, stars[i].z);
 gl.glEnd();
 }
 }
 }
}

 

Voronoi und die französiche Revolution


Processing 2.0

Hier nun 2 Anwendungsbeispiele für Voronoi Diagramme zum Thema „französische Revolution“.
Eine Erklärung, wie so ein Diagramm funktioniert findest du hier. Im ersten Beispiel wird eine Library verwendet, mit der man Schriftzüge in einzelne Punkte auflösen kann. Die Geomerative Library. Infos dazu gibts hier.

Beispiel 1: Hier werden die Schriftzüge der Werte Liberté  Égalité  Fraternité (Freiheit, Gleichheit, Brüderlichkeit) zerlegt, in Voronoi Bereiche aufgespalten und dann entsprechend eingefärbt. Blau für die Freiheit, Weiß für Gleichheit und Rot für Brüderlichkeit.

140109_082800_01

/** Copyright 2013 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import java.util.Calendar;
import megamu.mesh.*;
import geomerative.*;

RShape shp, shp1, shp2;
RPoint[] pnts, pnts1;
PImage qr;

String BspText1 = "Liberté  Égalité  Fraternité";

ArrayList<Integer> voroPoints;
Voronoi myVoronoi;
float[][] points;

void setup() {
size(4000, 1000);
voroPoints = new ArrayList<Integer> ();

translate(width/2, height*2/12);

RG.init(this);
// 3 Shape - Objekte werden erzeugt.
// Die Schrift mit dem Namen "Ubuntu-R.ttf" muss im data Ordner platziert werden
shp1 = RG.getText(BspText1, "Ubuntu-R.ttf", width*8/120, CENTER);

// Punkte an der Schriftkontur finden
//Abstand der Punkte
RCommand.setSegmentLength (3);
//Modus
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
//RCommand.setSegmentator(RCommand.ADAPTATIVE);

// Die Shapes werdem gezeichnet und positioneiert
shp1.translate(0, height*7/12);
fill(180, 160);

// Finden der Konturpunkte
pnts = shp1.getPoints();

// Variation der einzelnen Punkte
for (int i=0;i<pnts.length; i+=1) {
stroke(255, 0, 0);
point(pnts[i].x, pnts[i].y);

int var=(int) random(-2, 2);
int x = (int) pnts[i].x+var;
var=(int) random(-2, 2);
int y = (int) pnts[i].y+var;

//Damit nicht 2 Punkte die gleichen Koordinaten haben
boolean coordAvailable=true;
for (int j=0; j<voroPoints.size(); j+=2) {
if (((int)voroPoints.get(j)==x && (int)voroPoints.get(j+1)==y)
|| x==0 || y==0) {
coordAvailable=false;
}
}

if (coordAvailable) {
voroPoints.add(x);
voroPoints.add(y);
}
else println("sameCoord");
}
println("NumPoints: "+voroPoints.size());
createVoronoi () ;
qr =loadImage("qrcode.png");
}

void draw() {
background(255);
translate(width/2, 0);

//get and draw VoroRegions
MPolygon[] myRegions = myVoronoi.getRegions();

for (int i=0; i<myRegions.length; i++) {
// an array of points
float[][] regionCoordinates = myRegions[i].getCoords();

int col= (int)random(0, 3);
if (col==0) fill(0, 0, 255);
else if (col==1) fill(255, 40);
else fill(255, 0, 0);
stroke(80, 50);
strokeWeight(1);
myRegions[i].draw(this); // draw this shape
}

//draw Points
strokeWeight(2);
stroke(80, 150);
for (int i=0; i<voroPoints.size(); i+=2) {
point((int)voroPoints.get(i), (int)voroPoints.get(i+1));
}
image(qr, width/2-height/5, height-height/5, height/10, height/10);
saveFrame(timestamp()+"_##.png");
}

void createVoronoi () {
points = new float[voroPoints.size()/2][2];
for (int i=0; i<voroPoints.size(); i+=2) {

//Hierbei muss die Art des Objekts in der ArrayList festgelegt werden.
points[i/2][0] =(int) voroPoints.get(i);
points[i/2][1] =(int) voroPoints.get(i+1);
}
myVoronoi = new Voronoi( points );
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
}

//timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Beispiel 2: Visualisierung der Werte Liberté  Égalité  Fraternité (Freiheit, Gleichheit, Brüderlichkeit) in Form von  Voronoi Diagrammen.Fertig

/** Copyright 2013 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
*
* MOUSE
*
* KEYS
* Toggle Mode
* 0                   : Freiheit
* 1                   : Gleichheit
* 1                   : Brüderlichkeit
* s                   : save png
*/

import java.util.Calendar;
import megamu.mesh.*;

ArrayList<Integer> voroPoints;
Voronoi myVoronoi;
float[][] points;

//Freiheit = 0
//Gleichheit = 1
//Brüderlichkeit = 2
int mode=0;

void setup() {
size(800, 800);
voroPoints = new ArrayList<Integer> ();
createFreiheit();
noLoop();
}

void draw() {
createVoronoi();
if (mode==0) {
fill(0, 0, 190, 255);
stroke(200);
}
else if (mode==1) {
fill(255);
stroke(80);
}
else {
fill(220, 0, 0);
stroke(200);
}
if (voroPoints.size()>1) {

//getRegions
strokeWeight(1);
MPolygon[] myRegions = myVoronoi.getRegions();

for (int i=0; i<myRegions.length; i++)
{
// an array of points
float[][] regionCoordinates = myRegions[i].getCoords();
myRegions[i].draw(this); // draw this shape
}
}

// draw Points
strokeWeight(6);
for (int i=0; i<voroPoints.size(); i+=2) {
point((int)voroPoints.get(i), (int)voroPoints.get(i+1));
}
}

void createFreiheit() {
voroPoints = new ArrayList<Integer> ();

for (int i=160; i<width-80;i+=80) {
for (int j=160; j<height-80;j+=80) {

if (i==400 && j ==400) {
voroPoints.add(i+(int)random(-40, 40));
voroPoints.add(j+(int)random(-40, 40));
}
else {
voroPoints.add(i);
voroPoints.add(j);
}
}
}
}

void createGleichheit() {
voroPoints = new ArrayList<Integer> ();

for (int i=160; i<width-80;i+=80) {
for (int j=160; j<height-80;j+=80) {
voroPoints.add(i);
voroPoints.add(j);
}
}
}

void createBruederlichkeit() {
voroPoints = new ArrayList<Integer> ();

for (int i=160; i<width-80;i+=80) {
for (int j=160; j<height-80;j+=80) {
if (i==400 && j ==400) {
voroPoints.add(i);
voroPoints.add(j+20);
} else if (i==400 && j ==480) {
voroPoints.add(i);
voroPoints.add(j-20);
}else {
voroPoints.add(i);
voroPoints.add(j);
}
}
}
}

void createVoronoi () {
points = new float[voroPoints.size()/2][2];
for (int i=0; i<voroPoints.size(); i+=2) {

points[i/2][0] =(int) voroPoints.get(i);
points[i/2][1] =(int) voroPoints.get(i+1);
}
myVoronoi = new Voronoi( points );
}

void keyReleased() {
if (key == DELETE || key == BACKSPACE) background(360);
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
if (key == '0') {
mode=0;
createFreiheit();
}
if (key == '1') {
mode=1;
createGleichheit();
}
if (key == '2') {
mode=2;
createBruederlichkeit();
}
loop();
}

//timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Voronoi Diagramme


Processing 2.0

Voronoi Diagramme, benannt nach dem ukrainischen Mathematiker Georgi Feodosjewitsch Woronoi können für verschiedene Problemstellungen Lösungen liefern. Z.B.:

  • Standortsuche für öffentliche Einrichtungen (optimale Erreichbarkeit)
  • Bewertung von Standorten
  • Modellierung von Nähe

Was ist nun so ein Voronoi Diagramm?

131225_122847_1010Gegeben sind eine Menge von Punkten M. Die Voronoi Region eines Punktes enthält nun ausschließlich Punkte, welche genau diesem, und keinem anderen Punkt M am nächsten sind. Es sind Gebiete mit dem gleichen nächsten Nachbarn. Das Diagramm besteht aus Voronoi Knoten, an denen immer genau 3 Voronoi Kanten einander treffen.

Aufgabe: Nimm ein leeres Blatt Papier und  zeichne ungefähr 7 zufällig verteilte Punkte darauf. Konstruiere ein Voronoi Diagramm von Hand.

Anleitung: Verbinde alle benachbarten Punkte mit Hilfslinien. Teile diese Hilfslinien in der Mitte und zeichne eine Normale durch diesen Punkt. Die Normalen stellen die Voronoi Kanten, deren Schnittpunkte die Knoten dar. Siehe auch: HowVoronoiDiagramsWork

Beispiel: Für meine Implementierung des Voronoi Algorithmus verwende ich die Mesh Library von Lee Byron. Der Sketch bietet die Möglichkeit, Voronoi Punkte zu zeichnen und zu verschieben und so mit dem Algorithmus zu experimentieren.

/** Copyright 2013 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
*
* MOUSE
* LEFT                :Create/Drag VoroPoint
*
* KEYS
* s                   : save png
*/

import java.util.Calendar;
import megamu.mesh.*;

//Arraylist and Array um die Punkte zu speichen
ArrayList<Integer> voroPoints;
Voronoi myVoronoi;
float[][] points;

//Punkt, der gerade bewegt wird
int activePoint;

void setup() {
size(800, 800);
voroPoints = new ArrayList<Integer> ();
activePoint=10000;
}

void draw() {
background(255);

//Wenn mindestens 1 Punkt vorhanden ist wird gezeichnet
if (voroPoints.size()>1) {

//getRegions
strokeWeight(1);
stroke(80, 150);
MPolygon[] myRegions = myVoronoi.getRegions();
for (int i=0; i<myRegions.length; i++)
{
// an array of points
float[][] regionCoordinates = myRegions[i].getCoords();
myRegions[i].draw(this); // draw this shape
}
}

// draw Points
fill(255);
strokeWeight(6);
stroke(0);
for (int i=0; i<voroPoints.size(); i+=2) {
point((int)voroPoints.get(i), (int)voroPoints.get(i+1));
}
}

void createVoronoi () {
points = new float[voroPoints.size()/2][2];
for (int i=0; i<voroPoints.size(); i+=2) {

points[i/2][0] =(int) voroPoints.get(i);
points[i/2][1] =(int) voroPoints.get(i+1);
}
myVoronoi = new Voronoi( points );
}

//Abfrage, ob sich an der Mausposition ein Punkt befindet
void mousePressed() {
for (int i=0; i<voroPoints.size(); i+=2) {
int x =(int) voroPoints.get(i);
int y =(int) voroPoints.get(i+1);
if (mouseX<x+5 && mouseX>x-5 && mouseY<y+5 &&mouseY>y-5) activePoint=i;
}
}

//Wenn kein Punkt aktiv ist und gerade bewegt wird, wird ein neuer hinzugefügt
void mouseReleased() {
println("released");
if (activePoint==10000) {
voroPoints.add((int)mouseX);
voroPoints.add((int)mouseY);
createVoronoi();
}
activePoint = 10000;
}

//Punkt wird gezogen
void mouseDragged() {
if (activePoint!=10000) {
voroPoints.set(activePoint, mouseX);
voroPoints.set(activePoint+1, mouseY);
createVoronoi();
}
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
}

//timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Wahlergebnis


Processing 2.0

Unser Ziel war es die Wahlergebnisse 2013 möglichst einfach, informativ und ohne prozentuelle Zahlenwerte, visuell darzustellen.

Am Anfang hatten wir mehrere Lösungsvorschläge gesammelt, aber haben uns schließlich für eine Darstellung mithilfe von Kreisen entschlossen. Ein Kreis in der Mitte wird von mehreren Kreisen umschlossen, wobei in vier Ebenen (Österreich, Bundesländer, Bezirke, Gemeinden) die gesamte Verteilung der Stimmen, auf die bei der Wahl angetretenen Parteien abgebildet wird.

Um einen guten visuellen Effekt zu erzeugen haben wir die Radien der Kreise immer so vergrößert dass diese den gleichen Flächeninhalt wie der Kreis in der Mitte haben. Diesen Effekt erhält man indem man den Radius des ersten Kreises (in der Mitte) mit dem Faktor multipliziert, so erhält man den Radius des zweiten Kreises. Den Radius des dritten Kreises erhält man dann indem man den Radius des ersten Kreises mit multipliziert usw…

Die Kreise haben wir dann dem Prozentsatz nach, den die Parteien bei den Wahlen erzielt haben in Sektoren unterteilt und eingefärbt. Ausgegangen sind wir hier von den gültigen Stimmen.

wahl13klein wahlklein

Nationalratswahl 2008.pdf

Nationalratswahl 2013.pdf

// WahlVisualisierung Nationalratswahl 2008
//
// Copyright 2013 Thomas Koberger
//
// http://www.lernprocessing.wordpress.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//Damit im Pdf Mode gerendert werden kann
//Nötig, um die geforderte Auflösung zu erreichen
import processing.pdf.*;

//Array zum Einlesen der csv Datei
String [][] csv;
PImage fo;
//Array enthält die Parteifarben
color[] parteiFarbe = {
color(255, 0, 0), //SPÖ
color(0, 0, 0), //ÖVP
color(0, 0, 255), //FPÖ
color(255, 180, 0), //BZÖ
color(0, 255, 0), //Grüne
color(255, 255, 0), //Frank
color(255, 0, 100), //Neos
color(190, 0, 0), //KPÖ
color(255, 0, 255), //Pirat
color(0, 100, 255), //CPÖ
color(0, 100, 255), //Wandl
color(0, 100, 255), //M
color(0, 100, 255), //EUAUS
color(0, 100, 255), //SLP
color(0, 100, 255)
};

//Array enthält die Parteinamen
String [] parteien= {
"SPÖ",
"ÖVP",
"FPÖ",
"BZÖ",
"Grüne",
"Frank",
"Neos",
"KPÖ",
"Pirat",
"CPÖ",
"Wandl",
"M",
"EUAUS",
"SLP"
};

//Variable bestimmt später die Größe der Grafik
float weight;

float angle;

void setup() {
size(8000, 8000, PDF, "Wahl13.pdf");

//Definitionen
strokeWeight(1);
stroke(100);
ellipseMode(CENTER);
textFont(createFont("Tahoma", 96));
textAlign(CENTER, CENTER);
imageMode(CENTER);

//folgender Code ist für den Import der Daten aus
//aus einer .svg zuständig und stammt im Original von:
//for importing csv files into a 2d array
String lines[] = loadStrings("NRW13.csv");
int csvWidth=0;
//Berechnet die Anzahl der Spalten in der csv Datei
for (int i=0; i < lines.length; i++) {
//println(i+". "+lines[i]);
String [] chars=split(lines[i], ';');
if (chars.length>csvWidth) {
csvWidth=chars.length;
}
}
//Erstellt ein Array basierend auf die Anzahl der Zeilen und Spalten der csv Datei
csv = new String [lines.length][csvWidth];

//parse values into 2d array
for (int i=0; i < lines.length; i++) {
String [] temp = new String [lines.length];
temp= split(lines[i], ';');
for (int j=0; j < temp.length; j++) {
csv[i][j]=temp[j];
}
}
//Berechnet einen Wert, der die Größe der Grafik bestimmt,
//und aus der Anzahl der Stimmberechtigten ermittelt wird
weight = sqrt(parseFloat(csv[0][6])/PI)*2;
angle=0;
}

void draw () {
background(255);
pushMatrix();
translate(width/2, height/2);
drawBeschriftung();
drawLaender();
drawAustria();
popMatrix();
textSize(50);
textAlign(CENTER, CENTER);
//text("Quelle: http://www.bmi.gv.at/cms/BMI_wahlen/nationalrat/2008/files/Ergebnis_end.zip", width/2, height-height/50);
// Exit the program
fo =loadImage("qrcode.png");
image(fo,width/1.08,height/1.08,640,640);
println("Finished.");
exit();
}

void drawLaender() {
pushMatrix();
float weightBl=weight*sqrt(3);

for (int i=0; i < csv.length; i++) {

//draw Bundesländer
String Gkz=csv[i][0];
Gkz=Gkz.substring(1, 6);
//println(parseInt(Gkz)%10000);
if (parseInt(Gkz)%10000==0 && parseInt(Gkz)/10000!=0) {
float percentage= parseFloat(csv[i][6])/parseFloat(csv[0][6])*100;
float angleBl=radians(percentage*3.6);
int gkzBl = parseInt(Gkz)/10000;
int partyBl=getStrongestParty(i);
println("Bl: "+gkzBl);

//draw Bezirke
float weightBz=weight*sqrt(5);
pushMatrix();
for (int k=0; k < csv.length; k++) {
Gkz=csv[k][0];
Gkz=Gkz.substring(1, 6);
//println(Gkz+" 11: "+parseInt(Gkz));

if (parseInt(Gkz.substring(0, 1))==gkzBl && (parseInt(Gkz)-gkzBl*10000)%100 ==0 && (parseInt(Gkz)-gkzBl*10000) >0 ) {
float percentageBz= parseFloat(csv[k][6])/parseFloat(csv[0][6])*100;
float angleBz=radians(percentageBz*3.6);
int gkzBz = parseInt(Gkz.substring(1,3));
println(gkzBz+" Bz: " + csv[k][1]+ " gkz: "+csv[k][0]);
int partyBz=getStrongestParty(k);

//draw Gemeinden
float weightGmd=weight*sqrt(7);
pushMatrix();
for (int l=0; l < csv.length; l++) {
Gkz=csv[l][0];
Gkz=Gkz.substring(1, 6);

if (parseInt(Gkz.substring(0,1))==gkzBl && parseInt(Gkz.substring(1,3))==gkzBz && parseInt(Gkz.substring(3,5))!=0) {
println(gkzBl+" gkzBz: "+gkzBz+" Gmd: "+Gkz.substring(3,5)+" "+csv[l][1]+ " gkz: "+csv[l][0]);
float percentageGmd= parseFloat(csv[l][6])/parseFloat(csv[0][6])*100;
//println(parseFloat(csv[k][2])+" percBZ"+percentageBz+" perc"+percentage);
float angleGmd=radians(percentageGmd*3.6);
int gkzGmd = parseInt(csv[l][0]);
int partyGmd=getStrongestParty(l);
//println(gkzGmd);

//Zeichnet die Stimmverteilung der Gemeinden
drawParty( angleGmd, l, 7);
//Zeichnet die Beschriftung der Gemeinden
angle+=angleGmd/2;
rotate(angleGmd/2);
fill(100);
pushMatrix();
translate(weightGmd/2+10, 0);
if (degrees(angle)>90 && degrees(angle)<270) {
rotate(PI);
textAlign(RIGHT, CENTER);
}
else {
textAlign(LEFT, CENTER);
}
textSize(map(min(parseFloat(csv[l][6]),10000),100,15000,5,16));
fill(parteiFarbe[partyGmd]);
text(csv[l][1], 0, 0);
popMatrix();
rotate(angleGmd/2);
angle+=angleGmd/2;
//println(csv[k][0]+" : "+angle);
}
}
popMatrix();
angle-=angleBz;
//Zeichnet die Stimmverteilung der Bezirke
drawParty( angleBz, k, 5);
//Zeichnet die Beschriftung der Bezirke
angle+=angleBz/2;
rotate(angleBz/2);
fill(100);
pushMatrix();
translate(weightBz/2+10, 0);
if (degrees(angle)>90 && degrees(angle)<270) {
rotate(PI);
textAlign(RIGHT, CENTER);
}
else {
textAlign(LEFT, CENTER);
}

textSize(weight/160);
fill(parteiFarbe[partyBz]);
text(csv[k][1], 0, 0);
popMatrix();
angle+=angleBz/2;
rotate(angleBz/2);
}
}
popMatrix();
angle-=angleBl;
//Zeichnet die Stimmverteilung der Bundesländer
println(gkzBl+" angle: "+angleBl);
drawParty( angleBl, i, 3);
//Zeichnet die Beschriftung der Bezirke
angle+=angleBl/2;
rotate(angleBl/2);
fill(100);
pushMatrix();
translate(weightBl/2+weight/50, 0);
rotate(PI/2);
textSize(weight/45);
textAlign(CENTER, CENTER);
fill(parteiFarbe[partyBl]);
text(csv[i][1], 0, 0);
popMatrix();
rotate(angleBl/2);
angle+=angleBl/2;
}
}

popMatrix();
fill(255);
ellipse(0, 0, weight*sqrt(2), weight*sqrt(2));
}

void drawAustria() {
//Zeichnet den Inneren Kreis mit dem Ergebnis für ganz Österreich
pushMatrix();
angle=0;
//Liest für jede Partei die Stimmen in % aus
for (int i=0;i<26; i+=2) {
//Stimmen für die jeweilige Partei in %
float percentage=parseFloat(csv[0][i+8]);
//Winkel des Kreissektors jeder Partei
float angleAt=radians(percentage*3.6);
//Zeichnet den Kreissektor in der Farbe der Partei
stroke(200);
fill(255);
arc(0, 0, weight*sqrt(2), weight*sqrt(2), 0, angleAt, PIE);
fill(parteiFarbe[i/2]);
//Zeichnet den Kreissektor für die Beschriftung
noStroke();
arc(0, 0, weight, weight, 0, angleAt, PIE);
//Beschriftung der Kreissektoren
rotate(angleAt/2);
angle+=angleAt/2;
fill(100);
pushMatrix();
translate(weight/2+weight/100, 0);
if (degrees(angle)>90 && degrees(angle)<270) {
rotate(PI);
textAlign(RIGHT, CENTER);
}
else {
textAlign(LEFT, CENTER);
}
textSize(weight/40);
//if (percentage>0.5) text(parteien[i/2]+" "+csv[0][i+8]+"%", 0, 0);
if (percentage>0.5) text(parteien[i/2], 0, 0);
popMatrix();
angle+=angleAt/2;
rotate(angleAt/2);
}
popMatrix();
}

//Zeichnet die einzelnen Kreissektoren der Parteien für jeden Wahlkreis
void drawParty(float winkel, int i, int ebene) {
stroke(200);
fill(255);
arc(0, 0, weight*sqrt(ebene+1), weight*sqrt(ebene+1), 0, winkel, PIE);
noStroke();
float offset=0;
for (int j=0;j<26; j+=2) {
float breite=parseFloat(csv[i][j+8]);
fill(parteiFarbe[j/2]);
arc(0, 0, weight*sqrt(ebene-offset), weight*sqrt(ebene-offset), 0, winkel, PIE);
offset+=parseFloat(csv[i][j+8])/100;
}
}

int getStrongestParty(int index) {
int party=0;
float strongestParty=0;
for (int m=0;m<26; m+=2) {
//println(csv[i][1]+" :"+parseFloat(csv[i][m+8])+" strong:"+strongestParty+" bl: "+partyBl);
if (strongestParty<parseFloat(csv[index][m+8])) {
strongestParty=parseFloat(csv[index][m+8]);
party=m/2;
}
}
return(party);
//println(csv[i][1]+" :"+partyBl);
}

void drawBeschriftung() {
pushMatrix();
String ueberschrift= "Nationalratswahl 2013";
println(weight*sqrt(9)-weight*sqrt(8));
//textFont(createFont("Tahoma", (int)(weight*sqrt(9)-weight*sqrt(8))));

textSize((weight*sqrt(9)-weight*sqrt(8))/2);
textAlign(CENTER);
//translate(width/2,height/2);

fill(255, 0, 0);
ellipse(0, 0, weight*sqrt(9), weight*sqrt(9));
fill(255);
ellipse(0, 0, weight*sqrt(9)-(weight*sqrt(9)-weight*sqrt(8))/3, weight*sqrt(9)-(weight*sqrt(9)-weight*sqrt(8))/3);
fill(255, 0, 0);
ellipse(0, 0, weight*sqrt(9)-(weight*sqrt(9)-weight*sqrt(8))*2/3, weight*sqrt(9)-(weight*sqrt(9)-weight*sqrt(8))*2/3);
rotate(PI*7/6);
fill(0);
arc(0, 0, weight*sqrt(9), weight*sqrt(9), 0, PI*2/3, PIE);
fill(250);
ellipse(0, 0, weight*sqrt(8), weight*sqrt(8));

for (int i=0;i<ueberschrift.length();i++) {
rotate(PI*2/3/(ueberschrift.length())/2);
pushMatrix();
translate(weight*sqrt(8)/2+weight/100, 0);
rotate(PI/2);
fill(255);
text(ueberschrift.charAt(i), 0, 0);
popMatrix();
rotate(PI*2/3/(ueberschrift.length())/2);
}
popMatrix();
}

Kinect Einstieg


Die Kinect ist eine 3D Kamera. Sie nimmt in erster Linie nicht das Aussehen von Objekten auf, sondern deren Position im Raum.

Wie schafft die Kinect das?

Die Hardware besteht aus 2 Kameras. Einer normale RGB Kamera und einer Infrarot Kamera. Die RGB Kamera liefert ein Bild vergleichbar einer günstigen Webcam in einer Auflösung von 640 mal 480 Pixel. Die IR Kamera liefert ein IR Bild in der selben Auflösung. Außerdem ist die Kinect mit einem IR Projektor ausgestattet. Dieser projeziert Infrarot Pixel, die dann mit den entsprechenden Pixel der IR Kamera verrechnet werden. Daraus kann dann die Entfernung der Pixel vom Sensor errechnet werden.

121212_222007_369

IR Bild aus der Kinect

Die Kinect kann nun verschiedene Outputs produzieren. Dafür verwenden wir folgendes Beispielprogramm:

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
*
* MOUSE
*
* KEYS
* s                   : save png
*/

import SimpleOpenNI.*;
import java.util.Calendar;

SimpleOpenNI  kinect;

void setup()
{
size(640, 480);

kinect = new SimpleOpenNI(this);

// depthMap aktivieren
kinect.enableDepth();

// RGB Kamera aktivieren
//kinect.enableRGB();

// IR Kamera aktivieren
//kinect.enableIR();

// Scenemap aktivieren
//kinect.enableScene();
}

void draw()
{
// Update Kinect
kinect.update();

// Eine der vier Images können aktiviert werden, wenn auch in
// setup() die entsprechende Funktion aktiviert wurde
// Achtung: RGB und IR können nicht parallel genutzt werden!!!
image(kinect.depthImage(), 0, 0);
//image(kinect.rgbImage(),0,0);
//image(kinect.irImage(), 0, 0);
//image(kinect.sceneImage(),0,0);
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Das Programm erzeugt zuerst einmal ein Objekt des SimpleOpenNI Sensors namens kinect. Mit dessen Hilfe kann man nun die Funktionen der Kinect kontrollieren. Damit wir dann Output generieren können, müssen wir die verschiedenen Funktionen der Kinect aktivieren. Das passiert in der Funktion setup(). In draw() müssen dann die Daten aus dem Sensor abergerufen werden. Nun können die vom Sensor bereitgestellten Bilder ausgegeben werden.

Wie sind diese Bilder zu interpretieren?

1. depthImage()

121212_221759_2334

Dieses Bild wird von der Kinect aus den Tiefeninformationen generiert. Nahe Objekte werden heller dargestellt, als weiter entfernte. Sind Objekte weniger weit als etwa 50 cm funktioniert die Entfernungsmessung nicht. Diese Objekte werden schwarz dargestellt. Der Messbereich reicht bis 8m. Die Entfernung werden von der Kinect sehr genau gemessen. Wir werden später sehen, wie wir diese ermitteln können. Was an  dem Bild noch auffällt ist, dass es kleine scharze Bereiche enthält. Diese werden von Schatten verursacht, die näher gelegene Objekte auf weiter entfernte werfen. In diesen Bereichen kann natürlich keine Tiefeninfo abgerufen werden.

Wenn wir das Programm um folgende Zeile erweitern, können wir die Tiefeninformation an der Mausposition abrufen. Dafür verwenden wir die OpenNI Funktion depthMap(). Sie gibt ein int- Array mit der Entfernung des Punktes zum Sensor zurück.

  println(kinect.depthMap()[640*mouseY+mouseX]);

2. rgbImage()

121212_221837_865

Dieses Bild stammt von der RGB Kamera und ist qualitativ nicht besonders ansprechend. Allerdings ist sie doch recht interessant. Wir werden uns später ansehen, wie man die Farbinfos auf die Tiefenprojektion übertragen und somit ein farbiges 3 dimensionales Bild erstellen können.

3. irImage()

121212_222007_369

Output der IR Kamera. Hier kann man sehr gut die projezierten IR Punkte sehen.

4. sceneImage()

121212_224833_15438

Hier sind wir schon bei den höheren Funktionen der SimpleOpenNI. In diesem Modus versucht die Library Gegenstände im Bild zu erkennen. Wird ein Gegenstand erkannt, färbt ihn das System automatisch ein.

Shading


Processing 2.0

In Processing 2.0 basiert die Rendering Engine auf GLSL Shadern. D.h. bei jeder Darstellung auf dem Bildschirm wird von Processing automatisch ein geeigneter Shader gewählt. Der Vorteil dabei liegt darin, dass die Programme zu einem größeren Teil von der GPU ausgeführt werden. Außerdem entfällt der Datentransfer vom RAM in den Grafikspeicher. All das führt zu einer mehr oder weniger starken Beschleunigung der Programme.

Es kommen immer 2 Shader zum Einsatz:

  • Vertex Shader
    • Manipuliert die Geometrie der dargestellten Objekte.
    • Z.B. zum Verändern der Perspektive
    • Er kann keine Objekte hinzufügen, oder entfernen!
  • Fragment Shader
    • Wandelt die 2D Objekte in ein Pixel-Array um –> Rasterization
    • Berechnet Blending und Licht
    • Das Resultat ist die endgültige Pixelfarbe

     

Wie schon gesagt, wendet Processing einen geeigneten Shader für die jeweilige Aufgabe aus. Diese Aufgaben können folgendermaßen eingeteilt werden:

  1. gefärbte Polygone ohne Licht und Textur
  2. gefärbte Polygone mit Licht, aber ohneTextur
  3. gefärbte Polygone ohne Licht, aber mit Textur
  4. gefärbte Polygone mit Licht und Textur
  5. Linien
  6. Punkte

Jeder dieser verschiedenen Shader erwartet bestimmte In- und Outputs.

Beinhaltet ein draw() Durchlauf verschiedene dieser Elemente, kommen die entsprechenden Shader (immer bestehend aus einem Vertex– und einem FragmentShader) zum Einsatz.

Will man nun einen eigenen Shader anwenden, kann man entweder beide default Shader, oder nur den Fragment-Shader ersetzen. Dadurch werden die default Shader aber nur deaktiviert und können jederzeit wieder aktiviert werden.

Beispiel: Default Shader für 1.

shader

In diesem Beispiel schauen wir uns an, wie man die default Shader von Processing durch eigene ersetzen kann und wie diese Shader aufgebaut sind.

Zuerst einmal brauchen wir einen Vertex-Shader. Dieser gleicht dem default Shader in Processing.


uniform mat4 projmodelviewMatrix;

attribute vec4 inVertex;
attribute vec4 inColor;

varying vec4 vertColor;

void main() {
gl_Position = projmodelviewMatrix * inVertex;

vertColor = inColor;
}

Was macht nun dieser Vertex Shader? Er bekommt von Processing die projmodelviewMatrix übergeben und für jeden Punkt die Variablen inVertex und inColor. In void main()  wird dann das eigentliche C-Programm platziert. In unserem Fall werden nur inVertex mit der projmodelviewMatrix verrechnet und in gl_Position gespeichert, und der Wert inColor wird in vertColor gespeichert.

Die Variable vertColor wird dann an den Fragment Shader weitergereicht und in dessen Programmteil verarbeitet. Die Definitionen am Beginn des Shaders sind aus Kompatibilitätsgründen notwendig.

Hier jetzt der gesamte Fragment Shader.

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

varying vec4 vertColor;

void main() {
// Outputting pixel color (interpolated across triangle)
gl_FragColor = vertColor;
}

Diese beiden Shader (vertex.glsl und fragment.glsl) müssen nun im Unterordner /data im Sketchordner platziert werden. Der Einsatz dieser Shader wird in Processing mit der PShader Klasse kontrolliert. Mit loadShader(„fragment.glsl“, „vertex.glsl“) werden die Shader geladen. Es kann auch nur ein Fragment Shader als Parameter übergeben werden. Ist der Shader erst einmal geladen, wird er mit shader(basic) aktiviert und mit resetShader() deaktiviert.

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* MOUSE LEFT          : Shader ein/aus
*/

PShader basic;

boolean enabled = false;

void setup() {
size(640, 360, P3D);
basic = loadShader("fragment.glsl", "vertex.glsl");
}

void draw() {
background(0);
fill(255);
if (enabled == true) {
shader(basic);
println("shader");
}

translate(width/2, height/2, -200);
sphere(200);
//println(frameRate);
}

void mousePressed() {
enabled = !enabled;
if (!enabled == true) {
resetShader();
}
}

Um zu beweisen, dass die Shader auch aktiv sind, kann man die folgende Zeile in das Programm des Fragment Shaders statt der Originalzeile einfügen:

gl_FragColor = vertColor*vec4(0.1,0.6,0.8,1.0);

Speichert man dann und lässt das Programm laufen, ändert sich die Farbe des Objekts.

Rotations III


Eine weitere Variation der Rotations.

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

int rand=(int) random(0, 50);

void setup() {
size(1280, 1280);
background(0);
stroke(50, 100, 200, 5);
frameRate(50);
colorMode(HSB);
}

void draw() {
if (frameCount<=500) {
translate(width/2, height/2);
strokeWeight(frameCount/2);
float winkel=radians(frameCount*80);
rotate(winkel*rand);
stroke(map(radians(winkel)%(2*PI), 0, 2*PI, 0, 255), 255, 255, 5);
translate(cos(winkel)*100, sin(winkel)*100);
line(100, 0, 300, 0);
if (frameCount%50 == 0) saveFrame("rotations-"+rand+"-#####.png");
}
}


Rotations II


Processing 2.0

Beispiel: Das Auge

Das Programm von Rotations I wird nun um eine zufällig generierte Farbpalette erweitert. Diese Zufallsfarben weisen eine Gauss’sche Normalverteilung auf. Siehe Zufall

Außerdem ändert sich während der Laufzeit die Strichlänge, Farbe und Position.

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

Random generator;
int str=(int) random(7, 7);

int col[] = new int [str];
int baseCol= (int) random(0, 255);

void setup() {
size(2700, 2700);
background(0);
frameRate(120);
colorMode(HSB);
generator = new Random();
for (int i =0; i<col.length; i++) {
float gauss = (float) generator.nextGaussian();
col[i] = round( 40 * gauss + baseCol);
}
println(col);
}

void draw() {
if (frameCount<30000) {
translate(width/2, height/2);
//strokeWeight(frameCount/128);
strokeWeight(random(1, width/40));
rotate(radians(frameCount/2*(360/str)));
// println(str);
stroke(col[frameCount%str]-frameCount/200, 255, 255, 1);
line(width/4.5-frameCount/50, 0, random(width/8, width*4/10)-frameCount/50, 0);
if (frameCount%3000==0) saveFrame("rotate"+str+baseCol+"#####.png");
}
}

Rotations I


Processing 2.0

Ausgehend von einem Beispiel aus dem Artikel Transformationen hat mich interessiert, was mit einfachen Rotationen alles möglich ist.

Beispiel: Erst mal einfärbig und mit nur einer Rotation

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

void setup() {
size(900,900);
background(0);
stroke(50,100,200,5);
frameRate(25);
}

void draw() {
if(frameCount<500) {
translate(width/2,height/2);
strokeWeight(frameCount/4);
rotate(radians(frameCount*20));
line(50,0,350,0);
}
if(frameCount==500) saveFrame("rotate.png");
}

Beispiel: Etwas mehr Farbe

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

void setup() {
size(900,900);
background(0);
stroke(50,100,200,5);
frameRate(25);
colorMode(HSB);
}

void draw() {
if(frameCount<500) {
translate(width/2,height/2);
strokeWeight(frameCount/4);
println(radians(frameCount*20));
rotate(radians(frameCount*20));
stroke(map(radians(frameCount*20)%(2*PI),0,2*PI,0,255),255,255,5);
line(50,0,350,0);
}
if(frameCount==500) saveFrame("rotate.png");
}

Beispiel: Variation mit etwas schmäleren Strichen und mehr Wiederholungen

/** Copyright 2012 Thomas Koberger
*/

// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

void setup() {
size(900, 900);
background(255);
stroke(50, 100, 200, 5);
frameRate(120);
colorMode(HSB);
}

void draw() {
if (frameCount<50000) {
translate(width/2, height/2);
strokeWeight(frameCount/64);
rotate(radians(frameCount*20));
stroke(map(radians(frameCount*20)%(2*PI), 0, 2*PI, 0, 255), 255, 255, 5);
line(200, 0, 220, 0);
if (frameCount%500 == 0) saveFrame("background-######.png");
}
}

The Hunt


Processing 2.0

Für dieses Projekt habe ich den Flocking Algorithmus von Daniel Shiffman um eine Dimension erweitert um damit eine 3 dimensionale Szene erzeugen zu können. Darin gibt es Schwarmfische und Haie.

  • Die Schwarmfische bewegen sich nach den Regeln aus dem Flocking Artikel und 2 zusätzlichen Regeln. Sie bewegen sich immer zum Ursprung des Koordinatensystems und weichen den Haien aus.
  • Die Haie bewegen sich nach einem reduzierten Satz von Regeln. Sie halten einen bestimmten Abstand zueinander und sie bewegen sich in Richtung der durchschnittlichen Position der Schwarmfische in ihrer Nähe.

Die 3D Modelle der Fische stammen von http://www.turbosquid.com/ . Ich habe dann die Szene noch entsprechend ausgeleuchtet (–> siehe Artikel 3D Oberflächen und Licht).

Download Source

Flocking


Processing 2.0

Mit Flocking Algorithmen lassen sich sehr imposante Graphiken und Animationen erstellen. Flocking Algorithmen versuchen das Verhalten von Tieren in Schwärmen nachzubilden.

Dafür erstellt man eine Klasse, die ein Individuum repräsentiert, das sich in seinen Bewegungen an den, in der Nähe befindlichen Individuen orientiert. Dabei folgt jedes Individuum einigen einfachen Regeln.

In einer einfachen Implementierung von Daniel Shiffman auf http://processing.org/learning/topics/flocking.html sind das folgende:

  1. Separation (Aufteilung); wähle eine Richtung, die einer Häufung von Individuen entgegenwirkt.
    2 Individuen dürfen sich nicht am selben Ort befinden. Deshalb müssen sie einander ausweichen, wenn sie einen bestimmten Abstand unterschreiten.
  2. Alignment (Angleichung): wähle eine Richtung, die der mittleren Richtung der benachbarten Individuen entspricht.
    Jedes Individuum richtet seine Bewegungsrichtung nach den Nachbarn aus. D.h. es wird eine durchschnittl. Bewegungsrichtung der, innerhalb eines bestimmten Abstands befindlichen Individuen errechnet. Die eigenen Bewegungsrichtung passt sich dieser an.
  3. Cohesion (Zusammenhalt): wähle eine Richtung, die der mittleren Position der benachbarten Individuen entspricht.
    Alle Individuen immerhalb eines bestimmten Bereichs bewegen sich Richtung dessen Zentrum.

Diese 3 Regeln führen zu einem Verhalten, wie im untenstehenden Sketch von Daniel Shiffman. Es können bei Bedarf natürlich noch weitere Regeln definiert werden.

Starte Applet

Flock flock;

void setup() {
  size(640, 360);
  flock = new Flock();
  // Add an initial set of boids into the system
  for (int i = 0; i < 150; i++) {
    flock.addBoid(new Boid(width/2,height/2));
  }
}

void draw() {
  background(50);
  flock.run();
}

// Add a new boid into the System
void mousePressed() {
  flock.addBoid(new Boid(mouseX,mouseY));
}

// The Boid class

class Boid {

  PVector location;
  PVector velocity;
  PVector acceleration;
  float r;
  float maxforce;    // Maximum steering force
  float maxspeed;    // Maximum speed

  Boid(float x, float y) {
    acceleration = new PVector(0,0);
    velocity = new PVector(random(-1,1),random(-1,1));
    location = new PVector(x,y);
    r = 2.0;
    maxspeed = 2;
    maxforce = 0.03;
  }

  void run(ArrayList<Boid> boids) {
    flock(boids);
    update();
    borders();
    render();
  }

  void applyForce(PVector force) {
    // We could add mass here if we want A = F / M
    acceleration.add(force);
  }

  // We accumulate a new acceleration each time based on three rules
  void flock(ArrayList<Boid> boids) {
    PVector sep = separate(boids);   // Separation
    PVector ali = align(boids);      // Alignment
    PVector coh = cohesion(boids);   // Cohesion
    // Arbitrarily weight these forces
    sep.mult(1.5);
    ali.mult(1.0);
    coh.mult(1.0);
    // Add the force vectors to acceleration
    applyForce(sep);
    applyForce(ali);
    applyForce(coh);
  }

  // Method to update location
  void update() {
    // Update velocity
    velocity.add(acceleration);
    // Limit speed
    velocity.limit(maxspeed);
    location.add(velocity);
    // Reset accelertion to 0 each cycle
    acceleration.mult(0);
  }

  // A method that calculates and applies a steering force towards a target
  // STEER = DESIRED MINUS VELOCITY
  PVector seek(PVector target) {
    PVector desired = PVector.sub(target,location);  // A vector pointing from the location to the target
    // Normalize desired and scale to maximum speed
    desired.normalize();
    desired.mult(maxspeed);
    // Steering = Desired minus Velocity
    PVector steer = PVector.sub(desired,velocity);
    steer.limit(maxforce);  // Limit to maximum steering force
    return steer;
  }

  void render() {
    // Draw a triangle rotated in the direction of velocity
    float theta = velocity.heading2D() + radians(90);
    fill(200,100);
    stroke(255);
    pushMatrix();
    translate(location.x,location.y);
    rotate(theta);
    beginShape(TRIANGLES);
    vertex(0, -r*2);
    vertex(-r, r*2);
    vertex(r, r*2);
    endShape();
    popMatrix();
  }

  // Wraparound
  void borders() {
    if (location.x < -r) location.x = width+r;
    if (location.y < -r) location.y = height+r;
    if (location.x > width+r) location.x = -r;
    if (location.y > height+r) location.y = -r;
  }

  // Separation
  // Method checks for nearby boids and steers away
  PVector separate (ArrayList<Boid> boids) {
    float desiredseparation = 25.0f;
    PVector steer = new PVector(0,0,0);
    int count = 0;
    // For every boid in the system, check if it's too close
    for (Boid other : boids) {
      float d = PVector.dist(location,other.location);
      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      if ((d > 0) && (d < desiredseparation)) {
        // Calculate vector pointing away from neighbor
        PVector diff = PVector.sub(location,other.location);
        diff.normalize();
        diff.div(d);        // Weight by distance
        steer.add(diff);
        count++;            // Keep track of how many
      }
    }
    // Average -- divide by how many
    if (count > 0) {
      steer.div((float)count);
    }

    // As long as the vector is greater than 0
    if (steer.mag() > 0) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.normalize();
      steer.mult(maxspeed);
      steer.sub(velocity);
      steer.limit(maxforce);
    }
    return steer;
  }

  // Alignment
  // For every nearby boid in the system, calculate the average velocity
  PVector align (ArrayList<Boid> boids) {
    float neighbordist = 50;
    PVector sum = new PVector(0,0);
    int count = 0;
    for (Boid other : boids) {
      float d = PVector.dist(location,other.location);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(other.velocity);
        count++;
      }
    }
    if (count > 0) {
      sum.div((float)count);
      sum.normalize();
      sum.mult(maxspeed);
      PVector steer = PVector.sub(sum,velocity);
      steer.limit(maxforce);
      return steer;
    } else {
      return new PVector(0,0);
    }
  }

  // Cohesion
  // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
  PVector cohesion (ArrayList<Boid> boids) {
    float neighbordist = 50;
    PVector sum = new PVector(0,0);   // Start with empty vector to accumulate all locations
    int count = 0;
    for (Boid other : boids) {
      float d = PVector.dist(location,other.location);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(other.location); // Add location
        count++;
      }
    }
    if (count > 0) {
      sum.div(count);
      return seek(sum);  // Steer towards the location
    } else {
      return new PVector(0,0);
    }
  }
}

// The Flock (a list of Boid objects)

class Flock {
  ArrayList<Boid> boids; // An ArrayList for all the boids

  Flock() {
    boids = new ArrayList<Boid>(); // Initialize the ArrayList
  }

  void run() {
    for (Boid b : boids) {
      b.run(boids);  // Passing the entire list of boids to each boid individually
    }
  }

  void addBoid(Boid b) {
    boids.add(b);
  }
}

Erläuterungen zur Boid Klasse:

  • Die Vektoren location, velocity und acceleration kontrollieren Position, Geschwindigkeit und Beschleunigung.
  • In jedem draw() Durchlauf wird die acceleration (Beschleunigung) basierend auf der 3 Kräfte Separation, Alignment und Cohesion neu berechnet.
  • Separation: Es werden alle Boids durchlaufen und ein Distanzvektor errechnet. Weiters wird aus allen Distanzvektoren, die größer 0 und kleiner als ein desiredseparation genannter Wert sind, ein Durchsschnittdistanzsvektor namens steer errechnet. Von diesem wird dann noch der aktuelle Geschwindigkeitsvektor abgezogen.
  • Alignment: Auch hier werden wieder alle Boids durchlaufen. Dabei wird ein durchschnittlicher  Geschwindigkeitsvektor aller Boids errechnet, die sich innerhalb einer bestimmten Distanz (neighbordist) befinden. Auch von diesem wird dann wieder der Boid eigene Geschwindigkeitsvektor abgezogen.
  • Cohesion: Bei der Cohesion wird ein durchschnittlicher Positionsvektor aller, sich innerhalb einer best. Distanz befindenen Boids errechnet. In diese Richtung wird dann gesteuert.
  • In der flock-Methode der Boid Klasse werden dann alle Kräfte gewichtet und zur aktuellen Beschleunigung aufsummiert.
  • In update wird dann die aktuelle Beschleunigung zur aktuellen Geschwindigkeit hinzugerechnet. Dann wird noch die Geschwindigkeit auf die Maximalgeschwindigkeit begrenzt und die Beschleunigung auf 0 gesetzt.

3D Oberflächen und Licht


Processing 2.0

Betrachtet man ein reales Objekt, z.B. einen einfärbigen Würfel, so stellt man fest, dass alle Seiten unterschiedliche Farbschattierungen aufweisen. Das liegt am Spiel zwischen Licht und Schatten. Dieses Spiel gilt es so gut, wie möglich zu immitieren, will man realitätsnahe 3D Darstellungen generieren.

Eins vorweg: Processing ist dafür nur bedingt gut geeignet. Nichts desto trotz werden wir uns hier mit den Grundlagen von Lichtern und Oberflächen beschäftigen.

Um die Effekte der Kombination aus verschieden Lichtquellen und Oberflächen zu demonstrieren habe ich das Programm lightsTesterDemo geschrieben. Die lightsTester Klasse kann auch in anderen Projekten verwendet werden.

LightsTester funktioniert leider derzeit nicht online, muss also heruntergeladen werden.

Lichtquellen

Generell müssen Lichtquellen in jedem draw() Durchlauf, und nicht in setup() aufgerufen werden.

lights();

Die einfachste Möglichkeit in Processing mit Licht zu arbeiten bietet die Funktion lights(). Sie generiert ein Kombination aus verschieden Lichtquellen mit default Werten.

ambientLight(rot, grün, blau);

Dient als Hintergrundbeleuchtung und wird in der Regel in Kombination mit anderen Lichtquellen eingesetzt. Es beleuchtet Objekte von allen Seiten gleich stark.

directionalLight(rot, grün, blau, x, y, z);

Erzeut paralleles Licht aus einer bestimmten Richtung. Trifft es auf eine Oberfläche, so entsteht Steulicht, welches wiederum auf andere Objekte wirkt. Es hat eine bestimmte Farbe und eine Richtung.

pointLight(rot, grün, blau, x, y, z); Funktioniert derzeit nicht (Processing 2.0b3)!!!

Generiert eine punktförmige Lichtquelle. Farbe und Position müssen als Parameter angegeben werden.

spotLight(rot, grün, blau, x, y, z, dx, dy, dz, winkel, fokus);

Eine Lichtquelle, die wie ein Scheinwerfer funktioniert. Neben der Farbe und der Position müssen hier noch die Abstrahlrichtung (winkel) und die Konzentration der Lichtstärke im Zentrum (fokus) angegeben werden. In lights können die Werte für winkel und fokus über den alpha Kanal des entsprechenden Color Pickers gesteuert werden.

lightSpecular(rot, grün, blau);

Legt die Farbe der Glanzlichter fest. Hat nur Einfluss auf die jenigen Elemente, die im Code danach erstellt werden. D.h. die Glanzlichter müssen vor den anderen Lichtern gesetzt werden.

Oberflächen

Die Effekte von Lichtquellen hängen von der Oberflächenbeschaffenheit der beleuchteten Objekte ab. Deshalb gibt es in Processing eine Reihe von Oberflächeneigenschaften, die in Kombination mit den verwendeten Lichtquellen benutzt werden können.

ambient(rot, grün, blau);

Legt den Reflexionsgrad der Oberfläche in Kombination mit ambientLight() fest. Z.B. ambient(255,128,0) reflektiert rot zur Gänze, grün zur Hälfte und blau gar nicht.

emissive(rot, grün, blau);

Objekte können selbst Licht emittieren. D.h. sie leuchten selbst in der hier definierten Farbe. Alledings nur dann, wenn irgendeine Lichtquelle vorhanden ist.

specular(rot, grün, blau);

Damit definiert man die Farbe der Glanzlichter.

shininess(float);

Legt die Glanzintensität einer Oberfläche fest.

lightFalloff(constant, linear, quadratic);

Dieser Parameter legt den Lichtabfall über die Entfernung zur Lichtquelle mit Werten zwischen 0 und 1 fest. Er wirkt allerdings nur auf point spot und ambient Lights.

Beispiel: lights

Mit dem Licht und Oberflächen Tool kannst du verschiedenste Licht und Oberflächeneffekte testen. Es funktioniert derzeit eider nur im Java Modus und muss deshalb als .zip Datei heruntergeladen werden.


// Bewege die Maus, um die Position der Lichtquelle zu ändern

//GUI Library ControlP5
import controlP5.*;
ControlP5 controlP5;
ColorPicker cplamb, cpldir, cplpoi, cpambient, cpfill, cplspt, cplspc, cpemissive, cpspecular;
CheckBox checkbox;

public int R, G, B=128;
public int shininess, falloff=0;
int mode = 0;
int [] heights = new int [25];
boolean lightsOn, ambientOn, directionalOn, pointOn, spotOn, specOn =false;

void setup() {
size(800, 800, P3D);
smooth(1);
createControlP5();

for (int i=0;i< heights.length;i++) {
heights[i]=(int) random(100, 255);
}
}

void draw() {
background(0);

// Kontrolle über die Lichter
controlP5.draw();

ambient(cpambient.getColorValue());
emissive(cpemissive.getColorValue());
specular(cpspecular.getColorValue());
shininess(shininess);
lightFalloff(0, 0, falloff);

if (specOn) {
lightSpecular(red(cplspc.getColorValue()), green(cplspc.getColorValue()), blue(cplspc.getColorValue()));
}
if (lightsOn) lights();
if (ambientOn) {
ambientLight(red(cplamb.getColorValue()), green(cplamb.getColorValue()), blue(cplamb.getColorValue()),
map(mouseX, 0, width, 1, -1), -1, map(mouseY, 0, height, 1, -1));
}
if (directionalOn) directionalLight(red(cpldir.getColorValue()), green(cpldir.getColorValue()), blue(cpldir.getColorValue()),
map(mouseX, 0, width, 1, -1), 1, map(mouseY, height, 0, -1, 1));

if (pointOn) pointLight(red(cplpoi.getColorValue()), green(cplpoi.getColorValue()), blue(cplpoi.getColorValue()),
0, 100, 0);

if (spotOn) {
spotLight(red(cplspt.getColorValue()), green(cplspt.getColorValue()), blue(cplspt.getColorValue()),
map(mouseX, 0, width, 0, width), -1000, map(mouseY, 0, height, 0, height*2),
0, 1, -1, map(alpha(cplspt.getColorValue()),0,255,0,PI), map(alpha(cplspt.getColorValue()),0,255,255,0));
}

fill(cpfill.getColorValue());
drawBlocks();
}

void drawBlocks() {

// Betrachtungsperspektive ändern
translate(100, height/2, -500);
rotateX(PI/3);

//Zeichne Blocks
for (int i=0;i<5;i++) {
for (int j=0;j<5;j++) {
pushMatrix();
translate(120*i, 120*j, heights[i*5+j]/2);
//box(50, 50, heights[i*5+j]*1.2);
sphere(heights[i*5+j]/2);
popMatrix();
}
}
}

void createControlP5() {

// Steuerelemente erstellen
controlP5 = new ControlP5(this);

//Checkboxes erstellen
checkbox = controlP5.addCheckBox("lcheckBox", 10, 10);
checkbox.setColorForeground(color(120));
checkbox.setColorActive(color(255));
checkbox.setColorLabel(color(255));
checkbox.setItemsPerRow(8);
checkbox.setSpacingColumn(119);
checkbox.addItem("lights()", 0);
checkbox.addItem("ambientLight()", 0);
checkbox.addItem("directionalLight()", 0);
checkbox.addItem("pointLight()", 0);
checkbox.addItem("spotLight()", 0);
checkbox.addItem("lightSpecular()", 0);

//Colorpicker erstellen
ControlGroup fillColor = controlP5.addGroup("fill Color", 10, 40);
fillColor.activateEvent(false);
fillColor.close();
cpfill = controlP5.addColorPicker("fillpicker", 0, 0, 128, 0);
cpfill.setGroup(fillColor);

ControlGroup ambLightCol = controlP5.addGroup("ambientLight Color", 140, 40);
ambLightCol.activateEvent(false);
ambLightCol.close();
cplamb = controlP5.addColorPicker("amblpicker", 0, 0, 128, 0);
cplamb.setGroup(ambLightCol);
cplamb.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup dirLightCol = controlP5.addGroup("directionalLight Color", 270, 40);
dirLightCol.activateEvent(false);
dirLightCol.close();
cpldir = controlP5.addColorPicker("dirlpicker", 0, 0, 128, 0);
cpldir.setGroup(dirLightCol);
cpldir.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup poiLightCol = controlP5.addGroup("pointLight Color", 400, 40);
poiLightCol.activateEvent(false);
poiLightCol.close();
cplpoi = controlP5.addColorPicker("poilpicker", 0, 0, 128, 0);
cplpoi.setGroup(poiLightCol);
cplpoi.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup sptLightCol = controlP5.addGroup("spotLight Color", 530, 40);
sptLightCol.activateEvent(false);
sptLightCol.close();
cplspt = controlP5.addColorPicker("spotlpicker", 0, 0, 128, 0);
cplspt.setGroup(sptLightCol);
cplspt.setArrayValue(new float[] {
100, 100, 100, 100
}
);

ControlGroup lightSpecCol = controlP5.addGroup("lightSpec Color", 660, 40);
lightSpecCol.activateEvent(false);
lightSpecCol.close();
cplspc = controlP5.addColorPicker("speclpicker", 0, 0, 128, 0);
cplspc.setGroup(lightSpecCol);
cplspc.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup surfaceCol = controlP5.addGroup("ambient Color", 10, 120);
surfaceCol.activateEvent(false);
surfaceCol.close();
cpambient = controlP5.addColorPicker("ambpicker", 0, 0, 128, 0);
cpambient.setGroup(surfaceCol);
cpambient.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup emissiveCol = controlP5.addGroup("emissive Color", 140, 120);
emissiveCol.activateEvent(false);
emissiveCol.close();
cpemissive = controlP5.addColorPicker("emipicker", 0, 0, 128, 0);
cpemissive.setGroup(emissiveCol);
cpemissive.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup specularCol = controlP5.addGroup("specular Color", 270, 120);
specularCol.activateEvent(false);
specularCol.close();
cpspecular = controlP5.addColorPicker("specpicker", 0, 0, 128, 0);
cpspecular.setGroup(specularCol);
cpspecular.setArrayValue(new float[] {
100, 100, 100, 255
}
);

//Slider erstellen
controlP5.begin(10, 150);
controlP5.addSlider("shininess", 0, 255, 0, 400, 110, 128, 10);
controlP5.addSlider("falloff", 0, 1, 0, 595, 110, 128, 10);
controlP5.end();
}

// Aktion Radio Button
void controlEvent(ControlEvent theEvent) {
if (theEvent.isGroup()) {
//print("got an event from "+theEvent.group().name()+"\t");
for (int i=0;i<theEvent.group().arrayValue().length;i++) {
int n = (int)theEvent.group().arrayValue()[i];
print(n);
if (i==0) {
if (n==1)  lightsOn=true;
else lightsOn=false;
}
if (i==1) {
if (n==1)  ambientOn=true;
else ambientOn=false;
}
if (i==2) {
if (n==1)  directionalOn=true;
else directionalOn=false;
}
if (i==3) {
if (n==1)  pointOn=true;
else pointOn=false;
}
if (i==4) {
if (n==1)  spotOn=true;
else spotOn=false;
}
if (i==5) {
if (n==1)  specOn=true;
else specOn=false;
}
}
}
}

OpenCV Gesichtserkennung


Processing 2.0

Die Gesichtserkennung ist eine sehr gefragte Funktion von OpenCV. Sie beruht auf einem Verfahren, das mit Beschreibungsdateien arbeitet. Diese Dateien werden aus vielen (hunderten oder tausenden von) Bildern errechnet und als .xml Dateien abgespeichert. OpenCV bringt schon einige dieser Beschreibungsdateien mit. Vereinzelt findet man sie auch im Internet: Beschreibungsdateien.

Hier die offizielle Dokumentation.

In OpenCV 2.3 bereits inkludierte Dateien  (in Ubuntu 12.04 zu finden in: /usr/share/opencv/haarcascades/):

  • haarcascade_eye_tree_eyeglasses.xml
  • haarcascade_frontalface_alt.xml
  • haarcascade_lowerbody.xml
  • haarcascade_mcs_mouth.xml
  • haarcascade_profileface.xml
  • haarcascade_eye.xml
  • haarcascade_frontalface_default.xml
  • haarcascade_mcs_eyepair_big.xml
  • haarcascade_mcs_nose.xml
  • haarcascade_righteye_2splits.xml
  • haarcascade_frontalface_alt2.xml
  • haarcascade_fullbody.xml
  • haarcascade_mcs_eyepair_small.xml
  • haarcascade_mcs_righteye.xml
  • haarcascade_upperbody.xml
  • haarcascade_frontalface_alt_tree.xml
  • haarcascade_lefteye_2splits.xml
  • haarcascade_mcs_lefteye.xml
  • haarcascade_mcs_upperbody.xml

Eine gut Einführung, wie das ganze funktioniert hier auf englisch von Kyle Mcdonald.

1. Beschreibungsdatei laden:

Eine dieser Beschreibungsdateien wird im setup() geladen:

opencv.cascade(„/usr/share/opencv/haarcascades/“,“haarcascade_frontalface_alt.xml“);

2. Gesichter oder ähnliches erkennen:

In draw() wird dann die detect() – Funktion aufgerufen, um Gesichter (o. ä.) im Bild zu erkennen.

faceRect = opencv.detect(true);

3. Rechteck um Gesichter zeichnen:

Mit einer weiteren Zeile kann man sich erkannte Gesichter markieren lassen. Dafür muss allerdings vorher noch eine Library (jawa.awt) importieren und ein Rectangle – Array erstellen:

import java.awt.*;
Rectangle[] faceRect;

und in draw():

opencv.drawRectDetect(true);

Position und Größe auslesen:

Will man noch die Position und die Größe der erkannten Objekte auslesen, dann kann man das wie folgt aus dem Rectangle Array machen.

faceRect[index].x …x-Position
faceRect[index].y …y-Position
faceRect[index].width …Breite
faceRect[index].height …Höhe

Änderung der Indizes verhindern:

Wenn man sich in einer Szene mit mehreren Gesichtern die Indizes anzeigen lässt, fällt auf, dass diese nicht konstant dem gleichen Gesicht zugeordnet werden, sondern sie in einer Szene oft mehrmals wechseln und somit eine Zuordnung zu einem Gesicht nicht möglich ist. Daniel Shiffman hat hierzu einen Artikel auf seinem Weblog veröffentlicht, den ich für unsere Library passend adptiert habe.

Eine einfache Funktion, um Schnitte und Kameraschwenks zu erkennen, und dann alle gespeicherten Gesichter zu löschen, wurde von mir ergänzt.

Beispiel: Gesichtserkennung mit Webcam


// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3 und
// Which Face Is Which
// Daniel Shiffman
// April 25, 2011
// http://www.shiffman.net

/**
* KEYS
* Space                   : remember Frame
* s                       : save png
*/

import processing.video.*;
import monclubelec.javacvPro.*;
import java.awt.*; // pour classes Point , Rectangle..
import java.util.*;
// Für WebCam:
Capture cam1;
//GSMovie cam1;

OpenCV opencv; // deklariert ein OpenCV Objekt
Rectangle[] faceRect;

// A list of my Face objects
ArrayList<Face> faceList;

// how many have I found over all time
int faceCount = 0;

float  sum=0;
int scl=1;
int frame=0;
boolean delFaces;

void setup() {

// Für WebCam:
cam1= new Capture(this, 1280, 720);
//cam1 = new GSMovie(this, "maus.mov");

// für WebCam auskommentieren:
//cam1.play();
cam1.start();

// initialisiert OpenCV ---
opencv = new OpenCV(this);

//Vorsicht: bei der Arbeit mit einer Datei muss die Größe genau passen!!!
//opencv.allocate(640, 360);
opencv.allocate(cam1.width, cam1.height);

// Für WebCam:
// opencv.allocate(cam1.getSourceWidth(), cam1.getSourceHeight()); // initialisiert die Buffer von OpenCV
size (opencv.width(), opencv.height());

// Laden Beschreibungsdatei
opencv.cascade("/usr/share/opencv/haarcascades/", "haarcascade_frontalface_alt_tree.xml");

// Liste mit den Gesichtsobjekten
faceList = new ArrayList<Face>();
}

void draw() {

// Dateien und die WebCam brauchen etwas Zeit zum Laden
if (cam1.available()) {

// Einzelne Frames werden gelesen
cam1.read();
opencv.copy(cam1);

// Schnitt oder Kameraschwenk erkennen
println(abs(opencv.sum()-sum)/1000000);
if (abs(opencv.sum()-sum)/1000000 >7) delFaces=true;
else delFaces=false;
sum=opencv.sum();

// Erkennen
faceRect = opencv.detect(false);

image(opencv.getBuffer(), 0, 0);

//Rechteck zeichnen
//opencv.drawRectDetect(true);

// Code ab hier von Daniel Shiffman
// SCENARIO 1: faceList is empty
if (faceList.isEmpty()) {
// Just make a Face object for every face Rectangle
for (int i = 0; i < faceRect.length; i++) {
faceList.add(new Face(faceRect[i].x, faceRect[i].y, faceRect[i].width, faceRect[i].height));
}
// SCENARIO 2: We have fewer Face objects than face Rectangles found from OPENCV
}
else if (faceList.size() <= faceRect.length) {
boolean[] used = new boolean[faceRect.length];
// Match existing Face objects with a Rectangle
for (Face f : faceList) {
// Find faces[index] that is closest to face f
// set used[index] to true so that it can't be used twice
float record = 50000;
int index = -1;
for (int i = 0; i < faceRect.length; i++) {
float d = dist(faceRect[i].x, faceRect[i].y, f.r.x, f.r.y);
if (d < record && !used[i]) {
record = d;
index = i;
}
}
// Update Face object location
used[index] = true;
f.update(faceRect[index]);
}
// Add any unused faces
for (int i = 0; i < faceRect.length; i++) {
if (!used[i]) {
faceList.add(new Face(faceRect[i].x, faceRect[i].y, faceRect[i].width, faceRect[i].height));
}
}
// SCENARIO 3: We have more Face objects than face Rectangles found
}
else {
// All Face objects start out as available
for (Face f : faceList) {
f.available = true;
}
// Match Rectangle with a Face object
for (int i = 0; i < faceRect.length; i++) {
// Find face object closest to faces[i] Rectangle
// set available to false
float record = 50000;
int index = -1;
for (int j = 0; j < faceList.size(); j++) {
Face f = faceList.get(j);
float d = dist(faceRect[i].x, faceRect[i].y, f.r.x, f.r.y);
if (d < record && f.available) {
record = d;
index = j;
}
}
// Update Face object location
Face f = faceList.get(index);
f.available = false;
f.update(faceRect[i]);
}
// Start to kill any left over Face objects
for (Face f : faceList) {
if (f.available) {
f.countDown();
if (f.dead()) {
f.delete = true;
}
}
}
}

// Delete any that should be deleted
for (int i = faceList.size()-1; i >= 0; i--) {
Face f = faceList.get(i);
// Bei einem Schnitt werden alle Gesichter sofort gelöscht
if (f.delete || delFaces) {
faceList.remove(i);
}
}

// Draw all the faces
for (int i = 0; i < faceRect.length; i++) {
noFill();
stroke(255, 0, 0);
rect(faceRect[i].x*scl, faceRect[i].y*scl, faceRect[i].width*scl, faceRect[i].height*scl);
}

for (Face f : faceList) {
f.display();
}
}
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");

// for Movie
//frame+=500;
//cam1.jump(frame);
//cam1.play();
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

// Which Face Is Which
// Daniel Shiffman
// April 25, 2011
// http://www.shiffman.net

class Face {

// A Rectangle
Rectangle r;

// Am I available to be matched?
boolean available;

// Should I be deleted?
boolean delete;

// How long should I live if I have disappeared?
int timer = 63;

// Assign a number to each face
int id;

// Make me
Face(int x, int y, int w, int h) {
r = new Rectangle(x, y, w, h);
available = true;
delete = false;
id = faceCount;
faceCount++;
}

// Show me
void display() {
fill(0, 0, 255, timer*2);
stroke(0, 0, 255);
rect(r.x*scl, r.y*scl, r.width*scl, r.height*scl);
fill(255, timer*2);
text(""+id, r.x*scl+10, r.y*scl+30);
}

// Give me a new location / size
// Oooh, it would be nice to lerp here!
void update(Rectangle newR) {
r = (Rectangle) newR.clone();
}

// Count me down, I am gone
void countDown() {
timer--;
}

// I am deed, delete me
boolean dead() {
if (timer < 0) return true;
return false;
}
}

OpenCV mit Processing – Basics_2


Processing 2.0

Merkmale isolieren – Konturen finden

Die Funktion threshold() dient der Isolierung der für die Mustererkennung relevanten Konturen. Sie blendet Pixel aus, deren Helligkeitswert einen bestimmten Schwellenwert unterschreitet, oder übersteigt.

Beispiel: Threshold

// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* changing strokeweight and strokecaps on diagonals in a grid
*
* MOUSE
* left right Threshold 0-1
*
* KEYS

* n                   : show original Image

* 1                   : apply threshold "BINARY"
* 2                   : apply threshold "BINARY_INV"
* 3                   : apply threshold "TRUNK"
* 4                   : apply threshold "TOCERO"
* 5                   : apply threshold "TOCERO_INV"

* s                   : save png
*/

import monclubelec.javacvPro.*;
import java.util.*;

PImage img;

String url="http://kobe.bplaced.net/processing/016_16.JPG";
OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Lädt die Bilddatei
img=loadImage(url, "jpg");

// initialisiert OpenCV ---
opencv = new OpenCV(this);
opencv.allocate(img.width, img.height); // initialisiert die Buffer von OpenCV

opencv.copy(img); // lädt die PImage Datei in den OpenCV Buffer

size (opencv.width(), opencv.height());

// gibt das Bild aus
image(opencv.getBuffer(), 0, 0);

noLoop();
}

void draw() {

noLoop();
}

void keyReleased() {

// Zum speichern des Bildes
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");

// Originalbild anzeigen
if (key == 'n') {
opencv.copy(img);
image(opencv.getBuffer(), 0, 0);
loop();
}

// Threshold Binary
if (key == '1') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.threshold(map(mouseX,0,width,0,1), "BINARY"); // applique seuillage binaire avec seuil 0.5 sur le buffer princi
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Rot-Kanals
loop();
}

// Threshold Binary Invers
if (key == '2') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.threshold(map(mouseX,0,width,0,1), "BINARY_INV");
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Grün-Kanals
loop();
}

// Threshold Trunk
if (key == '3') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.threshold(map(mouseX,0,width,0,1), "TRUNK");
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Blau-Kanals
loop();
}

// Threshold Tozero
if (key == '4') {
opencv.threshold(map(mouseX,0,width,0,1), "TOZERO");
image(opencv.getBuffer(), 0, 0);
loop();
}

// Threshold Tozero Invers
if (key == '5') {
opencv.copy(img);
opencv.threshold(map(mouseX,0,width,0,1), "TOZERO_INV");
image(opencv.getBuffer(), 0, 0);
loop();
}
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Beispiel: Konturen finden

Um Merkmale analysieren zu können, ist es vorher notwendig die markanten Unterschiede zu finden. Geht es um Form, ist es wichtig die Konturen von Objekten zu finden. Für diese Aufgaben kommen die vier hier im Beispiel vorgestellten Filter in Frage.

  1. canny(), canny(Grenzwert1, Grenzwert2), canny(Grenzwert1, Grenzwert2, Faltungskern)
    Die Werte für den Grenzwert1 sind meist zwischen 100 und 200 optimal, Grenzwert2 = 2*Grenzwert1.
    Der Standard für den Faltungskern ist 3, das bedeutet es werden 3*3 Pixel analysiert.
  2. scobel(), scobel(Faltungskern, Maßstab)
    Faltungskern – siehe oben, Maßstab wird mit dem Faltungskern multipliziert.
  3. scharr(),  scharr(Maßstab)
  4. scobel2(), scobel2(Koeffizient), scobel2(Faltungskern, Maßstab, Koeffizient)


// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* changing strokeweight and strokecaps on diagonals in a grid
*
* MOUSE
*
*
* KEYS
* 1                   : show canny()
* 2                   : show Scobel()
* 3                   : show ScHARR
* 4                   : show scobel2
* n                   : show original Image

* s                   : save png
*/

import monclubelec.javacvPro.*;
import java.util.*;

PImage img;

String url="http://kobe.bplaced.net/processing/016_16.JPG";
OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Lädt die Bilddatei
img=loadImage(url, "jpg");

// initialisiert OpenCV ---
opencv = new OpenCV(this);
opencv.allocate(img.width, img.height); // initialisiert die Buffer von OpenCV

opencv.copy(img); // lädt die PImage Datei in den OpenCV Buffer

size (opencv.width(), opencv.height());

// gibt das Bild aus
image(opencv.getBuffer(), 0, 0);
noLoop();
}

void draw() {

noLoop();
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");

if (key == '1') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.canny(100,200,3); // Filter canny()
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Rot-Kanals
loop();
}

if (key == '2') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.sobel(3,0.9); // Filter Scobel
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Grün-Kanals
loop();
}

if (key == '3') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.scharr(0.4); // Filter ScHARR
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Blau-Kanals
loop();
}

if (key == '4') {
opencv.copy(img);
opencv.sobel2(3,4,1);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == 'n') {
opencv.copy(img);
image(opencv.getBuffer(), 0, 0);
loop();
}
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Veränderungen zwischen Frames erkennen

… kann man mit der Funktion:

  • sum()         …gibt die Summe der Helligkeitswerte aller Pixel zurück
  • sumRGB() …gibt ein Array mit der Summe der Helligkeitswerte der einzelnen Farbkanäle zurück
  • sumR()     ……gibt die Summe der Helligkeitswerte des R-Kanals zurück
  • sumG()     ……gibt die Summe der Helligkeitswerte des G-Kanals zurück
  • sumB()     ……gibt die Summe der Helligkeitswerte des B-Kanals zurück

Dies ist praktisch, da sich in Videos daraus leicht die ablesen lässt, wie stark sich einzele Frames voneineander unterscheiden.

Obiges Beispiel kann mit folgender Zeile ergänzt werden, um die sum()-Werte auszulesen:

(in draw(), case: ’n‘)

println(opencv.sum());

Mit sum() kann man das Maß der Veränderung zwischen zwei Frames in Zahlen auslesen. Oft braucht man diese Veränderungen auch als Bild. Dafür stellt OpenCV die Funtktion absDiff() zur Verfügung. Sie liest den aktuellen Buffer ein, vergleicht ihn mit dem Inhalt von Memory und speichert die Differenz als Bild-Buffer in Memory2 ab. Zusammen mit threshold(BINARY) kann man damit einen einfärbigen Hintergrund gut von einem Objekt isolieren.

Beispiel: Veränderung vom Frames zeichnen mit absDiff()


// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* KEYS
* Space                   : remember Frame
* s                       : save png
*/
import processing.video.*;
import monclubelec.javacvPro.*;
import java.util.*;

// Für WebCam:
Capture cam1;
//GSMovie cam1;

OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Für WebCam:
cam1= new Capture(this, 640, 360);
//cam1 = new GSMovie(this, "em.mpg");

// für WebCam:
cam1.start();
//cam1.play();

// initialisiert OpenCV ---
opencv = new OpenCV(this);

//Vorsicht: bei der Arbeit mit einer Datei muss die Größe genau passen!!!
opencv.allocate(640, 360);

// Für WebCam:
// opencv.allocate(cam1.width, cam1.height); // initialisiert die Buffer von OpenCV
size (opencv.width(), opencv.height());
frameRate(60);
}

void draw() {

// Dateien und die WebCam brauchen etwas Zeit zum Laden
if (cam1.available()) {

// Einzelne Frames werden gelesen
cam1.read();
opencv.copy(cam1);
image(opencv.getBuffer(), 0, 0);
opencv.absDiff();
image(opencv.getMemory2(), 0, 0);
}
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
}
void keyPressed() {
if (key==' ') {
opencv.remember();  // Schreibt den aktuellen Buffer in Memory
}
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Eine noch einfachere und nicht minder effiziente Art der Hintergrund- Subtraktion bieten der bgsMOG und bgsMOG2 Algotithmus.

Beispiel: Veränderung vom Frames zeichnen mit bgsMOG und bgsMOG2



// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* KEYS
* Space                   : remember Frame
* s                       : save png
*/
import processing.video.*;
import monclubelec.javacvPro.*;
import java.util.*;

// Für WebCam:
Capture cam1;
//Movie cam1;

OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Für WebCam:
cam1= new Capture(this, 640, 360);
//cam1 = new Movie(this, "em.mpg");

// für WebCam:
cam1.start();
//cam1.play();

// initialisiert OpenCV ---
opencv = new OpenCV(this);

//Vorsicht: bei der Arbeit mit einer Datei muss die Größe genau passen!!!
opencv.allocate(640, 360);
opencv.bgsMOGInit(20, 5, 0.5, 10);
//opencv.bgsMOG2Init(1000, 16, false);

// Für WebCam:
//opencv.allocate(cam1.width, cam1.height); // initialisiert die Buffer von OpenCV
size (opencv.width(), opencv.height());
frameRate(60);
}

void draw() {

// Dateien und die WebCam brauchen etwas Zeit zum Laden
if (cam1.available()) {

// Einzelne Frames werden gelesen
cam1.read();
opencv.copy(cam1);
image(opencv.getBuffer(), 0, 0);
opencv.bgsMOGApply(opencv.Buffer, opencv.BufferGray, 0);
//opencv.bgsMOG2Apply(opencv.Buffer, opencv.BufferGray, -1);
image(opencv.getBufferGray(), 0, 0);
}
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Ziegenproblem


Das Spiel besteht darin, eine Tür zu erraten, hinter dem ein Geldpreis versteckt ist. Der Preis wird zufällig hinter einer von drei Türen versteckt. Dann darf der Spieler auf ein Feld setzen. Darauf wird ein leeres Feld geöffnet, aber niemals das bereits gewählte. Jetzt hat der Spieler die Wahl, entweder bei seiner ersten Entscheidung zu bleiben, oder auf das andere Feld zu wechseln.

Siehe Wikipedia Ziegenproblem.

Beispiel: Ziegenproblem starte Applet


// 3 Programm Modes
// 1 .. play mode (vote yourself
// 2 .. simulation mode (calculates percentage)

//GUI Library ControlP5
import controlP5.*;
ControlP5 controlP5;
CheckBox checkbox;

PImage car, goat;

int mode = 0;
public int Iterations=10;
int trys, hits;
boolean doorIsOpen =false;
boolean showCar  = false;
boolean youWon=false;
boolean simulate= false;
boolean changeChoice =false;

// Erstelle Array mit dem Türen
Door[] door = new Door[3];

void setup() {
size(600, 400);
smooth();

reset();
trys=0;
rectMode(CENTER);
imageMode(CENTER);
textAlign(CENTER, CENTER);

car=loadImage("car.png");
goat=loadImage("goat.png");

fill(0);
stroke(255);
strokeWeight(4);

// Steuerelemente erstellen
controlP5 = new ControlP5(this);
controlP5.begin(10, 10);
controlP5.addButton("Play_Mode");
controlP5.addButton("Simulation_Mode").linebreak();
checkbox = controlP5.addCheckBox("checkBox", 180, 10);
checkbox.setColorForeground(color(120));
checkbox.setColorActive(color(255));
checkbox.setColorLabel(color(128));
checkbox.setItemsPerRow(1);
checkbox.setSpacingColumn(30);
checkbox.setSpacingRow(10);
checkbox.addItem("Change Choice", 0);
controlP5.addSlider("Iterations", 0, 10000, 10, 280, 10, 200, 10);
controlP5.addButton("Reset");
controlP5.addButton("OpenDoor");
controlP5.addButton("ShowCar");
controlP5.end();
}

void draw() {

//Modes wechseln
switch(mode) {
case 0:
drawScreenPlay();
break;
case 1:
drawScreenSimulation();
break;
default:
background(0);
break;
}
}

// Wenn aktiv gespielt wird
void drawScreenPlay() {
drawBackground();
}

// Simulation
void drawScreenSimulation() {
for (int j = 0; j<Iterations; j++) {
if (simulate) {
int selection = (int)random(0, 3);
door[selection].isChosen=true;
OpenDoor();
if (changeChoice) {
for (int i=0;i<3;i++) {
if (door[i].isChosen==false && door[i].isOpened==false) {
door[i].isChosen=true;
}
}
door[selection].isChosen=false;
}

ShowCar();

reset();
drawBackground();
}
}
simulate=false;
}

void drawBackground() {
background(50);
fill(255);
textSize(50);
text("Ziegenproblem", width/2, 100);
textSize(12);
text("Versuche: "+trys+" Treffer: "+hits+" Quote: "+(float)hits/trys*100+"%", width/2, 380);
if (youWon) {
textSize(40);
fill(200, 50, 50);
text("You Won!!!", width/2, 350);
}
for (int i=0;i<3;i++) {
door[i].render();
}
}

void mousePressed() {
if (mouseY > 100 &! showCar) {
for (int i=0;i<3;i++) {
door[i].isHit();
}
}
}

// Aktion Button Play Mode
public void Play_Mode() {
loop();
trys=0;
hits=0;
mode=0;
}

// Aktion Slider Iterations
public void Iterations(int wh) {
Iterations= int(wh);
}

// Aktion Button Simulation Mode
public void Simulation_Mode() {
simulate=true;
trys=0;
hits=0;
mode=1;
}

// Aktion Button Reset
public void Reset() {
reset();
}

// Aktion Radio Button
void controlEvent(ControlEvent theEvent) {
if (theEvent.isGroup()) {
changeChoice=!changeChoice;
}
}

// Aktion Button OpenDoor
public void OpenDoor() {
while (!doorIsOpen) {
int doorToOpen=int(random(0, 3));
if (!door[doorToOpen].isChosen && !door[doorToOpen].hasCar) {
door[doorToOpen].isOpened=true;
//println("DoorOpened: "+ doorToOpen);
doorIsOpen=true;
}
}
}

public void reset() {
doorIsOpen =false;
// create Doors
for (int i=0;i<3;i++) {
door[i] = new Door(i);
}

// verstecke Auto
int hideCarIn=(int) random(0, 3);
door[hideCarIn].hasCar=true;
boolean showCar  = false;
youWon=false;
}

// Aktion Button ShowCar
public void ShowCar() {

if (doorIsOpen) showCar  = true;
int hasCar=5;
int isChosen=5;
for (int i=0;i<3;i++) {
if (door[i].hasCar) hasCar=i;
if (door[i].isChosen) isChosen=i;
}
trys++;
if (hasCar==isChosen) {
hits++;
youWon=true;
}
}

class Door {

int index;
boolean isChosen, hasCar, isOpened;

Door (int index) {
this.index = index;
hasCar=false;
showCar=false;
isChosen=false;
}

void render() {
pushMatrix();
pushStyle();
stroke(255);
fill(0);
translate((index+1)*width/4, 240);

if (isChosen) {
strokeWeight(4);
stroke(0, 200, 50);
} else fill(0);
rect(0, 0, 100, 150);
stroke(255);
fill(0);
textSize(50);
if (hasCar && showCar) {
fill(255, 0, 40, 50);
image(car, 0, 0, 90, 90);
} else if (isOpened) {
fill(255, 0, 40, 50);
image(goat, 0, 0, 90, 90);
}
popStyle();
popMatrix();
}

void isHit() {
if (mouseX>((index+1)*width/4)-50
&& mouseX<((index+1)*width/4)+50
&& mouseY>240-75
&& mouseY<240+75) {
isChosen=true;
}
else  isChosen=false;
}
}

Minim – Audio Analyse


Pegel Spektrum zeichnen

Um ein Pegel Spektrum darzustellen, kann man aus der AudioSource den AudioBuffer mix auslesen. Die Methode toArray() des AudioBuffers mix gibt ein Float Array mit den einzelnen Pegeln im Buffer zurück. Diese Werte kann man dann verwenden, um ein Spektrum zu zeichnen. Ich habe für die Darstellung dann der Einfachheit halber Rechtecke gewählt.

Achtung: Es handelt sich hier um ein Spektrum der Pegel über die Zeit, nicht um ein Frequenzspektrum!

Beispiel: Spektrum zeichnen


// Library importieren
import ddf.minim.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;

int x, y;

// Anzahl der Peaks
int grid=128;

// Abstand zwischen den Peaks
int spacing=1;

// Ausschlagmaximum für Peaks festlegen
float yScale = 2;

void setup() {
size(1024, 400);
smooth();
noStroke();

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
input = minim.loadFile(&quot;http://mp3stream1.apasf.apa.at:8000&quot;);

// Wiedergabe starten
input.play();
}

void draw() {

// für etwas Bewegunsunschärfe
fill(50, 10);
rect(0, 0, width, height);

// Auslesen und speichern des Spektrums
float[] buffer = input.mix.toArray();

// Breite der Rechtecke berechnen
for (int i=1; i <= buffer.length; i+=buffer.length/grid) {
float x = map(i, 0, buffer.length, 0, width);
float y = map(buffer[i-1]*yScale, -1, 1, 0, height) ;
fill (102, 145, 250,100);

// Rechteck zeichnen
rect(x+spacing, height, width/grid-2*spacing, -y);
}
}

void stop()
{
// Player in schließen
input.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Frequenzen darstellen

Der Begriff Spekturm bezieht sich in der Regel auf ein Frequenzspektrum. Dieses kann mit Hilfe der Klasse FFT  und der forward() Methode aus dem AudioBuffer errechnet werden. Dafür werden die Pegel im Buffer einer Fourier-Transformation unterzogen. Als Ergebnis erhält man keine einzelnen Frequenzen, sondern Frequenzbänder.

Umgekehrt kann man auch aus einem Frequenzspektrum ein Zeit-Pegel Spektrum ausgeben. Die Methode dafür heißt inverse();

Der auswertbare Frequenzbereich kann außerdem die halbe Sample-Frequenz nicht übersteigen. Die default Sample-Frequenz bei .mp3 und auf CD ist 44100 Hz, was eine maximale Frequenz von 22050 Hz für die Auwertung ergibt.

Einfaches Frequenzspektrum zeichnen

Um die FFT Klasse nutzen zu können, müssen wir zusätzlich zum letzten Beispiel die minim.analysis – Bibliothek importieren.

import ddf.minim.analysis.*;

Der folgende Code stellt ein Abwandlung des obigen Beispiels dar. Er zeichnet die Frequenzspektren der beiden Kanäle (left und right).

Beispiel: FFT Frequenzspektrum linear

// Library importieren
import ddf.minim.*;
import ddf.minim.analysis.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;
FFT fftR, fftL;

int x, y;

// Anzahl der Peaks
int grid=32;

// Abstand zwischen den Peaks
int spacing=1;

// Ausschlagmaximum für Peaks festlegen
float yScale = 1;

void setup() {
size(1024, 400);
smooth();
noStroke();

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
input = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
input.play();
input.printControls();

// FFT-Instanz für die Spektrumsanalyse der beiden Kanäle
fftR = new FFT (input.bufferSize (), input.sampleRate ());
fftL = new FFT (input.bufferSize (), input.sampleRate ());
}

void draw() {

// für etwas Bewegunsunschärfe
fill(50, 10);
rect(0, 0, width, height);

// forwar FFT Analyse durchführen
fftR.forward(input.right);
fftL.forward(input.left);

// rechter Kanal
fill(255);
text("right Channel", 10, height/2+20);

// Breite der Rechtecke berechnen
for (int i=1; i <= fftR.specSize(); i+=fftR.specSize()/grid) {
float x = map(i, 0, fftR.specSize(), 0, width);
float y = map(fftR.getBand(i)*yScale, 0, 100, 0, height/2) ;
fill (102, 145, 250, 100);

// Rechteck zeichnen
rect(x+spacing, height, width/grid-2*spacing, -y);
}

// linker Kanal
fill(255);
translate(0, -height/2);
text("left Channel", 10, height/2+20);

// Breite der Rechtecke berechnen
for (int i=1; i <= fftL.specSize(); i+=fftL.specSize()/grid) {
float x = map(i, 0, fftL.specSize(), 0, width);
float y = map(fftL.getBand(i)*yScale, 0, 100, 0, height/2) ;
fill (102, 145, 250, 100);

// Rechteck zeichnen
rect(x+spacing, height, width/grid-2*spacing, -y);
}
}

void stop()
{
// Player in schließen
input.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Wie man an dem Spektrum unschwer erkennen kann, entspricht die Darstellung nicht unserer Wahrnehmung. Eine logarithmische Darstellung der Frequenzen ist hier angebracht. Mit FFT Klasse kann man eine solche Umwandlung mit der Methode logAverages() vornehmen.

Beispiel: FFT Frequenzspektrum logarithmisch



// Library importieren
import ddf.minim.*;
import ddf.minim.analysis.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;
FFT fftR, fftL;

int x, y;

// Anzahl der Peaks
int grid=20;

// Abstand zwischen den Peaks
int spacing=5;

// Ausschlagmaximum für Peaks festlegen
float yScale = 1;

void setup() {
  size(1024, 400);
  smooth();
  noStroke();

  // Konstruktor des Minim Objekts aufrufen
  minim = new Minim(this);

  // Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
  input = minim.loadFile("http://mp3stream1.apasf.apa.at:8000";,2048);

  // Wiedergabe starten
  input.play();
  input.printControls();

  // FFT-Instanz für die Spektrumsanalyse der beiden Kanäle
  fftR = new FFT (input.bufferSize (), input.sampleRate ());
  fftL = new FFT (input.bufferSize (), input.sampleRate ());
  fftR.logAverages(11, 16);
  fftL.logAverages(11, 16);
}

void draw() {

  // für etwas Bewegunsunschärfe
  fill(50, 10);
  rect(0, 0, width, height);

  // forwar FFT Analyse durchführen
  fftR.forward(input.right);
  fftL.forward(input.left);

  // rechter Kanal
  fill(255);
  text("right Channel", 10, height/2+20);

  // Breite der Rechtecke berechnen
  for (int i=0; i < fftR.avgSize(); i+=fftR.avgSize()/grid) {
    //println(fftR.avgSize());
    float x = map(i, 0, fftR.avgSize(), 0, width);
    //println(fftR.getAvg(i));
    float y = map(fftR.getAvg(i)*yScale, 0, 100, 0, height/5) ;
    fill (102, 145, 250, 100);

    // Rechteck zeichnen
    rect(x+spacing, height, width/grid-2*spacing, -y);
  }

  // linker Kanal
  fill(255);
  translate(0, -height/2);
  text("left Channel", 10, height/2+20);

  // Breite der Rechtecke berechnen
  for (int i=0; i < fftL.avgSize(); i+=fftL.avgSize()/grid) {
    float x = map(i, 0, fftL.avgSize(), 0, width);
    float y = map(fftL.getAvg(i)*yScale, 0, 100, 0, height/5) ;
    fill (102, 145, 250, 100);

    // Rechteck zeichnen
    rect(x+spacing, height, width/grid-2*spacing, -y);
  }
}

void stop()
{
  // Player in schließen
  input.close();
  // Minim Object stoppen
  minim.stop();

  super.stop();
}

Eine weitere Möglichkeit der FFT Klasse ist die Veränderung der Pegel einzelner Frequenzbereiche mit scaleBand() und setBand(). Siehe dazu die JavaDoc.

Beat Detection

Auch hier gibt es wieder 2 Möglichkeiten. Einerseits kann man nur mit den Levels (Amplituden) arbeiten - SOUND_ENERGY. Oder aber man nutzt FREQUENCY_ENERGY und greift die Levels in einzelnen Frequenzbändern ab.

Beispiel: BeatDetect mit SoundEnergy

// Library importieren
import ddf.minim.*;
import ddf.minim.analysis.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;
BeatDetect beat;

float eRadius;

void setup() {
size(512, 512);
smooth();
noStroke();

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
input = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
input.play();
input.printControls();

// Erstellt die BeatDetect Instanz
beat = new BeatDetect();

ellipseMode(CENTER_RADIUS);
}

void draw() {

// für etwas Bewegunsunschärfe
fill(50, 50);
rect(0, 0, width, height);

// Initiiert die BeatDetection
beat.detect(input.mix);

fill (102, 145, 250, 100);

// Trigger der BeatDetection
if ( beat.isOnset() ) eRadius = 3;

if ( eRadius < 0.1 ) eRadius = 0.1;

// Zeichnet die Kreise
for (float i=1;i<10;i+=0.3) {
fill(102, 145, 250, 10/i*i);
ellipse(width/2, height/2, eRadius*i*i, eRadius*i*i);
}
eRadius *= 0.95;
}

void stop()
{
// Player in schließen
input.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Beispiel: BeatDetect mit FrequencyEnergy

Hier kann man mit den Methoden isKick(), isSnare() und isHat() Peaks in den Frequenzbändern abrufen, die den jeweiligen Schlagzeugbausteinen entsprechen. Das funktioniert allerdings nicht bei allen Musiktypen gleich gut. Bei Problemen kann man noch auf die Funktion isRange(int low, int high, int threshold) zurückgreifen.

// Library importieren
import ddf.minim.*;
import ddf.minim.analysis.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;
BeatDetect beat;
BeatListener bl;

float radKick, radSnare, radHat;

void setup() {
size(1024, 512);
smooth();
noStroke();

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
input = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
input.play();
input.printControls();

// Erstellt die BeatDetect Instanz
// Im Frequency Mode müssen BufferSize und SampleRate übergeben werden.
beat = new BeatDetect(input.bufferSize(), input.sampleRate());

//Setzt die Zeit, in der der Algorithmus keine weiteren Beats meldet
beat.setSensitivity(200);
ellipseMode(CENTER_RADIUS);
textAlign(CENTER, CENTER);
}

void draw() {

// für etwas Bewegunsunschärfe
fill(50, 100);
rect(0, 0, width, height);

// Initiiert die BeatDetection
beat.detect(input.mix);

fill (102, 145, 250, 100);

// Trigger der BeatDetection
if ( beat.isKick() ) radKick = 2.5;
if ( beat.isSnare() ) radSnare = 2.5;
if ( beat.isHat() ) radHat = 2.5;

if ( radKick < 0.1 ) radKick = 0.1;
if ( radSnare < 0.1 ) radSnare = 0.1;
if ( radHat < 0.1 ) radHat = 0.1;

// Zeichnet die Kreise
for (float i=1;i<10;i+=0.3) {
fill(102, 145, 250, 10/i*i);
ellipse(width/4, height/2, radKick*i*i, radKick*i*i);
}

// Zeichnet die Kreise
for (float i=1;i<10;i+=0.3) {
fill(255, 0, 50, 10/i*i);
ellipse(width/2, height/2, radSnare*i*i, radSnare*i*i);
}

// Zeichnet die Kreise
for (float i=1;i<10;i+=0.3) {
fill(255, 200, 0, 10/i*i);
ellipse(width*3/4, height/2, radHat*i*i, radHat*i*i);
}

//Zeichnet den Text
fill(255);
text("Kick", width/4, height/2);
text("Snare", width/2, height/2);
text("Hat", width*3/4, height/2);

radKick *= 0.9;
radSnare *= 0.9;
radHat *= 0.9;
}

void stop()
{
// Player in schließen
input.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Aufgaben zu den Basics


  1. Erkläre die Bedeutung folgender Symbole und Programmanweisungen:
    boolean    for    >=    %    int   <=    &&    char
    | |    ‚     {    ++    String    }    +=    „“    = –     ;    ==
    —    if    !=    -=    //    else    >    *    /* */    while    <    /
  2. Wieviel bit Speicher reserviert eine Variable vom Typ boolean?
    a) 32    c) 16
    b) 8      d) 1
  3. Was ist das Ergebnis des folgenden Programms?
    int a=0;
    for (int i=0; i<5; i++){
        a++;
        a += i+1;
    }a) 10    c) 20
    b) 15    d) 0
  4. Nimm 2 Variablen, x und y. Wir wollen nun die Ausganswerte vertauschen, also von x nach y und von y nach x übertragen. Welches der folgenden Programme macht das?
    1. x = y;
      y = x;
    2. tempX = x;
       tempY = y;
       x = tempX;
       y = tempY;
    3. y = x;
      tempX = x;
      x = tempX;
    4. tempX = x;
      x = y;
      y = tempX;
  5. Schreibe Programme, die folgende Ausgaben produzieren:
    Tipp: Verwende Schleifen und Modulo(%)
                  
  6. Schreibe Programme, die folgende Ausgaben produzieren:
    Tipp: Verwende sin() und cos()!
           
  7. Schreibe Programme, die folgende Ausgaben produzieren:
    Tipp: Verwende random() und noise()!
                     
  8. Schreibe Programme, die folgende Ausgaben produzieren:
       
  9. Schreibe den kürzest möglichen Code, der die folgenden Zahlenreihen ausgibt. Verwende dafür nur +, -, *, 0,
    und %.
    012340123401234
    000010000100001
    111101111011110
  10. Schreibe ein Programm, das den Mauszeiger auf einem 10 Punkte-Raster einschnappen lässt.
    Tipp: Verwende dabei die round() Funktion.

Minim Sound abspielen und aufnehmen


Minim ist die Standard Audio-Bibliothek in Processing.

Sie bietet die Möglichkeit Sounds abzuspielen, aufzunehmen, zu verändern, zu synthetisieren und zu analysieren.

Minim

Um mit der Library arbeiten zu können, müssen wir zuerst ein Objekt der Minim-Klasse erstellen. Dieses Objekt kann dann zur Sound Ein- und Ausgabe benutzt werden. Dabei ist zu beachten, dass vor dem Schließen des Programms erst jegliche Ein- und Ausgaben geschlossen, und dann das Minim-Objekt gestoppt werden muss.

// Import der Bibliothek
import ddf.minim.*;

// erstellen eines Objektes (einer Instanz)
Minim minim;

// Aufruf des Konstruktors
minim = new Minim(this);

// Sound vom Eingang der Soundkarte
input = minim.getLineIn();

// Zuerst Eingabe schließen und dann Minim stoppen
input.close();
minim.stop();

Sound abspielen

Um Sound mit Minim abspielen zu können, muss zuerst eine Quelle definiert werden. Dies kann eine Datei (im /data-Ordner des Sketches), oder ein Eingang der Soundkarte sein. Mögliche Dateitypen sind: WAV, AIFF, AU, SND, and MP3.

Interessant ist vielleicht, dass die Dateien zurückgespult werden müssen. D.h. wenn sie an ihr Ende kommen, ist es so, wie bei einem analogen Plattenspieler. Der dreht sich noch, auch wenn keine Musik gespielt wird.

Für die Soundwiedergabe gibt es grundsätzlich 3 Möglichkeiten:

AudioSample

  • Datei wird vor dem Abspielen in den Speicher geladen.
  • Für sehr kurze Sequenzen geeignet.
  • Kann nur getriggert, nicht gelooped, oder  sonst etwas werden.
  • AudioSample sample = loadSample(„mySample.mp3“);

Beispiel: Trigger

Audio Snippet

  • keine Echtzeit Effekte
  • kein Zugriff auf Samples
  • Datei wird vor dem Abspielen in den Speicher geladen.
  • AudioSnippet snippet = loadSnippet(„mySnippet.mp3“);

AudioPlayer

  • zum Apspielen längerer Audio Dateien.
  • Datei wird „on the fly“ decodiert. Das spart Speicher, erhöht aber die Latenzzeit.
  • AudioPlayer player = loadFile(„myFile.mp3“);

Beispiel: Player

Beispiel: Abspielen des FM4 Streams

// Library importieren
import ddf.minim.*;

// Objekte erstellen
Minim minim;
AudioPlayer in;

void setup()
{
size(512, 200);

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
in = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
in.play();
}

void draw()
{

}

void stop()
{
// Player in schließen
in.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Methoden des AudioPlayer:

  • cue(int millis): legt die Abspielposition fest (in ms vom Start).
  • getMetaData(): gibt die ID3 Tags als String zurück.
    • album(), author(), comment(), composer(), copyright(), date(), disc(), encoded(), fileName(), genre(), length(), orchestra(), publisher(), title(), track().
  • isLooping(): gibt true zurück, wenn das Stück noch mehr als einmal gespielt werden muss.
  • isPlaying(): bei Wiedergabe true
  • length(): gibt die länge des Stücks in Millisekunden als int zurück.
  • loop(): schaltet looping ein.
  • loop(int n): schaltet looping ein. Spielt das Stück n mal ab.
  • pause(): pausiert die Wiedergabe
  • play(): startet die Wiedergabe
  • play(int millis): startet die Wiedergabe bei millis Millisekunden.
  • position(): gibt die aktuelle Position im Stück zurück
  • rewind(): spult an den Beginn.
  • setLoopPoints(int start, int end): setzt loopPoints ()
  • skipp(int millis): Spult millis Millisekunden

Außerdem bietet der AudioPlayer noch einige vererbte Methoden.

  • printControls(): gibt die Einstellungsmöglichkeiten des Eingangsgerätes auf der Konsole aus.
    Die zurückgegebenen Einstellungen sind hier beschrieben.
  • in.setVolume(int volume): legt die Lautstärke fest. Der Bereich ist von der jeweiligen Soundkarte abhängig (bei mir 0-65536).
  • in.getVolume(): gibt die aktuelle Lautstärkeneinstellung zurück.
  • mix.level(), left.level(), right.level(): gibt den Pegel des jeweiligen Kanals als float zurück

Aufgabe: Verändere das obige Programm so, dass man die Lautstärke verändern kann und der Pegel auf einfache Art und weise visualisiert wird.

Sound aufnehmen

Mit der AudioRecorder Klasse kann man den Inet-Radio-Sound auch aufnehmen. Dabei wird der Dateiname des aufgenommenen Stücks aus den Metadaten des Streams generiert. Bei FM4 ist das leider nur „FM4“. Bei anderen Sendern kann man vielleicht aber auch den Titel und den Interpreten auslesen.

// Library importieren
import ddf.minim.*;

// Objekte erstellen
Minim minim;
AudioPlayer in;
AudioRecorder recorder;

int x, y;

void setup() {
size(512, 200);
y=height/2;

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
in = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
in.play();

// Recorder erstellen
// Dateiname für Aufnahme festlegen
recorder = minim.createRecorder(in, in.getMetaData().title()+"_"+timestamp()+"_##.wav", true);

noFill();
background(0);
stroke(255);
}

void draw() {
// wenn gerade aufgenommen wird, wird rot gezeichnet, sonst weiß!
if (recorder.isRecording()) stroke(255, 0, 0);
else stroke(255);

// kleine Anmation
fill(0, 5);
rect(0, 0, width, height);
noFill();
// Kreisgröße Abhängig von Lautstärke
float dim = in.mix.level () * width;
// Kreis x-Position verschieben
x += in.mix.level() * 20;
// Kreis zeichnen
ellipse (x, y, dim, dim);
if (x > width) {
x = 0;
}
}

void keyReleased()
{
// mit "r" wird die Aufnahme gestartet
if ( key == 'r' ) {
if (recorder.isRecording()) recorder.endRecord();
else {
// Dateiname für Aufnahme festlegen
recorder = minim.createRecorder(in, in.getMetaData().title()+"_"+timestamp()+"_##.wav", true);
recorder.beginRecord();
}
}

// erst mit "s" wird die Aufnahme gespeichert
if ( key == 's' ) {
recorder.save();
println("Done saving.");
}
}

void stop()
{
// Player in schließen
in.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}


OpenCV mit Processing – Basics_1


Processing 2.0

Um mit OpenCV arbeiten zu können, muss man erst die Library impotieren, dann ein OpenCV Objekt deklarieren und intitialisieren.

import monclubelec. javacvPro.*

OpenCV opencv;
in setup():
opencv = new OpenCV(this);

OpenCV arbeitet mit Puffern. Beim Aufruf der allocate() Funktion werden die unten im Bild dargestellten Puffer erstellt.

Diese Buffer sind dann auf unterschiedliche Arten ansprechbar. Einfachste Art: Man zeigt das OpenCV Bildobjekt im Programmfenster:

opencv.copy(img); // kopiert PImage in den OpenCV Buffer
image(opencv.image(), 0, 0); //zeichnet das OpenCV Bildobjekt links oben

Will man auf das Graustufen-Bild zugreifen:
opencv.copyToGray(img);
image(opencv.getBufferGray(), 0, 0);

Ausgabe der Kanalbilder RGB:
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
//die Umkehroperation wäre hier mergerRGB()
image(opencv.getBufferR(),0, 0);// zeigt das Bild des Rot-Kanals

Es besteht auch die Möglichkeit, diese Buffer separat für verschiedene Bildgrößen zu erstellen. Z. B. um Objekte zu vergleichen.

opencv.allocateBuffer( width, height);
opencv.allocateMemory( width, height);
opencv.allocateMemory2( width, height);

Mit diversen remember()-, copyTo()-, remember2()-, restore()- und getMemory()- Funktionen kann man diese Buffer nutzen, um Bilder zwischen zu speichern.

Hier ein Überblick von: http://www.mon-club-elec.fr/pmwiki_reference_lib_javacvPro/pmwiki.php?n=Main.Presentation.

Beispiel: Einfache Bildmanipulation unter Verwendung der OpenCV Buffer und Filter


// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* changing strokeweight and strokecaps on diagonals in a grid
*
* MOUSE
*
*
* KEYS
* r                   : show Red Channel Image
* g                   : show Green Channel Image
* b                   : show Blue Channel Image
* w                   : show Gray Image
* n                   : show original Image
* h                   : flip Image horizontaly
* v                   : flip Image verticaly
* i                   : invert Image

* 1                   : apply "blur" Filter
* 2                   : increase Brightness
* 3                   : decrease Brightness
* 4                   : increase Contrast
* 5                   : decrease Contrast
* 6                   : multiply Red
* 7                   : multiply Green
* 8                   : multiply Blue
* 9                   : smooth Image

* s                   : save png
*/

import monclubelec.javacvPro.*;
import java.util.*;

PImage img;

String url="http://kobe.bplaced.net/processing/016_16.JPG";
OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Lädt die Bilddatei
img=loadImage(url, "jpg");

// initialisiert OpenCV ---
opencv = new OpenCV(this);
opencv.allocate(img.width, img.height); // initialisiert die Buffer von OpenCV

opencv.copy(img); // lädt die PImage Datei in den OpenCV Buffer

size (opencv.width(), opencv.height());

// gibt das Bild aus
image(opencv.getBuffer(), 0, 0);

noLoop();
}

void draw() {

noLoop();
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");

if (key == 'r') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferR(), 0, 0);// zeigt das Bild des Rot-Kanals
loop();
}

if (key == 'g') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferG(), 0, 0);// zeigt das Bild des Grün-Kanals
loop();
}

if (key == 'b') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferB(), 0, 0);// zeigt das Bild des Blau-Kanals
loop();
}

if (key == 'w') {
opencv.copyToGray(img);
image(opencv.getBufferGray(), 0, 0);
loop();
}

if (key == 'n') {
opencv.copy(img);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == 'h') {
opencv.flip(opencv.HORIZONTAL);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == 'v') {
opencv.flip(opencv.VERTICAL);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == 'i') {
opencv.invert();
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '1') {
opencv.blur(10);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '2') {
opencv.brightness(+10);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '3') {
opencv.brightness(-10);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '4') {
opencv.contrast(+10);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '5') {
opencv.contrast(-10);
image(opencv.getBuffer(), 0, 0);
loop();
}
if (key == '6') {
opencv.multiply(0.9, 1, 1);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '7') {
opencv.multiply(1, 0.9, 1);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '8') {
opencv.multiply(1, 1, 0.9);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '9') {
opencv.smooth(3);
image(opencv.getBuffer(), 0, 0);
loop();
}
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Beispiel: Alles wie oben, nur mit Video aus Datei

// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* changing strokeweight and strokecaps on diagonals in a grid
*
* MOUSE
*
*
* KEYS
* r                   : show Red Channel Image
* g                   : show Green Channel Image
* b                   : show Blue Channel Image
* w                   : show Gray Image
* n                   : show original Image
* h                   : flip Image horizontaly
* v                   : flip Image verticaly
* i                   : invert Image

* 1                   : apply "blur" Filter
* 2                   : increase Brightness
* 3                   : decrease Brightness
* 4                   : increase Contrast
* 5                   : decrease Contrast
* 6                   : multiply Red
* 7                   : multiply Green
* 8                   : multiply Blue
* 9                   : smooth Image

* s                   : save png
*/

import monclubelec.javacvPro.*;
import processing.video.*;
import java.util.*;

// Für WebCam:
Capture cam1;
//Movie cam1;

PImage img, imgSrc;
char mode='n';

OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Für WebCam:
cam1= new Capture(this, 1280, 720);
//cam1 = new Movie(this, "em.mpg");

// für WebCam auskommentieren:
cam1.start();

// initialisiert OpenCV ---
opencv = new OpenCV(this);

//Vorsicht: bei der Arbeit mit einer Datei muss die Größe genau passen!!!
opencv.allocate(1280, 720);

// Für WebCam:
// opencv.allocate(cam1.getSourceWidth(), cam1.getSourceHeight()); // initialisiert die Buffer von OpenCV

size (opencv.width(), opencv.height());
}

void draw() {

// Dateien und die WebCam brauchen etwas Zeit zum Laden
if (cam1.available()) {

// Einzelne Frames werden gelesen
cam1.read();

switch (mode) {
case 'n':
opencv.copy(cam1);
image(opencv.getBuffer(), 0, 0);
break;

case 'r':
opencv.copy(cam1); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferR(), 0, 0);// zeigt das Bild des Rot-Kanals
break;

case 'g':
opencv.copy(cam1); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferG(), 0, 0);// zeigt das Bild des Grün-Kanals
break;

case 'b':
opencv.copy(cam1); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferB(), 0, 0);// zeigt das Bild des Blau-Kanals
break;

case 'w':
opencv.copyToGray(cam1);
image(opencv.getBufferGray(), 0, 0);
break;

case 'h':
opencv.copy(cam1);
opencv.flip(opencv.HORIZONTAL);
image(opencv.getBuffer(), 0, 0);
break;

case 'v':
opencv.copy(cam1);
opencv.flip(opencv.VERTICAL);
image(opencv.getBuffer(), 0, 0);
break;

case 'i':
opencv.copy(cam1);
opencv.invert();
image(opencv.getBuffer(), 0, 0);
break;

case '1':
opencv.copy(cam1);
opencv.blur(30);
image(opencv.getBuffer(), 0, 0);
break;

case '2':
opencv.copy(cam1);
opencv.brightness(+30);
image(opencv.getBuffer(), 0, 0);
break;

case '3':
opencv.copy(cam1);
opencv.brightness(-30);
image(opencv.getBuffer(), 0, 0);
break;

case '4':
opencv.copy(cam1);
opencv.contrast(+30);
image(opencv.getBuffer(), 0, 0);
break;

case '5':
opencv.copy(cam1);
opencv.contrast(-30);
image(opencv.getBuffer(), 0, 0);
break;

case '6':
opencv.copy(cam1);
opencv.multiply(0.7, 1, 1);
image(opencv.getBuffer(), 0, 0);
break;

case '7':
opencv.copy(cam1);
opencv.multiply(1, 0.7, 1);
image(opencv.getBuffer(), 0, 0);
break;

case '8':
opencv.copy(cam1);
opencv.multiply(1, 1, 0.7);
image(opencv.getBuffer(), 0, 0);
break;

case '9':
opencv.copy(cam1);
opencv.smooth(3);
image(opencv.getBuffer(), 0, 0);
break;
}
}
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");

if (key == 'r') {
mode='r';
}

if (key == 'g') {
mode='g';
}

if (key == 'b') {
mode='b';
}

if (key == 'w') {
mode='w';
}

if (key == 'n') {
mode='n';
}

if (key == 'h') {
mode='h';
}

if (key == 'v') {
mode='v';
}

if (key == 'i') {
mode='i';
}

if (key == '1') {
mode='1';
}

if (key == '2') {
mode='2';
}

if (key == '3') {
mode='3';
}

if (key == '4') {
mode='4';
}

if (key == '5') {
mode='5';
}
if (key == '6') {
mode='6';
}

if (key == '7') {
mode='7';
}

if (key == '8') {
mode='8';
}

if (key == '9') {
mode='9';
}
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}


OpenCV mit Processing Installation auf Ubuntu 12.04


Processing 2.0

1. OpenCV installieren

Da OpenCV in den Paketquellen von Ubuntu enthalten ist, gestaltet sich die Installation denkbar einfach:

sudo apt-get install libopencv-*

Bei mir haben dann noch einige Pakete gefehlt:

sudo apt-get install libgstreamer-plugins-base0.10-dev libgstreamer0.10-dev libglib2.0-dev

2. Die Processing OpenCV Library 

Für Processing gibt es eine OpenCV Library, die sich allerdings nur für OpenCV 1.0 nutzen lässt. Nun hat Xavier Hinault eine neue Library veröffentlicht, die auch mit der Version 2.3.1 zurecht kommt. Sie basiert auf den Java Wrappern von Samuel Audet und heißt javacvPro.

Sie kann wie jede andere Processing Library einfach hier heruntergeladen und der Inhalt nach /home/user/processing/modes/java/libraries entpackt werden. Dann ist sie mit Sketch > Import Library > javacvPro zu importieren.

Installationsanleitungen für Windows, Mac und Linux:

http://codeanticode.wordpress.com/2011/11/21/opencv-2-in-processing/

Danach kann man alle Beispiele der Projektseite in Processing laufen lassen. z.B.:

// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

// Exemple fonction canny()

import monclubelec.javacvPro.*; // importe la librairie javacvPro

PImage img;

String url="http://www.mon-club-elec.fr/mes_images/online/lena.jpg"; // String contenant l'adresse internet de l'image à utiliser

OpenCV opencv; // déclare un objet OpenCV principal

void setup(){ // fonction d'initialisation exécutée 1 fois au démarrage

//-- charge image utilisée ---
img=loadImage(url,"jpg"); // crée un PImage contenant le fichier à partir adresse web

//--- initialise OpenCV ---
opencv = new OpenCV(this); // initialise objet OpenCV à partir du parent This
opencv.allocate(img.width, img.height); // initialise les buffers OpenCv à la taille de l'image

opencv.copy(img); // charge le PImage dans le buffer OpenCV

//--- initialise fenêtre Processing
size (opencv.width()*2, opencv.height()); // crée une fenêtre Processing de la 2xtaille du buffer principal OpenCV
//size (img.width, img.height); // aalternative en se basant sur l'image d'origine

//--- affiche image de départ ---
image(opencv.getBuffer(),0,0); // affiche le buffer principal OpenCV dans la fenêtre Processing

//--- opérations sur image ---

//-- toutes ces formes sont possibles :
//opencv.canny(); // applique le filtre de canny sur le buffer principal OpenCV avec paramètres par défaut
//opencv.canny(100,200); //applique le filtre de canny sur le buffer principal OpenCV avec paramètres - noyau 3x3 par défaut
opencv.canny(1000,2000,5); //applique le filtre de canny sur le buffer OpenCV désigné avec paramètres

//opencv.canny(opencv.Buffer,100,400); //applique le filtre de canny sur le buffer OpenCV désigné avec paramètres - noyau 3x3 par défaut
//opencv.canny(opencv.Buffer,100,200,3); //applique le filtre de canny sur le buffer OpenCV désigné avec paramètres

//opencv.invert(); // pour dessin au trait noir sur blanc

//--- affiche image finale ---
image(opencv.getBuffer(),opencv.width(),0); // affiche le buffer principal OpenCV dans la fenêtre Processing

noLoop(); // stop programme
}

void  draw() { // fonction exécutée en boucle

}

Weichzeichnen (Blur)


Objekte weichzeichnen zu können ist, ein Muss, will man ansprechende Graphiken erstellen. Die Processing eigene weichzeichen-Funktion blur() hat einen entscheidenden Nachteil. Sie ist sehr langsam. Will man Objekte in einer Animation (in Echtzeit) weichzeichnen ist sie somit obsolet.

Mario Klingemann hat einen sehr schnellen Weichzeichen-Algorithmus geschrieben, welcher wiederum von PhiLho erweitert wurde, so dass er nun auch mit dem Alpha Kanal umgehen kann. Damit hat man die Möglichkeit auch mit transparentem Hintergrund zu arbeiten.

// ==================================================
// fastblur, reworked by PhiLho from the Processing Forum
// by Mario Klingemann http://incubator.quasimondo.com/processing/superfast_blur.php
// ==================================================
void fastBlur(PImage img, int radius){
if (radius<1){
return;
}
int w=img.width;
int h=img.height;
int wm=w-1;
int hm=h-1;
int wh=w*h;
int div=radius+radius+1;
int a[]=new int[wh]; // i've added this
int r[]=new int[wh];
int g[]=new int[wh];
int b[]=new int[wh];
int asum,rsum,gsum,bsum,x,y,i,p,p1,p2,yp,yi,yw; // and the asum here
int vmin[] = new int[max(w,h)];
int vmax[] = new int[max(w,h)];
int[] pix=img.pixels;
int dv[]=new int[256*div];
for (i=0;i<256*div;i++){
dv[i]=(i/div);
}

yw=yi=0;

for (y=0;y<h;y++){
asum=rsum=gsum=bsum=0; // asum
for(i=-radius;i<=radius;i++){
p=pix[yi+min(wm,max(i,0))];
asum+=(p>>24) & 0xff;
rsum+=(p & 0xff0000)>>16;
gsum+=(p & 0x00ff00)>>8;
bsum+= p & 0x0000ff;
}
for (x=0;x<w;x++){
a[yi]=dv[asum];
r[yi]=dv[rsum];
g[yi]=dv[gsum];
b[yi]=dv[bsum];

if(y==0){
vmin[x]=min(x+radius+1,wm);
vmax[x]=max(x-radius,0);
}
p1=pix[yw+vmin[x]];
p2=pix[yw+vmax[x]];

asum+=((p1>>24) & 0xff)-((p2>>24) & 0xff); // asum
rsum+=((p1 & 0xff0000)-(p2 & 0xff0000))>>16;
gsum+=((p1 & 0x00ff00)-(p2 & 0x00ff00))>>8;
bsum+= (p1 & 0x0000ff)-(p2 & 0x0000ff);
yi++;
}
yw+=w;
}

for (x=0;x<w;x++){
asum=rsum=gsum=bsum=0;
yp=-radius*w;
for(i=-radius;i<=radius;i++){
yi=max(0,yp)+x;
asum+=a[yi]; // asum
rsum+=r[yi];
gsum+=g[yi];
bsum+=b[yi];
yp+=w;
}
yi=x;
for (y=0;y<h;y++){
pix[yi] = (dv[asum]<<24) | (dv[rsum]<<16) | (dv[gsum]<<8) | dv[bsum];
if(x==0){
vmin[y]=min(y+radius+1,hm)*w;
vmax[y]=max(y-radius,0)*w;
}
p1=x+vmin[y];
p2=x+vmax[y];

asum+=a[p1]-a[p2]; // asum
rsum+=r[p1]-r[p2];
gsum+=g[p1]-g[p2];
bsum+=b[p1]-b[p2];

yi+=w;
}
}
}

Wie setzt man den Algorithmus ein?

Wie wir der ersten Zeile entnehmen können, verlangt die Funktion beim Aufruf ein PImage und einen Integer als Parameter. Wir können nun also ein *.jpg laden und dieses, bevor wir es am Bildschirm ausgeben, mit dem Algorithmus weichzeichnen.

Beispiel FotoBlur:   starte Applet

PImage img;

void setup() {
img=loadImage("blume.JPG");
size(img.width, img.height);
}

void draw() {
img=loadImage("blume.JPG");
fastBlur(img,mouseX/5);
image(img, 0, 0);
}

Wollen wir aber von Processing gezeichnete Objekte weichzeichen, müssen wir diese in einen sog. FrameBuffer zeichnen. Siehe hochauflösende Bilder ausgeben.

Beispiel  Bluring selbst gezeichneter Objekte: Starte Applet

Hier wird ein Graphik-Objekt (ellipse()) in einen Offscreen Buffer gezeichnet und dieser dann mit fastBlur() weichgezeichnet.


/*---------------------------------------------------------
By Thomas Koberger
https://lernprocessing.wordpress.com/
---------------------------------------------------------*/

PImage img;
PGraphics pg;

void setup() {
img=loadImage("blume.JPG");
size(img.width, img.height);
}

void draw() {
image(img, 0, 0);
pg= createGraphics(width, height, P2D);
pg.beginDraw();
pg.noFill();
pg.strokeWeight(8);
pg.stroke(50, 100, 250);
pg.ellipse(width/2, height/2, 200, 200);
pg.loadPixels();
fastBlur(pg,mouseX/10);
pg.endDraw();
image(pg, 0, 0);
}

Beispiel Agenten weichzeichnen: starte Applet

Hier werden Agenten in kleine FrameBuffer gerendert, weichgezeichnet und dann angezeigt. Außerdem wird das ganze Bild, jeweils weichgezeichnet und im nächsten Frame als Hintergrund verwendet.

SourceCode siehe Applet!

Hochauflösende Bilder ausgeben


Will man einen Sketch hochauflösend (z.B. für den Druck) ausgeben, kann man alles, was in einem draw() Durchlauf gezeichnet wird, in einen sog. offScreen Buffer zeichnen und dann als Graphik speichern. Diese Technik wird auch „Double Buffering “ genannt.

Dafür erstellt man erst ein PGraphics Objekt. Dafür gibt man Breite, Höhe und den Renderer an.

PGraphics pg = createGraphics(width, height, JAVA2D);

// from http://amnonp5.wordpress.com/2012/01/28/25-life-saving-tips-for-processing/

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

void draw() {
background(255);
smooth();
strokeWeight(10);
fill(255, 0, 0);
ellipse(width/2, height/2, 200, 200);
}

void keyPressed() {
if (key == 's') {
save("normal.png");
saveHiRes(5);
exit();
}
}

void saveHiRes(int scaleFactor) {

// erstellt neues PGraphics Objekt
PGraphics hires = createGraphics(width*scaleFactor, height*scaleFactor, JAVA2D);

//alles ab hier wird in das PGraphics Objekt gezeichnet
beginRecord(hires);
//und skaliert
hires.scale(scaleFactor);
draw();
endRecord();
//das PGraphics Objekt wird als .png gespeichert
hires.save("hires.png");
}

Anstatt wie hier alle Zeichenvorgänge von draw() in das PGraphics Objekt zu rendern, kann man mit folgenden Anweisungen auch direkt darauf zeichnen.

hires.strokeWeight(10);PGraphics
hires.fill(255, 0, 0);
hires.ellipse(width/2, height/2, 200, 200);

Visualisierung aus Textanalyse


Inspiriert von einem Sketch von Diane Lange, in dem „Die Bürgschaft“ von Schiller visualisiert wird, wollte ich auch mal was in der Richtung machen. Der Rechner sollte bei mir aber kein Graphiker sein, sondern ein Maler. Mir war es auch nicht so wichtig, dass der Connex zwischen dem visualisierten Buch und dem Bild, das daraus entstehen sollte allzu deutlich hervor tritt. Es ging mir eher darum, dem Computer, basierend auf einfachen Textattributen (Anzahl der Wörter, Anzahl unterschiedlicher Wörter,  durchschnittl. Satzlänge, und einigen Verhältnissen daraus) schöne und sich durchaus voneindander unterscheidende Bilder generieren zu lassen.

Die Grundform ergibt sich aus Lissajous Figuren, deren Komplexität sich aus den Attributen des Textes ergibt. Es werden in 2 Schritten erst die stark weichgezeichneten Formen im Hintergrund gezeichnet. Das könnte man wahrscheinlich viel effizienter programmieren. Mir war das allerdings nicht so wichtig, da der Output ja ohnehin Bilder sein sollten. In einem zweiten Schritt werden dann die feinen Strukturen im Vordergrund gezeichnet, wobei hier alle unterschiedlichen Wörter einmal entlang der Form der Lissajous Figur platziert und dann mit dem nächsten Wort im Text verbunden werden. Diese Verbindungslinien werden nicht nur innerhalb der Figuren in schwarz, sondern auch über diese hinaus und dann in einem hellen Grauton gezeichnet.

starte Applet

Source Code findet sich im Applet!

PeasyCam


Um die Ansicht einer Szene oder eines Bildes zu verändern gibt es im Allgemeinen zwei Möglichkeiten.

  1. Man verschiebt das Objekt.
  2. Man verändert den Standort des Betrachters.

Gerade in 3D Umgebungen ist es oft kompliziert mehrere Objekte so zu verschieben und zu drehen wie man das beabsichtigt. Hier ist es meist einfacher den Standort des Betrachter, sprich die Kamera zu verschieben und zu drehen.

Und genau dafür ist die PeasyCam Library da: http://mrfeinberg.com/peasycam/

Man braucht der Kamera nur die Position und den Punkt, auf den sie zeigen soll mitzuteilen.

Installation: siehe Libraries

Allgemeines:

import peasy.*;
PeasyCam cam;

void setup() {

  cam = new PeasyCam(this, 0, 0, 0, 500);

}

Die PeasyCam kann nach dem Import der Library mit der Maus gesteuert werden.

Was bedeuten die fünf Parameter der PeasyCam?
PeasyCam(this … Bezug auf den aktuellen Sketch, x-Position des Zielpunkts, y-Position des Zielpunkts, z-Position des Zielpunkts, Radius der Kugel, auf der sich die Cam bewegt);

Mausparameter:

  • Links-Click und ziehen –>  drehen
  • Mausrad oder Rechts-Click und ziehen –> zoom
  • Mittel-Click und ziehen –> Schwenken
  • DoppelClick –> reset

Die Cam wird auf einer Kugel mit einem bestimmten Abstand platziert und dann um dem Punkt, auf den sie „zeigt“ gedreht.

Außerdem ist es auch möglich die Kamera mittels Programmanweisungen zu positionieren.

Beispiel: Kamera mit Maus bewegen

In diesem Beispiel zeichnen wir das Koordinatensystem aus 3D Basics und bewegen die Kamera mit der Maus.

Starte Applet

import processing.opengl.*;
import peasy.*;

PeasyCam cam;

void setup() {
size(640, 640, OPENGL);
translate(width/2, height/2, 0);
cam = new PeasyCam(this, 0, 0, 0, 1000);
}
void draw() {
background(0);
textSize(20);
stroke(255);
drawAxes();
}

void drawAxes() {
stroke(255, 0, 0);
line(-300, 0, 0, 300, 0, 0);
text("+x", 300, 0, 0);
text("-x", -330, 0, 0);
stroke(0, 255, 0);
line(0, -300, 0, 0, 300, 0);
text("+y", 0, 330, 0);
text("-y", 0, -300, 0);
stroke(0, 0, 255);
line(0, 0, -300, 0, 0, 300);
text("+z", 0, 0, 330);
text("-z", 0, 0, -300);
}

Beispiel: Ändern des Betrachtungspunktes mittels Code

Mit der PeasyCam Funktion lookAt(x,y,z) kann man den Punkt der Betrachtung ändern. Das machen wir in diesem Beispiel innerhalb des Events mousePressed().

lookAt(x,y,z);

Wie zeichnen zwei Punkte in unsere 3D Umgebung und zentrieren die Kamera dann bei MausClick links und rechts auf je einen dieser Punkte.

Es ist zudem möglich die Position und Rotation der Kamera mit getPosition() und getRotation() (als float Array) auszulesen und in der Konsole auszugeben.

Starte Applet

import processing.opengl.*;
import peasy.*;

PeasyCam cam;

void setup() {
size(640, 640, OPENGL);
translate(width/2, height/2, 0);
cam = new PeasyCam(this, 0, 0, 0, 1000);
}
void draw() {
background(0);
textSize(20);
stroke(255);
drawAxes();
drawPoint();
}

//zeichnet die beiden Punkte
void drawPoint() {
pushStyle();
strokeWeight(3);
point(-400, 0, 0);
point(400, 0, 0);
popStyle();
}

void drawAxes() {
stroke(255, 0, 0);
line(-300, 0, 0, 300, 0, 0);
text("+x", 300, 0, 0);
text("-x", -330, 0, 0);
stroke(0, 255, 0);
line(0, -300, 0, 0, 300, 0);
text("+y", 0, 330, 0);
text("-y", 0, -300, 0);
stroke(0, 0, 255);
line(0, 0, -300, 0, 0, 300);
text("+z", 0, 0, 330);
text("-z", 0, 0, -300);
}

void mousePressed() {
//Ändert den Punkt der Betrachtung
if( mouseButton==LEFT) cam.lookAt(400, 0, 0);
else cam.lookAt(-400, 0, 0);

//gibt die Position und Drehung der Kamera in der Konsole aus
println("Position x: "+cam.getPosition()[0]+
" y: "+  cam.getPosition()[1]+
" z: "+  cam.getPosition()[2]);
println("Rotation x: "+cam.getRotations()[0]+
" y: "+  cam.getRotations()[1]+
" z: "+  cam.getRotations()[2]);
}

Kinect Installation auf Ubuntu 12.04


Processing 2.0

Mit dem Kinect Sensor und Processing lassen sich wirklich tolle Sachen machen. Z.B.: 2 Arbeiten von Cedric Kiefer: unnamed-soundsculpture, magic story telling.

Buchtip:

Als guter Einstieg in englischer Sprache eignet sich : Making-Things-See.

Hardware:

Um die Kinect am Computer betreiben zu können braucht man eine externe Stromversorgung. Die gibt es auch im Bundle, welches ich auf die Schnelle im Amazon Webshop nicht mehr finden konnte. Es sollte aber nach wie vor verfügbar sein.

Installation:

Um unter Processing mit dem Kinect Sensor arbeiten zu können, braucht man einen Treiber inkl. Java/Processing wrapper. Hier gibt es im derzeit 2 Möglichkeiten.

  1. Den Treiber des OpenKinect Projekts mit einem Wrapper von Daniel Shiffman. Dieser funktioniert in der vorliegenden Version allerdings nur auf dem Mac.Er kann aber unter dieser Anleitung von Nikolaus Gradwohl auch für Linux kompiliert werden.
  2. Für alle Plattformen ist hingegen die simple-openni verfügbar. Die Installation unter Windows und Linux funktioniert lt. Anleitung. Einzig die Version des Treibers im Linux Paket geht aus der Anleitung nicht klar hervor. Hier muss die Version im /Ordner/kinect/….. installiert werden.Dann noch den Wrapper herunterladen und wie beschrieben im /libraries Ordner im Sketchbook entpacken und schon kanns mit dem Beipielcode von der Seite ans Testen gehen.Tipp: Auf Ubuntu 12.04 wird die Kinect nach dem Verbinden sofort von dem Programm Guvcview belegt. Damit das nicht passiert, muss die Datei /etc/modprobe.d/blacklist.conf als root mit der Zeile blacklist gspca_kinect angereichert werden.

import SimpleOpenNI.*;

SimpleOpenNI  context;

void setup()
{
context = new SimpleOpenNI(this);

// enable depthMap generation
context.enableDepth();

// enable camera image generation
context.enableRGB();

background(200,0,0);
size(context.depthWidth() + context.rgbWidth() + 10, context.rgbHeight());
}

void draw()
{
// update the cam
context.update();

// draw depthImageMap
image(context.depthImage(),0,0);

// draw camera
image(context.rgbImage(),context.depthWidth() + 10,0);
}

void keyReleased() {
//if (key == DELETE || key == BACKSPACE) background(360);
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
println("Frame saved");
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

3 D Basics


Processing 2.0

In Processing ist es auch möglich in 3D zu arbeiten. Dies gibt uns die Möglichkeit alle bisher programmierten Bilder und Animationen um eine Dimension zu erweitern.

Im Gegensatz zu 2D, wo wir nur die Achsen x und y haben, kommt in 3D eine z-Achse dazu, die vom Ursprung (links oben) aus direkt aus dem Bildschirm heraus zeigt.

Damit wir die 3. Dimension nutzen können, müssen wir Processing sagen, dass wir nun in 3D arbeiten wollen und einen entsprechenden Renderer wählen.

Der Einfachheit halber wurden in Processing 2 die beiden vormals zu Verfügung stehenden Renderer P3D und OPENGL zusammengeführt und die OpenGL Library in den Processing Core aufgenommen. Das hat zur Folge, dass nun auch OpenGL direkt im P3D Modus programmiert werden kann. Darüber hinaus verbessert sich auch die Kompatibilität des Processing Codes über die Plattformen hinweg verbessert.

Wir benutzen also immer den P3D Renderer:

size(600, 600, P3D);

So, und jetzt können wir (fast) alle einfachen Zeichenfunktionen von Processing auch in 3D nutzen. Wir müssen dafür allerdings immer zusätzlich zu den x-, und y-Koordinaten auch eine z-Koordinate angeben.

Einen letzten wichtigen, und manchmal etwas verwirrendenPunkt stellt die Tatsache dar, dass der Betrachter in einer 3-dimensionalen Darstellung ja auch seine Position haben muss. Um das zu verdeutlichen spielen wir mit der Funktion translate() im folgenden Beispiel.

translate(width/2,height/2, 0);

Mit dieser Zeile verschieben wir das Koordinatensystem von links oben in die Mitte des Programmfensters. Die 0 als z-Koordinate bewirkt, dass wir eine Szene so betrachten, als ob wir in 2D arbeiten würden. Wenn wir jetzt aber eine negative Zahl als z-Koordinate wählen, werden die gezeichneten Objekte kleiner, da wir den Ursprung des Koordinatensystems von uns weg bewegt haben.

Bei positiven z-Werten bewegen wir uns also weiter vor das Objekt, bei negativen weiter dahinter(bis wir es gar nicht mehr sehen können!!!)!

Beispiel Drehbares Koordinatensystem: starte Applet

Achtung: Funktioniert im Android Mode!!!

void setup() {
size(640, 640, P3D);
}
void draw() {
background(0);
textSize(20);
stroke(255);
translate(width/2, height/2, -200);
println((height/2.0) / tan(PI*60.0 / 360.0));
rotateX(map(mouseY, 0, height, -PI, PI));
rotateY(map(mouseX, 0, height, -PI, PI));
drawAxes();
}

void drawAxes() {
stroke(255, 0, 0);
line(-300, 0, 0, 300, 0, 0);
text("+x", 300, 0, 0);
text("-x", -330, 0, 0);
stroke(0, 255, 0);
line(0, -300, 0, 0, 300, 0);
text("+y", 0, 330, 0);
text("-y", 0, -300, 0);
stroke(0, 0, 255);
line(0, 0, -300, 0, 0, 300);
text("+z", 0, 0, 330);
text("-z", 0, 0, -300);
}

Wenn wir das Koordinatensystem drehen fällt natürlich sofort auf, dass sich die Größe der Beschriftung mit der Entfernung von der Elemete ändert. Näheres erscheint größer, weiter entferntes kleiner. Das entspricht auch der Realität und stellt eben den großen Unterschied zu einer 2D Darstellung dar.

3 D spezifische Elemente (Körper):

Kugel

sphere(int size);

Zeichnet ein (natürlich!) 3-Dimensionale Kugel mit der Größe size. Die Kugel wird dabei immer auf den Punkt 0,0,0 zentriert.

sphereDetail(int detail);

Diese Anweisung bestimmt die Auflösung der Textur. 3D Renderer zeichnen nämlich keine Kurven, sondern zerlegen sie in einzelne Punkte, welche dann mit geraden Linien verbunden werden. Der Wert detail bestimmt die Anzahl der Punkte des Umfangs. Z.B.: detail 40 würde bedeuten, dass 360/40, also alle 9 Grad ein Punkt gezeichnet wird.

fill() und stroke() können wie gewohnt eingesetzt werden.

Beispiel Einfache Kugel: starte Applet

Achtung: Beispiel funktioniert nicht im Android Mode!!!

Im JavaScript Mode wird die Kugel nicht transparent dargestellt, und im Java Mode kommt es zu Darstellungsproblemen bei den Achsen!

Quellcode siehe Applet!

Würfel:

Wie Sphere, nur mit der Anweisung box().

box(Seitenlänge);

Komplexe Formen:

Sollten komplexere Formen benötigt werden, kann man diese entweder über beginShape(), vertex(x,y,z) und endShape() in Processing erstellen, oder man importiert fertige 3D Objekte (*.obj) mit Hilfe der loadShape(*.obj) Funktion.

Beispiel 3-seitige Pyramide: starte Applet

//Zeichne 3-seitige Pyramide
  beginShape(TRIANGLE);
  vertex(-1, -1, 1);
  vertex( -1, 1, 1);
  vertex( 0, 0, -1);

  vertex(-1, 1, 1);
  vertex( 1, 0, 1);
  vertex( 0, 0, -1);

  vertex(-1, -1, 1);
  vertex( 1, 0, 1);
  vertex( 0, 0, -1);

  vertex(-1, -1, 1);
  vertex( 1, 0, 1);
  vertex( -1, 1, 1);

  endShape();

Quellcode siehe Applet!

Sieht noch nicht sehr beeindruckend aus. Was noch feht ist die richtige Beleuchtung. Siehe Artikel 3D Oberflächen und Licht.

Visualisierung von länderbezogenen Daten


Mich interessieren Daten. Oft kann man Daten in Tabellenform aber schwer fassen. Deshalb habe ich versucht Daten die österreichischen Bundesländer zu visualisieren.

D.h. konkret möchte ich die Arbeitslosenquote der Bundesländer farblich als Österreichkarte darstellen. Dafür brauchen wir als erstes eine Karte Österreichs als *.svg Datei. Die ist schnell auf Wikipedia gefunden: Map of Austria.

Vorbereiten der Vektorgrafik

Ich habe diese Karte zwecks einfacherer Analyse in Processing leicht modifiziert. Das ist sehr einfach, da es sich bei .svg Dateien eigentlich nur um XML Dateien handelt. Man kann sie einfach in jedem Texteditor öffnen. Unter Linux geschieht das dann auch noch gleich mit Code-Highlighting. Folgende Tags wurden von mir einfach aus der Datei entfernt.

<title>Austria</title>
 <desc>Political map of Austria</desc>
 <filter id="f1">
 <feGaussianBlur in="SourceGraphic" stdDeviation="10"/>
 </filter>
 <path id="Shadow" filter="url(#f1)" ..........................................>

Modifizierte Datei:

Data Mining

Das Aufspüren und Aufbereiten der Daten ist in diesem Fall sehr einfach. Die Statistik Austria bietet sie in verschiedenen Formen an. Ich beschränke mich vorerst auf die Quote der Bundesländer im Jahr 2010 und kopiere die ensprechende Tabelle.

Die erste Visualisierung

Die .svg Datei Österreichs wird geladen und dann in die einzelenen Pfade der Bundesländer zerlegt. Die Daten der Arbeitslosenzahlen werden in einem Array in einer Reihe, den Pfaden der Bundesländern entsprechend abgelegt. Dann wird jeder Pfad gezeichnet und entsprechend der ArbeitslosenQuote eingefärbt.

Starte Applet

// PShape Objekte erzeugen
PShape austria;
PShape[] bundesl;

//Titel
String title="Arbeitslosenqote Bundesländer 2010";

// Daten von Statistik Austria
float[] arblQuote = {
3.9, //Burgenland
3.9, //Carinthia
3.6, //Lower_Austria
3.7, //Upper_Austria
2.9, //Salzburg
4.2, //Styria
2.8, //Tyrol
3.9, //Vorarlberg
7.3  //Vienna
};

//Grundfarbe der Visualisierung
int col=210;

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

//Vektorgrafik laden
austria = loadShape("Map_of_Austria.svg");

//Pfade der einzelnen Bundesländer extrahieren
bundesl = austria.getChildren();

//Formatierung des Originals deaktivieren
austria.disableStyle();
noLoop();
colorMode(HSB, 360, 100, 100);
textAlign(CENTER, CENTER);
}

void draw() {
background(0, 0, 100);
stroke(col, 20, 99);

//Zeichnen der Bundesländer
for (int i=0; i<bundesl.length; i++) {
//Füllfarbe festlegen
fill(col, arblQuote[i]*13, 100);
//Zeichnen der Pfade
shape(bundesl[i], 0, 0);
println(bundesl[i].getName());
}
//Titel
textSize(20);
text(title, width/4,height*1/20);
}

Legende:

Eine Legende wäre jetzt natürlich noch nett. Dafür sollte man natürlich auch noch den Farbbereich automatisch an den Wertbereich der Daten anpassen.

starte Applet

/* @pjs preload="Map_of_Austria.svg"; */

// PShape Objekte erzeugen
PShape austria;
PShape[] bundesl;

//Titel and Source
String title="Arbeitslosenqote Bundesländer 2010";
String source=" Quelle: Statistik Austria";

// Daten von Statistik Austria
float[] arblQuote = {
3.9, //Burgenland
3.9, //Carinthia
3.6, //Lower_Austria
3.7, //Upper_Austria
2.9, //Salzburg
4.2, //Styria
2.8, //Tyrol
3.9, //Vorarlberg
7.3  //Vienna
};

//Grundfarbe der Visualisierung
int col=210;

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

//Vektorgrafik laden
austria = loadShape("Map_of_Austria.svg");

//Pfade der einzelnen Bundesländer extrahieren
bundesl = austria.getChildren();

//Formatierung des Originals deaktivieren
austria.disableStyle();
noLoop();
colorMode(HSB, 360, 100, 100);
textAlign(CENTER, CENTER);
rectMode(CENTER);
}

void draw() {
background(0, 0, 100);
stroke(col, 20, 99);

// Koordinatensystem speichern
pushMatrix();
translate(0, 30);

//Zeichnen der Bundesländer
for (int i=0; i<bundesl.length; i++) {
//Füllfarbe festlegen
fill(col, map(arblQuote[i],getMinValue(arblQuote),getMaxValue(arblQuote),20,100), 100);
//Zeichnen der Pfade
shape(bundesl[i], 0, 0);
println(bundesl[i].getName());
}
//Koordinatensystem repositionieren
popMatrix();

//Legende zeichnen
legende(10, 50, 2.8, 7.3);

text(source, width/2, height*19/20);
//Titel
textSize(20);
text(title, width/4, height*1/20);
}

void legende(int x, int y, float valMin, float valMax) {
pushMatrix();
translate(x+10, y+10);
fill(0, 0, 0);
text("Legende:", 30, 0);
translate(0, 25);

//Min und Max Werte für die Beschriftung finden
int minVal=floor(valMin);
int maxVal=ceil(valMax);
float step= (float) (maxVal-minVal)/5;

//Farbfelder und Beschriftungen zeichnen
for (int i =0; i<6; i++) {
fill(col, map(minVal+step*i,getMinValue(arblQuote),getMaxValue(arblQuote),20,100), 100);
rect (0, i*20, 20, 10);
String legValue = " %";
fill(0, 0, 0);
text( (float)round(((float) minVal+step*i) * 100 )/100+" %", 30, i*20);
}
popMatrix();
}

float getMaxValue ( float[] vals) {
float [] values= new float [vals.length];
arrayCopy(vals,values);
Arrays.sort(values);
return values[values.length-1];

float getMinValue ( float[] vals) {
float [] values= new float [vals.length];
arrayCopy(vals,values);
Arrays.sort(values);
return values[0];
}

Beschriftung:

Damit das Ganze einen echten Mehrwert gegenüber der konventionellen Erstellung in einem Grafikprogramm bietet, sollte das Programm die Grafik automatisch beschriften. Dafür müssen wir die in der original-.svg Datei englischsprachigen Titel durch deutschsprachige ersetzen.

Die Quoten und Namen der Bundesländer sollen in der Mitte der jeweiligen Pfade platziert werden. Dafür müssen wir die minimalen und maximalen x und y Koordinaten aller Punkte eines Pfades ermitteln. Dann zeichnen wir genau in die Mitte.

Starte Applet

Änderungen zum Beispiel vorher:

  1. Hintergrundfarbe geändert, damit die Beschriftung besser lesbar wird.
    background(0, 0, 80);
    
  2. In draw() werden die Koordinaten der Beschriftungstexte ermittelt und dann gezeichnet.
    <pre>//Zeichnen der Quoten
    fill(360,0,100);
    float [] coords = new float [2];
    coords= getCoords(bundesl[i].getChild(0));
    textSize(18);
    text(  (float)round(((float) arblQuote[i]) * 100 )/100+" %", coords[0],coords[1]);
    
  3. Funktion zur Ermittlung der Koordinaten.
    float [] getCoords (PShape thisShape) {
    
    float xMax=0;
    float yMax=0;
    float xMin=100000;
    float yMin=100000;
    float [] coords = new float [2];
    
    //Ermitteln der maximalen und minimalen Pfad-Koordinaten
    for (int i =0; i<thisShape.getVertexCount(); i++) {
    if (thisShape.getVertexX(i)>xMax)  xMax=thisShape.getVertexX(i);
    if (thisShape.getVertexY(i)>yMax)  yMax=thisShape.getVertexY(i);
    if (thisShape.getVertexX(i)<xMin)  xMin=thisShape.getVertexX(i);
    if (thisShape.getVertexY(i)<yMin)  yMin=thisShape.getVertexY(i);
    }
    coords[0]=(xMin+xMax)/2;
    coords[1]=(yMin+yMax)/2;
    return coords;
    }
    

Interaktivität:

starte Applet

    1. Erweitern der Datenbasis
      Damit in unserer Anwendung nun auch Entwicklungen sichtbar werden, verwende ich nicht nur die Daten aus dem Jahr 2010, sondern eine ganze Tabelle mit den Daten der Jahre 1995-2010, wie sie von Statistik Austriaveröffentlicht werden. Damit diese von Processing auf einfache Weise automatisch eingelesen werden können, habe ich eine .csv Tabelle der folgender Struktur erstellt:

      • unnötige Spalten und Zeilen gelöscht
      • die , durch . ersetzt
      • die Bundesländer-Beschriftung von links nach rechts kopiert
      • im Sketch Ordner als list.csv gespeichert

      Download: list.csv

      1995;1996;1997;1998;1999;2000;2001;2002;2003;2004;2005;2006;2007;2008;2009;2010;
      3.8;4.2;4.3;3.4;3.6;3.2;4.1;4.3;4.2;5.6;6;5;3.7;3.6;4.6;3.9;Burgenland
      3;2.9;3.4;3.5;3.5;3.1;3.2;2.7;3.4;4.6;4.8;4.4;3.9;3.4;4.2;3.9;Kärnten
      3.4;3.8;3.8;4.4;3.3;3;3.2;3.6;3.5;4.2;4.3;4;3.6;3.4;4.3;3.6;Niederösterreich
      3.2;3.7;3.5;3.5;3.5;3.1;2.9;3.1;3.3;3.7;4;3.2;3.2;2.6;4;3.7;Oberösterreich
      2.7;3.2;3.2;3.4;2.7;2.3;1.9;2.8;2.2;3.7;3.2;3.1;3;2.5;3.2;2.9;Salzburg
      3.5;4.1;4;3.8;3.2;3.2;3.7;3.8;4;3.7;4.1;3.9;3.7;3.4;4.6;4.2;Steiermark
      2.9;3.2;3.4;2.9;2.5;2.5;2.3;2;2.6;3.3;3.5;2.9;2.8;2.4;2.9;2.8;Tirol
      3.3;3.9;3.9;3.6;3.5;2.4;2.4;2.5;4.1;4.1;5.3;4.4;3.6;3.9;4.9;3.9;Vorarlberg
      5.3;5.9;6.3;6.3;5.7;5.7;5.8;7.3;7.8;8.9;9.1;8.8;8.3;6.7;7.5;7.3;Wien

    2. Automatisches Einlesen der Daten aus einer .csv -Datei
      Hier habe ich als Ausgansbasis ein Skript von che-wei wang verwendet: http://cwwang.com/2008/02/23/csv-import-for-processing/. Mit ein paar kleinen  Modifikationen liest es auch in unserem Programm die .csv-Datei ein. Und speichert die Daten in einem 2-dimenstionalen Array.
    3. Ermitteln der minimalen und maximalen Werte
      Das ist jetzt nicht mehr ganz so einfach, wie vorher, aber kein allzu großes Problem, das in setup() erledigt wird:

      //Ermitteln der minimalen und maximalen Werte für die Farbwahl
      for (int j=0; j<csv[0].length;j++) {
      for (int i=0; i<arblQuote.length;i++) {
      arblQuote[i]=parseFloat(csv[i+1][j]);
      }
      if (getMinValue(arblQuote)<minVal) minVal=getMinValue(arblQuote);
      if (getMaxValue(arblQuote)>maxVal) maxVal=getMaxValue(arblQuote);
      }
      

      Damit werden dann in weiterer Folge unsere Werte mit konstanten Farbwerten über alle Jahre hinweg beschriftet.

    4. Übertragen der Jahreszahlen und des aktuell gewählten Jahres in entsprechende Variablen
      //Auslesen der Jahre
      years=new int[csv[0].length-1];
      for (int i=0; i< csv[0].length-1;i++) {
      years[i]=parseInt(csv[0][i]);
      }
      selYear=0;
      
    5. In draw(): Daten des aktuellen Jahres übertragen.
      //Daten des aktiven Jahres laden
      for (int i=0; i<arblQuote.length;i++) {
      arblQuote[i]=parseFloat(csv[i+1][selYear]);
      }
      
    6. Für jedes Bundesland den Namen anzeigen, wenn sich die Maus darüber befindet. (Funktioniert beim Bundesland Tirol nur bedingt!)
      //Namen anzeigen
      if (bundesl[i].getChild(0).contains(mouseX, mouseY)) {
      fill(0, 0, 40);
      textSize(14);
      text(bundesl[i].getName(), coords[0], coords[1]+20);
      }
      
    7. Interface, für die Auswahl der Jahre
      int yearSelector (int [] years) {
      
      noFill();
      pushMatrix();
      strokeWeight(1.5);
      
      //Linie zeichnen
      stroke(0, 70, 90);
      line(width-5, 0, width-5, height);
      strokeWeight(1);
      
      //Beschriftung
      translate(width-50, -10);
      for (int i=0; i<years.length; i++) {
      translate(0, height/years.length);
      
      //Hervorheben des gewählten Jahres
      if (years[i]==years[selYear]) {
      fill(0, 70, 90);
      triangle(35, 0, 45, -10, 45, 10);
      textSize(24);
      fill(360);
      }
      else {
      textSize(12);
      fill(0, 0, 60);
      }
      text(years[i], 0, 0);
      }
      popMatrix();
      return selYear ;
      }
      
    8. Mausaktivität abrufen um damit das gewünschte Jahr zu wählen
      //Mausposition auswerten
      void mouseMoved () {
      if (mouseX > width-60 && mouseX<width) {
      selYear=(int) map(mouseY, 0, height, 0, years.length);
      }
      }
      

Und diverse kleinere Modifikationen, z.B.: die Anpassung der Textpositionen an die Länge des Textinhalts usw. Den gesamten Quellcode findest du im Applet.


	

updated

processing - tutorial

Processing 2.0

In Variablen kann man nicht nur einzelne Werte speichern, sondern mehrere, die ein- oder zweidimensional organisiert sind. Das nennt man dann ein – oder zweidimensionale Arrays. Sie unterscheiden sich nur wenig von „normalen“ Variablen und werden in der Form array[] (eindimensional) oder array [] [] (zweidimensional) deklariert. Der Datentyp (int, float, uws.) muss außerdem festgelegt werden.

float [] eindimensional = new float [200];

Die obige Anweisung deklariert ein eindimensionales Datenfeld mit dem Namen eindimensional, in dem 200 Werte gespeichert werden können (0-199). Aufgerufen werden die einzelnen Felder dann mit eindimensional[i], wobei i dann für eine Zahl von 0 bis 199 steht.

float[] [] zweidimensional = new float [200] [200];

Hier haben wir ein Beispiel für ein zweidimensionales Feld, das dann mit zweidimensional[i][i] mit Werten befüllt werden kann. So können die Werte dann natürlich auch wieder ausgelesen werden.

Beispiel: starte Applet

Dieses Beispiel liest die Farbinformationen jedes…

Ursprünglichen Post anzeigen 122 weitere Wörter

MySQL Datenbankanbindung mit PHP


Wie praktisch ist es doch, wenn man die in einem Script anfallenden Daten in einer Datenbank speichern kann. Dazu verwenden wir in diesem Fall eine MySQL Datenbank auf http://www.bplaced.net/. Auf der Plattform sind nach der Anmeldung bis zu 8 Datenbanken erstellbar.

Mein Beispiel sollte die Ergebnisse eines Facebook Users bei einem Online-Game (programmiert in ProcessingJS) zwecks Highscore Listenerstellung in einer Datenbank abspeichern.

Wir werden uns hier anschauen, wie man Daten mit der Hilfe von kleinen PHP Scripts von Processing aus in eine MySQL Datenbank übertragen kann. Das ist besonders bei der Verwendung von ProcessingJS praktisch, da man vom Browser aus keine Berechtigung hat, Dateien zu erstellen und zu verändern. Somit bleibt eigentlich nur der Weg über die Datenbank.

Also beginnen wir mit der Erstellung der Datenbank. Das kann auf bplaced.net sehr kompfortabel über phpmyadmin erledigt werden.

PhpMyAdmin Tutorial: http://www.vms-tutorial.de/wiki/PhpMyAdmin#Zugang_zu_phpMyAdmin

Ich will mich hier nicht weiter mit Datenbankgrundlagen beschäftigen. Einschlägige Tutorials sind überall im Netz zu finden. Zum Beispiel hier: PHP und MySQL Einführung

Datenbank erstellen

Wir erstellen also hier nun eine Datenbank laut Anleitung: http://eass.bplaced.net/4-Datenbanken.

Beliebiger Name, bei mir: „facebook„. Und darin eine Tabelle mit dem Namen „Ergebnisse„.

In dieser Tabelle legen wir nun 4 Spalten an:

  1. ID: mit autoincrement, bewirkt eine eindeutige Kennung für jeden Eintrag
  2. fbid: speichert in userem Beispiel eine Zahl vom Typ bigint mit den jeweiligen Facebook-UserID’s
  3. name: Name des Facebook-Users als String
  4. score:  Das Ergebnis bei einem fiktiven Facebook-Spiel

Datenbank füllen:

Wenn die Struktur der Datenbank steht können wir versuchen Daten mit PHP per get oder post über http in die Datenbank einzutragen.

Hilfreich ist hier diese Seite: http://www.tutorialspoint.com/mysql/index.htm. Sie enthält für alle wichtigen Aufgaben Quellcode in SQL und PHP. Mehr braucht man nur selten!

Vorsicht: XXXXXX ist mit entsprechenden Werten zu ersetzten!

<?PHP

//Variablen für den Verbindugsaufbau
$dbhost = 'localhost';
$dbuser = 'XXXXXX';
$dbpass = 'XXXXXX';
$dbname = 'kobe_facebook';

//Verbindung wird aufgebaut
$dblink = mysql_connect("$dbhost", "$dbuser", "$dbpass")  or die ('I cannot connect to the database because: ' . mysql_error());
mysql_select_db("$dbname");

//Variablen werden aus über GET entgegengenommen.
$fbid = $_GET['fbid'];
$name = $_GET['name'];
$score = $_GET['score'];

//Variablen werden in die Datenbank geschrieben
$result = mysql_query("INSERT INTO ergebnisse (fbid, name, score) VALUES ('$fbid','$name','$score')");

//Abfrage auf Fehler
if(! $result )
{
die('Could not enter data: ' . mysql_error());
}

//Datenbankverbindung wird geschlossen
mysql_close($dblink);
?>

So. Will man dieses Script mit ProcessingJS nutzen kann, muss man es im Sketchordner unter /template_js speichern. Ist der Ordner nicht vorhanden, kann er mit im Processing Menü mit –> JavaScript –> Show Custom Template erstellt werden. Wenn man nun den Sketch „Exportiert“, wird von Processing automatisch ein Ordner mit dem Namen applet_js erstellt, der dann alles nötige (auch den Inhalt von /template_js) enthält. Der Inhalt dieses Ordners wird jetzt per FTP (z.B.: mit Filezilla) auf bplaced hochgeladen.

Nun ist es möglich die Tabellen-Spalten fbid, name und score mit den von uns über http:// übertragenen Werten zu befüllen.

Ein entsprechender Request könnte so aussehen:

http://username.bplaced.net/folder/setfb.php?fbid=88888888&name=richard%20stallman&score=89

Wobei username der Benutzername bei pblaced und folder der Ordner auf pblaced ist, in dem sich das PHP-Script befindet.

Wenn eine Probeübertragung geklappt hat, kann man nun auch Scripte schreiben, die Daten auslesen, oder die Datenbank updaten.

Daten auslesen:

Damit man überhaupt, um bei unserem Beispiel HighscoreListe zu bleiben, entschieden werden kann, ob jemand schon registriert ist, oder nicht, nehmen wir eine kleine Änderung im Code vor (nach dem Verbindungsaufbau):

//Variablen werden aus über GET entgegengenommen.
$fbid = $_GET['fbid'];

//der Eintrag zur entsprechenden fbid wird aufgerufen
$result = mysql_query("SELECT * FROM ergebnisse WHERE fbid='$fbid'", $dblink);

//kein Eintrag zur fbid
if(mysql_num_rows($result)==0){
echo"noData;";
}

//Eintrag vorhanden
while($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
echo "isthere;";
echo $row['score'];
}

Wenn jemand schon in der Datenbank registriert ist, sollte nämlich kein neuer Eintrag erstellt werden, sondern ein Update stattfinden. Andernfalls wird ein neuer Eintrag erstellt.

Getestet kann das Script so werden:

http://username.bplaced.net/folder/fbidlookup.php?fbid=88888888

Update eines Datensatzes:

Ist zu einem bestimmten User schon ein Eintrag vorhanden und er übertrifft aber seine persönliche Bestmarke, dann soll der Eintrag score ein Update erfahren. Die Umsetzung ist hier so, dass das PHP Script ein Update durchführt, aber nur aufgerufen wird, wenn dies gewünscht ist.


//Variablen werden aus über GET entgegengenommen.
$fbid = $_GET['fbid'];
$name = $_GET['name'];
$score = $_GET['score'];

//Update des Wertes für score
$result = mysql_query("UPDATE ergebnisse SET score=$score WHERE fbid=$fbid");

//Falls das nicht funktioniert hat
if(! $result )
{
die('Could not enter data: ' . mysql_error());
}

http://username.bplaced.net/folder/updatefb.php?fbid=88888888&name=richard%20stallman&score=89

Kommunikation Processing mit PHP:

Processing bringt die Funktion loadStrings() mit, die  Ergebnisse einer solchen Anfrage in ein StringArray schreibt. Dieses Array kann dann mit Hilfer der split()- Funktion weiter zerlegt werden. So werden auch komplexe Abfagen mit relativ wenig Code analysierbar.

</pre>
//Aufruf des PHP Scripts fbidlookup mit der fbid als Parameter

String [] InList=loadStrings("http://username.bplaced.net/folder/fbidlookup.php?fbid="+uid);

// Ergebnis ausplitten
String[] list = split(InList[0], ';');
<blockquote>

Ausgabe des Scripts:
InList[0] = isthere;89

list[0]=“isthere“;

list[1]=“89″;

Geschafft. Nur eine Kleinigkeit: list[1] kann natürlich nicht mit einer float oder int -Variable verglichen werden, da sie vom Typ String ist.

Mit der Anweisung

parseFloat(list[1]

kann man mit dem Inhalt aber zum Beispiel eine float Variable füllen.

Fertig!

Ergänzung:

Hat man größere Datenmengen zu übertragen, wird das den Programmablauf empfindlich stören. Die Lösung wäre dann die Abfragen in einem eigenen Thread vorzunehmen.

Threads


Wenn man Daten zur Programmlauffzeit aus einer Datei oder gar dem Internet laden will, hat man ein Problem: Das Programm bleibt stehen und läuft erst dann weiter, wenn die angefragten Daten übermittelt worden sind. Das kann mit unter sehr lange dauern und der User muß warten. Das Programm ruckelt und die User Experience ist schlecht.

Als Lösung für dieses Problem gibt es in Java die Möglichkeit zeitintensive Code- Abschnitte in eigenen Programmabschnitten laufen zu lassen, und nur jeden Frame abzufragen, ob die Daten bereits verfügbar sind, oder nicht.

Sind diese verfügbar, werden sie abgerufen und verarbeitet. Sind sie es nicht, läuft das Programm einfach ohne sie weiter.

Dies ist auch in Processing möglich. Es bringt die Klasse (thread) schon mit. Die einfachste Implementierung würde dann wie folgt aussehen.

void setup() { size(200,200);
// Die Funktion eine Funktion soll in einem separaten thread ausgeführt werden
thread("eineFunction");
}

void draw() {

}

void eineFunction() {
// Diese Funktion wird einem eigenen Thread ausgeführt.
}

Damit kann man nun noch nicht soviel anfangen. Daniel Shiffman hat hier dokumentiert, wie man die Thread-Klasse von Processing so erweitern und adaptieren kann, um sie sinnvoll nutzen zu können.

Ich habe die Klasse dann um eine Abfrage mit der YQL (siehe Geodaten und mehr mit der YQL) erweitert:

Wir schreiben also unsere eingene Klasse, nämlich die SimpleThread-Klasse. Um die Funktionen der Thread Klasse von Processing nutzen zu können, erweitern wir diese mit folgender Code-Zeile:

class SimpleThread extends Thread {

Dann daklarieren wir darin unsere Variablen:

boolean running;           // Is the thread running?  Yes or no?
boolean available;         // Daten verfügbar?  Yes or no?
int wait;                  // How many milliseconds should we wait in between executions?
String id;                 // Thread name
int count;                 // counter
float lat, lng;            // Längen und Breitengrad
PApplet parent;            //Wir brauchen eine Instanz von Processing, damit hier seine Funktionen zur Verfügung stehen
XMLElement xmlResponse;

Einige von diesen ebnötigen wir, damit die Thread Klasse funktioniert, andere um unsere Abfrage laufen lassen zu können. Z.B.: das PApplet parent ist eine Instanz der Processing PApplet Klasse. Diese benötigen wir, um Processing-Funktionen in unserer Klasse nutzen zu können.

Dann folgt der Konstruktor, welcher definiert, welche Variablen bei der Erzeugung einer Instanz an die Thread Klasse übergeben werden müssen. Da ist unter anderem wieder unser PApplet dabei.

// Constructor, create the thread
// It is not running by default
SimpleThread (PApplet theApplet, int w, String s) {
wait = w;
running = false;
id = s;
count = 0;
parent = theApplet;
}

Mit dabei können die originalen Processing Thread-Funktionen überschrieben werden, indem wir sie mit eigenen Funktionen gleichen namens ersetzen und sie um unseren eigenen Code ergänzen.

Ein Thread muss gestartet und beendet werden. Dies machen wir mit void start() und void quit(). Bei der Startfunktion ist die Anweisung in der letzten Zeile interessant: super.start(); Sie ruft die Processing Thread Funktion start() auf, damit nach unseren eigenen Anweisungen auch alles aufgerufen wird, was nötig ist um einen Thread erfolgreich zu starten(),

// Overriding "start()"
void start () {
// Set running equal to true
running = true;
// Print messages
println("Starting thread (will execute every " + wait + " milliseconds.)");
// Do whatever start does in Thread, don't forget this!
super.start();
}
// Our method that quits the thread
void quit() {
System.out.println("Quitting.");
running = false;  // Setting running to false ends the loop in run()
// IUn case the thread is waiting. . .
interrupt();
}

Es folgt nun noch der für uns interessanteste Teil des Ganzen, nämlich die Funktion void run(), welche ausgeführt wird, wenn der Thread läuft. Dort kommen nun unsere Abfragen hinein.

// We must implement run, this gets triggered by start()
void run () {
while (running) {
//println(id + ": " + count);
count++;
loc=loc.replace(' ', '_');
loc=loc.replace(",", "%2C");
println ("loc: "+loc);
try {
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

xmlResponse = new XMLElement(parent, restUrl);
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();
lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};

available=true;

// Ok, let's wait for however long we should wait
try {
sleep((long)(wait));
}
catch (Exception e) {
}
}
System.out.println(id + " thread is done!");  // The thread is done when we get to the end of run()
}

Zu guter Letzt brauchen wir noch einige Funktionen, um mit unserem Thread kommunizieren und ihn steueren zu können. Alles zusammen sieht nun so aus:


SimpleThread thread1;

String loc;

void setup() {
size(200, 200);

//ein neuer Thread wird erzeugt
thread1 = new SimpleThread(this, 100, "a");
thread1.start();
loc = "vienna";
}

void draw() {
background(255);
fill(0);
println(thread1.available());
// wenn die Daten verfügbar sind, werden sie abgerufen und eine neue Abfrage übergeben
if (thread1.available()) {
println(thread1.getLat()+" " +thread1.getLng());
thread1.setLoc("paris");
}
}

class SimpleThread extends Thread {

boolean running;           // Is the thread running?  Yes or no?
boolean available;         // Daten verfügbar?  Yes or no?
int wait;                  // How many milliseconds should we wait in between executions?
String id;                 // Thread name
int count;                 // counter
float lat, lng;            // Längen und Breitengrad
PApplet parent;            //Wir brauchen eine Instanz von Processing, damit hier seine Funktionen zur Verfügung stehen
XMLElement xmlResponse;

// Constructor, create the thread
// It is not running by default
SimpleThread (PApplet theApplet, int w, String s) {
wait = w;
running = false;
id = s;
count = 0;
parent = theApplet;
}

float getLat() {
return lat;
}

float getLng() {
return lng;
}

int getCount() {
return count;
}

boolean available() {
return available;
}

void setLoc(String newLoc) {
available=false;
loc=newLoc;
}

// Overriding "start()"
void start () {
// Set running equal to true
running = true;
// Print messages
println("Starting thread (will execute every " + wait + " milliseconds.)");
// Do whatever start does in Thread, don't forget this!
super.start();
}

// We must implement run, this gets triggered by start()
void run () {
while (running) {
//println(id + ": " + count);
count++;
loc=loc.replace(' ', '_');
loc=loc.replace(",", "%2C");
println ("loc: "+loc);
try {
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

xmlResponse = new XMLElement(parent, restUrl);
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();
lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};

available=true;

// Ok, let's wait for however long we should wait
try {
sleep((long)(wait));
}
catch (Exception e) {
}
}
System.out.println(id + " thread is done!");  // The thread is done when we get to the end of run()
}
// We must implement run, this gets triggered by start()
void run () {
while (running) {
//println(id + ": " + count);
count++;
loc=loc.replace(' ', '_');
loc=loc.replace(",", "%2C");
println ("loc: "+loc);
try {
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

xmlResponse = new XMLElement(parent, restUrl);
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();
lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};

available=true;

// Ok, let's wait for however long we should wait
try {
sleep((long)(wait));
}
catch (Exception e) {
}
}
System.out.println(id + " thread is done!");  // The thread is done when we get to the end of run()
}
// Our method that quits the thread
void quit() {
System.out.println("Quitting.");
running = false;  // Setting running to false ends the loop in run()
// IUn case the thread is waiting. . .
interrupt();
}
}

Geodaten auf Karte einzeichnen


Wenn man Daten visualisieren will, geht es sehr oft darum Geodaten zu verarbeiten und zu modifizieren.

Die Geodaten holen wir uns über die YQL aus Placemarker. Die Koordinaten liegen dann in Form von 2 floats (Breiten– und Längengrad) vor.

Beim Zeichnen der Koordinaten auf einer (Welt-) Karte ergibt sich zunächst das Problem, dass man eine Kugeloberfläche nicht verzerrungsfrei auf einer ebenen Fläche abbilden kann. Es gibt sehr viele verschiedene Möglichkeiten, wie man mit dem Problem umgehen kann. Einen guten Überblick liefert diese Seite.

Ich habe mich für die (meiner Meinung nach) einfachsten Möglichkeit der Plate Carrée („plane square“ oder Plattkarte) Projektion entschieden.

Hier kann man die Längen- und Breitenkoordinaten direkt in x- und y Koordinaten umrechnen.

float x = map(longitude, -180, 180, 0, width);
float y = map(latitude, 90, -90, 0, height);
Mehr Info zur „Plattkarte“ gibt es auf Wikipedia.

Für diese Projekton hat Till Nagel eine eigene Placemarker-Klasse geschrieben, die ich für mein Projekt leicht verändert habe.

class PlaceMarker {

String name;
float lat;
float lng;

PlaceMarker(String name, float lat, float lng) {
this.name = name;
this.lat = lat;
this.lng = lng;
}

float getX() {
return map(lng, -180, 180, 0, width);
}

float getY() {
return map(lat, 90, -90, 0, height);
}

void display(int results) {

// Equirectangular projection
float x = map(lng, -180, 180, 0, width);
float y = map(lat, 90, -90, 0, height);
}

String toString() {
return name + " (" + lat + ", " + lng + ")";
}
}

try-catch Block


Ein sog. try-catch Block kann genutzt werden, um einen Codeabschnitt zu umschließen, der voraussichtlich einmal eine Fehlermeldung zurück geben wird. Dies ist z.B. bei Schreibvorgängen auf der Festplatte und noch viel eher beim Abrufen von Daten aus dem Internet der Fall.

Der try-catch Block kann einen Fehler (oder besser eine Ausnahme – Exeption) abfangen und so den Absturz des Programms verhindern.

try {
// hier läuft der fehleranfällige Code

} catch (Fehlerklasse Fehlervariable) {

// Fehlerbehandlung
};

Ablauf:

Passiert im try-Abschnitt wirklich ein Fehler (es wird eine Exception „geworfen“), wird die catch-Funktion aufgerufen und die Fehlermeldung in der Fehlervariable gespeichert. Nun hat man die Möglichkeit im Catch Abschnitt mit dem Fehler umzugehen (Abfrage nochmal starten, oder was auch immer….) und die Fehlervariable in der Konsole auszugeben, damit man dort auch sieht, dass ein Fehler passiert ist. Das Programm stürzt ja nicht mehr ab.

Praktisches Beispiel Internet Abfrage :

try {
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

xmlResponse = new XMLElement(parent, restUrl);
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();
float lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
float lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());

PlaceMarker placeMarker = new PlaceMarker(name, col1, col2, lat, lng);
placeMarkers.add(placeMarker);
println("MarkersSize: " + placeMarkers.size());
//println(i + ": " + placeMarker);
//println(i + ". " + name + " (" + lat + ", " + lng + ")");
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};

Man könnte jetzt auf die Idee kommen bei jeder Fehlermeldung einen try – catch Block einzusezen, um den Programmabsturz zu verhindern. Dies sollte man allerdings dringend vermeiden. Die Folge wäre Code, der zwar nicht macht was er soll, aber auch nicht abstürzt und vielleicht nicht einmal eine Fehlermeldung ausspuckt.

Fazit: try-catch Blöcke sollten nicht eingesetzt werden, um schlechte Programmierung zu verschleiern.

Geodaten und mehr mit Yahoo Query Language


Das wollten wir doch immer schon. Geodaten mit beliebigen Suchbegriffen verbinden und noch vieles mehr. Ein Service von Yahoo, nämlich die YQL (Yahoo Query Language) macht es möglich. Es handelt sich dabei um eine an die Syntax von SQL angelehnte Abfragesprache, die mittlerweile sehr vielen Datenbanken abfragen durchführen kann. Eine gute englischsprachige Erklärung zur YQL gibt es von Till Nagel.

Wir nutzen die YQL, um Ortskoordinaten abzufragen. Ein Account bei Yahoo ist dafür nicht einmal nötig.

Info über Datenbanken und eine Abfragekonsole zum Testen der Anfragen gibt es unter: http://developer.yahoo.com/yql/console/

Dort findet man unter den Beispielabfragen:

select * from geo.places where text=“vienna“

Als Antwort auf die Anfrage bekommt man folgende XML Datei:
Sie enhält im Tag centroid die gewünschten Daten, nämlich den mit latitude getagden Breitengrad und den mit longitude getagden Längengrad.

Diese wollen wir nun mit Processing auslesen. Dafür verwenden wir das Processing Objekt XMLElement (ab Processing 2.0 nur noch XML).

Folgende Processing Funktion gibt uns Längen- und Breitengrad zu einem gesuchten Ort zurück.

XMLElement xmlResponse;

void getLoc(String loc) {

// Da die Daten aus Twitter stammen, enthalten sie teilweise Leerzeichen und Beistriche, ersetzt werden müssen,
//um keine Fehlermeldung zu provozieren.
loc=loc.replace(' ', '_');
loc=loc.replace(",", "%2C");
println ("loc: "+loc);

//Es ist nicht sicher, dass der Ortsname auch tatsächlich existiert. Dann würde das Programm abbrechen.
// Um das zu vermeiden wird hier ein try catch Block verwendet.
try {

//Abfrage
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

//Abfangen der Antwort
xmlResponse = new XMLElement(this, restUrl);

//Analyse der Antwort
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();

//Auslesen der Geodaten
float lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
float lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};
}

Link zum Placemarker Projekt: http://developer.yahoo.com/geo/placemaker/