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

Advertisements

3 Kommentare

  1. Pingback: Processing – Über dieses Weblog « processing – tutorial

  2. Pingback: Coeeu-rndmpt // jeudi 22 novembre |

  3. Pingback: Coeeu-rndmpt // jeudi 22 novembre | Plateforme C

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: