Nicht nur Facebook, sondern auch jeder Linux-Nutzer kann, dank freier Libraries, Gesichter aus Fotos extrahieren und realen Personen zuordnen. Mike Schilli zeigt, wie das geht.
Facebook-Nutzer nehmen es schon als gegeben hin, dass das soziale Netzwerk auf hochgeladenen Bildern Personen aus dem Freundeskreis an ihren Gesichtern erkennt. Einige freie Libraries, die jeder Linux-Nutzer von Github herunterladen kann, extrahieren ebenfalls Gesichter aus Fotos und vergleichen sie mit vorher erkannten, erlauben es also dem Heimnutzer, zum Beispiel in der privaten Urlaubsfotosammlung Personen zu erkennen und die Bilder entsprechend zu markieren.
Dabei läuft bei maschineller Gesichtserkennung einiges hinter den Kulissen ab. Zunächst muss ein Algorithmus aus den Millionen von Pixeln eines Fotos ein gesichtsähnliches Objekt herausfieseln (Abbildung 1): Zwei runde, dunklere Bereiche als Augen, ein hervorstehender Zinken in der Mitte als Nase, darunter eine waagrechte Linie als Mund und wiederum darunter ein Kinn – das könnte ein Gesicht sein (Abbildung 2).
Ein gutes Face-Recognition-Programm erkennt aber nicht nur formatfüllende Gesichter auf Porträtfotos, sondern auch solche, die nur ein paar Hundert Pixel umfassen, weil die Personen weiter entfernt sind oder verzerrt, weil sie seitlich in die Kamera schauen.
Zielsicher nach Training
Aus dem großen Rauschen eines Bildes ist alles, was ein Gesicht sein könnte, zu extrahieren. Dazu eignen sich neuronale Netzwerke [2] hervorragend. Sie springen nicht auf fixe Pixelwerte an, denn selbst zwei Fotos von ein und derselben Person unterscheiden sich auf Pixelebene gewaltig. Vielmehr wird das Netzwerk während der Lernphase mit Millionen unterschiedlicher Gesichter trainiert und erkennt anschließend alles zielsicher, was auch nur so ähnlich aussieht.
Installation leicht gemacht
Das Github-Projekt Face_Recognition von Adam Geitgey [3] nutzt die weit verbreitete Library »dlib«, um Gesichter auf Fotos und Videos zu erkennen. Mit
git clone https://github.com/ageitgey/face_recognition.git
legt »git« im lokalen Verzeichnis einen Klon des Github-Repository an. Das dort im Top-Verzeichnis liegende »Dockerfile« übernimmt die Installationsorgie abhängiger Projekte. Der Aufruf
docker build -t face .
im Projektverzeichnis lädt alles Erforderliche vom Netz, kompiliert »dlib« und lädt auch noch ein 100 MByte schweres neuronales Modell, das bereits auf Gesichter trainiert ist, in den Container. Eine Kaffeepause wäre praktisch.
Um das Skript in Listing 1 zur Gesichtserkennung im Container aufzurufen, kopiert der User es in das »examples«-Verzeichnis des Github-Klons und ruft
Listing 1
face-box.py
01 #!/usr/bin/python3
02 import face_recognition as fr
03 import sys
04 from PIL import Image, ImageDraw
05
06 try:
07 _, img_name = sys.argv
08 except:
09 raise SystemExit(
10 "usage: " + sys.argv[0] + " image")
11
12 img = fr.load_image_file(img_name)
13 faces = fr.face_locations(img)
14
15 pil = Image.fromarray(img)
16 pil = pil.convert("RGBA")
17
18 tmp = Image.new(
19 'RGBA', pil.size, (0,0,0,0))
20 draw = ImageDraw.Draw(tmp)
21
22 for (y0, x1, y1, x0) in faces:
23 draw.rectangle(((x0, y0), (x1, y1)),
24 fill=(30, 0, 0, 200))
25 del draw
26 pil = Image.alpha_composite(pil, tmp)
27 pil = pil.convert("RGB")
28
29 img_name = img_name.replace(".", "-box.")
30 pil.save(img_name)
docker run -v `pwd`:/build -it face bash -c "cd /build; python3 face-box.py pic.jpg"
auf. Dies bindet das aktuelle Verzeichnis (in dem das Python-Skript liegt) an das Directory »/build« innerhalb des Containers. Das Bash-Kommando wechselt dorthin und ruft »face-box.py« im Container auf, wo die Gesichtserkennung installiert ist. Als Parameter an »face-box.py« übergibt der Bash-Befehl das Bild »pic.jpg« (Abbildung 1), das den Autor in der höllisch heißen Wüste von Arizona zeigt. Aus dem Skript kommt die Datei »pic-box.jpg«, in der, wie in Abbildung 2 gezeigt, der Algorithmus mein Gesicht unter der Baseball-Kappe gefunden und eingerahmt hat. Da das aktuelle Verzeichnis an den Container gebunden ist, liegt die Box-Datei nach Abschluss des Docker-Kommandos dort auch außerhalb des Containers.
Dabei kommt Listing 1 mit nur 30 Zeilen recht schlank daher, nicht umsonst heißt der Slogan des Projekts “The world’s simplest facial recognition”. Die eigentliche Gesichtserkennung läuft mit »face_locations()« (Zeile 13) auf einem mit »load_image_file()« geladenen Jpeg-Bild ab. Zurück kommt eine Liste mit Rechteckkoordinaten von erkannten Gesichtern, denn der Algorithmus sucht auf Bildern mit mehreren Personen nicht nur ein Gesicht, sondern alle auf einen Rutsch heraus. Die »for«-Schleife in Zeile 22 iteriert über alle Viererblocks dieser Koordinaten und malt mittels der Klasse »ImageDraw« aus dem Fundus der PIL-Library ein halbtransparentes graues Rechteck an den Gesichtskoordinaten.
Nicht blickdicht
Für die Transparenz braucht das Bild erst mal eine temporäre Leinwand in »tmp« mit einem Alpha-Channel (Bildmodus RGBA), der den Durchsichtigkeitswert angibt (200 von 255 in Zeile 24). Der Fill-Parameter für die zu verwendende Farbe steht auf leichtem Rot (30). Allerdings kann die PIL-Library aus einem Bild mit Alpha-Channel kein Jpeg mehr machen, sodass Zeile 27 daraus ein RGB-Bild ohne Alpha-Channel macht, bevor »pil.save()« das Jpeg unter einem Namen mit »-box«-Suffix ablegt. Das Verfahren funktioniert relativ zuverlässig, von einigen krassen Ausreißern abgesehen, wie das in Abbildung 4 gezeigte Bild einer Tropfsteinhöhle zeigt, in deren Stalaktiten (Abbildung 5) das neuronale Netzwerk meint, meine Gesichtszüge zu erkennen.
Nicht perfekt, passt schon
Aber »face_recognition« kann noch mehr als Gesichter in Bildern finden, nämlich erkennen, zu welcher Person das Gesicht gehört. Um zwei in verschiedenen Bildern lokalisierte Gesichter miteinander zu vergleichen, kann der Algorithmus ebenfalls nicht einfach rohe Bilder pixelmäßig abgleichen. Vielmehr muss er die Gesichtsausschnitte normieren, entzerren und dann eine Reihe von Merkmalen extrahieren. Ein Mensch würde vielleicht auf die Größe der Nase oder die Augenfarbe achten, wie hoch die Stirn ist oder die Dicke der Augenbrauen.
Der Algorithmus zur Gesichtserkennung lernt hingegen mit Millionen von Testbildern in der Lernphase anhand von passenden und nicht passenden Bildern, welche Merkmale die meisten Treffer und wenigsten False Positives erzeugen. Hinterher stehen dort aber nur nichtssagende Zahlenkolonnen. Wie beim Machine Learning üblich, weiß danach kein Mensch, aufgrund welcher Argumente der Algorithmus nun entscheidet.
Abbildung 3 zeigt die Eckdaten des aus Abbildung 1 extrahierten Referenzgesichts, die die Funktion »face_encodings()« geliefert hat. Der Algorithmus zum Gesichtsvergleich nimmt von jedem erkannten Gesicht diese Eckdaten und vergleicht sie. Stimmen zwei Datensätze ungefähr überein, handelt es sich wahrscheinlich um dieselbe Person.

Abbildung 3: Die Eckdaten des Gesichts des Autors aus Abbildung 1.
Mit diesem Rüstzeug kann ein Skript ein Gesicht aus dem Referenzbild extrahieren und das Ergebnis mit Gesichtern auf anderen Bildern vergleichen. Als praktische Anwendung habe ich das Skript in Listing 3 auserkoren, das meine eigene Fotosammlung (immerhin umfasst sie 36 525 Aufnahmen) nach solchen Bildern durchsucht, auf denen ich abgebildet bin.
Listing 3
face-search.py
01 #!/usr/bin/python3
02 import face_recognition as fr
03 import dbm
04 import re
05 from photos import photos
06 import sys
07
08 try:
09 _, ref_img_name, search_path = sys.argv
10 except ValueError as e:
11 raise SystemExit("usage: " +
12 sys.argv[0] + " ref_img search_path")
13
14 cache = dbm.open('cache', 'c')
15
16 ref_img = fr.load_image_file(ref_img_name)
17 ref_face = fr.face_encodings(ref_img)[0]
18
19 for photo in photos(search_path):
20 if photo in cache:
21 print(photo + " already seen")
22 continue
23 cache[photo] = "1"
24
25 try:
26 img = fr.load_image_file(photo)
27 except:
28 continue
29
30 for face in fr.face_encodings(img):
31 hits = \
32 fr.compare_faces([ref_face], face)
33 if any(hit for hit in hits):
34 print(photo)
Die Dateihierarchie ist nach dem Schuhschachtelprinzip angelegt. Die Sammlung von Hand zu durchforsten wäre also sehr arbeitsintensiv. Aber ich könnte einem KI-System das Foto aus Abbildung 1 zeigen und durch die Bildersammlung rattern lassen, um zu sehen, ob das Gesicht in Abbildung 1 auch auf anderen Fotos zu erkennen ist.
Ganze Sammlung auspacken
Hierzu definiert Listing 2 zunächst einen Iterator über alle Jpeg-Fotos auf der Festplatte unter dem Verzeichnis »/photos«. Dabei überspringt es andere Formate und alle Einträge in ».cache«-Verzeichnissen, wo mein Bildverarbeitungsprogramm die Thumbnails ablegt, die bei der Gesichtsanalyse außen vor bleiben sollen. Der Iterator »photos()« ab Zeile 5 nimmt das Startverzeichnis entgegen und orgelt dann durch alle gefundenen Dateien, die der »yield()«-Operator in Zeile 12 Stück für Stück ausgibt, wenn das Hauptprogramm nach mehr verlangt.
Listing 2
photos.py
01 #!/usr/bin/python3
02 import os
03 import re
04
05 def photos(dir):
06 for root, dirs, files in os.walk(dir):
07 if re.search(r'\.cache', root):
08 continue
09 for file in files:
10 if re.search(r'jpg$', file,
11 re.IGNORECASE):
12 yield(os.path.join(root, file))
13
14 # testing
15 if __name__ == "__main__":
16 for photo in photos("/photos"):
17 print(photo)
Die Zeilen 8 bis 12 in Listing 3 prüfen, ob der User auf der Kommandozeile sowohl ein Referenzbild als auch den Top-Suchpfad für die Fotos angegeben hat. Das erste Element von »sys.argv« enthält den Skriptnamen, der in der Unterstrich-Variablen (»_«) landet und verworfen wird, dann lädt Zeile 17 das Referenzbild. Die nächste Zeile extrahiert das abgebildete Gesicht unter dem Index 0 der zurückkommenden Koordinaten-Liste.
Später orgelt Zeile 19 durch alle von »photos.py« gefundenen Jpeg-Dateien, für jede ruft das Skript die Funktion »compare_faces()« aus dem Fundus von »face_recognition« mit den Gesichtswerten aus dem Referenzbild auf. Das Konstrukt »any(hit for hit in hits)« prüft, ob nur eines der auf dem aktuellen Bild erkannten Gesichter mit dem auf dem Referenzbild übereinstimmt. In diesem Fall führt eines der Elemente in der Liste »hits« den Wert »True« und Zeile 34 druckt den Pfad zur Bilddatei auf der Standardausgabe aus, wo es der erstaunte User aufschnappt und es sich mit einem Foto-Viewer zu Gemüte führt.
Listing 4 zeigt den Aufruf des Skripts im Docker-Container und dessen Ausgabe. Ich war überwältigt von der Vielzahl von Fotos aus der Perl-Gründerzeit, auf denen der Algorithmus eine jugendlichere Ausgabe von Michael Schilli erkannte.
Listing 4
run.sh
1 $ docker run -v /photos:/photos -v `pwd`:/build -it face bash -c "cd /build; python3 face-search.py me.jpg /photos" 2 /photos/2001/12/29/13:55:38.jpg 3 /photos/2001/07/22/11:47:27.jpg 4 /photos/2001/07/22/10:35:33.jpg 5 /photos/2001/07/22/15:43:23.jpg 6 [...]
So viele Nerds
Das Verfahren ist allerdings nicht perfekt und macht zum Teil Fehler. Bei Bildern, die ich auf Open-Source-Konferenzen geschossen habe und die Dutzende junge Nerds zeigen, nimmt der Algorithmus an, es handle sich um mich, obwohl ich auf den Auslöser gedrückt habe.
Da künstliche Intelligenz verschwenderisch mit Rechenzeit umgeht, dauert das Durchforsten des Bilderbaums lange. Bricht das Skript ab, wäre es bedauerlich, wieder von vorne anfangen zu müssen. Deswegen merkt sich Listing 3 alle bearbeiteten Bilder in der persistenten Datei »cache«. Praktischerweise schließt Python diese bei Programmabbruch, sodass das Skript sie lediglich eingangs mit dem Flag »c« öffnen muss (um sie gegebenenfalls erstmals anzulegen) und dann auf das Dictionary »cache« zugreifen kann, um zu sehen, ob es den Namen der gerade gefundenen Datei bereits enthält.
Brave New World
Da das Verfahren alle Gesichter eines Bildes extrahieren und einordnen kann, eröffnen sich ganz neue Möglichkeiten der Informationsbeschaffung. Anhand einer großen Anzahl von Bildern, zum Beispiel von in der Öffentlichkeit positionierten Überwachungskameras, ergeben sich potente Methoden der Überwachung. Mittels eines Fotos einer Verdachtsperson könnte ein KI-System herausfinden, mit welchen anderen Personen sie sich in der Öffentlichkeit herumtreibt. (uba)
Online PLUS
Im Screencast demonstriert Michael Schilli das Beispiel: https://www.linux-magazin.de/videos/
Infos
-
Listings zu diesem Artikel: https://www.linux-magazin.de/static/listings/magazin/2018/06/snapshot/
-
“Machine Learning is Fun!”, Part 4: https://medium.com/@ageitgey/machine-learning-is-fun-part-4-modern-face-recognition-with-deep-learning-c3cffc121d78
-
“The world’s simplest facial recognition api”: https://github.com/ageitgey/face_recognition#face-recognition












ich verstehe 95% von dem was hier steht nicht ;-) werde das ganze aber morgen ausprobieren weil es genau das ist was ich suche