Gesichtserkennung auf Video: Raspberry Pi und Neural Compute Stick

Vor etwa einem Jahr veröffentlichte Intel Movidius mit dem Movidius Neural Compute Stick (NCS) ein Gerät zur effizienten Inferenz von Faltungs-Neuronalen Netzen. Dieses Gerät ermöglicht die Verwendung neuronaler Netze zur Erkennung oder Erkennung von Objekten unter Bedingungen eines begrenzten Stromverbrauchs, einschließlich bei Robotikaufgaben. NCS verfügt über eine USB-Schnittstelle und verbraucht nicht mehr als 1 Watt. In diesem Artikel werde ich über die Erfahrungen mit der Verwendung von NCS mit dem Raspberry Pi für die Erkennung von Gesichtern in Videos sprechen, einschließlich der Schulung des Mobilenet-SSD-Detektors und des Starts auf Raspberry.

Der gesamte Code befindet sich in meinen beiden Repositories: Detektortraining und Gesichtserkennungsdemos .



In meinem ersten Artikel habe ich bereits über die Gesichtserkennung mit NCS geschrieben: Dann sprachen wir über einen YOLOv2- Detektor, den ich vom Darknet- Format in das Caffe- Format konvertiert und dann auf NCS gestartet habe. Der Konvertierungsprozess erwies sich als nicht trivial: Da diese beiden Formate die letzte Schicht des Detektors auf unterschiedliche Weise definieren, musste die Ausgabe des neuronalen Netzwerks mithilfe eines Codes von Darknet separat auf der CPU analysiert werden. Außerdem hat mich dieser Detektor sowohl in der Geschwindigkeit (bis zu 5,1 FPS auf meinem Laptop) als auch in der Genauigkeit nicht zufrieden gestellt - später war ich überzeugt, dass es aufgrund seiner Empfindlichkeit gegenüber der Bildqualität schwierig war, mit Raspberry Pi ein gutes Ergebnis zu erzielen.

Am Ende habe ich beschlossen, nur meinen Detektor zu trainieren. Die Wahl fiel auf den SSD- Detektor mit Mobilenet- Encoder: Die leichte Faltung von Mobilenet ermöglicht es Ihnen, ohne Qualitätsverlust hohe Geschwindigkeiten zu erreichen, und der SSD-Detektor ist YOLO nicht unterlegen und funktioniert sofort mit dem NCS.

Funktionsweise des Mobilenet-SSD-Detektors
Beginnen wir mit Mobilenet. In dieser Architektur ist abgeschlossen 3 mal3Die Faltung (auf allen Kanälen) wird durch zwei leichte Faltungen ersetzt: erstens 3 mal3separat für jeden Kanal und dann vervollständigen 1 mal1Faltung. Nach jeder Faltung werden BatchNorm und Nichtlinearität (ReLU) verwendet. Die allererste Netzwerkfaltung, die ein Bild als Eingabe empfängt, bleibt normalerweise vollständig. Diese Architektur kann die Komplexität von Berechnungen aufgrund einer leichten Abnahme der Qualität von Vorhersagen erheblich reduzieren. Es gibt eine erweiterte Option , aber ich habe sie noch nicht ausprobiert.

SSD (Single Shot Detector) funktioniert folgendermaßen: An den Ausgängen mehrerer Faltungscodierer werden zwei aufgehängt 1 mal1Faltungsschicht: Eine sagt die Wahrscheinlichkeiten von Klassen voraus, die andere - die Koordinaten des Begrenzungsrahmens. Es gibt eine dritte Ebene, die die Koordinaten und Positionen der Standardrahmen auf der aktuellen Ebene angibt. Die Bedeutung ist: Die Ausgabe einer Schicht ist natürlich in Zellen unterteilt; näher am Ende des neuronalen Netzwerks werden sie kleiner (in diesem Fall aufgrund der Faltung mit stride=2 ) und das Sichtfeld jeder Zelle nimmt zu. Für jede Zelle auf jeder von mehreren ausgewählten Ebenen legen wir mehrere Standardrahmen unterschiedlicher Größe und mit unterschiedlichen Seitenverhältnissen fest und verwenden zusätzliche Faltungsschichten, um Koordinaten zu korrigieren und Klassenwahrscheinlichkeiten für jeden dieser Rahmen vorherzusagen. Daher berücksichtigt ein SSD-Detektor (wie YOLO) immer die gleiche Anzahl von Frames. Das gleiche Objekt kann auf verschiedenen Ebenen erkannt werden: Während des Trainings wird das Signal an alle Frames gesendet, die sich ziemlich stark mit dem Objekt schneiden, und während der Anwendung werden die Erkennungen mithilfe der NMS (Non Maximum Suppression) kombiniert. Die letzte Schicht kombiniert die Erkennungen aller Schichten, berücksichtigt ihre vollständigen Koordinaten, schneidet die Wahrscheinlichkeitsschwelle ab und erzeugt NMS.

Detektortraining


Architektur


Der Code zum Trainieren des Detektors befindet sich hier .

Ich entschied mich, den vorgefertigten Mobilenet-SSD-Detektor zu verwenden, der auf dem PASCAL VOC0712 trainiert wurde, und ihn zu trainieren, um Gesichter zu erkennen. Erstens hilft es, das Netz viel schneller zu trainieren, und zweitens müssen Sie das Rad nicht neu erfinden.

Das ursprüngliche Projekt enthielt das Skript gen.py , das die .prototxt Modelldatei buchstäblich sammelte und die .prototxt ersetzte. Ich habe es in mein Projekt übertragen und die Funktionalität ein wenig erweitert. Mit diesem Skript können Sie vier Arten von Konfigurationsdateien generieren:

  • Zug : Am Eingang - eine Trainings-LMDB-Basis, am Ausgang - eine Schicht mit der Berechnung der Verlustfunktion und ihrer Gradienten gibt es BatchNorm
  • Test : Am Eingang - der Test-LMDB-Basis, am Ausgang - der Schicht mit der Berechnung der Qualität (mittlere durchschnittliche Genauigkeit) befindet sich BatchNorm
  • Bereitstellen : Bei der Eingabe - dem Image, bei der Ausgabe - der Ebene mit Vorhersagen fehlt BatchNorm
  • deploy_bn : An der Eingabe - dem Image, an der Ausgabe - der Ebene mit Vorhersagen befindet sich BatchNorm

Ich habe die letztere Option später hinzugefügt, damit Sie in Skripten das Raster aus BatchNorm laden und konvertieren können, ohne die LMDB-Datenbank zu berühren. Andernfalls würde ohne die Datenbank nichts funktionieren. (Im Allgemeinen erscheint es mir seltsam, dass in Caffe die Datenquelle in der Netzwerkarchitektur festgelegt ist - dies ist zumindest nicht sehr praktisch).

Wie sieht die Netzwerkarchitektur aus (kurz)
  • Login: 300 mal300 mal3
  • Full Conv Conv : 32 Kanäle, stride=2
  • Mobilenet-Faltung conv1 - conv11 : 64, 128, 128, 256, 256, 512 ... 512 Kanäle, einige haben stride=2
  • Erkennungsschicht: 19 mal19 mal512
  • Mobilenet Convolution Conv12, Conv13 : 1024 Kanäle, Conv12 hat stride=2
  • Erkennungsschicht: 10 mal10 mal1024
  • Volle Faltung conv14_1, conv14_2 : 256, 512 Kanäle, der erste kernel_size=1 , der zweite stride=2
  • Erkennungsschicht: 5 mal5 mal512
  • Volle Faltung conv15_1, conv15_2 : 128, 256 Kanäle, der erste kernel_size=1 , der zweite stride=2
  • Erkennungsschicht: 3 mal3 mal256
  • Komplette Conv16_1-, Conv16_2-Windungen : 128, 256 Kanäle, der erste kernel_size=1 , der zweite stride=2
  • Erkennungsschicht: 2 mal2 mal256
  • Volle Faltung conv17_1, conv17_2 : 64, 128 Kanäle, der erste kernel_size=1 , der zweite stride=2
  • Erkennungsschicht: 1 mal1 mal128
  • Endschicht-Erkennungsausgabe


Ich habe die Netzwerkarchitektur leicht korrigiert. Liste der Änderungen:

  • Offensichtlich hat sich die Anzahl der Klassen auf 1 geändert (ohne Hintergrund).
  • Einschränkungen des Seitenverhältnisses der geschnittenen Patches während des Trainings: geändert von [0.5,2.0]auf [0,7,1,4](Ich habe beschlossen, die Aufgabe etwas zu vereinfachen und nicht aus zu gestreckten Bildern zu lernen).
  • Von den Standardrahmen blieben nur quadratische übrig, zwei für jede Zelle. Ich habe ihre Größe stark reduziert, da Gesichter beim klassischen Objekterkennungsproblem deutlich kleiner sind als Objekte.

Caffe berechnet die Standardrahmengrößen wie folgt: mit einer minimalen Rahmengröße sund maximal LEs entsteht ein kleiner und großer Rahmen mit Abmessungen sund  sqrtsL. Da ich so kleine Gesichter wie möglich erkennen wollte, berechnete ich den vollen stride für jede Erkennungsschicht und setzte die minimale Rahmengröße damit gleich. Mit diesen Parametern befinden sich die kleinen Standardrahmen nahe beieinander und überschneiden sich nicht. Wir haben also zumindest die Garantie, dass der Schnittpunkt mit dem Objekt für eine Art Framework existiert. Ich habe die maximale Größe doppelt so hoch eingestellt. Für die Ebenen conv16_2, conv17_2 habe ich die Abmessungen für das Auge gleich eingestellt. Auf diese Weise, s,Lfür alle Schichten waren: (16,32),(32,64),(64,128),(128,214),(214,300),(214,300)

Wie einige Standardframes aussehen (Rauschen zur Klarheit)


Daten


Ich habe zwei Datensätze verwendet : WIDER Face und FDDB . WIDER enthält viele Bilder mit sehr kleinen und verschwommenen Gesichtern, und FDDB neigt eher zu großen Bildern von Gesichtern (und einer Größenordnung kleiner als WIDER). Das Anmerkungsformat unterscheidet sich geringfügig, es handelt sich jedoch bereits um Details.

Für das Training habe ich nicht alle Daten verwendet: Ich habe zu kleine Gesichter (weniger als sechs Pixel oder weniger als 2% der Bildbreite), alle Bilder mit einem Seitenverhältnis von weniger als 0,5 oder mehr als 2 und alle im WIDER-Datensatz als „verschwommen“ gekennzeichneten Bilder ausgeworfen. da sie größtenteils sehr kleinen Personen entsprachen und ich zumindest irgendwie das Verhältnis von kleinen und großen Gesichtern ausrichten musste. Danach habe ich alle Frames quadratisch gemacht und die kleinste Seite erweitert: Ich habe festgestellt, dass ich nicht sehr an Gesichtsproportionen interessiert bin, und die Aufgabe für das neuronale Netzwerk wurde ein wenig vereinfacht. Ich habe auch alle Schwarzweißbilder weggeworfen, von denen es nur wenige gab und auf denen das Datenbankerstellungsskript abstürzt.

Um sie für Schulungen und Tests zu verwenden, müssen Sie eine LMDB-Basis daraus zusammenstellen. Wie es geht:

  • Für jedes Bild wird ein Markup im .xml Format erstellt.
  • Eine train.txt Datei wird mit Zeilen der Form "path/to/image.png path/to/labels.xml" erstellt. train.txt Datei wird zum Testen erstellt.
  • Eine Datei test_name_size.txt wird mit Zeilen der Form "test_image_name height width"
  • Erstellt eine labelmap.prototxt Datei mit numerischen labelmap.prototxt


Das ssd-caffe/scripts/create_annoset.py (Beispiel aus dem Makefile):

 python3 /opt/movidius/ssd-caffe/scripts/create_annoset.py --anno-type=detection \ --label-map-file=$(wider_dir)/labelmap.prototxt --min-dim=0 --max-dim=0 \ --resize-width=0 --resize-height=0 --check-label --encode-type=jpg --encoded \ --redo $(wider_dir) \ $(wider_dir)/trainval.txt $(wider_dir)/WIDER_train/lmdb/wider_train_lmdb ./data 

labelmap.prototxt
 item { name: "none_of_the_above" label: 0 display_name: "background" } item { name: "face" label: 1 display_name: "face" } 



Beispiel .xml Markup
 <?xml version="1.0" ?> <annotation> <size> <width>348</width> <height>450</height> <depth>3</depth> </size> <object> <name>face</name> <bndbox> <xmin>161</xmin> <ymin>43</ymin> <xmax>241</xmax> <ymax>123</ymax> </bndbox> </object> </annotation> 


Die gleichzeitige Verwendung von zwei Datensätzen bedeutet nur, dass Sie die entsprechenden Dateien sorgfältig paarweise zusammenführen müssen, ohne zu vergessen, die Pfade korrekt zu registrieren und die Datei für das Training zu mischen.

Danach können Sie mit dem Training beginnen.

Schulung


Code für das Modelltraining finden Sie in meinem Colab Notebook .

Ich habe das Training bei Google Colaboratory absolviert, weil mein Laptop das Grid kaum getestet hat und im Allgemeinen aufgelegt hat. Durch die Zusammenarbeit konnte ich das Netz schnell genug und kostenlos trainieren. Der einzige Haken ist, dass ich ein SSD-Caffe-Kompilierungsskript für das Colaboratory schreiben musste (einschließlich so seltsamer Dinge wie das Neukompilieren von Boost und das Bearbeiten der Quelle), was ungefähr 40 Minuten dauert. Weitere Details finden Sie in meiner vorherigen Veröffentlichung .

Das Labor hat noch eine weitere Funktion: Nach 12 Stunden stirbt das Auto und löscht alle Daten dauerhaft. Der beste Weg, um Datenverlust zu vermeiden, besteht darin, Ihre Google-Festplatte im System zu mounten und dort alle 500-1000 Trainingsiterationen Netzwerkgewichte zu speichern.

Mein Detektor konnte in einer Sitzung im Colaboratory 4.500 Iterationen verlernen und wurde in zwei Sitzungen vollständig geschult.

Die Qualität der Vorhersagen (mittlere durchschnittliche Genauigkeit) des von mir hervorgehobenen Testdatensatzes (Zusammenführung von WIDER und FDDB mit den oben aufgeführten Einschränkungen) betrug für das beste Modell etwa 0,87. Um mAP auf den während des Trainings gespeicherten Skalen zu messen, gibt es ein Skript scripts/plot_map.py .

Der Detektor arbeitet an einem (sehr seltsamen) Beispiel aus einem Datensatz:


Starten Sie auf NCS


Eine Gesichtserkennungsdemo ist hier .

Um ein neuronales Netzwerk für den Neural Compute Stick zu kompilieren, benötigen Sie das Movidius NCSDK : Es enthält Dienstprogramme zum Kompilieren und Profilieren neuronaler Netzwerke sowie die C ++ - und Python-APIs. Es ist erwähnenswert, dass kürzlich die zweite Version veröffentlicht wurde, die nicht mit der ersten kompatibel ist: Alle API-Funktionen wurden aus irgendeinem Grund umbenannt, das interne Format der neuronalen Netze geändert, FIFO wurde hinzugefügt, um mit NCS zu interagieren, und (schließlich) die automatische Konvertierung von Float 32-Bit in float 16 bit, was in C ++ so fehlte. Ich habe alle meine Projekte auf die zweite Version aktualisiert, aber ein paar Krücken gelassen, um die Kompatibilität mit der ersten zu gewährleisten.

Nach dem Training des Detektors lohnt es sich, BatchNorm-Schichten mit benachbarten Windungen zusammenzuführen, um das neuronale Netzwerk zu beschleunigen. Das Skript merge_bn.py dies von hier aus , das ich auch aus dem Mobilenet-SSD-Projekt ausgeliehen habe.

Dann müssen Sie das Dienstprogramm mvNCCompile , zum Beispiel:

 mvNCCompile -s 12 -o graph_ssd -w ssd-face.caffemodel ssd-face.prototxt 

Im Makefile des Projekts gibt es dafür ein graph_ssd Ziel. Die resultierende graph_ssd Datei ist eine Beschreibung des neuronalen Netzwerks in einem von NCS verstandenen Format.

Nun erfahren Sie, wie Sie mit dem Gerät selbst interagieren. Der Prozess ist nicht sehr kompliziert, erfordert jedoch eine ziemlich große Menge an Code. Die Reihenfolge der Aktionen ist ungefähr wie folgt:

  • Gerätebeschreibung anhand der Seriennummer abrufen
  • Gerät öffnen
  • Lesen Sie die kompilierte neuronale Netzwerkdatei in den Puffer (als Binärdatei).
  • Erstellen Sie ein leeres Berechnungsdiagramm für NCS
  • Platzieren Sie das Diagramm mit den Daten aus der Datei auf dem Gerät und wählen Sie bei der Eingabe / Ausgabe FIFO aus. Puffer mit Dateiinhalt kann jetzt freigegeben werden
  • Detektorstart:
    • Holen Sie sich das Bild von der Kamera (oder von einer anderen Quelle)
    • Verarbeiten: auf die gewünschte Größe skalieren, in float32 konvertieren und in den Bereich [-1,1] umwandeln
    • Laden Sie das Bild auf das Gerät hoch und fordern Sie eine Inferenz an
    • Fordern Sie ein Ergebnis an (das Programm wird blockiert, bis das Ergebnis empfangen wird).
    • Analysieren Sie das Ergebnis, wählen Sie die Frames der Objekte aus (über das Format - weiter)
    • Vorhersagen anzeigen

  • Geben Sie alle Ressourcen frei: Entfernen Sie das FIFO und das Berechnungsdiagramm, schließen Sie das Gerät und entfernen Sie das Handle

Fast jede Aktion mit NCS hat eine eigene Funktion, und in C ++ sieht sie sehr umständlich aus, und Sie müssen die Freigabe aller Ressourcen sorgfältig überwachen. Um den Code nicht zu überladen, habe ich eine Wrapper-Klasse für die Arbeit mit NCS erstellt . Darin ist die gesamte Initialisierungsarbeit im Konstruktor und in der Funktion load_file sowie bei der Freigabe von Ressourcen - im Destruktor - verborgen, und die Arbeit mit NCS wird auf den Aufruf von 2-3 Klassenmethoden reduziert. Darüber hinaus gibt es eine praktische Funktion zum Erklären aufgetretener Fehler.

Erstellen Sie einen Wrapper, indem Sie die Eingabegröße und die Ausgabegröße (Anzahl der Elemente) an den Konstruktor übergeben:

 NCSWrapper NCS(NETWORK_INPUT_SIZE*NETWORK_INPUT_SIZE*3, NETWORK_OUTPUT_SIZE); 

Wir laden die kompilierte Datei mit dem neuronalen Netzwerk und initialisieren gleichzeitig alles, was wir brauchen:

 if (!NCS.load_file("./models/face/graph_ssd")) { NCS.print_error_code(); return 0; } 

Wir konvertieren das Bild in float32 ( image ist cv::Mat im Format CV_32FC3 ) und laden es auf das Gerät herunter:

 if(!NCS.load_tensor_nowait((float*)image.data)) { NCS.print_error_code(); break; } 

Wir erhalten das Ergebnis ( result ist ein Free float Zeiger, der Ergebnispuffer wird vom Wrapper unterstützt); Bis zum Ende der Berechnungen ist das Programm gesperrt:

 if(!NCS.get_result(result)) { NCS.print_error_code(); break; } 

Tatsächlich verfügt der Wrapper auch über eine Methode, mit der Sie Daten laden und gleichzeitig das Ergebnis load_tensor((float*)image.data, result) können: load_tensor((float*)image.data, result) . Ich habe mich aus einem Grund geweigert, es zu verwenden: Mit separaten Methoden können Sie die Codeausführung etwas beschleunigen. Nach dem Laden des Images bleibt die CPU im Leerlauf, bis das Ergebnis der Ausführung mit NCS eintrifft (in diesem Fall sind es ca. 100 ms). Zu diesem Zeitpunkt können Sie nützliche Arbeit leisten: Lesen Sie einen neuen Frame und konvertieren Sie ihn sowie zeigen Sie frühere Erkennungen an . Genau so wird das Demo-Programm implementiert, in meinem Fall erhöht es die FPS leicht. Sie können noch weiter gehen und die Bildverarbeitung und den Gesichtsdetektor asynchron in zwei verschiedenen Streams starten - dies funktioniert wirklich und ermöglicht es Ihnen, etwas schneller zu werden, ist jedoch nicht im Demo-Programm implementiert.

Der Detektor gibt daher ein Float-Array der Größe 7*(keep_top_k+1) . Hier ist keep_top_k der Parameter, der in der .prototxt Datei des Modells angegeben ist und angibt, wie viele Erkennungen (in der Reihenfolge abnehmender Konfidenz) zurückgegeben werden sollen. Dieser Parameter sowie der Parameter, der für das Filtern von Erkennungen nach dem minimalen Konfidenzwert und nicht maximalen Unterdrückungsparametern verantwortlich ist, können in der .prototxt Datei des Modells in der allerletzten Ebene konfiguriert werden. Es ist erwähnenswert, dass NCS immer keep_top_k Erkennungen zurückgibt, wenn Caffe so viele Erkennungen zurückgibt, wie im Bild gefunden wurden, sodass die keep_top_k konstant ist.

Das Ergebnisarray selbst ist wie folgt organisiert: Wenn wir es als Matrix mit keep_top_k+1 Zeilen und 7 Spalten betrachten, gibt es in der ersten Zeile im ersten Element die Anzahl der Erkennungen, und ab der zweiten Zeile gibt es Erkennungen selbst im Format "garbage, class_index, class_probability, x_min, y_min, x_max, y_max" . Die Koordinaten werden im Bereich [0,1] angegeben, daher müssen sie mit der Höhe / Breite des Bildes multipliziert werden. Die verbleibenden Elemente des Arrays sind Müll. In diesem Fall wird die nicht maximale Unterdrückung automatisch durchgeführt, noch bevor das Ergebnis erhalten wird (anscheinend direkt bei NCS).

Detektoranalyse
 void get_detection_boxes(float* predictions, int w, int h, float thresh, std::vector<float>& probs, std::vector<cv::Rect>& boxes) { int num = predictions[0]; float score = 0; float cls = 0; for (int i=1; i<num+1; i++) { score = predictions[i*7+2]; cls = predictions[i*7+1]; if (score>thresh && cls<=1) { probs.push_back(score); boxes.push_back(Rect(predictions[i*7+3]*w, predictions[i*7+4]*h, (predictions[i*7+5]-predictions[i*7+3])*w, (predictions[i*7+6]-predictions[i*7+4])*h)); } } } 


Raspberry Pi Startfunktionen


Das Demo-Programm selbst kann auf einem normalen Computer oder Laptop mit Ubuntu oder auf einem Raspberry Pi mit einem Raspbian Stretch ausgeführt werden. Ich verwende ein Raspberry Pi 2 Modell B, aber die Demo sollte auch auf anderen Modellen funktionieren. Das Makefile des Projekts enthält zwei Ziele für make switch_desk : make switch_desk für den Computer / Laptop und make switch_rpi für den Raspberry Pi. Der grundlegende Unterschied im Programmcode besteht darin, dass im ersten Fall OpenCV zum Lesen von Daten von der Kamera und im zweiten Fall von der RaspiCam- Bibliothek verwendet wird. Um die Demo auf Raspberry auszuführen, müssen Sie sie kompilieren und installieren.

Nun ein sehr wichtiger Punkt: Installation von NCSDK. Wenn Sie die Standardinstallationsanweisungen auf dem Raspberry Pi befolgen, endet nichts Gutes: Das Installationsprogramm versucht, SSD-Caffe und Tensorflow zu ziehen und zu kompilieren. Stattdessen muss das NCSDK im Nur-API-Modus kompiliert werden . In diesem Modus sind nur C ++ - und Python-APIs verfügbar (dh es ist nicht möglich, Diagramme für neuronale Netzwerke zu kompilieren und zu profilieren). Dies bedeutet, dass der Graph des neuronalen Netzwerks zuerst auf einem normalen Computer kompiliert und dann nach Raspberry kopiert werden muss. Der Einfachheit halber habe ich dem Repository zwei kompilierte Dateien hinzugefügt, für YOLO und für SSD.

Ein weiterer interessanter Punkt ist die rein physische Verbindung von NCS mit Raspberry. Es scheint nicht schwierig zu sein, es an einen USB-Anschluss anzuschließen, aber Sie müssen bedenken, dass sein Gehäuse die anderen drei Anschlüsse blockiert (es ist ziemlich gesund, da es als Heizkörper fungiert). Der einfachste Ausweg besteht darin, es über ein USB-Kabel anzuschließen.

Beachten Sie auch, dass die Ausführungsgeschwindigkeit für verschiedene USB-Versionen unterschiedlich ist (für dieses spezielle neuronale Netzwerk: 102 ms für USB 3.0, 92 ms für USB 2.0).

Nun zur Leistung des NCS. Laut Dokumentation verbraucht es bis zu 1 Watt (bei 5 Volt am USB-Anschluss sind es bis zu 200 mA; zum Vergleich: Die Himbeerkamera verbraucht bis zu 250 mA). Wenn es von einem normalen 5-Volt-Ladegerät mit 2 Ampere betrieben wird, funktioniert alles hervorragend. Der Versuch, zwei oder mehr NCS mit Raspberry zu verbinden, kann jedoch zu Problemen führen. In diesem Fall wird empfohlen, einen USB-Splitter mit der Möglichkeit einer externen Stromversorgung zu verwenden.

Auf Raspberry ist die Demo langsamer als auf einem Computer / Laptop: 7,2 FPS gegenüber 10,4 FPS. Dies ist auf mehrere Faktoren zurückzuführen: Erstens ist es unmöglich, Berechnungen auf der CPU loszuwerden, aber sie werden viel langsamer ausgeführt. Zweitens wirkt sich die Datenübertragungsgeschwindigkeit aus (für USB 2.0).

Zum Vergleich habe ich in meinem ersten Artikel versucht, einen Gesichtsdetektor auf Raspberry YOLOv2 zu verwenden, aber es hat sehr schlecht funktioniert: Bei einer Geschwindigkeit von 3,6 FPS fehlen selbst bei einfachen Frames viele Gesichter. Anscheinend ist es sehr empfindlich gegenüber den Parametern des Eingabebildes, dessen Qualität bei der Raspberry-Kamera alles andere als ideal ist. SSD funktioniert viel stabiler, obwohl ich die Videoeinstellungen in den RapiCam-Einstellungen ein wenig anpassen musste. er vermisst auch manchmal die Gesichter im Rahmen, tut dies aber ziemlich selten. Um die Stabilität in realen Anwendungen zu erhöhen, können Sie einen einfachen Schwerpunkt-Tracker hinzufügen.

Übrigens: Dasselbe kann in Python reproduziert werden, es gibt ein Tutorial zu PyImageSearch (Mobilenet-SSD wird für die Objekterkennungsaufgabe verwendet).

Andere Ideen


Ich habe auch einige Ideen getestet, um das neuronale Netzwerk selbst zu beschleunigen:

Erste Idee: Sie können nur die Erkennung der Ebenen conv11 und conv13 und alle zusätzlichen Ebenen entfernen. Sie erhalten einen Detektor, der nur kleine Gesichter erkennt und etwas schneller arbeitet. Alles in allem nicht wert.

Die zweite Idee war interessant, funktionierte aber nicht: Ich versuchte, Windungen aus dem neuronalen Netzwerk mit Gewichten nahe Null zu werfen, in der Hoffnung, dass es schneller werden würde. Es gab jedoch nur wenige solcher Windungen, und ihre Entfernung verlangsamte das neuronale Netzwerk nur geringfügig (die einzige Vermutung: Dies liegt an der Tatsache, dass die Anzahl der Kanäle nicht mehr eine Zweierpotenz ist).

Fazit


Ich habe lange darüber nachgedacht, Gesichter auf Raspberry zu erkennen, als Teilaufgabe meines Roboterprojekts. Ich mochte die klassischen Detektoren in Bezug auf Geschwindigkeit und Qualität nicht und entschied mich, neuronale Netzwerkmethoden auszuprobieren, während ich gleichzeitig den Neural Compute Stick testete. Als Ergebnis gab es zwei Projekte auf GitHub und drei Artikel auf Habré (einschließlich des aktuellen). Im Allgemeinen passt das Ergebnis zu mir - höchstwahrscheinlich werde ich diesen Detektor in meinem Roboter verwenden (vielleicht wird es einen weiteren Artikel darüber geben). Es ist erwähnenswert, dass meine Lösung möglicherweise nicht optimal ist - dennoch handelt es sich um ein Schulungsprojekt, das teilweise aus Neugier für das NCS erstellt wurde. Trotzdem hoffe ich, dass dieser Artikel jemandem nützlich sein wird.

Source: https://habr.com/ru/post/de424973/


All Articles