OpenCV trainieren

Nun kommen wir zur «face recognition» – wir wollen also nicht nur ein Gesicht erkennen, sondern auch zu welcher Person es gehört.

Der Code dieses Kapitels basiert mit Modifikationen auf einem Github-Artikel von Ramiz Raja

Übersicht

Wir werden in drei Schritten vorgehen:

  1. Daten sammeln – Bilder von Personen sammeln, welche erkannt werden sollen.
  2. Training – Die Bilder und die Namen der dazugehörigen Personen der Gesichtserkennung füttern, so dass diese lernen kann.
  3. Erkennen – Der Gesichtserkennung neue Bilder der erkannten Personen füttern und testen ob diese korrekt erkannt werden.

Gesichtserkennung in OpenCV

OpenCV hat drei verschiedene Methoden eingebaut, wie Gesichter erkannt werden können (3 verschiedene sogenannte «Face Recognizer»):

  1. EigenFaces Face Recognizer - cv2.face.createEigenFaceRecognizer()
  2. FisherFaces Face Recognizer - cv2.face.createFisherFaceRecognizer()
  3. Local Binary Patterns Histograms (LBPH) Face Recognizer - cv2.face.createLBPHFaceRecognizer()

Mit einer Zeile Code können wir die Gesichtserkennungs-Methode umstellen. Wir wollen die drei Methoden kurz miteinander vergleichen:

EigenFaces Face Recognizer

Wenn wir jemanden erkennen, so geschieht dies anhand spezieller Eigenschaften, wie z.B. Augen, Nase, Backen, Stirn und vor allem den Übergängen dazwischen. Wenn wir mehrere Gesichter vergleichen, dann vergleichen wir vor allem diese Teile, weil dort die Unterschiede am deutlichsten sind.

Genau so funktioniert der EigenFaces-Algorithmus: Er schaut sich die Trainingsbilder der Personen an und versucht diejenigen Elemente zu extrahieren, welche sich am meisten hervorheben. Weniger interessante Elemente werden ignoriert, wodurch auch noch gerade Speicher gespart wird. Den extrahierten Elementen sagt man «Principal Components». Sie lassen sich auch darstellen:

«Principal Components» einer Reihe von Gesichter

Die «Principal Components» stellen Gesichter dar. Man sagt ihnen «Eigen Faces».

Der Algorithmus nimmt auch die Lichtverhältnisse der Bilder als wichtig war. Damit ist er nicht immer der best-geeignete Gesichtserkennungs-Algorithmus.

Bei der eigentlichen Erkennung, werden aus dem unbekannten Bild die «Principal Components» extrahiert und mit den trainierten Daten verglichen. So kann das Gesicht einer Person zugeordnet werden.

FisherFaces Face Recognizer

Bei diesem Algorithmus handelt es sich um eine verbesserte Version des EigenFaces-Algorithmus: Statt wichtige Elemente aller Gesichter zu extrahieren, versucht FisherFaces Unterschiede zwischen den Personen zu finden. Auch diese Features lassen sich darstellen:

Unterschiede einer Trainingsreihe

Wiederum stellen die extrahierten Features Gesichter dar. Man sagt diesen «Fisher Faces».

Immer noch ist dieser Algorithmus anfällig auf harte Grenzen, etwa durch Schattenwurf. Diese harten Kanten werden vom Algorithmus als wichtige Features extrahiert.

Local Binary Patterns Histograms (LBPH) Face Recognizer

Die beiden vorherigen Algorithmen werden beide durch die Beleuchtungssitutaion beeinflusst. Der LBPH-Face Recognizer soll dieses Problem lösen.

Dies soll dadurch erreicht werden, dass nicht das Bild als Ganzes angeschaut wird, sondern indem lokale Strukturen gefunden und einzelne Bildpunkte mit ihren Nachbarn verglichen werden. Für jeden Bildpunkt und seine Nachbarn wird so ein lokales Binärmuster erzeugt. Alle solchen Muster eines Bildes werden in Form eines Histogramms abgespeichert. Daher der Name «LBPH».

Soll ein unbekanntes Gesicht zugewiesen werden, so wird daraus ein Histogramm erstellt und dieses mit den Histogrammen vom Training verglichen.

LPB-Bilder mit unterschiedlicher Beleuchtung

Umsetzung in Python

Die Gesichtserkennung kann in 3 Schritte unterteilt werden:

  1. Daten vorbereiten
  2. Face Recognizer trainieren
  3. Face Recognizer testen

Wichtige Module importieren

Zusätzlich zu den bereits im vorherigen Kapitel verwendeten Packages müssen wir in Thonny neu das Package opencv-contrib-python installieren.

Dann können wir die Module wie folgt importieren:

#import OpenCV module
import cv2
#import os module for reading training data directories and paths
import os
#import numpy to convert python lists to numpy arrays as 
#it is needed by OpenCV face recognizers
import numpy as np

Trainings-Daten

Die Bilder für das Training werden in nummerierten Unterordner des Trainings-Ordners training-data abgelegt. Person1 steht also für den Namen der ersten Person und beinhaltet mehrere Bilder mit ihrem Gesicht. Die Struktur sieht wie folgt aus:

projekt-ordner
   |
   |------training-data
   |        |------ Person1
   |        |       |-- 1.jpg
   |        |       |-- 2.jpg
   |        |       |-- ...
   |        |
   |        |------ Person2
   |                |-- 1.jpg
   |                |-- 2.jpg
   |                |-- ...
   |
   |------test-data
            |-- test1.jpg
            |-- test2.jpg
            |-- ...

Erstelle die benötigte Ordner-Struktur und stelle ein Trainings-Set zusammen:

  • mindestens 2 Personen
  • pro Person mindestens 10 Bilder

Sie können Berühmtheiten im Internet suchen, oder Bilder von sich selbst verwenden (sie brauchen aber mindestens 10 Bilder). Legen sie pro Person mindesten ein Bild zur Seite für die Test-Phase. Diese Bild kommt in den Ordner test-data.

Ihr könnt auch Trainingssets untereinander austauschen!

subjects = []

Erstelle in deinem Projekt-Ordner einen neue Python-Datei.

  • Importiere die benötigten Module
  • Lege die leere Liste für deine Personen fest

Trainings-Daten vorbereiten

Der OpenCV-Face Recognizer wünscht sich die Trainingsdaten in einem bestimmten Format. Wir müssen also unsere Daten aufbereiten, so dass wir ihm zwei Listen liefern können: Eine Liste mit dem Gesichtsausschnitt als Bild und eine zweite Liste mit der Nummer der dazugehörigen Person. Für 2 Personen mit je 2 Bildern würden wir also folgendes tun:

PERSON-1 PERSON-2
img1 img1
img2 img2

Dann würden wir in den Bildern die Gesichter erkennen und in einer Liste speichern. In der zweiten Liste wären dann die Nummern der Person:

FACES LABELS
person1_img1_face 1
person1_img2_face 1
person2_img1_face 2
person2_img2_face 2

Die genauen Schritte lassen sich weiter unterteilen:

  1. Lese alle Ordnernamen der Personen im Trainingsordner. In diesem Beispiel also s1, s2, ...
  2. Extrahiere für jede Person die Nummer. Unsere Ordner haben eine spezielle Benennungskonvention: In s1 steht s für «subject» und die 1 ist die Nummer. Die hier extrahierte Nummer wird für die Bilder im nächsten Schritt als label verwendet.
  3. Lies alle Bilder der Person und extrahiere das Gesicht. (Für EigenFaces und FisherFaces muss das Gesicht auf eine Standard-Grösse verkleinert oder vergrössert werden.)
  4. Füge jedes Gesicht der Liste faces und das passende label der Liste labels an der korrekten Stelle hinzu.

Wir schreiben eine Funktion welche ein Gesicht erkennt und extrahiert. Dabei gehen wir davon aus, dass maximal ein Gesicht pro Bild existiert.

#function to detect face using OpenCV
def detect_face(img):
    #convert the test image to gray image as opencv face detector expects gray images
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    #load OpenCV face detector, I am using LBP which is fast
    face_cascade = cv2.CascadeClassifier(cv2.data.lbpcascades + "lbpcascade_frontalface.xml")
    #there is also a more accurate but slow Haar classifier:
    #face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")

    #let's detect multiscale (some images may be closer to camera than others) images
    #result is a list of faces
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5);

    #if no faces are detected then return original img
    if (len(faces) == 0):
        return None, None

    #under the assumption that there will be only one face,
    #extract the face area
    (x, y, w, h) = faces[0]

    #extract and resize face
    face = cv2.resize(gray[y:y+w, x:x+h],(200,200))

    #return only the resized face part of the image
    return face, faces[0]

Jetzt haben wir ein detect_face() und können dieses verwenden, um unsere Daten vorzubereiten. Im Code sind die 4 oben beschrieben Schritte im Kommentar markiert.

#this function will read all persons' training images, detect face from each image
#and will return two lists of exactly same size, one list 
# of faces and another list of labels for each face
def prepare_training_data(data_folder_path):

    #------STEP-1--------
    #get the directories (one directory for each subject) in data folder
    dirs = os.listdir(data_folder_path)

    #list to hold all subject faces
    faces = []
    #list to hold labels for all subjects
    labels = []
    label = 0
    #let's go through each directory and read images within it
    for dir_name in dirs:

        #ignore system files like .DS_Store or Thumbs.db
        if dir_name.startswith(".") or dir_name == "Thumbs.db":
            continue;

        #------STEP-2--------
        subjects.append(dir_name)

        #build path of directory containing images for current subject subject
        #sample subject_dir_path = "training-data/s1"
        subject_dir_path = data_folder_path + "/" + dir_name

        #get the images names that are inside the given subject directory
        subject_images_names = os.listdir(subject_dir_path)

        #------STEP-3--------
        #go through each image name, read image, 
        #detect face and add face to list of faces
        for image_name in subject_images_names:

            #ignore system files like .DS_Store or Thumbs.db
            if image_name.startswith(".") or image_name == "Thumbs.db":
                continue;

            #build image path
            image_path = subject_dir_path + "/" + image_name

            #read image
            image = cv2.imread(image_path)

            #display an image window to show the image 
            cv2.imshow("Training on image...", image)
            cv2.waitKey(100)

            #detect face
            face, rect = detect_face(image)

            #------STEP-4--------
            #for the purpose of this tutorial
            #we will ignore faces that are not detected
            if face is not None:
                #add face to list of faces
                faces.append(face)
                #add label for this face
                labels.append(label)

        label = label + 1

    cv2.destroyAllWindows()

    return faces, labels

Nun wollen wir die Trainings-Daten vorbereiten. Dazu rufen wir unsere prepare_training_data()-Funktion auf und geben einige Infos aus, damit wir wissen was gemacht wird und wie viele Gesichter erkannt wurden.

#let's first prepare our training data
#data will be in two lists of same size
#one list will contain all the faces
#and other list will contain respective labels for each face
print("Preparing data...")
faces, labels = prepare_training_data("training-data")
print("Data prepared")

#print total faces and labels
print("Total faces: ", len(faces))
print("Total labels: ", len(labels))
  • Füge deinem Python-Skript die beiden Funktionen detect_face() und prepare_training_data() hinzu.
  • Rufe nun prepare_training_data() wie oben beschrieben auf und schaue wieviele Gesichter erkannt wurden.

Du solltest einen Output erhalten der in etwa wie folgt aussieht:

Preparing data...
Data prepared
Total faces:  23
Total labels:  23

Die Zahl kann natürlich abweichen, aber beide Listen sollten gleich lange sein!

Trainieren

Wir können nun einen der besprochenen Face Recognizer trainieren. Zuerst müssen wir uns für einen der drei entscheiden:

#create our LBPH face recognizer 
face_recognizer = cv2.face.LBPHFaceRecognizer_create()

#or use EigenFaceRecognizer by replacing above line with 
#face_recognizer = cv2.face.EigenFaceRecognizer_create()

#or use FisherFaceRecognizer by replacing above line with 
#face_recognizer = cv2.face.FisherFaceRecognizer_create()

Dann füttern wir ihm einfach die vorbereiteten Daten in Form der beiden Listen:

#train our face recognizer of our training faces
face_recognizer.train(faces, np.array(labels))

Die labels-Liste müssen wir dabei in ein numpy-Array umwandeln.

Ergänze dein Skript um den obenstehenden Code.

Testen

Im letzten Teil wollen wir nun schauen, ob unserer Face Recognizer korrekt trainiert wurde und die Personen im Test-Set erkennt.

Zuerst definieren wir zwei Hilfsfunktionen um Gesichter zu umranden und mit Text zu beschriften:

#function to draw rectangle on image 
#according to given (x, y) coordinates and 
#given width and height
def draw_rectangle(img, rect):
    (x, y, w, h) = rect
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)

#function to draw text on give image starting from
#passed (x, y) coordinates. 
def draw_text(img, text, x, y):
    cv2.putText(img, text, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)

Nun können wir die predict()-Funktion des Face Recognizers verwenden, um Gesichter zu erkennen und mit Hilfe unserer Hilfsfunktionen zu markieren. Dazu schreiben wir eine eigene predict()-Funktion. (Wir wollen das ja auf alle Bilder im test-Ordner anwenden.)

#this function recognizes the person in image passed
#and draws a rectangle around detected face with name of the 
#subject
def predict(img):
    #detect face from the image
    face, (x,y,w,h) = detect_face(img)

    #predict the image using our face recognizer 
    label, confidence = face_recognizer.predict(face)
    #get name of respective label returned by face recognizer
    label_text = subjects[label]

    #draw a rectangle around face detected
    draw_rectangle(img, (x,y,w,h))
    #draw name of predicted person
    draw_text(img, label_text, x, y-5)
    #draw confidence of predicted person
    draw_text(img, str(int(confidence)), x, y+h+20)

    return img

Jetzt haben wir unsere predict()-Funktion und können sie auf unsere Test-Bilder anwenden:

print("Predicting images...")

test_image_names = os.listdir("test-data")

for test_image_name in test_image_names:
    #ignore system files like .DS_Store or Thumbs.db
    if test_image_name.startswith(".") or test_image_name == "Thumbs.db":
        continue;
    test_img = cv2.imread("test-data/" + test_image_name)
    predicted_img = predict(test_img)
    cv2.imshow(test_image_name, predicted_img)

print("Prediction complete, press any key to close windows")

cv2.waitKey(0)
cv2.destroyAllWindows()
exit()

Füge deinem Skript den restlichen Code hinzu und teste es aus!

  1. Teste die Ergebnisse mit den drei verschiedenen Face Recognizer.
  2. Vergleiche die Ergebnisse in Abhängigkeit der Bild-Grösse (resize in der Funktion detect_face(), Bilder 20x20 bis ca. 800x800 machen Sinn)

Versuche die Zeit zu messen die für das Training verwendet wird. Du kannst das Modul time verwenden:

import time

t = time.process_time()
#do some stuff
elapsed_time = time.process_time() - t
  1. Vergleiche die Trainingszeit für die drei verschiedenen Face Recognizer.
  2. Vergleiche die Trainingszeit in Abhängigkeit der Bild-Grösse (resize in der Funktion detect_face()