Wochenendprojekt: Gebärdensprache und Erkennung statischer Gesten mit Scikit-Learn

Lassen Sie uns eine Pipeline für maschinelles Lernen erstellen, die das Gebärdensprachalphabet lesen kann, indem Sie nur ein Rohbild der Hand einer Person betrachten.

Dieses Problem besteht aus zwei Teilen:

  1. Erstellen eines Erkenners für statische Gesten, bei dem es sich um einen Klassifikator für mehrere Klassen handelt, der die Gesten der statischen Gebärdensprache vorhersagt.
  2. Suchen Sie die Hand im Rohbild und geben Sie diesen Bildabschnitt an den statischen Gestenerkenner (den Klassifikator für mehrere Klassen) weiter.

Sie können meinen Beispielcode und Datensatz für dieses Projekt hier erhalten.

Zunächst einige Hintergrundinformationen.

Die Gestenerkennung ist ein offenes Problem im Bereich der Bildverarbeitung, einem Bereich der Informatik, der es Systemen ermöglicht, die menschliche Sicht zu emulieren. Die Gestenerkennung hat viele Anwendungen zur Verbesserung der Mensch-Computer-Interaktion, und eine davon ist im Bereich der Gebärdensprachübersetzung, bei der eine Videosequenz symbolischer Handgesten in die natürliche Sprache übersetzt wird.

Eine Reihe fortschrittlicher Methoden dafür wurde entwickelt. Hier sehen wir uns an, wie Sie die Erkennung statischer Gesten mithilfe der Scikit-Lern- und Scikit-Bildbibliotheken durchführen.

Teil 1: Erstellen eines Erkenners für statische Gesten

Für diesen Teil verwenden wir einen Datensatz, der Rohbilder und eine entsprechende CSV-Datei mit Koordinaten enthält, die den Begrenzungsrahmen für die Hand in jedem Bild angeben. (Verwenden Sie die Datei Dataset.zip, um den Beispieldatensatz abzurufen. Extrahieren Sie gemäß den Anweisungen in der Readme-Datei.)

Dieser Datensatz ist benutzerbezogen organisiert und die Verzeichnisstruktur des Datensatzes ist wie folgt. Die Bildnamen geben das durch das Bild dargestellte Alphabet an.

dataset |----user_1 |---A0.jpg |---A1.jpg |---A2.jpg |---... |---Y9.jpg |----user_2 |---A0.jpg |---A1.jpg |---A2.jpg |---... |---Y9.jpg |---- ... |---- ...

Der Erkenner für statische Gesten ist im Wesentlichen ein Klassifikator für mehrere Klassen, der auf Eingabebildern trainiert wird, die die 24 statischen Gebärdensprachgesten darstellen (AY, außer J).

Das Erstellen eines Erkenners für statische Gesten unter Verwendung der Rohbilder und der CSV-Datei ist ziemlich einfach.

Um die Mehrklassenklassifizierer aus der Scikit-Lernbibliothek zu verwenden, müssen wir zuerst den Datensatz erstellen. Das heißt, jedes Bild muss in einen Merkmalsvektor (X) konvertiert werden, und jedes Bild hat eine Beschriftung, die der entspricht Gebärdensprachalphabet, das es bezeichnet (Y).

Der Schlüssel besteht nun darin, eine geeignete Strategie zu verwenden, um das Bild zu vektorisieren und aussagekräftige Informationen zu extrahieren, die dem Klassifizierer zugeführt werden. Die einfache Verwendung der Rohpixelwerte funktioniert nicht, wenn wir einfache Klassifizierer mit mehreren Klassen verwenden möchten (im Gegensatz zur Verwendung von Faltungsnetzwerken).

Um unsere Bilder zu vektorisieren, verwenden wir den HOG-Ansatz (Histogram of Oriented Gradients), da nachgewiesen wurde, dass bei Problemen wie diesem gute Ergebnisse erzielt werden. Andere Merkmalsextraktoren, die verwendet werden können, umfassen lokale Binärmuster und Haarfilter.

Code:

Wir verwenden Pandas in der Funktion get_data (), um die CSV-Datei zu laden. Zwei Funktionen-crop ()und convertToGrayToHog ()werden verwendet, um den erforderlichen Schweinevektor zu erhalten und ihn an die Liste der Vektoren anzuhängen, die wir erstellen, um den Klassifikator für mehrere Klassen zu trainieren.

# returns hog vector of a particular image vector def convertToGrayToHOG(imgVector): rgbImage = rgb2gray(imgVector) return hog(rgbImage) # returns cropped image def crop(img, x1, x2, y1, y2, scale): crp=img[y1:y2,x1:x2] crp=resize(crp,((scale, scale))) return crp #loads data for multiclass classification def get_data(user_list, img_dict, data_directory): X = [] Y = [] for user in user_list: user_images = glob.glob(data_directory+user+'/*.jpg') boundingbox_df = pd.read_csv(data_directory + user + '/' + user + '_loc.csv') for rows in boundingbox_df.iterrows(): cropped_img = crop( img_dict[rows[1]['image']], rows[1]['top_left_x'], rows[1]['bottom_right_x'], rows[1]['top_left_y'], rows[1]['bottom_right_y'], 128 ) hogvector = convertToGrayToHOG(cropped_img) X.append(hogvector.tolist()) Y.append(rows[1]['image'].split('/')[1][0]) return X, Y

Der nächste Schritt besteht darin, die Ausgabebezeichnungen (die Y-Werte) in numerische Werte zu codieren. Wir tun dies mit dem Label-Encoder von sklearn.

In unserem Code haben wir dies wie folgt gemacht:

Y_mul = self.label_encoder.fit_transform(Y_mul)

Dabei wird das label_encoder-Objekt im Konstruktor der Gestenerkennungsklasse wie folgt erstellt:

self.label_encoder = LabelEncoder().fit(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y'])

Sobald dies erledigt ist, kann das Modell mit einem beliebigen Klassifizierungsalgorithmus für mehrere Klassen Ihrer Wahl aus der Scikit-Lern-Toolbox trainiert werden. Wir haben unsere mithilfe der Support Vector Classification mit einem linearen Kernel trainiert.

Das Trainieren eines Modells mit sklearn umfasst nicht mehr als zwei Codezeilen. So geht's:

svcmodel = SVC(kernel='linear', C=0.9, probability=True) self.signDetector = svcmodel.fit(X_mul, Y_mul) 

Die Hyperparameter (in diesem Fall C = 0,9) können mithilfe einer Rastersuche eingestellt werden. Lesen Sie hier mehr darüber.

In diesem Fall wissen wir nicht viel über die Daten als solche (dh die Schweinevektoren). Es wäre daher eine gute Idee, Algorithmen wie xgboost (Extreme Gradient Boosting) oder Random Forest Classifiers zu verwenden und zu sehen, wie diese Algorithmen funktionieren.

Teil 2: Erstellen des Localizers

Dieser Teil erfordert etwas mehr Aufwand als der erste.

Im Allgemeinen werden wir die folgenden Schritte ausführen, um diese Aufgabe abzuschließen.

  1. Erstellen Sie einen Datensatz mit Bildern von Händen und Teilen, die nicht von Hand sind, unter Verwendung des angegebenen Datensatzes und der Begrenzungsrahmenwerte für jedes Bild.
  2. Trainieren Sie einen binären Klassifikator , um Hand- / Nicht-Hand-Bilder anhand des obigen Datensatzes zu erkennen.
  3. (Optional) Verwenden Sie Hard Negative Mining , um den Klassifikator zu verbessern.
  4. Verwenden Sie für das Abfragebild einen Schiebefensteransatz mit verschiedenen Maßstäben , um den interessierenden Bereich zu isolieren.

Hier werden wir keine Bildverarbeitungstechniken wie Filtern, Farbsegmentierung usw. verwenden. Die Scikit-Bildbibliothek wird zum Lesen, Zuschneiden, Skalieren, Konvertieren von Bildern in Graustufen und Extrahieren von Schweinevektoren verwendet.

Erstellen des Hand- / Nicht-Hand-Datensatzes:

Der Datensatz kann mit einer beliebigen Strategie erstellt werden. Eine Möglichkeit, dies zu tun, besteht darin, zufällige Koordinaten zu generieren und dann das Verhältnis von Schnittfläche zu Vereinigungsfläche (dh den Grad der Überlappung mit dem angegebenen Begrenzungsrahmen) zu überprüfen, um festzustellen, ob es sich um einen Nichthandabschnitt handelt. (Ein anderer Ansatz könnte darin bestehen, die Koordinaten mithilfe eines Schiebefensters zu bestimmen. Dies ist jedoch schrecklich langsam und unnötig.)

""" This function randomly generates bounding boxes Returns hog vector of those cropped bounding boxes along with label Label : 1 if hand ,0 otherwise """ def buildhandnothand_lis(frame,imgset): poslis =[] neglis =[] for nameimg in frame.image: tupl = frame[frame['image']==nameimg].values[0] x_tl = tupl[1] y_tl = tupl[2] side = tupl[5] conf = 0 dic = [0, 0] arg1 = [x_tl,y_tl,conf,side,side] poslis.append( convertToGrayToHOG(crop(imgset[nameimg], x_tl,x_tl+side,y_tl,y_tl+side))) while dic[0] <= 1 or dic[1] < 1: x = random.randint(0,320-side) y = random.randint(0,240-side) crp = crop(imgset[nameimg],x,x+side,y,y+side) hogv = convertToGrayToHOG(crp) arg2 = [x,y, conf, side, side] z = overlapping_area(arg1,arg2) if dic[0] <= 1 and z <= 0.5: neglis.append(hogv) dic[0] += 1 if dic[0]== 1: break label_1 = [1 for i in range(0,len(poslis)) ] label_0 = [0 for i in range(0,len(neglis))] label_1.extend(label_0) poslis.extend(neglis) return poslis,label_1

Training eines binären Klassifikators:

Sobald der Datensatz fertig ist, kann das Training des Klassifikators genau wie in Teil 1 beschrieben durchgeführt werden.

Normalerweise wird in diesem Fall eine Technik namens Hard Negative Mining verwendet, um die Anzahl falsch positiver Erkennungen zu verringern und den Klassifikator zu verbessern. Ein oder zwei Iterationen von Hard Negative Mining mit einem Random Forest Classifier reichen aus, um sicherzustellen, dass Ihr Classifier akzeptable Klassifizierungsgenauigkeiten erreicht, die in diesem Fall über 80% liegen.

Schauen Sie sich den Code hier an, um eine Beispielimplementierung desselben zu erhalten.

Hände in Testbildern erkennen:

Um den obigen Klassifikator tatsächlich zu verwenden, skalieren wir das Testbild um verschiedene Faktoren und verwenden dann einen Schiebefenster-Ansatz für alle, um das Fenster auszuwählen, das den interessierenden Bereich perfekt erfasst. Dies erfolgt durch Auswahl des Bereichs, der dem Maximum der Konfidenzwerte entspricht, die vom binären Klassifizierer (Hand / Nicht-Hand) über alle Skalen verteilt werden.

Die Testbilder müssen skaliert werden, da wir ein Fenster mit einer festgelegten Größe (in unserem Fall 128 x 128) über alle Bilder ausführen, um den interessierenden Bereich auszuwählen, und es ist möglich, dass der interessierende Bereich nicht perfekt in diese Fenstergröße passt .

Beispielimplementierung und Gesamterkennung über alle Skalen hinweg.

Alles zusammenfügen

Nachdem beide Teile fertig sind, müssen Sie sie nur noch nacheinander aufrufen, um die endgültige Ausgabe zu erhalten, wenn Sie ein Testbild erhalten.

That is, given a test image, we first get the various detected regions across different scales of the image and pick the best one among them. This region is then cropped out, rescaled (to 128x128) and its corresponding hog vector is fed to the multi-class classifier (i.e., the gesture recognizer). The gesture recognizer then predicts the gesture denoted by the hand in the image.

Key points

To summarize, this project involves the following steps. The links refer to the relevant code in the github repository.

  1. Building the hand/not-hand dataset.
  2. Converting all the images i.e., cropped sections with the gestures and the hand, not-hand images, to its vectorized form.
  3. Building a binary classifier for detecting the section with the hand and building a multi-class classifier for identifying the gesture using these data sets.
  4. Using the above classifiers one after the other to perform the required task.

Suks and I worked on this project as part of the Machine Learning course that we took up in college. A big shout out to her for all her contributions!

Also, we wanted to mention Pyimagesearch, which is a wonderful blog that we used extensively while we were working on the project! Do check it out for content on image processing and opencv related content.

Cheers!