Dies ist eine alte Version des Dokuments!
Analysieren und Generieren von menschlichen Gesichtern mit Hilfe eines Autoencoder-Netzwerkes und einer Hauptkomponentenanalyse.
Gruppe: Lars Bonczek
Dieses Projekt ist inspiriert von Videos verschiedener YouTube-Kanäle, so etwa CodeParade (https://youtu.be/4VAkrUNLKSo) und carykh (https://youtu.be/NTlXEJjfsQU). Bei der Umsetzung habe ich mich aber nicht an diesen Vorbildern orientiert.
Das Ziel meines Projektes war es in erster Linie, eine sogenannte Hauptkomponentenanalyse zu verwenden, um die wichtigsten Merkmale eines menschlichen Gesichtes zu isolieren. Mir war aber schon zu Beginn bewusst, dass eine Hauptkomponentenanalyse alleine für diesen Zweck nur bedingt geeignet ist. Also plante ich, zusätzlich ein Convolutional Neural Network zu entwerfen und trainieren, welches Merkmale aus Bildern menschlicher Gesichter extrahieren kann und aus diesen Merkmalen auch wieder entsprechende Bilder generieren kann. Diese Art von Netzwerk nennt man Autoencoder.
Die Hauptkomponentenanalyse (englisch Pricipal Component Analysis, kurz PCA) ist ein Verfahren aus der Linearen Algebra, bei dem für einen Vektorraum eine neue Basis ermittelt wird, sodass die neuen Basis-Vektoren die Achsen der größten Ausdehnung (Hauptkomponenten) einer Punktwolke in dem Vektorraum beschreiben (siehe Abbildung 1).
Diese Basis-Vektoren lassen sich bestimmen, indem man die Eigenvektoren der Kovarianzmatrix der Punktwolke berechnet. Dies geht wie folgt:
Führt man eine Hauptkomponentenanalyse für Bilder von menschlichen Gesichtern durch, so nennt man die ermittelten Hauptkomponenten auch „Eigengesichter“, da sie menschlichen Gesichtern ähneln. Den durch die Hauptkomponenten aufgespannten Vektorraum nennt man „feature space“.
Wenn die Hauptkomponentenanalyse auf Bilder der Größe $h\times w$ mit drei Farbkanälen angewendet wird, gilt für die Anzahl der Dimensionen $k$ eines jeden Vektors $k=3hw$. Für normale Bildgrößen ist die Durchführung des 3. Schrittes des o. g. Algorithmus somit sehr aufwändig, da die Berechnung $C=\Delta\Delta^T$ mit $\Delta\in\mathbb{R}^{k,n}$ die riesige Matrix $C\in\mathbb{R}^{k,k}$ ergibt. Schon für Bilder der Größe $200\times 200$ hat die Kovarianzmatrix $C$ ganze 14,4 Milliarden Einträge.
In solchen Fällen, in denen $k\gg n$, empfiehlt sich also die folgende Alternative für den Algorithmus oben:
Ein Autoencoder ist ein neuronales Netzwerk, das ohne menschliche Unterstützung eine effiziente Kodierung von sich ähnlichen Daten lernen kann. Alles was man dazu benötigt sind viele Beispiele von der Art Daten, die später kodiert werden soll.
Ein Autoencoder besteht aus zwei Teilen, dem Encoder und dem Decoder. Diese überschneiden sich in der Mitte des Netzwerkes in der Schicht, die nachher die kodierten Daten enthält (siehe Abbildung 2). Diese Schicht hat die Funktion eines „Flaschenhalses“, sie ist signifikant kleiner als die Eingabe- und Ausgabeschicht des Autoencoders. Zum Training erhält das Netzwerk nun Datenvektoren. Der „Flaschenhals“ in der Mitte zwingt den Encoder-Teil nun dazu, diese Vektoren zu abstrahieren und durch kleinere Code-Vektoren darzustellen. Der Decoder-Teil hat die Aufgabe, diese Code-Vektoren zu dekodieren und dabei so nah wie möglich an die originalen Datenvektoren zu kommen. Trainiert man dieses Netzwerk mit ausreichend vielen Beispielen, so wird es mit der Zeit immer besser darin, die Originale wiederherzustellen. Möchte man den Autoencoder nun nur zum Kodieren oder nur zum Dekodieren verwenden, kann man einfach nur den Encoder- bzw. Decoder-Teil alleine verwenden.
Autoencoder eignen sich sehr gut dafür, Merkmale von Gesichtern zu extrahieren, da ein Bild eines Gesichts sich sehr viel platzsparender beschreiben lässt, wenn man nicht die RGB-Werte aller Pixel speichert, sondern nur die Merkmale, die ein bestimmtes Gesicht von anderen Gesichtern unterscheidet. Wenn man also die Struktur des Netzwerkes und die Größe des „Flaschenhalses“ geschickt wählt, sollte der Autoencoder automatisch lernen, in den Code-Vektoren die Merkmale der Gesichter abzubilden.
In diesem Projekt sollen Bilder kodiert werden, deshalb empfiehlt sich ein sogenanntes Convolutional Neural Network (kurz CNN). Diese Art von Netzwerk ist gut zur Analyse von Bildern geeignet, da in den ersten Schichten des Netzwerkes zunächst kleine Ausschnitte des vorliegenden Bildes unabhängig voneinander verarbeitet werden. Erst in späteren Schichten werden diese Ausschnitte dann zusammengeführt, sodass das Netzwerk mit jeder Convolution-Schicht immer größere und komplexere Strukturen des Bildes erkennen kann, ohne von Beginn an das Bild in seiner Gesamtheit betrachten zu müssen. Dies spart viele Ressourcen und erzielt trotzdem im Allgemeinen sehr gute Ergebnisse.
Ein CNN besteht im Allgemeinen aus drei Arten von Schichten: Convolutions, Subsampling- bzw. Pooling-Schichten und herkömmlichen Fully-connected-Schichten (siehe Abbildung 3).
Convolutions behalten die Breite und Höhe der dreidimensionalen Neuronen-Matrix, die sie erhalten, bei, und verändern nur die Tiefe. Sie verfügen über einen Filter-Kernel, der eine beliebige Anzahl an Filtern auf jeden Punkt der erhaltenen Matrix inklusive seiner lokalen Umgebung anwendet. Das Ergebnis für jeden Filter wird an dem selben Punkt in die ausgegebene Matrix geschrieben, sodass die Tiefe der ausgegebenen Matrix der Anzahl an Filtern entspricht, die Breite und Höhe aber unverändert bleibt.
Pooling-Schichten reduzieren die Breite und Höhe der erhaltenen Matrix. Sie tun dies, indem sie benachbarte Punkte der Matrix zusammenfassen. Beim Max-Pooling wird dazu für jedes 2×2 Quadrat nur der größte Wert in die ausgegebene Matrix geschrieben. So halbieren sich Höhe und Breite der Matrix, die Tiefe bleibt aber unverändert. Dies hat den Zweck, überflüssige Daten zu verwerfen, sodass spätere Convolutions größere Strukturen erkennen können.
Ein CNN besteht aus einer Abfolge von je einer oder mehreren Convolutions, gefolgt von je einer Pooling-Schicht. Ist die Größe der Neuronen-Matrix ausreichend reduziert, folgen noch eine oder mehrere Fully-connected-Schichten, um die Daten abschließend auszuwerten und beispielsweise zu kategorisieren. Nach jeder Schicht folgt meist die ReLU-Aktivierungsfunktion, die definiert ist als $f(x)=\text{max}(0,x)$.
Mein erster Schritt war es, eine einfache Hauptkomponentenanalyse von Porträtfotos durchzuführen. Dafür suchte ich mir im Internet einen öffentlich zugänglichen Datensatz von Fotos, die von einer Universität zu Forschungszwecken veröffentlicht wurden. Dann implementierte ich die Hauptkomponentenanalyse in Python. Dabei unterliefen mir einige Fehler, sodass die Ergebnisse zunächst nicht meinen Erwartungen entsprachen.
Die ersten fünf Bilder des Utrecht ECVP Datensatzes:
Die ersten fünf berechneten Eigengesichter (mit Fehler in der Berechnung):
Die ersten fünf berechneten Eigengesichter (immer noch fehlerhaft):
Die ersten fünf Eigengesichter (korrekt berechnet):
Die ersten fünf Bilder des Datensatzes nach Abbildung in den „feature space“ mit 95% der Kovarianz:
Das folgende Bild stammt auch aus dem Datensatz, war aber nicht Teil der Hauptkomponentenanalyse. Man sieht daran, dass die Abstraktion nicht besonders gut funktioniert hat. Links ist das Original zu sehen, rechts die Rekonstruktion:
Nachdem die Hauptkomponentenanalyse einigermaßen funktionierte, entwickelte ich eine kleine Webanwendung, die es dem Benutzer ermöglicht, den Bildvektor im „feature space“ mit Hilfe von Schiebereglern zu manipulieren. Diese Webanwendung lief vollständig im Browser des Benutzers, beim Laden der Seite wurden alle Eigenvektoren vom Server heruntergeladen. Für das Laden der Dateien verwendete ich das Skript loadnpy.js. Für die nötigen Berechnungen verwendete ich die Bibliothek numjs.
So sah diese Anwendung aus (der verwendete Datensatz war hier ein anderer):
Das Ergebnis war für mich nicht zufriedenstellend. Da hier jedes dargestellte Gesicht nur eine Linearkombination der ermittelten Hauptkomponenten ist, können die Schieberegler nur lineare Veränderungen an dem Bild vornehmen. Eine Translation, Rotation oder Skalierung des Bildes sind aber keine linearen Veränderungen des Bildvektors. Ich wollte jedoch zum Beispiel die Möglichkeit haben, mit einem Regler die Größe des Gesichts oder die Position des Mundes verändern zu können. Also machte ich mich daran, einen Autoencoder zu Entwerfen und Trainieren. Dieser würde theoretisch beliebig komplexe Merkmale isolieren können.
Nun stand ich vor dem Problem, dass ich keinen ausreichend großen Datensatz von Bildern für das Training finden konnte. Es gibt zwar riesige Datensätze von Bildern, auf denen Gesichter zu sehen sind, die Position und Neigung des Gesichtes variieren dann aber zu stark, um ein Netzwerk zu trainieren. Also schrieb ich ein Skript, welches Gesichter in Bildern sucht und die Bilder dann so rotiert und zuschneidet, dass alle Bilder gleich groß sind und die Augen sich immer an derselben Stelle befinden. Dafür habe ich die Face Recognition Bibliothek verwendet. Alle Bilder wurden auf 224×224 Pixel Größe zugeschitten, da das Netzwerk, welches ich mit den ausgerichteten Bildern trainieren wollte, nur Bilder dieser Größe akzeptiert. Die Bilder wurden außerdem so skaliert, dass der Abstand der Augen immer bei 56 Pixeln liegt.
Ein beispielhaftes Bild, vor und nach der Ausrichtung:
Bilder, die zu niedrig aufgelöst sind, oder wo sich das Gesicht zu nah am Rand des Bildes befindet, werden von dem Skript automatisch verworfen. Es gibt noch weitere Kriterien, nach denen Bilder aussortiert werden. So werden die Abstände der beiden Augen zur Nase verglichen. Diese sollten nicht zu verschieden sein, da die Person sonst vermutlich nach links oder rechts schaut. Außerdem werden Bilder verworfen, die unscharf oder monochrom sind. Dies wird im Abschnitt aligner.py näher beschrieben.
Mithilfe dieses Skripts wandelte ich nun eine Datenbank mit über hundert Gigabyte an Bildern von berühmten Persönlichkeiten aus der Google Bildersuche in einen brauchbaren Datensatz mit gut 60.000 ausgerichteten Bildern um. Also galt es nun, mit diesen Bildern ein Netzwerk zu trainieren. Zunächst versuchte ich, ein eigenes Autoencoder-Netzwerk von Grund auf zu entwerfen und trainieren. Dies scheiterte aber an meiner fehlenden Erfahrung mit Neuronalen Netzwerken und schlicht an fehlender Rechenleistung. Deswegen entschied ich mich dafür, ein bewährtes CNN zu verwenden, das auf die Klassifizierung von Bildern trainiert wurde, und dieses für meine Zwecke anzupassen und weiter zu trainieren. Meine Wahl viel nach einiger Recherche auf das VGG16-Netzwerk. Dabei handelt es sich um ein CNN mit 13 Convolutions und 3 Fully-connected-Schichten (siehe Abbildung 11), welches bei der ImageNet Large Scale Visual Recognition Challenge 2014 eingereicht wurde und eine Genauigkeit von 92,7% bei der top-5 Klassifizierung der Bilder aus der ImageNet-Datenbank erreichte (Simonyan & Zisserman, 2014).
Um aus diesem Klassifizierungs-Netzwerk einen Autoencoder zu machen, musste ich das Netzwerk einfach an der Ausgabeschicht spiegeln. Die Ausgabeschicht wurde so der „Flaschenhals“ des Autoencoders. Das originale VGG16-Netzwerk hat allerdings eine Softmax-Aktivierungsfunktion als Ausgabeschicht. Die Softmax-Funktion sorgt dafür, dass die Aktivitätslevel aller Neuronen der Ausgabeschicht zwischen null und eins liegen und dass deren Summe eins ist. Dies ist für Klassifizierungsaufgaben essentiell, da so das Aktivitätslevel jedes einzelnen Ausgabe-Neurons als die Wahrscheinlichkeit interpretiert werden kann, mit der das betrachtete Bild zu der entsprechenden Klasse gehört. Die Summe aller Wahrscheinlichkeiten muss natürlich eins, also 100%, sein. In meinem Fall gab es aber keinen Grund dafür, deshalb verwendete ich stattdessen eine ReLU-Aktivierungsfunktion. Nur als Ausgabeschicht meines Autoencoders verwendete ich nicht ReLU sondern die Sigmoid-Aktivierungsfunktion, die nur Werte zwischen null und eins ausgibt. So konnte ich die Ausgabe mit 255 multiplizieren und sofort als gültiges Bild verwenden. Dies ist die Struktur des Netzwerkes, welches ich letztendlich trainierte:
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 224, 224, 3) 0 _________________________________________________________________ conv2d_1 (Conv2D) (None, 224, 224, 64) 1792 _________________________________________________________________ conv2d_2 (Conv2D) (None, 224, 224, 64) 36928 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 112, 112, 64) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 112, 112, 128) 73856 _________________________________________________________________ conv2d_4 (Conv2D) (None, 112, 112, 128) 147584 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 56, 56, 128) 0 _________________________________________________________________ conv2d_5 (Conv2D) (None, 56, 56, 256) 295168 _________________________________________________________________ conv2d_6 (Conv2D) (None, 56, 56, 256) 590080 _________________________________________________________________ conv2d_7 (Conv2D) (None, 56, 56, 256) 590080 _________________________________________________________________ max_pooling2d_3 (MaxPooling2 (None, 28, 28, 256) 0 _________________________________________________________________ conv2d_8 (Conv2D) (None, 28, 28, 512) 1180160 _________________________________________________________________ conv2d_9 (Conv2D) (None, 28, 28, 512) 2359808 _________________________________________________________________ conv2d_10 (Conv2D) (None, 28, 28, 512) 2359808 _________________________________________________________________ max_pooling2d_4 (MaxPooling2 (None, 14, 14, 512) 0 _________________________________________________________________ conv2d_11 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ conv2d_12 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ conv2d_13 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ max_pooling2d_5 (MaxPooling2 (None, 7, 7, 512) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 25088) 0 _________________________________________________________________ dense_1 (Dense) (None, 4096) 102764544 _________________________________________________________________ dense_2 (Dense) (None, 4096) 16781312 _________________________________________________________________ dense_3 (Dense) (None, 1000) 4097000 _________________________________________________________________ dense_4 (Dense) (None, 4096) 4100096 _________________________________________________________________ dense_5 (Dense) (None, 4096) 16781312 _________________________________________________________________ dense_6 (Dense) (None, 25088) 102785536 _________________________________________________________________ reshape_1 (Reshape) (None, 7, 7, 512) 0 _________________________________________________________________ up_sampling2d_1 (UpSampling2 (None, 14, 14, 512) 0 _________________________________________________________________ conv2d_14 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ conv2d_15 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ conv2d_16 (Conv2D) (None, 14, 14, 512) 2359808 _________________________________________________________________ up_sampling2d_2 (UpSampling2 (None, 28, 28, 512) 0 _________________________________________________________________ conv2d_17 (Conv2D) (None, 28, 28, 512) 2359808 _________________________________________________________________ conv2d_18 (Conv2D) (None, 28, 28, 512) 2359808 _________________________________________________________________ conv2d_19 (Conv2D) (None, 28, 28, 256) 1179904 _________________________________________________________________ up_sampling2d_3 (UpSampling2 (None, 56, 56, 256) 0 _________________________________________________________________ conv2d_20 (Conv2D) (None, 56, 56, 256) 590080 _________________________________________________________________ conv2d_21 (Conv2D) (None, 56, 56, 256) 590080 _________________________________________________________________ conv2d_22 (Conv2D) (None, 56, 56, 128) 295040 _________________________________________________________________ up_sampling2d_4 (UpSampling2 (None, 112, 112, 128) 0 _________________________________________________________________ conv2d_23 (Conv2D) (None, 112, 112, 128) 147584 _________________________________________________________________ conv2d_24 (Conv2D) (None, 112, 112, 64) 73792 _________________________________________________________________ up_sampling2d_5 (UpSampling2 (None, 224, 224, 64) 0 _________________________________________________________________ conv2d_25 (Conv2D) (None, 224, 224, 64) 36928 _________________________________________________________________ conv2d_26 (Conv2D) (None, 224, 224, 3) 1731 ================================================================= Total params: 276,738,667 Trainable params: 262,023,979 Non-trainable params: 14,714,688 _________________________________________________________________
Dieses Netzwerk kann mit dem folgenden Befehl erzeugt werden:
python autoencoder.py --network-pattern="conv(3,3,64)>conv(3,3,64)>maxpool(2,2)>conv(3,3,128)>conv(3,3,128)>maxpool(2,2)>conv(3,3,256)>conv(3,3,256)>conv(3,3,256)>maxpool(2,2)>conv(3,3,512)>conv(3,3,512)>conv(3,3,512)>maxpool(2,2)>conv(3,3,512)>conv(3,3,512)>conv(3,3,512)>maxpool(2,2)>flatten()>dense(4096)>dense(4096)>dense(1000)" --shape=224,224,3 --out=<out_dir>
Dabei ist <out_dir>
das Ausgabeverzeichnis, in dem die Netzwerk-Datei gespeichert wird. Weitere Information zu diesem Befehl gibt es im Abschnitt autoencoder.py.
Dieses Netzwerk trainierte ich zunächst mit einem kleinen Teil der Trainingsdaten und, nachdem ich vielversprechende Ergebnisse erhielt, mit allen 63.943 Bildern. Davon verwendete ich 20%, also 12788 Bilder, zur Validierung. Auf den folgenden Bildern ist links ein Originalbild zu sehen und rechts die Rekonstruktion des Autoencoders nach 50 Epochen Training:
Wie im letzten Beispiel zu erkennen ist, gab es unter den ausgerichteten Bildern auch einige Bilder, die eigentlich keine menschlichen Gesichter enthalten, aber trotzdem so erkannt wurden. Dies kann zu interessanten Ergebnissen führen.
Die ursprünglich entwickelte Webanwendung konnte ich so nicht mehr verwenden. Bisher hatte die Anwendung ja alle Bilder auf der Seite des Benutzers im Browser berechnet. Dies war nun nicht mehr möglich, da der Browser die Kodierung und Dekodierung der Bilder mithilfe des Autoencoders hätte vornehmen müssen. Also entschied ich mich dazu, die Anwendung so umzugestalten, dass der Browser nur die Werte der Schieberegler an den Server sendet und dieser dann das Bild dazu generiert. Dazu verwendete ich das Framework Flask und für die Darstellung im Browser noch Bootstrap und jQuery. Die neue Version der Webanwendung sah dann so aus:
Eine neue Funktion dieser Version ist die Möglichkeit, ein eigenes Bild hochzuladen. Der Server sucht dann nach einem Gesicht in dem Bild, richtet dieses aus und versucht es möglichst gut zu rekonstruieren. Außerdem gibt es nun die Möglichkeit, zufällige Gesichter zu generieren. Dabei wird die Box-Muller-Methode eingesetzt, sodass die zufällig erzeugten Gesichter normalverteilt sind und dieselbe Standardabweichung aufweisen wie die Trainingsdaten.
Das Ergebnis dieses Projektes ist eine Reihe von Python-Scripts, die verwendet werden können, um die folgenden Operationen durchzuführen:
Außerdem gibt es eine Webanwendung, die für eine beliebige Gesichts-Kodierung das entsprechende dekodierte Gesicht anzeigen kann. Die Parameter des kodierten Gesichts können mit Schiebereglern angepasst werden, sodass man in Echtzeit den Einfluss der Parameter auf das dekodierte Gesicht sieht.
Das Skript aligner.py
findet in einer Auswahl von unbekannten Bildern menschliche Gesichter und schneidet diese aus. Dabei werden die Bilder so skaliert und rotiert, dass Höhe und Breite der resultierenden Bilder den festgelegten Werten entsprechen, und sodass sich die Augen der Personen in einem festgelegten Abstand in der Mitte der Bilder befinden. Außerdem werden Bilder aussortiert, die monochrom, unscharf oder zu niedrig aufgelöst sind. Der ganze Vorgang kann auf mehrere, parallel laufende Prozesse aufgeteilt werden.
Die Schärfe eines Bildes wird durch die Formel $$LAP\_VAR(I)=\sum_m^M\sum_n^N\left(\left|L(m,n)\right|-\bar{L}\right)^2$$ berechnet, wobei $\bar{L}$ definiert ist als $$\bar{L}=\frac{1}{NM}\sum_m^M\sum_n^N\left|L(m,n)\right|$$ und $L(m,n)$ die Convolution des Bildes mit der Laplace'schen Maske $$L=\frac{1}{6} \left(\begin{array}{rrr} 0 & -1 & 0 \\ -1 & 4 & -1 \\ 0 & -1 & 0 \\ \end{array}\right)$$ ist (Pech-Pacheco, Cristóbal, Chamorro-Martínez & Fernández-Valdivia, 2000). Dies ist wie folgt implementiert:
def get_sharpness(image): return np.var(np.abs(ndimage.laplace(np.mean(image, axis=2))))
Um zu ermitteln, ob ein Bild monochrom ist, wird das Bild zunächst in einen neuen Farbraum übertragen. \begin{align*} rg \;&=\; R-G\\ yb \;&=\; \frac{1}{2}(R+G)-B \end{align*} Nun kann die Farbigkeit des Bildes nach der Formel \begin{align*} \hat{M}^{(3)} \;&=\; \sigma_{rgyb}+0.3*\mu_{rgyb},\\ \sigma_{rgyb} \;&:=\; \sqrt{\sigma_{rg}^2+\sigma_{yb}^2},\\ \mu_{rgyb} \;&:=\; \sqrt{\mu_{rg}^2+\mu_{yb}^2}, \end{align*} berechnet werden, wobei $\sigma_{rg}$ und $\mu_{rg}$ die Standardabweichung und der Mittelwert von $rg$ sind und $\sigma_{yb}$ und $\mu_{yb}$ die Standardabweichung und der Mittelwert von $yb$ (Hassler & Süsstrunk, 2003). Dies ist wie folgt implementiert:
def get_color_level(image): r = image[:,:,0].flatten().astype('int32') g = image[:,:,1].flatten().astype('int32') b = image[:,:,2].flatten().astype('int32') rg = np.absolute(r - g) yb = np.absolute(0.5 * (r + g) - b) rg_mean = np.mean(rg) rg_std = np.std(rg) yb_mean = np.mean(yb) yb_std = np.std(yb) std = np.sqrt((rg_std ** 2) + (yb_std ** 2)) mean = np.sqrt((rg_mean ** 2) + (yb_mean ** 2)) return std + (0.3 * mean)
python aligner.py [options] <target_height> <target_width> <target_eye_width> <image_file_pattern> <out_dir>
Durchsucht alle Bild-Dateien, die durch das Muster image_file_pattern
beschrieben werden. Das Muster darf die Wildcards *
und ?
enthalten, in einer Linux-Shell müssen diese aber escaped werden (z.B. files/\*.png
). Alle gefundenen Gesichter werden in dem Verzeichnis out_dir
gespeichert.
Verfügbare Optionen:
--processes=<nr>
--skip=<n>
--max-nose-ratio=<value>
--min-color=<value>
--min-sharpness=<value>
Das Skript grouper.py
gruppiert Bilder, die Gesichter enthalten, nach Ähnlichkeit. Im Optimalfall werden so alle Bilder von derselben Person einer Gruppe zugeordnet. Damit nicht alle Bilder zuerst analysiert und dann untereinander verglichen werden müssen (wie etwa beim Single-/Full-Linkage oder K-means Clustering) ist der Gruppierungsalgorithmus sehr primitiv:
Dieser Algorithmus ist zwar sehr effizient, da jedes Bild nur einmal betrachtet werden muss und sofort einer Gruppe zugewiesen werden kann, allerdings hängt das Ergebnis dieses Algorithmus stark von der Reihenfolge ab, in der die Bilder betrachtet werden. Da dieses Skript aber letztendlich sowieso keine wichtige Rolle gespielt hat, habe ich es so belassen.
python grouper.py [options] <image_file_pattern> <out_dir>
Gruppiert alle Bild-Dateien, die durch das Muster image_file_pattern
beschrieben werden. Das Muster darf die Wildcards *
und ?
enthalten, in einer Linux-Shell müssen diese aber escaped werden (z.B. files/\*.png
). Die gruppierten Gesichter werden in dem Verzeichnis out_dir
gespeichert.
Verfügbare Optionen:
--tolerance=<value>
--move
--group-in-dirs
--save-face-encodings
Das Skript autoencoder.py
kann zum Erstellen und Trainieren eines Autoencoders und zum Kodieren und Dekodieren von Bildern mit diesem Autoencoder verwendet werden.
python autoencoder.py [-s] [-t] [-d] [-e] [options] [--out=<out_dir>]
Mit den Optionen -s
, -t
, -e
und -d
können die Operationen gewählt werden, die durchgeführt werden sollen.
-s
gibt eine Zusammenfassung des geladenen Autoencoders aus.-t
trainiert den geladenen Autoencoder.-e
kodiert die geladenen Bilder mit dem geladenen Autoencoder.-d
dekodiert die geladenen Bilder mit dem geladenen Autoencoder.
Die Option --out=<out_dir>
gibt das Ausgabeverzeichnis an. An diesem Ort werden alle Dateien abgelegt, die während der Durchführung der gewünschten Operation entstehen.
Optionen zum Laden eines Autoencoders:
--network-file=<network-file>
.h5
-Datei.--recompile
--network-pattern=<network_layout>
--out
angegebenen Verzeichnis. Die Struktur-Syntax wird hier näher beschrieben.--shape=<height>,<width>,<depth>
--images
die Trainingsdaten angegeben werden (s.u.), das Programm erkennt das Format dann automatisch.Optionen zum Laden von (kodierten) Bildern:
--images=<img_file_pattern>
img_file_pattern
beschrieben werden. Das Muster darf die Wildcards *
und ?
enthalten, in einer Linux-Shell müssen diese aber escaped werden (z.B. files/\*.png
).--encoded=<npy_file_pattern>
.npy
-Dateien zum Dekodieren, die durch das Muster npy_file_pattern
beschrieben werden. Das Muster darf die Wildcards *
und ?
enthalten, in einer Linux-Shell müssen diese aber escaped werden (z.B. files/\*.npy
).Optionen zum Training:
--batch-size=<batch_size>
batch_size
für das Training an, d.h. wie viele Bilder gleichzeitig verarbeitet werden. Eine höhere batch_size
beschleunigt das Training, benötigt aber mehr Arbeitsspeicher.--epochs=<epochs>
--checkpoint
aktiviert ist.--checkpoint
val_loss
gesunken ist), gespeichert werden soll.--validation-images=<file_pattern>
img_file_pattern
beschrieben werden. Das Muster darf die Wildcards *
und ?
enthalten, in einer Linux-Shell müssen diese aber escaped werden (z.B. files/\*.png
). Falls diese Option nicht verwendet wird, wird stattdessen ein bestimmter Anteil der Trainingsdaten zur Validierung verwendet. Dieser Anteil kann mit der Option --validation-split
angepasst werden.--validation-split=<validation_split>
--validation-images
nicht verwendet wird. Achtung: Diese Option sollte nicht verwendet werden, falls es möglich sein soll, das Training fortzusetzen, da die Daten zur Validierung so jedes Mal neu zufällig aus den Trainingsdaten gewählt werden. Standard: $0.2$--workers=<nr>
--show-train-history
--save-train-history
history.tsv
im Ausgabeverzeichnis.--initial-epoch=<nr>
Die Netzwerk-Struktur-Syntax ermöglicht es, den Aufbau eines Netzwerkes mithilfe eines simpel aufgebauten Codes zu beschrieben. Dieser besteht aus der Beschreibung der einzelnen Schichten in Folge, getrennt jeweils durch das Zeichen >
. Die Beschreibung einer Schicht ist immer zusammengesetzt aus der Art der Schicht gefolgt von einer Liste von Parametern. So beschreibt zum Beispiel conv(3,3,32)
eine Convolution-Schicht mit einer Kernel-Größe von 3×3 und 32 Filtern. Die folgenden Schicht-Typen sind verfügbar:
conv(x,y,z)
erzeugt eine Convolution-Schicht mit einer Kernel-Größe von $x\times y$ und $z$ Filtern.maxpool(x,y)
erzeugt eine Max-Pooling-Schicht mit einer Pool-Größe von $x\times y$.dense(n)
erzeugt eine Fully-connected-Schicht mit $n$ Neuronen.flatten()
erzeugt eine Schicht, die eine mehrdimensionale Neuronen-Matrix in eine Dimension plättet.Es muss allgemein nur die erste Hälfte des Autoencoders beschrieben werden. Die zweite Hälfte erzeugt das Programm dann automatisch, indem es die angegeben Schichten spiegelt. Dabei werden Max-Pooling-Schichten in Upsampling-Schichten und Flatten-Schichten in Reshape-Schichten umgewandelt.
Ein beispielhafter Autoencoder könnte mit diesem Code erzeugt werden:
python autoencoder.py --network-pattern="conv(3,3,32)>maxpool(2,2)>flatten()>dense(1000)" --shape=100,100,3 --out=<out_dir>
Dieser Code erzeugt das folgende Netzwerk:
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 100, 100, 3) 0 _________________________________________________________________ conv2d_1 (Conv2D) (None, 100, 100, 32) 896 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 50, 50, 32) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 80000) 0 _________________________________________________________________ dense_1 (Dense) (None, 1000) 80001000 _________________________________________________________________ dense_2 (Dense) (None, 80000) 80080000 _________________________________________________________________ reshape_1 (Reshape) (None, 50, 50, 32) 0 _________________________________________________________________ up_sampling2d_1 (UpSampling2 (None, 100, 100, 32) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 100, 100, 3) 867 ================================================================= Total params: 160,082,763 Trainable params: 160,082,763 Non-trainable params: 0 _________________________________________________________________
Das Skript pca.py
führt eine Hauptkomponentenanalyse über eine Liste von Bildern oder Numpy-Arrays durch.
python pca.py [options] <img_file_pattern> <out_dir>
Durchsucht alle Bild- oder .npy
-Dateien, die durch das Muster image_file_pattern
beschrieben werden. Das Muster darf die Wildcards *
und ?
enthalten, in einer Linux-Shell müssen diese aber escaped werden (z.B. files/\*.png
). Die Ergebnisse der Hauptkomponentenanalyse werden in dem Verzeichnis out_dir
in den folgenden Dateien gespeichert:
meanvec.npy
enthält den berechneten Durchschnittsvektor.eigvecs.npy
enthält die berechneten Eigenvektoren/Hauptkomponenten.transformed.npy
enthält die übergebenen Vektoren in den durch die Hauptkomponenten aufgespannten Vektorraum abgebildet.Verfügbare Optionen:
--covar-dropoff=<covar-dropoff>
--calculate-l-matrix
--use-einsum
np.dot
die Funktion np.einsum
zur Berechnung der Kovarianz- oder Hilfsmatrix verwendet werden soll. np.einsum
benötigt weniger Arbeitsspeicher, braucht aber viel länger.Zusätzlich zu den oben genannten Scripts gibt es noch eine interaktive Webanwendung, die für eine beliebige Gesichts-Kodierung das entsprechende dekomprimierte Gesicht anzeigen kann. Die Parameter des kodierten Gesichts können mit Schiebereglern angepasst werden, sodass man in Echtzeit den Einfluss der Parameter auf das dekodierte Gesicht sieht. Diese Anwendung ist mit Hilfe des Web-Frameworks Flask umgesetzt.
Um die Webanwendung mit dem Flask-Webserver auf dem eigenen Computer zu starten, muss zuerst die Konfigurations-Datei src/flaskapp/config.py
angepasst werden. Die Kommentare in der Datei beschreiben die einzelnen Einstellungen.
Nun kann der Server gestartet werden, indem man folgenden Befehl im Hauptverzeichnis des Projektes ausführt:
flask run
Die Webanwendung ist dann unter der URL http://localhost:5000 zu finden.
Pech-Pacheco, J. L., Cristóbal, G., Chamorro-Martínez, J., & Fernández-Valdivia, J. (2000). Diatom autofocusing in brightfield microscopy: a comparative study (http://optica.csic.es/papers/icpr2k.pdf)
Hassler, D., & Süsstrunk, S. (2003). Measuring colourfulness in natural images (https://infoscience.epfl.ch/record/33994/)
Simonyan, K., & Zisserman, A. (2014). Very Deep Convolutional Networks for Large-Scale Image Recognition (https://arxiv.org/abs/1409.1556)
Abbildung 1: Von Nicoguaro - Eigenes Werk, CC BY 4.0, https://commons.wikimedia.org/w/index.php?curid=46871195
Abbildung 2: Von Michela Massi - Eigenes Werk, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=80177333
Abbildung 3: Von Aphex34 - Eigenes Werk, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=45679374
Abbildung 12: https://neurohive.io/en/popular-networks/vgg16/