Init.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
**/.DS_Store
|
||||||
BIN
Seminararbeit.pdf
Normal file
BIN
Seminararbeit.pdf
Normal file
Binary file not shown.
689
src/Deepfake.ipynb
Normal file
689
src/Deepfake.ipynb
Normal file
File diff suppressed because one or more lines are too long
247
src/Gesichterextrahierer.py
Normal file
247
src/Gesichterextrahierer.py
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ein Skript das sowohl als eigenständiges Programm genutzt werden kann, als auch als
|
||||||
|
Modul importiert werden kann. Ermöglicht das Extrahieren und bearbeiten von Gesichtern
|
||||||
|
in Videos.
|
||||||
|
|
||||||
|
Nutzung als Modul:
|
||||||
|
1. Mit dem Pfad zur Kaskade initalisieren.
|
||||||
|
2. Optional die Bildergröße der Ausgabe mit 'setzeBildergroesse' setzen.
|
||||||
|
3. Video laden mit 'lade'.
|
||||||
|
4. Video mit den anderen Methoden
|
||||||
|
- fürGesichterMache
|
||||||
|
- extrahiereGesichter
|
||||||
|
- extrahiereUndValidiereGesichter
|
||||||
|
bearbeiten.
|
||||||
|
|
||||||
|
Nutzung als Skript:
|
||||||
|
-> Mit Python3 und der '-h' Option starten um die Hilfe angezeigt zu bekommen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, cv2, time, os
|
||||||
|
import numpy as np
|
||||||
|
import face_recognition
|
||||||
|
|
||||||
|
|
||||||
|
class Gesichterextrahierer:
|
||||||
|
|
||||||
|
def __init__(self, pfad_cascade: str):
|
||||||
|
self.CASCADE = cv2.CascadeClassifier(pfad_cascade)
|
||||||
|
self.bildgroesse_output = 128
|
||||||
|
self.counter = 0
|
||||||
|
|
||||||
|
|
||||||
|
def setzeBildgroesse(self, bildgroesse_output):
|
||||||
|
self.bildgroesse_output = bildgroesse_output
|
||||||
|
|
||||||
|
|
||||||
|
def lade(self, pfad_video):
|
||||||
|
self.pfad_video = pfad_video
|
||||||
|
self.video = cv2.VideoCapture(pfad_video)
|
||||||
|
self.fps = self.video.get(cv2.CAP_PROP_FPS)
|
||||||
|
self.frame_height = int(self.video.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||||
|
self.frame_width = int(self.video.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||||
|
|
||||||
|
|
||||||
|
def play(self):
|
||||||
|
"""
|
||||||
|
Spielt das geladene Video in einem neuen Fenster ab. Escape beendet die Wiedergabe.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
ist_bild, bild = self.video.read() # Nächsten Frame einlesen
|
||||||
|
if cv2.waitKey(1) == 27 or not ist_bild:
|
||||||
|
break
|
||||||
|
cv2.imshow('Deepfakeskript - Video', bild)
|
||||||
|
time.sleep(1/self.fps-1)
|
||||||
|
self.video.set(cv2.CAP_PROP_POS_FRAMES, 1) # Video zurücksetzen, damit es wieder eingelesen werden kann
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
|
||||||
|
|
||||||
|
def fuerUnskalierteGesichterMache(self, funktion, max_anzahl_gesichter, speichern=True) -> int: # Nachträglich hinzugefügt, benötigt Verbesserungen
|
||||||
|
if speichern: # Vorbereitung für das Abspeichern des neuen Videos
|
||||||
|
pfad_Video_ohne_Endung = ".".join(self.pfad_video.split(".")[:-1])
|
||||||
|
video_writer = cv2.VideoWriter(f'{pfad_Video_ohne_Endung}_geändert.mp4', cv2.VideoWriter_fourcc(*'mp4v'), self.fps, (self.frame_width, self.frame_height))
|
||||||
|
|
||||||
|
ist_bild, bild = self.video.read() # Ersten Frame einlesen
|
||||||
|
|
||||||
|
while ist_bild: # Solange das Video nicht zu Ende ist
|
||||||
|
bild_grau = cv2.cvtColor(bild, cv2.COLOR_BGR2GRAY)
|
||||||
|
gesichter = self.CASCADE.detectMultiScale(bild_grau, scaleFactor=1.3, minNeighbors=5, minSize=(self.bildgroesse_output,)*2 ) # Gesichter erkennen
|
||||||
|
if len(gesichter):
|
||||||
|
x, y, breite, hoehe = gesichter[0] # Bearbeitet nur das erste Gesicht
|
||||||
|
gesicht = bild[y:y+hoehe, x:x+breite] # Kopieren des Gesichts
|
||||||
|
rueckgabe_funktion = funktion( gesicht ) # Die übergebene Funktion ausführen
|
||||||
|
if type(rueckgabe_funktion) == bool and rueckgabe_funktion == False: break # Mit dem nächsten Frame fortfahren
|
||||||
|
if speichern:
|
||||||
|
if type(rueckgabe_funktion) != bool:
|
||||||
|
bild[y:y+hoehe, x:x+breite] = rueckgabe_funktion # Rückgabe der Funktion in das Bild einsetzen
|
||||||
|
video_writer.write(bild) # Frame an das neue Video anhängen
|
||||||
|
print("\r"+"Es wurden erfolgreich %d Bilder bearbeitet." % self.counter, end='')
|
||||||
|
self.counter += 1
|
||||||
|
elif speichern:
|
||||||
|
video_writer.write(bild) # Frame an das neue Video anhängen auch wenn kein Gesicht gefunden wurde
|
||||||
|
|
||||||
|
ist_bild, bild = self.video.read() # Nächsten Frame einlesen
|
||||||
|
if self.counter >= max_anzahl_gesichter:
|
||||||
|
break
|
||||||
|
|
||||||
|
if speichern: video_writer.release()
|
||||||
|
self.video.set(cv2.CAP_PROP_POS_FRAMES, 1) # Video zurücksetzen, damit es wieder eingelesen werden kann
|
||||||
|
x = self.counter
|
||||||
|
self.counter = 0
|
||||||
|
return x
|
||||||
|
|
||||||
|
def gesichterEinrahmen(self):
|
||||||
|
def rahmenHinzufuegen(bild):
|
||||||
|
farbe = [0, 0, 255]
|
||||||
|
for y in range(len(bild)):
|
||||||
|
for x in range(len(bild[0])):
|
||||||
|
if x <= 4 or x >= len(bild[0])-4 or y <= 4 or y >= len(bild)-4:
|
||||||
|
bild[y][x] = farbe
|
||||||
|
return bild
|
||||||
|
self.fuerGesichterMache(rahmenHinzufuegen, 10000000)
|
||||||
|
|
||||||
|
def fuerGesichterMache(self, funktion, max_anzahl_gesichter, speichern=True) -> int:
|
||||||
|
"""
|
||||||
|
Fürt die übergebene Funktion für alle Gesichter in dem Video aus. Es werden maximal ein Gesicht pro Bild erkannt.
|
||||||
|
|
||||||
|
|
||||||
|
:funktion: Eine Funktion, der beim Ausführen ein Parameter, der Bildausschnitt des Gesichts, als Liste übergeben wird.
|
||||||
|
Gibt die Funktion False zurück wird das gerade bearbeitete Bild übersprungen.
|
||||||
|
Gibt die Funktion True wird der Counter erhöht und das nächste Bild geladen, sofern nicht das Maximum erreicht wurde.
|
||||||
|
Gibt die Funktion ein Bild als Liste zurück, wird dies anstelle des übergebenen Gesichts in das Bild des Videos eingesetzt. Das Ein- und Ausgabeliste muss die gleiche Form haben.
|
||||||
|
Es kann von der Funktion aus `self.counter` zugegriffen werden, um die Anzahl an bearbeiteten Bildern zu erhalten.
|
||||||
|
:max_anzahl_gesichter: Definiert die Anzahl an Gesichtern, die maximal bearbeitet werden. Die Funktion endet vorzeitig wenn die Videodatei zu Ende ist.
|
||||||
|
:speichern: Bei True wird eine Videodatei abgespeichert, die die mögliche Änderungen beinhaltet.
|
||||||
|
Bei False ist dies nicht der Fall.
|
||||||
|
:return: Gibt die Anzahl an bearbeiteten Bildern zurück.
|
||||||
|
"""
|
||||||
|
if speichern: # Vorbereitung für das Abspeichern des neuen Videos
|
||||||
|
pfad_Video_ohne_Endung = ".".join(self.pfad_video.split(".")[:-1])
|
||||||
|
video_writer = cv2.VideoWriter(f'{pfad_Video_ohne_Endung}_geändert.mp4', cv2.VideoWriter_fourcc(*'mp4v'), self.fps, (self.frame_width, self.frame_height))
|
||||||
|
|
||||||
|
ist_bild, bild = self.video.read() # Ersten Frame einlesen
|
||||||
|
|
||||||
|
while ist_bild: # Solange das Video nicht zu Ende ist
|
||||||
|
bild_grau = cv2.cvtColor(bild, cv2.COLOR_BGR2GRAY)
|
||||||
|
gesichter = self.CASCADE.detectMultiScale(bild_grau, scaleFactor=1.3, minNeighbors=5, minSize=(self.bildgroesse_output,)*2 ) # Gesichter erkennen
|
||||||
|
if len(gesichter):
|
||||||
|
x, y, breite, hoehe = gesichter[0] # Bearbeitet nur das erste Gesicht
|
||||||
|
gesicht = bild[y:y+hoehe, x:x+breite] # Kopieren des Gesichts
|
||||||
|
gesicht_skaliert = cv2.resize( gesicht, (self.bildgroesse_output,)*2 )
|
||||||
|
rueckgabe_funktion = funktion( gesicht_skaliert ) # Die übergebene Funktion ausführen
|
||||||
|
if type(rueckgabe_funktion) == bool and rueckgabe_funktion == False: break # Mit dem nächsten Frame fortfahren
|
||||||
|
if speichern:
|
||||||
|
if type(rueckgabe_funktion) != bool:
|
||||||
|
rueckgabe_funktion = cv2.resize(rueckgabe_funktion, (breite, hoehe))
|
||||||
|
bild[y:y+hoehe, x:x+breite] = rueckgabe_funktion # Rückgabe der Funktion in das Bild einsetzen
|
||||||
|
video_writer.write(bild) # Frame an das neue Video anhängen
|
||||||
|
print("\r"+"Es wurden erfolgreich %d Bilder bearbeitet." % self.counter, end='')
|
||||||
|
self.counter += 1
|
||||||
|
elif speichern:
|
||||||
|
video_writer.write(bild) # Frame an das neue Video anhängen auch wenn kein Gesicht gefunden wurde
|
||||||
|
|
||||||
|
ist_bild, bild = self.video.read() # Nächsten Frame einlesen
|
||||||
|
if self.counter >= max_anzahl_gesichter:
|
||||||
|
break
|
||||||
|
|
||||||
|
if speichern: video_writer.release()
|
||||||
|
self.video.set(cv2.CAP_PROP_POS_FRAMES, 1) # Video zurücksetzen, damit es wieder eingelesen werden kann
|
||||||
|
x = self.counter
|
||||||
|
self.counter = 0
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def extrahiereGesichter(self, max_anzahl_gesichter: int, ordner_ausgabe: str):
|
||||||
|
"""
|
||||||
|
Findet alle Gesichter aus dem Video und speichert sie ab.
|
||||||
|
|
||||||
|
:max_anzahl_gesichter: Definiert die Anzahl an Gesichtern, die maximal bearbeitet werden. Die Funktion endet vorzeitig wenn die Videodatei zu Ende ist.
|
||||||
|
:ordner_ausgabe: Den Pfad zum Ordner in dem die Bilder abgespeichert werden, dieser muss vorhanden sein.
|
||||||
|
"""
|
||||||
|
def funktion(bild):
|
||||||
|
cv2.imwrite(os.path.join(ordner_ausgabe, f'Gesicht_{self.counter}.png'), bild)
|
||||||
|
return True
|
||||||
|
|
||||||
|
print("Exportiere Bilder nach %s." % ordner_ausgabe)
|
||||||
|
anzahl_extrahierter_bilder = self.fuerGesichterMache(funktion, max_anzahl_gesichter, speichern=False)
|
||||||
|
print("\r"+"Es wurden erfolgreich %d Bilder nach %s exportiert." % (anzahl_extrahierter_bilder, ordner_ausgabe))
|
||||||
|
|
||||||
|
|
||||||
|
def extrahiereUndValidiereGesichter(self, referenzbild: list, max_anzahl_gesichter: int, ordner_ausgabe: str, toleranz=0.6):
|
||||||
|
"""
|
||||||
|
Findet alle Gesichter aus dem Video und speichert sie ab, falls sie dem Referenzgesicht ausreichend ähneln.
|
||||||
|
|
||||||
|
:referenzbild: Ein Bild in Form einer Liste das zum Vergleich bei der Validierung verwendet wird.
|
||||||
|
:max_anzahl_gesichter: Definiert die Anzahl an Gesichtern, die maximal bearbeitet werden. Die Funktion endet vorzeitig wenn die Videodatei zu Ende ist.
|
||||||
|
:ordner_ausgabe: Den Pfad zum Ordner in dem die Bilder abgespeichert werden, dieser muss vorhanden sein.
|
||||||
|
:toleranz: Die euklidische Distanz, die maximal zwischen den Vekoren, die die zu vergleichenden Gesichter repräsentieren,
|
||||||
|
liegen darf, damit diese als von der gleichen Person gelten. (siehe face_recongnition.compare_faces)
|
||||||
|
"""
|
||||||
|
encoding_referenz = face_recognition.face_encodings(referenzbild)
|
||||||
|
def funktion(bild):
|
||||||
|
try:
|
||||||
|
encoding_bild = face_recognition.face_encodings(bild)[0] # Gesicht in ein Vektorrepräsentation umwandeln
|
||||||
|
if face_recognition.compare_faces(encoding_referenz, encoding_bild, tolerance=toleranz)[0]:
|
||||||
|
cv2.imwrite(os.path.join(ordner_ausgabe, f'Gesicht_{self.counter}.png'), bild)
|
||||||
|
return True
|
||||||
|
except Exception: pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("Validiere und exportiere Bilder nach %s." % ordner_ausgabe)
|
||||||
|
anzahl_extrahierter_bilder = self.fuerGesichterMache(funktion, max_anzahl_gesichter, speichern=False)
|
||||||
|
print("\r"+"Es wurden erfolgreich %d Bilder nach %s exportiert." % (anzahl_extrahierter_bilder, ordner_ausgabe))
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv): # Wird ausgeführt, wenn das Skript direkt ausgeführt wird
|
||||||
|
bildgroesse_ausgabe = 128
|
||||||
|
max_anzahl_bilder = 50000
|
||||||
|
pfad_cascade = "./daten/cascades/haarcascade_frontalface_default.xml"
|
||||||
|
ordner_ausgabe = "./daten/Gesichter"
|
||||||
|
pfad_validierungbild = ""
|
||||||
|
toleranz = 0.6
|
||||||
|
gesichter_einrahmen = False
|
||||||
|
|
||||||
|
for index, argument in enumerate(argv):
|
||||||
|
if argument[0] == '-':
|
||||||
|
if 'g' == argument[1]:
|
||||||
|
bildgroesse_ausgabe = int(argv[index+1])
|
||||||
|
elif 'a' == argument[1]:
|
||||||
|
max_anzahl_bilder = int(argv[index+1])
|
||||||
|
elif 'c' == argument[1]:
|
||||||
|
pfad_cascade = argv[index+1]
|
||||||
|
elif 'o' == argument[1]:
|
||||||
|
ordner_ausgabe = argv[index+1]
|
||||||
|
elif 'v' == argument[1]:
|
||||||
|
pfad_validierungbild = argv[index+1]
|
||||||
|
elif 't' == argument[1]:
|
||||||
|
toleranz = float(argv[index+1])
|
||||||
|
elif 'r' == argument[1]:
|
||||||
|
gesichter_einrahmen = True
|
||||||
|
elif 'h' == argument[1]:
|
||||||
|
print("Nutzung: %s [Optionen] [Videodatei]\n" % argv[0])
|
||||||
|
print("""Optionen:
|
||||||
|
-g : Größe der Ausgabe Bilder in Pixel
|
||||||
|
-a : Maximale Anzahl der zu extrahierenden Bildern
|
||||||
|
-c : Pfad für die Haarcascade
|
||||||
|
-o : Zielordner für die Ausgabe
|
||||||
|
-h : Drucken dieser Hilfenachricht
|
||||||
|
-v : Pfad zu einem Validierungsbild
|
||||||
|
-t : Toleranz für die Gesichtsvalidierung\n""")
|
||||||
|
return
|
||||||
|
|
||||||
|
g = Gesichterextrahierer(pfad_cascade)
|
||||||
|
g.setzeBildgroesse(bildgroesse_ausgabe)
|
||||||
|
g.lade(argv[-1])
|
||||||
|
if pfad_validierungbild:
|
||||||
|
validierungsbild = cv2.imread(pfad_validierungbild)
|
||||||
|
g.extrahiereUndValidiereGesichter(validierungsbild, max_anzahl_bilder, ordner_ausgabe, toleranz=toleranz)
|
||||||
|
elif gesichter_einrahmen:
|
||||||
|
g.gesichterEinrahmen()
|
||||||
|
else:
|
||||||
|
g.extrahiereGesichter(max_anzahl_bilder, ordner_ausgabe)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv)
|
||||||
60
src/LoggingCallback.py
Normal file
60
src/LoggingCallback.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
"""
|
||||||
|
Die Klasse LoggingCallback, erbt von tensorflow.keras.callbacks.Callback
|
||||||
|
|
||||||
|
Wird als Instanz beim Trianieren mit der fit-Methode als callback übergeben. Die Methoden, welche mit "on_..."
|
||||||
|
beginnen werden zu dem entsprechenden Zeitpunkt wärend des Trainingsprozesses aufgerufen und dokumentieren den
|
||||||
|
Trainingsvortschritt in der Datei 'train.log' im Verzeichnis des Modells. Bei jedem Trainingsbeginn wird ein
|
||||||
|
Gesicht von jeder der beiden Personen durch das Modell geschickt und im Verzeichnis 'Bilder' Abgespeichert.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tensorflow.keras.callbacks
|
||||||
|
import time, os, cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
class LoggingCallback(tensorflow.keras.callbacks.Callback):
|
||||||
|
def __init__(self, pfad_modell: str, bild_A: list, bild_B: list):
|
||||||
|
self.pfad_modell = pfad_modell
|
||||||
|
self.bild_A = bild_A.reshape(1, 128, 128, 3)
|
||||||
|
self.bild_B = bild_B.reshape(1, 128, 128, 3)
|
||||||
|
try: os.mkdir(os.path.join(self.pfad_modell, 'Bilder/'))
|
||||||
|
except FileExistsError: pass
|
||||||
|
|
||||||
|
def log(self, text: str):
|
||||||
|
with open(os.path.join(self.pfad_modell, 'train.log'), "a") as datei:
|
||||||
|
datei.writelines("{};{}\n".format(time.time(), text) )
|
||||||
|
|
||||||
|
def speichereBild(self, bild: list, pfad: str):
|
||||||
|
bild = cv2.normalize(bild, None, alpha = 0, beta = 255, norm_type = cv2.NORM_MINMAX, dtype = cv2.CV_32F)
|
||||||
|
bild = bild.astype(np.uint8)
|
||||||
|
cv2.imwrite(pfad, bild)
|
||||||
|
|
||||||
|
def on_train_begin(self, logs=None):
|
||||||
|
self.log("Starte Training;")
|
||||||
|
predicted_A = self.model.predict(self.bild_A)[0]
|
||||||
|
predicted_B = self.model.predict(self.bild_B)[0]
|
||||||
|
self.speichereBild(predicted_A, os.path.join(self.pfad_modell, "Bilder/{}_Bild_A.png".format(time.time()) ))
|
||||||
|
self.speichereBild(predicted_B, os.path.join(self.pfad_modell, "Bilder/{}_Bild_B.png".format(time.time()) ))
|
||||||
|
|
||||||
|
def on_epoch_begin(self, epoch, logs=None):
|
||||||
|
logString = "on_epoch_begin;epoch;{};".format(epoch)
|
||||||
|
for key, val in logs.items():
|
||||||
|
logString += "{};{};".format(key, val)
|
||||||
|
self.log(logString)
|
||||||
|
|
||||||
|
def on_epoch_end(self, epoch, logs=None):
|
||||||
|
logString = "on_epoch_end;epoch;{};".format(epoch)
|
||||||
|
for key, val in logs.items():
|
||||||
|
logString += "{};{};".format(key, val)
|
||||||
|
self.log(logString)
|
||||||
|
|
||||||
|
def on_train_batch_begin(self, batch, logs=None):
|
||||||
|
logString = "on_train_batch_begin;batch;{};".format(batch)
|
||||||
|
for key, val in logs.items():
|
||||||
|
logString += "{};{};".format(key, val)
|
||||||
|
self.log(logString)
|
||||||
|
|
||||||
|
def on_train_batch_end(self, batch, logs=None):
|
||||||
|
logString = "on_train_batch_end;batch;{};".format(batch)
|
||||||
|
for key, val in logs.items():
|
||||||
|
logString += "{};{};".format(key, val)
|
||||||
|
self.log(logString)
|
||||||
79
src/README.md
Normal file
79
src/README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
___
|
||||||
|
Das Projekt ist wie folgt strukturiert:
|
||||||
|
|
||||||
|
```
|
||||||
|
│
|
||||||
|
├── Deepfake.ipynb
|
||||||
|
├── Gesichterextrahierer.py
|
||||||
|
├── LoggingCallback.py
|
||||||
|
├── README.md
|
||||||
|
├── daten
|
||||||
|
│ ├── cascades
|
||||||
|
│ │ └── haarcascade_frontalface_default.xml
|
||||||
|
│ ├── lernen
|
||||||
|
│ │ └── Gesichter
|
||||||
|
│ │ ├── A
|
||||||
|
│ │ └── B
|
||||||
|
│ └── modelle
|
||||||
|
│ └── Autoencoder
|
||||||
|
│ ├── modell.info
|
||||||
|
│ ├── Biden
|
||||||
|
│ │ ├── train.log
|
||||||
|
│ │ └── Gesichter
|
||||||
|
│ └── Norris
|
||||||
|
│ ├── train.log
|
||||||
|
│ └── Gesichter
|
||||||
|
└── skripte
|
||||||
|
├── extrahiere_val_loss.awk
|
||||||
|
├── frame_shuffle.py
|
||||||
|
├── log_Analyse.awk
|
||||||
|
└── plot.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
```📒 Deepfake.ipynb```
|
||||||
|
_Das zentrale Jupyter Notebook, führe dies aus um ein künstliches Neuronales Netz zu erstellen und zu trainieren._
|
||||||
|
|
||||||
|
```🐍 Gesichterextrahierer.py```
|
||||||
|
_Der Teil des Programms, der es ermöglicht Gesichter aus Videos auszuschneiden und zu manipulieren._
|
||||||
|
|
||||||
|
```🐍 LoggingCallback.py```
|
||||||
|
_Ein Klasse, die dafür zuständig ist den Vortschritt während des Trainierens zu dokumentieren._
|
||||||
|
|
||||||
|
```📃 README.md```
|
||||||
|
_Diese Datei._
|
||||||
|
|
||||||
|
```🧑🏼🦲 haarcascade_frontalface_default.xml```
|
||||||
|
_Eine essenzielle Datei, die für das erkennen von Gesichtern im Gesichterextrahierer notwendig ist._
|
||||||
|
|
||||||
|
```📂 Gesichter```
|
||||||
|
_In diesem Ordner werden in den Unterordnern A und B die Extrahierten Gesichter der Personen gespeichert, deren Gesichter ausgetauscht werden sollen. Diese Bilder werden dann zum Lernen verwendet._
|
||||||
|
|
||||||
|
```📂 modelle```
|
||||||
|
_Hier werden, in einzelnen Unterordnern, die Modelle gespeichert. Wird einem Modell ein neuer Name gegeben, wird hier ein neuer Ordner erstellt._
|
||||||
|
|
||||||
|
```📂 Autoencoder```
|
||||||
|
_Ein beispielhafter Ordner, der die Daten zu dem Modell mit dem Namen 'Autoencoder' enthält. Alle Dateien und Ordner, die hier enthalten sind werden automatisch generiert. Die Unterordner 'Biden' und 'Norris' enthalten jeweils einen Autoencoder, deren Namen zuvor gewählt werden kann._
|
||||||
|
|
||||||
|
```🪵 train.log```
|
||||||
|
_Eine Textdatei, in der Lernfortschritt dokumentiert wird._
|
||||||
|
|
||||||
|
```📂 Bilder```
|
||||||
|
_Im Laufe des Trainings werden immer wieder Bilder mit dem Modell erstellt, welche hier dann abgespeichert werden. Die Bilder, die ein 'A' im Namen haben, wurden mit dem Modell komprimiert und so ähnlich wie möglich wiederhergestellt. Die Bilder, die ein 'B' im Namen haben, wurden komprimiert und als die jeweils andere Person wiederhergestellt. Die Zahl im Namen ist die Unixzeit zu der das Bild entstanden ist._
|
||||||
|
|
||||||
|
```ℹ️ modell.info```
|
||||||
|
_Eine Textdatei mit einer Übersicht über die Struktur des Modells._
|
||||||
|
|
||||||
|
```📂 skripte```
|
||||||
|
_Ein Ordner, der ein paar hilfreich Hilfsprogramme enthält._
|
||||||
|
|
||||||
|
```📄 extrahiere_val_loss.awk```
|
||||||
|
_Ein AWK-Skript, dass eine train.log-Datei einliest und eine Liste an Zeitpunkten mit dem passenden Fehlerwert zurückgibt. Es wird für die plot.sh-Datei benötigt._
|
||||||
|
|
||||||
|
```🐍 frame_shuffle.py```
|
||||||
|
_Ein Python-Programm, dass die Bilder in einem Video mischt._
|
||||||
|
|
||||||
|
```📄 log_Analyse.awk```
|
||||||
|
_Ein AWK-Skript, dass eine übergebene train.log-Datei analysiert und eine Zusammenfassung über den Trainingsverlauf gibt._
|
||||||
|
|
||||||
|
```💲 plot.sh```
|
||||||
|
_Ein Bash-Skript, dass aus einer oder mehreren übergebenen train.log-Dateien einen Diagramm erstellt._
|
||||||
33314
src/daten/cascades/haarcascade_frontalface_default.xml
Normal file
33314
src/daten/cascades/haarcascade_frontalface_default.xml
Normal file
File diff suppressed because it is too large
Load Diff
0
src/daten/lernen/Gesichter/A/.gitkeep
Normal file
0
src/daten/lernen/Gesichter/A/.gitkeep
Normal file
0
src/daten/lernen/Gesichter/B/.gitkeep
Normal file
0
src/daten/lernen/Gesichter/B/.gitkeep
Normal file
7
src/requirements.txt
Normal file
7
src/requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
tensorflow>=2.6.0
|
||||||
|
opencv-contrib-python
|
||||||
|
matplotlib
|
||||||
|
face_recognition
|
||||||
|
pyyaml
|
||||||
|
h5py
|
||||||
|
jupyterlab
|
||||||
30
src/skripte/_gib_gewichte_aus_h5dump.awk
Normal file
30
src/skripte/_gib_gewichte_aus_h5dump.awk
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/{/ && gruppe_model_weights { gruppe_model_weights++ }
|
||||||
|
/}/ && gruppe_model_weights { gruppe_model_weights-- }
|
||||||
|
|
||||||
|
/{/ && gruppe_model_weights && gruppe_layers_1 { gruppe_layers_1++ }
|
||||||
|
/}/ && gruppe_model_weights && gruppe_layers_1 { gruppe_layers_1--; if(!gruppe_layers_1) sub(" >.*$", "", gruppen_namen); }
|
||||||
|
|
||||||
|
/{/ && gruppe_model_weights && gruppe_layers_2 { gruppe_layers_2++ }
|
||||||
|
/}/ && gruppe_model_weights && gruppe_layers_2 { gruppe_layers_2--; if(!gruppe_layers_2) sub(" >.*$", "", gruppen_namen); }
|
||||||
|
|
||||||
|
/GROUP "model_weights"/ { gruppe_model_weights = 1; gruppen_namen = "model_weights" }
|
||||||
|
|
||||||
|
/GROUP/ && !/model_weights/ && gruppe_model_weights {
|
||||||
|
gsub("\"", "", $2);
|
||||||
|
if(!gruppe_layers_1) { gruppe_layers_1 = 1; }
|
||||||
|
else { gruppe_layers_2 = 1; }
|
||||||
|
gruppen_namen = gruppen_namen " > " $2;
|
||||||
|
print "\n#G " gruppen_namen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/(.*): / && gruppe_model_weights && gruppe_layers_1 && gruppe_layers_2 {
|
||||||
|
if( !match($1, idx_regex) || !idx_regex) {
|
||||||
|
idx_regex = $1;
|
||||||
|
sub("[0-9]+\\):", "[0-9]+\\):", idx_regex);
|
||||||
|
sub("\\(", "\\(", idx_regex);
|
||||||
|
name = idx_regex
|
||||||
|
gsub("[\\\\|:]", "", name)
|
||||||
|
print "\n#N " name;
|
||||||
|
}
|
||||||
|
printf("%.4f %.4f %.4f %.4f ", $2, $3, $4, $5);
|
||||||
|
}
|
||||||
7
src/skripte/_speichere_modell_als_h5.py
Normal file
7
src/skripte/_speichere_modell_als_h5.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/python3
|
||||||
|
|
||||||
|
from tensorflow.keras.models import load_model
|
||||||
|
import sys
|
||||||
|
|
||||||
|
modell = load_model(sys.argv[1])
|
||||||
|
modell.save(f"./modell.h5")
|
||||||
38
src/skripte/frame_shuffle.py
Normal file
38
src/skripte/frame_shuffle.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ein simples Skript, das es ermöglicht die Bilder in einem Video zu mischen.
|
||||||
|
Wenn die Datei direkt als Skript ausgeführt wird, wird das erste Argument als Pfad für das zu bearbeitende Video verwendet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import cv2, sys, random, time
|
||||||
|
|
||||||
|
def shuffle(pfad_video):
|
||||||
|
video = cv2.VideoCapture(pfad_video)
|
||||||
|
fps = video.get(cv2.CAP_PROP_FPS)
|
||||||
|
frame_count = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||||
|
frame_height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||||
|
frame_width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||||
|
|
||||||
|
pfad_video_ohne_endung = '.'.join(pfad_video.split('.')[:-1])
|
||||||
|
|
||||||
|
video_writer = cv2.VideoWriter(f'{pfad_video_ohne_endung}_gemischt.mp4', cv2.VideoWriter_fourcc(*'mp4v'), fps, (frame_width, frame_height))
|
||||||
|
|
||||||
|
frame_num_liste = list(range(1, frame_count+1))
|
||||||
|
random.shuffle(frame_num_liste)
|
||||||
|
|
||||||
|
startzeit = time.time()
|
||||||
|
for ind, val in enumerate(frame_num_liste):
|
||||||
|
if ind%30 == 0:
|
||||||
|
verbleibende_min = ( (time.time()-startzeit)/(ind+1) )*( frame_count-ind )/60
|
||||||
|
print(f"\rFortschritt: {int(ind/frame_count*100)}%, geschätzte verbleibende Zeit: {verbleibende_min:.2f} Minuten", end='')
|
||||||
|
video.set(cv2.CAP_PROP_POS_FRAMES, val)
|
||||||
|
ist_bild, bild = video.read()
|
||||||
|
video_writer.write(bild)
|
||||||
|
|
||||||
|
video.release()
|
||||||
|
video_writer.release()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
shuffle(sys.argv[1])
|
||||||
21
src/skripte/gib_val_loss.awk
Normal file
21
src/skripte/gib_val_loss.awk
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# AWK-Skript, dass die train.log Datei eines Modells einliest und eine Liste der 'val_loss' Werte
|
||||||
|
# mit den Sekunden die bereits fürs Training verwendet wurden ausgibt. Wird von 'plot.sh' verwendet.
|
||||||
|
#
|
||||||
|
# Output-Format:
|
||||||
|
# <Trainierte Sekunden> <Loss-Wert zu dem Zeitpunkt>
|
||||||
|
|
||||||
|
func gibWert(pattern) {
|
||||||
|
for(i=1; i<=NF; i++){
|
||||||
|
if($i == pattern){return i+1;}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN { FS = ";"; }
|
||||||
|
|
||||||
|
/Starte Training/{ letzter_zeitpunkt = $1 }
|
||||||
|
/val_loss/{
|
||||||
|
dauer += $1-letzter_zeitpunkt;
|
||||||
|
letzter_zeitpunkt = $1;
|
||||||
|
print dauer, "\t", $gibWert("val_loss")
|
||||||
|
}
|
||||||
68
src/skripte/log_Analyse.awk
Normal file
68
src/skripte/log_Analyse.awk
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Awk-Skript zum Analysieren der Log-Dateien, die beim Trainieren des Neuronalen Netzes anfallen
|
||||||
|
BEGIN { FS = ";"; }
|
||||||
|
|
||||||
|
func gibFeldnummer(pattern) {
|
||||||
|
for(i=1; i<=NF; i++){
|
||||||
|
if($i == pattern){return i;}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
func runden(zahl) {
|
||||||
|
return int(zahl+0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trainingsdauer -----------------------------------------------------------------------------------------
|
||||||
|
{
|
||||||
|
if(match($0, "Starte Training")){
|
||||||
|
trainingsrunde_nr += 1
|
||||||
|
sekunden += vorheriger_Zeitstempel-zeitstempel_Start;
|
||||||
|
zeitstempel_Start = $1;
|
||||||
|
if(sekunden > 200000){print trainingsrunde_nr}
|
||||||
|
}
|
||||||
|
vorheriger_Zeitstempel = $1;
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
sekunden += vorheriger_Zeitstempel-zeitstempel_Start;
|
||||||
|
stunden = int(sekunden/3600); sekunden -= stunden*3600;
|
||||||
|
minuten = int(sekunden/60); sekunden -= minuten*60;
|
||||||
|
printf("Trainingsdauer:\t\t %sh %smin %ss\n", stunden, minuten, runden(sekunden));
|
||||||
|
}
|
||||||
|
|
||||||
|
# Anzahl/Dauer an Epochen -----------------------------------------------------------------------------------------
|
||||||
|
/on_epoch_begin/{ epochen++; epochen_start=$1;}
|
||||||
|
/on_epoch_end/{ avg_epochen_dauer += $1-epochen_start; avg_counter_epochen++;}
|
||||||
|
END {avg_epochen_dauer /= avg_counter_epochen; printf("Epochen:\t\t %s a ~%ss\n", epochen, avg_epochen_dauer);}
|
||||||
|
|
||||||
|
# Anzahl/Dauer an Batches -----------------------------------------------------------------------------------------
|
||||||
|
/on_train_batch_begin/{ batches++; train_batch_start=$1;}
|
||||||
|
/on_train_batch_end/{ avg_train_batch_dauer += $1-train_batch_start; avg_counter_batch++;}
|
||||||
|
END {avg_train_batch_dauer /= avg_counter_batch; printf("Trainierte Batches:\t %s a ~%ss\n", batches, avg_train_batch_dauer);}
|
||||||
|
|
||||||
|
# Loss -----------------------------------------------------------------------------------------
|
||||||
|
BEGIN { min_loss = 1000000000;}
|
||||||
|
|
||||||
|
/loss/ {
|
||||||
|
loss = $(gibFeldnummer("loss")+1);
|
||||||
|
avg_loss+=loss; x++;
|
||||||
|
if(max_loss < loss){max_loss=loss;};
|
||||||
|
if(min_loss > loss){min_loss=loss;};
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
avg_loss /= x;
|
||||||
|
printf("LOSS (MIN/MAX/AVG):\t %.10f / %.10f / %.10f \n", min_loss, max_loss, avg_loss);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Val Loss ------------------------------------------------------------------------------------
|
||||||
|
BEGIN { min_val_loss = 1000000000;}
|
||||||
|
|
||||||
|
/val_loss/{
|
||||||
|
val_loss = $(gibFeldnummer("val_loss")+1);
|
||||||
|
avg_val_loss+=val_loss; y++;
|
||||||
|
if(max_val_loss < val_loss){max_val_loss=val_loss;};
|
||||||
|
if(min_val_loss > val_loss){min_val_loss=val_loss;};
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
avg_val_loss /= y;
|
||||||
|
printf("VAL_LOSS (MIN/MAX/AVG):\t %.10f / %.10f / %.10f \n", min_val_loss, max_val_loss, avg_val_loss);
|
||||||
|
}
|
||||||
40
src/skripte/plot.sh
Normal file
40
src/skripte/plot.sh
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Bash-Skript, das aus den als Argumente übergebenen 'train.log' Dateien ein Diagramm macht.
|
||||||
|
# Verwendet AWK um die Daten zu formatieren und gnuplot um das Diagramm zu erstellen.
|
||||||
|
|
||||||
|
plot_befehl="plot "
|
||||||
|
for dateiname in $@; do
|
||||||
|
name=$(echo $dateiname | awk 'BEGIN {FS="/";} { print $3 }' - | sed -e "s/DenseLayers\?_.*_//g" ) # Namen aus dem Pfad extrahieren
|
||||||
|
dateiname_daten=plot-script_$name.temp
|
||||||
|
awk -f skripte/gib_val_loss.awk $dateiname > $dateiname_daten # Datei mit den zu plotenden Daten erstellen
|
||||||
|
plot_befehl+="'$dateiname_daten' using (\$1/3600):2 title \"$(echo $name | sed -e "s/e-/x10\^-^/" )\" with line linewidth 4," # An den Plot-Befehl anfügen
|
||||||
|
done
|
||||||
|
echo $plot_befehl
|
||||||
|
|
||||||
|
|
||||||
|
GNUPLOT_COMMAND=$(cat << EOF
|
||||||
|
set xrange [-0.1:];
|
||||||
|
set yrange [:];
|
||||||
|
set xlabel "Trainingzeit in Stunden" font ",16";
|
||||||
|
set xlabel offset 2;
|
||||||
|
set xtics offset 1;
|
||||||
|
set ylabel "Fehler" font ",16";
|
||||||
|
set ylabel offset -2;
|
||||||
|
set ytics offset -1;
|
||||||
|
set logscale y;
|
||||||
|
set grid;
|
||||||
|
set border 3;
|
||||||
|
set border linewidth 2;
|
||||||
|
set tics scale 1;
|
||||||
|
set tics font ",14";
|
||||||
|
set key font ",14";
|
||||||
|
set tics nomirror;
|
||||||
|
set terminal qt size 700, 400;
|
||||||
|
$plot_befehl
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
gnuplot -p -e "$GNUPLOT_COMMAND" # Diagramm erstellen
|
||||||
|
|
||||||
|
rm -f plot-script_*.temp # Zuvor erstellte Dateien entfernen
|
||||||
14
src/skripte/trainingsplan.sh
Normal file
14
src/skripte/trainingsplan.sh
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
run_with_latentspace_changed () {
|
||||||
|
LATENT_SPACE=$1
|
||||||
|
echo "Starte LatentSpace$LATENT_SPACE..." >> train.info
|
||||||
|
sed -i "s/NAME = '.*'/NAME = 'DenseLayers_LatentSpace$LATENT_SPACE-SGD_lr=5e-1'/" Deepfake.py
|
||||||
|
sed -i "s/tf\.keras\.optimizers\..*(.*)/tf.keras.optimizers.SGD(learning_rate=5e-1)/" Deepfake.py
|
||||||
|
sed -i "s/Dense(.*)) #MARKER_LATENT_SPACE/Dense( $LATENT_SPACE )) #MARKER_LATENT_SPACE/" Deepfake.py
|
||||||
|
sed -i "s/shape=(.*,) )) #MARKER_INPUT_DECODER/shape=($LATENT_SPACE,) )) #MARKER_INPUT_DECODER/" Deepfake.py
|
||||||
|
sed -i "s/+.*#MARKER_ZEIT/+ 6*60*60 #MARKER_ZEIT/" Deepfake.py
|
||||||
|
python Deepfake.py
|
||||||
|
}
|
||||||
|
|
||||||
|
run_with_latentspace_changed 100
|
||||||
Reference in New Issue
Block a user