Die Live-Gesichtserkennung ist ein Problem, mit dem die automatisierte Sicherheitsabteilung immer noch konfrontiert ist. Mit den Fortschritten bei Convolutions Neural Networks und speziell kreativen Methoden von Region-CNN wurde bereits bestätigt, dass wir uns mit unseren aktuellen Technologien für überwachte Lernoptionen wie FaceNet, YOLO für eine schnelle und lebendige Gesichtserkennung in einer realen Umgebung entscheiden können .
Um ein überwachtes Modell zu trainieren, müssen wir Datensätze unserer Zieletiketten abrufen, was immer noch eine mühsame Aufgabe ist. Wir benötigen eine effiziente und automatisierte Lösung für die Datensatzgenerierung mit minimalem Etikettierungsaufwand durch Benutzereingriffe.

Vorgeschlagene Lösung -

Einführung: Wir schlagen eine Pipeline zur Generierung von Datensätzen vor, die einen Videoclip als Quelle verwendet, alle Gesichter extrahiert und zu begrenzten und genauen Bildsätzen gruppiert, die eine bestimmte Person darstellen. Jeder Satz kann leicht durch menschliche Eingaben mit Leichtigkeit beschriftet werden.

Technische Details: Wir werden opencv lib für die Extraktion von Bildern pro Sekunde aus dem eingegebenen Videoclip verwenden. 1 Sekunde erscheint angemessen, um relevante Daten und begrenzte Frames für die Verarbeitung abzudecken.
Wir werden die face_recognition Bibliothek (unterstützt von dlib) verwenden, um die Flächen aus den Rahmen zu extrahieren und sie für Feature-Extraktionen auszurichten.
Anschließend extrahieren wir die vom Menschen beobachtbaren Merkmale und gruppieren sie mithilfe des von scikit-learn bereitgestellten DBSCAN-Clusters . Für die Lösung werden alle Gesichter ausgeschnitten, Beschriftungen erstellt und in Ordnern gruppiert, damit Benutzer sie als Datensatz für ihre Schulungsanwendungsfälle anpassen können.

Herausforderungen bei der Implementierung: Für ein größeres Publikum planen wir, die Lösung für die Ausführung in einer CPU anstelle einer NVIDIA-GPU zu implementieren. Die Verwendung einer NVIDIA-GPU kann die Effizienz der Pipeline erhöhen.
Die CPU-Implementierung der Gesichtseinbettungsextraktion ist sehr langsam (30+ Sekunden pro Bild). Um das Problem zu lösen, implementieren wir sie mit parallelen Pipeline-Ausführungen (was ~ 13 Sekunden pro Bild ergibt) und führen ihre Ergebnisse später für weitere Clustering-Aufgaben zusammen. Wir führen tqdm zusammen mit PyPiper Fortschrittsaktualisierungen und die Größenänderung von Frames ein, die aus dem Eingangsvideo extrahiert wurden, um eine reibungslose Ausführung der Pipeline zu gewährleisten.

Eingabe: Footage.mp4
 Ausgabe:

Erforderliche Python3-Module:
os, cv2, numpy, tensorflow, json, re, shutil, time, pickle, pyPiper, tqdm, imutils, face_recognition, dlib, warnings, sklearn



Abschnitt "Snippets":
Für den Inhalt der Datei FaceClusteringLibrary.py, die alle Klassendefinitionen enthält, folgen die Snippets und Erläuterungen zu deren Funktionsweise.

Klassenimplementierung von ResizeUtilsbietet Funktion rescale_by_height und rescale_by_width.
"Rescale_by_width" ist eine Funktion, die 'image' und 'target_width' als Eingabe verwendet. Es skaliert / verkleinert die Bildabmessung für die Breite, um die zu erfüllen target_width. Die Höhe wird automatisch berechnet, damit das Seitenverhältnis gleich bleibt. rescale_by_height ist auch das gleiche, aber anstelle der Breite wird die Höhe angestrebt.

           
class ResizeUtils: 
    
    
    def rescale_by_height(self, image, target_height, 
                        method = cv2.INTER_LANCZOS4): 
          
        
        
        w = int(round(target_height * image.shape[1] / image.shape[0])) 
        return (cv2.resize(image, (w, target_height),  
                             interpolation = method)) 
  
    
    
    def rescale_by_width(self, image, target_width, 
                        method = cv2.INTER_LANCZOS4): 
                              
        
        
        h = int(round(target_width * image.shape[0] / image.shape[1])) 
        return (cv2.resize(image, (target_width, h), 
                            interpolation = method)) 

Es folgt die Definition der FramesGeneratorKlasse. Diese Klasse bietet Funktionen zum Extrahieren von JPG-Bildern durch sequentielles Lesen des Videos. Wenn wir ein Beispiel für eine Eingangsvideodatei nehmen, kann diese eine Bildrate von ~ 30 fps haben. Wir können daraus schließen, dass es für 1 Sekunde Video 30 Bilder gibt. Selbst für ein 2-minütiges Video beträgt die Anzahl der zu verarbeitenden Bilder 2 * 60 * 30 = 3600. Die Verarbeitung der Bilder ist zu hoch und die vollständige Pipeline-Verarbeitung kann Stunden dauern.

Aber es kommt noch eine Tatsache, dass sich Gesichter und Menschen nicht innerhalb einer Sekunde ändern können. Wenn man also ein 2-minütiges Video betrachtet, ist das Generieren von 30 Bildern für 1 Sekunde umständlich und wiederholt. Stattdessen können wir nur 1 Bildausschnitt in 1 Sekunde aufnehmen. Bei der Implementierung von „FramesGenerator“ wird nur 1 Bild pro Sekunde aus einem Videoclip ausgegeben.

In Anbetracht der Tatsache, dass die gedumpten Bilder face_recognition/dlibfür die Gesichtsextraktion verarbeitet werden, versuchen wir, einen Schwellenwert für die Höhe von nicht mehr als 500 und die Breite auf 700 zu beschränken. Diese Begrenzung wird durch die Funktion "AutoResize" festgelegt, die weitere Aufrufe vornimmt rescale_by_height oder rescale_by_width deren Größe verringert Das Bild, wenn Grenzwerte erreicht werden, das Seitenverhältnis jedoch beibehalten wird.

Beim folgenden Snippet AutoResize versucht die Funktion, die Dimension eines bestimmten Bildes zu begrenzen. Wenn die Breite größer als 700 ist, verkleinern wir sie, um die Breite 700 beizubehalten und das Seitenverhältnis beizubehalten. Eine weitere hier festgelegte Grenze ist, dass die Höhe nicht größer als 500 sein darf.

class FramesGenerator: 
    def __init__(self, VideoFootageSource): 
        self.VideoFootageSource = VideoFootageSource 
  
    
    
    def AutoResize(self, frame): 
        resizeUtils = ResizeUtils() 
  
        height, width, _ = frame.shape 
  
        if height > 500: 
            frame = resizeUtils.rescale_by_height(frame, 500) 
            self.AutoResize(frame) 
          
        if width > 700: 
            frame = resizeUtils.rescale_by_width(frame, 700) 
            self.AutoResize(frame) 
          
        return frame 

Es folgt das Snippet für die GenerateFramesFunktion. Es fragt die fps ab, um zu entscheiden, unter wie vielen Frames 1 Bild ausgegeben werden kann. Wir löschen das Ausgabeverzeichnis und beginnen mit der Iteration durch die Frames. Bevor wir ein Bild sichern, ändern wir die Größe des Bildes, wenn es den in der AutoResize Funktion angegebenen Grenzwert erreicht .

def GenerateFrames(self, OutputDirectoryName): 
    cap = cv2.VideoCapture(self.VideoFootageSource) 
    _, frame = cap.read() 
 
    fps = cap.get(cv2.CAP_PROP_FPS) 
    TotalFrames = cap.get(cv2.CAP_PROP_FRAME_COUNT) 
 
    print("[INFO] Total Frames ", TotalFrames, " @ ", fps, " fps") 
    print("[INFO] Calculating number of frames per second") 
 
    CurrentDirectory = os.path.curdir 
    OutputDirectoryPath = os.path.join( 
      CurrentDirectory, OutputDirectoryName) 
 
    if os.path.exists(OutputDirectoryPath): 
        shutil.rmtree(OutputDirectoryPath) 
        time.sleep(0.5) 
    os.mkdir(OutputDirectoryPath) 
 
    CurrentFrame = 1
    fpsCounter = 0
    FrameWrittenCount = 1
    while CurrentFrame < TotalFrames: 
        _, frame = cap.read() 
        if (frame is None): 
            continue
          
        if fpsCounter > fps: 
            fpsCounter = 0
 
            frame = self.AutoResize(frame) 
 
            filename = "frame_" + str(FrameWrittenCount) + ".jpg"
            cv2.imwrite(os.path.join( 
              OutputDirectoryPath, filename), frame) 
 
            FrameWrittenCount += 1
          
        fpsCounter += 1
        CurrentFrame += 1
 
    print('[INFO] Frames extracted') 

Es folgt der Ausschnitt für die FramesProviderKlasse. Es erbt "Node", mit dem die Bildverarbeitungspipeline erstellt werden kann. Wir implementieren "Setup" - und "Run" -Funktionen. Alle in der Funktion "setup" definierten Argumente können die Parameter enthalten, die vom Konstruktor zum Zeitpunkt der Objekterstellung als Parameter erwartet werden. Hier können wir sourcePath Parameter an das FramesProvider Objekt übergeben. Die Setup-Funktion wird nur einmal ausgeführt. Die Funktion "Ausführen" wird ausgeführt und gibt weiterhin Daten aus, indem die emit Funktion zur Verarbeitung der Pipeline close aufgerufen wird, bis die Funktion aufgerufen wird.



Hier, im "Setup", akzeptieren wir sourcePath als Argument und durchlaufen alle Dateien im angegebenen Frames-Verzeichnis. Unabhängig von der Dateierweiterung .jpg(die von der Klasse generiert wird FrameGenerator) fügen wir sie der Liste "filesList" hinzu.

Während der run Funktionsaufrufe werden alle JPG-Bildpfade aus "filesList" mit Attributen gepackt, die die eindeutige "id" und "imagePath" als Objekt angeben, und zur Verarbeitung an die Pipeline ausgegeben.

  
class FramesProvider(Node): 
    def setup(self, sourcePath): 
        self.sourcePath = sourcePath 
        self.filesList = [] 
        for item in os.listdir(self.sourcePath): 
            _, fileExt = os.path.splitext(item) 
            if fileExt == '.jpg': 
                self.filesList.append(os.path.join(item)) 
        self.TotalFilesCount = self.size = len(self.filesList) 
        self.ProcessedFilesCount = self.pos = 0
  
    
    def run(self, data): 
        if self.ProcessedFilesCount < self.TotalFilesCount: 
            self.emit({'id': self.ProcessedFilesCount,  
                'imagePath': os.path.join(self.sourcePath,  
                              self.filesList[self.ProcessedFilesCount])}) 
            self.ProcessedFilesCount += 1
              
            self.pos = self.ProcessedFilesCount 
        else: 
            self.close() 

Es folgt die Klassenimplementierung von " FaceEncoder ", die "Node" erbt und in die Bildverarbeitungspipeline übertragen werden kann. In der Funktion "Setup" akzeptieren wir den Wert "Detection_Method" für den Aufruf der Gesichtserkennung "face_recognition / dlib". Es kann einen Detektor auf CNN-Basis oder einen Detektor auf Schweinebasis haben.
Die Funktion "Ausführen" entpackt die eingehenden Daten in "ID" und "ImagePath".

Anschließend liest es das Bild aus "imagePath" und führt die in der Bibliothek "face_recognition / dlib" definierte "face_location" aus, um das ausgerichtete Gesichtsbild auszuschneiden, das unsere Region von Interesse ist. Ein ausgerichtetes Gesichtsbild ist ein rechteckiges zugeschnittenes Bild, bei dem Augen und Lippen an einer bestimmten Stelle im Bild ausgerichtet sind (Hinweis: Die Implementierung kann bei anderen Bibliotheken, z. B. opencv, abweichen).

Außerdem rufen wir die in "face_recognition / dlib" definierte Funktion "face_encodings" auf, um die Gesichtseinbettungen aus jeder Box zu extrahieren. Durch diese eingebetteten schwebenden Werte können Sie die genaue Position von Features in einem ausgerichteten Gesichtsbild erreichen.

Wir definieren die Variable "d" als ein Array von Feldern und entsprechenden Einbettungen. Jetzt packen wir die "ID" und das Array von Einbettungen als "Codierungsschlüssel" in ein Objekt und senden es an die Bildverarbeitungspipeline.

class FaceEncoder(Node): 
    def setup(self, detection_method = 'cnn'): 
        self.detection_method = detection_method 
        
  
    def run(self, data): 
        id = data['id'] 
        imagePath = data['imagePath'] 
        image = cv2.imread(imagePath) 
        rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 
  
        boxes = face_recognition.face_locations( 
               rgb, model = self.detection_method) 
  
        encodings = face_recognition.face_encodings(rgb, boxes) 
        d = [{"imagePath": imagePath, "loc": box, "encoding": enc}  
                         for (box, enc) in zip(boxes, encodings)] 
  
        self.emit({'id': id, 'encodings': d}) 

Es folgt eine Implementierung, DatastoreManagerdie wiederum von "Node" erbt und in die Bildverarbeitungspipeline eingesteckt werden kann. Das Ziel der Klasse besteht darin, das Array "encodings" als Pickle-Datei zu sichern und die Pickle-Datei mit dem Parameter "id" eindeutig zu benennen. Wir möchten, dass die Pipeline Multithreading ausführt.
Um das Multithreading zur Leistungsverbesserung zu nutzen, müssen wir die asynchronen Aufgaben ordnungsgemäß trennen und versuchen, die Notwendigkeit einer Synchronisierung zu vermeiden. Um eine maximale Leistung zu erzielen, lassen wir die Threads in der Pipeline die Daten unabhängig voneinander in eine einzelne separate Datei schreiben, ohne andere Thread-Vorgänge zu stören.

Wenn Sie sich überlegen, wie viel Zeit in der verwendeten Entwicklungshardware ohne Multithreading gespart wurde, betrug die durchschnittliche Extraktionszeit für das Einbetten ~ 30 Sekunden. Nach der Multithread-Pipeline (mit 4 Threads) verringerte sie sich auf ~ 10 Sekunden, jedoch mit den Kosten einer hohen CPU-Auslastung.
Da der Thread ungefähr 10 Sekunden dauert, treten keine häufigen Schreibvorgänge auf und beeinträchtigen unsere Multithread-Leistung nicht.

Ein anderer Fall, wenn Sie darüber nachdenken, warum Gurke anstelle von JSON-Alternative verwendet wird? Die Wahrheit ist, dass JSON eine bessere Alternative zu Gurke ist. Pickle ist für die Speicherung und Kommunikation von Daten sehr unsicher. Pickles können böswillig geändert werden, um ausführbare Codes in Python einzubetten. Die JSON-Dateien sind für Menschen lesbar und können schneller codiert und decodiert werden. Das einzige, was pickle gut kann, ist das fehlerfreie Ablegen von Python-Objekten und -Inhalten in Binärdateien.

Da wir nicht planen, die Pickle-Dateien zu speichern und zu verteilen und für eine fehlerfreie Ausführung, verwenden wir Pickle. Andernfalls werden JSON und andere Alternativen dringend empfohlen.



class DatastoreManager(Node): 
    def setup(self, encodingsOutputPath): 
        self.encodingsOutputPath = encodingsOutputPath 
    def run(self, data): 
        encodings = data['encodings'] 
        id = data['id'] 
        with open(os.path.join(self.encodingsOutputPath,  
                   'encodings_' + str(id) + '.pickle'), 'wb') as f: 
            f.write(pickle.dumps(encodings)) 

Es folgt die Implementierung der Klasse PickleListCollator. Es wurde entwickelt, um Arrays von Objekten in mehreren Pickle-Dateien zu lesen, zu einem Array zusammenzuführen und das kombinierte Array in eine einzelne Pickle-Datei zu kopieren.

Hier gibt es nur eine Funktion, GeneratePickle die akzeptiert, outputFilepath die die einzelne Ausgabe-Pickle-Datei angibt, die das zusammengeführte Array enthält.

class PicklesListCollator: 
    def __init__(self, picklesInputDirectory): 
        self.picklesInputDirectory = picklesInputDirectory 
      
    
    
    
    
    
    def GeneratePickle(self, outputFilepath): 
        datastore = [] 
  
        ListOfPickleFiles = [] 
        for item in os.listdir(self.picklesInputDirectory): 
            _, fileExt = os.path.splitext(item) 
            if fileExt == '.pickle': 
                ListOfPickleFiles.append(os.path.join( 
                    self.picklesInputDirectory, item)) 
  
        for picklePath in ListOfPickleFiles: 
            with open(picklePath, "rb") as f: 
                data = pickle.loads(f.read()) 
                datastore.extend(data) 
  
        with open(outputFilepath, 'wb') as f: 
            f.write(pickle.dumps(datastore)) 

Das Folgende ist die Implementierung der FaceClusterUtilityKlasse. Es ist ein Konstruktor definiert, der "EncodingFilePath" mit Wert als Pfad zur zusammengeführten Pickle-Datei verwendet. Wir lesen das Array aus der Pickle-Datei und versuchen, sie mithilfe der DBSCAN-Implementierung in der Scikit-Bibliothek zu gruppieren. Im Gegensatz zu k-means erfordert der DBSCAN-Scan nicht die Anzahl der Cluster. Die Anzahl der Cluster hängt vom Schwellenwertparameter ab und wird automatisch berechnet.
Die DBSCAN-Implementierung wird in "scikit" bereitgestellt und akzeptiert auch die Anzahl der Threads für die Berechnung.

Hier haben wir eine Funktion "Cluster", die aufgerufen wird, um die Array-Daten aus der Pickle-Datei zu lesen, "DBSCAN" auszuführen, die eindeutigen Cluster als eindeutige Flächen zu drucken und die Beschriftungen zurückzugeben. Die Beschriftungen sind eindeutige Werte, die Kategorien darstellen, mit denen die Kategorie für ein im Array vorhandenes Gesicht identifiziert werden kann. (Der Array-Inhalt stammt aus der Pickle-Datei).

class FaceClusterUtility: 
      
    def __init__(self, EncodingFilePath): 
        self.EncodingFilePath = EncodingFilePath 
      
    
    
    
    
    def Cluster(self): 
        InputEncodingFile = self.EncodingFilePath 
        if not (os.path.isfile(InputEncodingFile) and
                os.access(InputEncodingFile, os.R_OK)): 
            print('The input encoding file, ' +
                    str(InputEncodingFile) + 
                    ' does not exists or unreadable') 
            exit() 
  
        NumberOfParallelJobs = -1
  
        
        
        
        
        print("[INFO] Loading encodings") 
        data = pickle.loads(open(InputEncodingFile, "rb").read()) 
        data = np.array(data) 
  
        encodings = [d["encoding"] for d in data] 
  
        
        print("[INFO] Clustering") 
        clt = DBSCAN(eps = 0.5, metric ="euclidean", 
                      n_jobs = NumberOfParallelJobs) 
                        
        clt.fit(encodings) 
  
        
        
        labelIDs = np.unique(clt.labels_) 
        numUniqueFaces = len(np.where(labelIDs > -1)[0]) 
        print("[INFO] # unique faces: {}".format(numUniqueFaces)) 
  
        return clt.labels_ 

Es folgt die Implementierung einer TqdmUpdateKlasse, die von "tqdm" erbt. tqdm ist eine Python-Bibliothek, die einen Fortschrittsbalken in der Konsolenoberfläche anzeigt.
Die Variablen "n" und "total" werden von "tqdm" erkannt. Die Werte dieser beiden Variablen werden verwendet, um den erzielten Fortschritt zu berechnen.
Die Parameter "done" und "total_size" in der Funktion "update" werden als Werte angegeben, wenn sie an das Aktualisierungsereignis im Pipeline-Framework "PyPiper" gebunden sind. Das super().refresh()ruft die Implementierung der Funktion "Aktualisieren" in der Klasse "tqdm" auf, die den Fortschrittsbalken in der Konsole visualisiert und aktualisiert.

class TqdmUpdate(tqdm): 
      
    
    
    
    def update(self, done, total_size = None): 
        if total_size is not None: 
            self.total = total_size 
              
        self.n = done 
        super().refresh() 

Es folgt die Implementierung der FaceImageGeneratorKlasse. Diese Klasse bietet Funktionen zum Generieren einer Montage, eines zugeschnittenen Porträtbilds und einer Anmerkung für zukünftige Schulungszwecke (z. B. Darknet YOLO) aus den Beschriftungen, die sich nach dem Clustering ergeben.

Der Konstruktor erwartet EncodingFilePath als zusammengeführten Pickle-Dateipfad. Es wird verwendet, um alle Gesichtscodierungen zu laden. Wir interessieren uns jetzt für den „imagePath“ und die Gesichtskoordinaten zur Erzeugung des Bildes.

Der Aufruf von "GenerateImages" erledigt den beabsichtigten Job. Wir laden das Array aus der zusammengeführten Pickle-Datei. Wir wenden die eindeutige Operation auf Etiketten an und durchlaufen die Etiketten. Innerhalb der Iteration der Beschriftungen listen wir für jede eindeutige Beschriftung alle Array-Indizes mit derselben aktuellen Beschriftung auf.
Diese Array-Indizes werden erneut iteriert, um jede Fläche zu verarbeiten.

Für die Gesichtsverarbeitung verwenden wir den Index, um den Pfad für die Bilddatei und die Koordinaten des Gesichts zu erhalten.
Die Bilddatei wird aus dem Pfad der Bilddatei geladen. Die Koordinaten des Gesichts werden zu einer Porträtform erweitert (und wir stellen außerdem sicher, dass es nicht mehr als die Abmessungen des Bildes erweitert) und es wird zugeschnitten und als Porträtbild in eine Datei kopiert.
Wir beginnen erneut mit den ursprünglichen Koordinaten und erweitern sie ein wenig, um Anmerkungen für zukünftige überwachte Schulungsoptionen für verbesserte Erkennungsfähigkeiten zu erstellen.

Zur Annotation haben wir es nur für „Darknet YOLO“ entworfen, aber es kann auch für jedes andere Framework angepasst werden. Schließlich erstellen wir eine Montage und schreiben sie in eine Bilddatei.



class FaceImageGenerator: 
    def __init__(self, EncodingFilePath): 
        self.EncodingFilePath = EncodingFilePath 
  
    
    
    
    
    
    
      
    
    
    
    
    
    
    def GenerateImages(self, labels, OutputFolderName = "ClusteredFaces"
                                            MontageOutputFolder = "Montage"): 
        output_directory = os.getcwd() 
  
        OutputFolder = os.path.join(output_directory, OutputFolderName) 
        if not os.path.exists(OutputFolder): 
            os.makedirs(OutputFolder) 
        else: 
            shutil.rmtree(OutputFolder) 
            time.sleep(0.5) 
            os.makedirs(OutputFolder) 
  
        MontageFolderPath = os.path.join(OutputFolder, MontageOutputFolder) 
        os.makedirs(MontageFolderPath) 
  
        data = pickle.loads(open(self.EncodingFilePath, "rb").read()) 
        data = np.array(data) 
  
        labelIDs = np.unique(labels) 
          
        
        for labelID in labelIDs: 
            
            
            
            
              
            print("[INFO] faces for face ID: {}".format(labelID)) 
  
            FaceFolder = os.path.join(OutputFolder, "Face_" + str(labelID)) 
            os.makedirs(FaceFolder) 
  
            idxs = np.where(labels == labelID)[0] 
  
            
            
            portraits = [] 
  
            
            counter = 1
            for i in idxs: 
                  
                
                image = cv2.imread(data[i]["imagePath"]) 
                (o_top, o_right, o_bottom, o_left) = data[i]["loc"] 
  
                height, width, channel = image.shape 
  
                widthMargin = 100
                heightMargin = 150
  
                top = o_top - heightMargin 
                if top < 0: top = 0
                  
                bottom = o_bottom + heightMargin 
                if bottom > height: bottom = height 
                  
                left = o_left - widthMargin 
                if left < 0: left = 0
                  
                right = o_right + widthMargin 
                if right > width: right = width 
  
                portrait = image[top:bottom, left:right] 
  
                if len(portraits) < 25: 
                    portraits.append(portrait) 
  
                resizeUtils = ResizeUtils() 
                portrait = resizeUtils.rescale_by_width(portrait, 400) 
  
                FaceFilename = "face_" + str(counter) + ".jpg"
  
                FaceImagePath = os.path.join(FaceFolder, FaceFilename) 
                cv2.imwrite(FaceImagePath, portrait) 
  
  
                widthMargin = 20
                heightMargin = 20
  
                top = o_top - heightMargin 
                if top < 0: top = 0
                  
                bottom = o_bottom + heightMargin 
                if bottom > height: bottom = height 
                  
                left = o_left - widthMargin 
                if left < 0: left = 0
                  
                right = o_right + widthMargin 
                if right > width: 
                    right = width 
  
                AnnotationFilename = "face_" + str(counter) + ".txt"
                AnnotationFilePath = os.path.join(FaceFolder, AnnotationFilename) 
                  
                f = open(AnnotationFilePath, 'w') 
                f.write(str(labelID) + ' ' +
                        str(left) + ' ' + str(top) + ' ' +
                        str(right) + ' ' + str(bottom) + "\n") 
                f.close() 
  
  
                counter += 1
  
            montage = build_montages(portraits, (96, 120), (5, 5))[0] 
              
            MontageFilenamePath = os.path.join( 
               MontageFolderPath, "Face_" + str(labelID) + ".jpg") 
                 
            cv2.imwrite(MontageFilenamePath, montage) 

 

Speichern Sie die Datei unter FaceClusteringLibrary.py, die alle Klassendefinitionen enthält.

Es folgt eine Datei Driver.py, die die Funktionen zum Erstellen einer Pipeline aufruft.

from FaceClusteringLibrary import *
  
if __name__ == "__main__": 
      
    
    framesGenerator = FramesGenerator("Footage.mp4") 
    framesGenerator.GenerateFrames("Frames") 
  
  
    
    CurrentPath = os.getcwd() 
    FramesDirectory = "Frames"
    FramesDirectoryPath = os.path.join(CurrentPath, FramesDirectory) 
    EncodingsFolder = "Encodings"
    EncodingsFolderPath = os.path.join(CurrentPath, EncodingsFolder) 
  
    if os.path.exists(EncodingsFolderPath): 
        shutil.rmtree(EncodingsFolderPath, ignore_errors = True) 
        time.sleep(0.5) 
    os.makedirs(EncodingsFolderPath) 
  
    pipeline = Pipeline( 
                    FramesProvider("Files source", sourcePath = FramesDirectoryPath) |  
                    FaceEncoder("Encode faces") |  
                    DatastoreManager("Store encoding"
                    encodingsOutputPath = EncodingsFolderPath),  
                    n_threads = 3, quiet = True) 
                      
    pbar = TqdmUpdate() 
    pipeline.run(update_callback = pbar.update) 
  
    print() 
    print('[INFO] Encodings extracted') 
  
  
    
    CurrentPath = os.getcwd() 
    EncodingsInputDirectory = "Encodings"
    EncodingsInputDirectoryPath = os.path.join( 
          CurrentPath, EncodingsInputDirectory) 
  
    OutputEncodingPickleFilename = "encodings.pickle"
  
    if os.path.exists(OutputEncodingPickleFilename): 
        os.remove(OutputEncodingPickleFilename) 
  
    picklesListCollator = PicklesListCollator( 
                    EncodingsInputDirectoryPath) 
    picklesListCollator.GeneratePickle( 
           OutputEncodingPickleFilename) 
  
    
    time.sleep(0.5) 
  
  
    
    
    EncodingPickleFilePath = "encodings.pickle"
      
    faceClusterUtility = FaceClusterUtility(EncodingPickleFilePath) 
    faceImageGenerator = FaceImageGenerator(EncodingPickleFilePath) 
      
    labelIDs = faceClusterUtility.Cluster() 
    faceImageGenerator.GenerateImages( 
      labelIDs, "ClusteredFaces", "Montage") 

Montage Ausgabe:

Fehlerbehebung -
Frage 1: Der gesamte PC friert beim Extrahieren der Gesichtseinbettung ein.
Lösung: Die Lösung besteht darin, die Werte in der Frame-Größenänderungsfunktion zu verringern, wenn Frames aus einem eingegebenen Videoclip extrahiert werden. Denken Sie daran, dass ein zu starkes Verringern der Werte zu einer falschen Gesichtsclusterung führt. Anstatt die Größe des Rahmens zu ändern, können wir eine Frontalflächenerkennung einführen und die Frontalflächen nur zur Verbesserung der Genauigkeit ausschneiden.

Frage 2: Der PC wird langsam, während die Pipeline ausgeführt wird.
Lösung: Die CPU wird maximal ausgelastet. Um die Verwendung zu begrenzen, können Sie die Anzahl der im Pipeline-Konstruktor angegebenen Threads verringern.

Frage 3: Das Ausgabe-Clustering ist zu ungenau.
Lösung: Der einzige Grund für den Fall kann sein, dass die aus dem eingegebenen Videoclip extrahierten Bilder sehr Gesichter mit einer sehr geringen Auflösung haben oder die Anzahl der Bilder sehr gering ist (ca. 7-8). Holen Sie sich bitte einen Videoclip mit hellen und klaren Bildern von Gesichtern oder für den letzteren Fall ein 2-minütiges Video oder einen Mod mit Quellcode für die Extraktion von Videobildern.

Den vollständigen Code und die zusätzlich verwendete Datei finden Sie unter dem Github-Link: https://github.com/cppxaxa/FaceRecognitionPipeline_GeeksForGeeks
 
Referenzen:
1. Adrians Blog-Beitrag zum Face-Clustering
2. PyPiper-Handbuch
3. OpenCV-Handbuch
4. StackOverflow

machine learning