Archiv der Kategorie: libraries

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

Image


// 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

ImageImageImageImageImageImage

// 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)

ImageImageImageImageImage


// 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()

Image


// 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

Image



// 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);
}

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

Image


// 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

Image

// 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

Image

// 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

Image

// 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.

Image

// 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();
}

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.

Image

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.

Image

Beispiel: Einfache Bildmanipulation unter Verwendung der OpenCV Buffer und Filter

ImageImageImageImageImageImageImageImageImageImageImageImageImageImageImage


// 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.:

Image

// 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

}

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

Image

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

Image

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]);
}

Twitter API für Processing


Processing 2.0

Eine sehr gute Beschreibung der Funktionsweise in englischer Sprache findet man hier: http://blog.blprnt.com/blog/blprnt/updated-quick-tutorial-processing-twitter.

1. Twitter Library herunterladen und im Sketch platzieren

Für die Kommunikation mit der Twitter API gibt es schon eine fertige Java Library, nämlich twitter4j. Du kannst „latest stable version“  hier herunterladen. Nutzen kann man die Library, nachdem man .zip Datei extrahiert und dann das Java Archiv twitter4j-core-2.2.5.jar in den (villeicht noch nicht vorhandenen) /code Ordner im Sketch Ordner kopiert hat. Man kann das auch bewerkstelligen, indem man die Datei einfach auf die Processing IDE zieht.

Link zur Javadoc von twitter4j.

Sehr interessant ist, dass es von der Library auch eine Android Version gibt. Falls das jemand getestet hat, bitte ich im Feedback.

2. Einen Entwickler Account bei Twitter erstellen

  • Gehe auf die Homepage https://dev.twitter.com/.
  • Klicke „create an App“ und dann „sign in“
  • Formular ausfüllen und Nutzungsbedingungen akzeptieren.
  • Nun bist du auf diese Seite gelangt:Image
  • Von dieser Seite brauchst du den Consumer key und das Consumer secret.
  • Klicke Create my access token
  • Nun hast du auch Access token und das Access token secret. Damit kannst du nun deinen Processing Sketch mit der Twitter API verbinden.

3. Sketch erstellen und twitter4j einbinden

import java.util.*;
import twitter4j.conf.*;

//Verbindung herstellen
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setOAuthConsumerKey("eigenerSchlüssel");
cb.setOAuthConsumerSecret("eigenerSchlüssel");
cb.setOAuthAccessToken("eigenesToken");
cb.setOAuthAccessTokenSecret("eigenesToken");

//Abfrage starten
Twitter twitter = new TwitterFactory(cb.build()).getInstance();
Query query = new Query("pink floyd now playing");
query.setRpp(5);

//tryCatch, damit das Programm nicht abstürzt, wenn die Abfrage
//mal schief geht
try {

  //Abrufen der Seiten mit den Suchergebnissen
for (int j=1; j<=15 ; j++) {
QueryResult result = twitter.search(query.page(j));
ArrayList tweets = (ArrayList) result.getTweets();

    //Abrufen der einzelnen Tweets
for (int i = 0; i < tweets.size(); i++) {
Tweet tweet = (Tweet) tweets.get(i);
String user = tweet.getFromUser();
String msg = tweet.getText();
long geo=tweet.getFromUserId();
Date d = tweet.getCreatedAt();

      //Abrufen des Heimatortes des Users
User USER = twitter.showUser(geo);
String location = USER.getLocation();

println("Page: "+j+"  "+i+": Tweet by " + user + " at " + d +"from: "+location );
}
}
}
catch (TwitterException te) {
println("Couldn't connect: " + te);
};

  • Mit der TwitterFactory stellt die twitter4j Library ein Objekt zu Verfügung, das eine Abfrage durchführen kann.Twitter twitter = new TwitterFactory(cb.build()).getInstance();
  • Mittels der Query kann die Abfrage spezifiziert werden.
    Query query = new Query(„pink floyd now playing“);
  • Mit query.setRpp(5) stellt man die Anzahl der Treffer pro Seite ein (maximal 100). Außerdem kann man mit query.since(Datum) und query.until(Datum) die Suche zeitlich eingrenzen.
  • Man kann maximal 15 Seiten mit max. 100 Treffern pro Seite abrufen.
    //Abrufen der Seiten mit den Suchergebnissen

    for (int j=1; j<=15 ; j++) {
  • Die Treffer einer Seite werden in einem QueryResult Objekt gespeichert und dann ine eine ArrayList kopiert.
    QueryResult result = twitter.search(query.page(j));

    ArrayList tweets = (ArrayList) result.getTweets();
  • Aus dieser ArrayList rufen wir dann Treffer für Treffer ab und extrahieren die benötigten Daten aus dem Tweet Objekt.

//Abrufen der einzelnen Tweets
for (int i = 0; i < tweets.size(); i++) {
Tweet tweet = (Tweet) tweets.get(i);
String user = tweet.getFromUser();
String msg = tweet.getText();
long geo=tweet.getFromUserId();
Date d = tweet.getCreatedAt();

  • Falls man noch den Heimatort des Posters abrufen möchte, kann man das mit diesem Code machen:

//Abrufen des Heimatortes des Users
User USER = twitter.showUser(geo);
String location = USER.getLocation();

Da dies die Heimatort Einträge aus dem UserProfil sind, bekommt man natürlich auch Einträge wie: „Zwischen Seite 299 und 300“. Damit muss man leben.

Geomerative Library


Download: http://www.ricardmarxer.com/geomerative/

Diese Library erweitert die graphischen Möglichkeiten von Processing (2D). Sie bietet u.a. die Möglichkeit Punkte an der Kontur von Vektorgraphiken zu finden. In der .zip Datei befinden sich viele Beispieldateien mit Kommentaren.

Sehr hilfreich fand ich auch dieses Tutorial: http://freeartbureau.org/blog/2011/09/18/geomerative-tutorial/.

Mit RFont kann man eine Schrift (eine .ttf – Datei muss sich im /data Ordner des Sketches befinden!!!) laden. Diese wird dann mit einem String verknüpft und einer RGroup zugeordnet. Mit RGroup.getPoints() bekommt man dann die Konturpunkte. Ein Nachteil dieser Methode ist, dass es in weiterer Folge schwierig ist, auf einzelne Buchstaben zuzugreifen und diese dynamisch zu ändern.

Beispiel: Punkte an der Schriftkontur

Image

import geomerative.*;

RFont font;
String BspText = "A B";
RPoint[] pnts;

void setup() {
size(400, 200);
smooth();
stroke(255);
background(100);

// Hier wird die Library initialisiert und die zu verwendende
// Schrift erzeugt.
// Achtung: Schrift muss zuerst mit Tools --> Create Font
// erzeugt werden.
RG.init(this);
font = new RFont("FreeSans.ttf", 150, RFont.CENTER);

// Punkte an der Schriftkontur finden

//Abstand der Punkte
RCommand.setSegmentLength (5);
//Modus
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);

//RCommand.setSegmentStep(5);
//RCommand.setSegmentator(RCommand.UNIFORMSTEP);

//RCommand.setSegmentAngle(random(0,HALF_PI));
//RCommand.setSegmentator(RCommand.ADAPTATIVE);

if (BspText.length() > 0) {

// Erzeugen einer neuer Gruppe von Graphiken
RGroup grp;

//Zuordnen des Texts
grp = font.toGroup(BspText);

//Finden der Punkte
pnts = grp.getPoints();
}
}

void draw() {
translate(width/2, height*7/8);
for (int i=0; i<pnts.length; i++) {
//Zeichnen der Punkte
point(pnts[i].x, pnts[i].y);
}
}

Beispiel:   starte Applet

In diesem Beispiel müssen die Positionen einzelner Buchstaben einer Gruppe verändert werden. Deshalb wird hier nicht mit der RGroup – Klasse, sondern mit der RShape – Klasse gearbeitet.

Image


import geomerative.*;

RShape shp, shp1;
RPoint[] pnts, pnts1;

String BspText = "O c c u p y";
String BspText1 = "W A L L ST";

int occupylen, actframe;

void setup() {
size(1480, 800);
strokeWeight(1);
smooth();
fill(120, 50);
background(0);
translate(width/2, height*4/8);

// Hier wird die Library initialisiert und die zu verwendende
// Schrift erzeugt.
// Achtung: Schrift muss zuerst mit Tools --> Create Font
// erzeugt werden.
RG.init(this);

// 2 Shape Objekte werden zugewiesen
shp = RG.getText(BspText, "Ubuntu-R.ttf", width*8/70, CENTER);
shp1 = RG.getText(BspText1, "Ubuntu-R.ttf", width*8/50, CENTER);

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

// Shape wirde gezeichnet
shp.draw();

// für Abbruch nach einem Durchlauf
pnts = shp.getPoints();
occupylen=pnts.length;

// damit der Text Wall ST wie gewünscht dargestellt wird
shp1.translate(width/20, height*3/8);

//mit children[i] kann man in Shapes immer auf die Buchstaben zugreifen
shp1.children[4].translate(width/5, height/40);
shp1.children[4].scale(0.6, 0.6);
shp1.children[5].translate(width*2/9, height/40);
shp1.children[5].scale(0.6, 0.6);

shp1.draw();

// Hier werden die beiden Shapes zu einer zusammengefügt
shp.addChild(shp1);
pnts = shp.getPoints();
}

void draw() {

translate(width/2, height*4/8);

// damit gleich mit dem Zeichnen begonnen wird
actframe=frameCount+occupylen;

//für Abbruch nach einem Durchlauf der Konturpunkte
if (frameCount<pnts.length) {
for (int j=0; j<pnts.length; j++) {
//Zeichnen der Punkte
if (dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y)<random(0, height)
&& dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y)>height/30
&&(int)random(height/20)==3) {
stroke(map(dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y), height/160, height*3/4, 0, 255),
map(pnts[actframe%pnts.length].x-pnts[j].x, -height/2, height/2, 255, 0),
map(dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y), height/160, height*3/4, 255, 0),
map(dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y), height/160, height*3/4, 10, 0));
line(pnts[actframe%pnts.length].x+random(-height/160, height/160), pnts[actframe%pnts.length].y+random(-height/160, height/160), pnts[j].x+random(-height/160, height/160), pnts[j].y+random(-height/160, height/160));
}
}
}
}

Libraries


Libraties sind Programme, genauer genommen Programmpakete, die Programmierer anderen Processing- Nutzern zur Verfügung stellen. Sie erweitern die ohnehin schon großartigen Möglichkeiten von Processing. Libraries gibt es für alle möglichen Einsatzbereiche. Einige sind schon in der Processing-Installation enthalten. Man nennt diese die Core-Libraries:

  • Video
  • Network
  • DXF Export
  • Java Skript
  • Minim Audio
  • OpenGL
  • OpenGL2
  • PDF Export
  • Serial I/O

Darüber hinaus gibt es noch eine ganze Menge sog. User-Libraries, die frei genutzt werden können, in Punkto Stabilität aber  nicht immer mit den Core- Libraries mithalten können.

Eine gute Übersicht findet man hier auf der offiziellen Processing-Seite.

Um eine Library in Processing zu verwenden muss sie immer separat mit der import– Anweisung in den Sketch eingebunden werden. Außerdem gibt in der Processing IDE auch die Möglichkeit das im Menü unter Sketch –> import Library –> zu erledigen.

import processing.pdf.*;

Installation anderer Libraries
Will man andere Libraries in Processing einbinden, lädt man diese aus dem Net, entpackt sie mit einem entsprechendem Programm und verschiebt sie in den /modes/java/libraries Ordner im Processing Verzeichnis.

Processing neu starten und man kann sie wie oben dann in den Sketch importieren.