Mit diesem Blogpost beginne ich die vierte Reihe der Serie. Es wird darum gehen, Machine learning auch auf einen Gerät nutzen zu können (im Zweifelsfall offline zu betreiben). Dabei halte ich mich am roten Faden – der Postauto-Erkennung.
Das Konzept, dass ich mit dir umsetzen möchte, ist einfach. Zuerst machen wir eine Object Detection von allgmein bekannten Objekten (Autos, Personen, Vögel, Hunde, Fahrräder,…). Wenn unser System ein Auto erkennt, wird das auf ein zweites ML-Modell angewendet, welches prüft, ob es ein Postauto ist.
Das Bild oben zeigt, wie das aufgenommene Foto analysiert werden soll. Zuerst soll ein Objekt erkannt und die Klasse des Objektes, also ob Auto, Person usw., ausgewertet werden. Falls die Klasse (oder der Typ) des Objektes ein Auto ist, muss das Bild von einem zweiten Machine Learning Modell ausgewertet werden. Dieses Modell bewertet lediglich, ob es sich um ein Postauto handelt. Nach dem obigen Bild würde dann eine Art Benachrichtigung ausgegeben. In jedem Fall werden die Analysedaten und Bildschnippsel zur späteren Verwendung abgelegt bzw. bei bestehender Internetverbindung in die Cloud übertragen.
Jetson Nano einrichten
Klasse! Dann sollten wir mal anfangen.
Falls du den NVidia Jetson Nano noch nicht eingerichtet hast, habe ich hier für dich eine kleine Empfehlung zusammen geschrieben, was du alles benötigst, um die beschriebene Lösung umzusetzen.
Im Prinzip reicht IoT Edge schon aus. Aber im Prinzip ist Autofahren ja auch kinderleicht. Es sind die Details, die oft Steine in den Weg legen.
Erste Schritte
Wenn du alles eingerichtet hast, legen wir los. Ich gehe mal davon aus, dass du nun bereits den IoT Hub in Azure angelegt, und deinen Jetson Nano mit diesem registriert hast. Weiter gehe ich davon aus, dass du VSCode installiert hast und es wie im Post “AI-powered Security Cam Part3 : ML-Model on the edge” erweitert hast. Um es einfach zu halten, kannst du den Code für das aktuelle Projekt clonen: GitHub Repo “IoTEdgeObjectTracking“. Die nachfolgenden Kommandos clonen den Code und öffnen VSCode mit dem entsprechenden Verzeichnis als Workspace.
git clone https://github.com/totosan/IoTEdgeObjectTracking cd IoTEdgeObjectTracking code .
Entwickeln der Anwendung
Die IoT Edge Solution
Nun gehen wir gemeinsam durch die Anwendung. Beginnen möchte ich mit der grundlegenden Struktur. Die Anwendung besteht aus mehreren Teilen. Diese Teile werden in einer IoT Edge Solution zusammengefügt. IoT Edge ist ein auf einem Device laufender Managed Service, der im Grunde Docker Container hostet und orchestriert. Gleichzeitig kann er mit dem IoT Hub in Azure kommunizieren, um entweder eine neue Konfiguration zu erhalten oder einfach nur den Zustand des Devices abzugleichen. Mindestens jedoch, wird der IoT Hub benötigt, um dem IoT Edge-Device eine Identität zuzuordnen.
Die Bilder oben zeigen, wie der IoT Hub Geräte mit Docker Images versorgt. Zudem weiß IoT Hub genau, welches Gerät, welche Konfiguration erhalten muss. Ein IoT Edge Device kann auch für eine längere Zeit “offline” arbeiten, sodass Konnektivität nicht zum Problem werden kann.
Meine IoT Edge Lösung besitzt vier Module. Eines davon wird lediglich durch Konfiguration in den Deployment-Files beschrieben (Azure Blobstorage), die drei anderen sind Custom Code.
Diese Module werden von der IoT Edge Runtime Engine parallel ausgeführt/gehostet, sodass jeder Container unabhängig von den Anderen “arbeiten” kann. Das Prinzip von IoT Edge ist simpel. Die Runtime hosted alle definierten Container, welche sich dann über eine message-basierte Kommunikation miteinander austauschen können. Alternativ bestehen natürlich auch alle anderen Kommunikationswege, die man auch mit anderen Container Hosts hätte (Docker Desktop, Kubernetes, Podman,…). Ich meine zum Beispiel auch REST für die Direktkommunikation, wie in dem nachfolgendem Bild.
Das nächste Bild zeigt, wie die Container in meiner Anwendung miteinander kommunizieren.
Das Modul YoloModule (Sorry für den Namen; als ich das Modul angelegt habe, war mir nicht bewusst, was noch alles drin sein würde – ich sollte es mal refactorn – du bist herzlich eingeladen mit zu machen) verfügt über ein ML-Modell (Yolo – You only look once ) zum Erkennen von Objekten in der Breite. D.h. die Bilder, die dieses Modul analysiert, werden als erstes nur auf allgemeine Objekte geprüft. Mit allgemeinen Objekten meine ich “car”,”people”,”bicycle”,”motorbike”,”dog”,… . Für eine erste Einschätzung reicht dies, da mein Ziel das Erkennen des Postautos ist. Mit diesem Konstrukt ist natürlich viel mehr möglich, ich halte die Dinge aber erstmal recht überschaubar.
Wenn du mehr Ideen dazu hast, kannst du mir vielleicht einen Kommentar hinterlassen, was für dich ein toller Use Case wäre.
Wenn der Detektor ein Auto erkannt hat, macht das Modul einen REST Call zum PostcarDetector Modul auf und übermittelt das aktuelle Frame, in dem das Auto erkannt wurde. Das PostcarDetector Modul macht daraufhin ein Prediction mit einem weiteren ML-Modell, mit der Einschätzung, ob das Auto in dem Bild ein Postauto ist (Classification). Die REST Antwort ist entweder “Post” oder “NoPost”. Daraufhin übernimmt die Weiterverarbeitung nun wieder das YoloModule.
Das YoloModule macht übrigens noch ein zusätzliches Object Tracking, um nicht jedesmal bei einer Detektion eines Autos wieder das PostcarDetector Modul zu fragen, ob es ein Postauto ist; das würde sehr viel Performance kosten. Zum Object Tracking werde ich später noch Details zur Verfügung stellen.
Wenn das YoloModule die Antwort des PostcarDetector Moduls erhält, sendet es eine entsprechende IoTEdge-Message an den Messagebus. Auf diese Nachricht kann nun das SpeechModule reagieren und den Text als Sprachausgabe “Ich sehe ein Postauto” ausgeben. Für meinen Jetson Nano habe ich einige Texte mit dem Speech Service aus Azure in Audio-Dateien abgespeichert, sodass diese gleich auf einem angeschlossenen Speaker ausgegeben werden können (übrigens hatte ich gehörig “Fummelei”, bis ich den ersten Ton über den Container an den Speaker ausgeben konnte.. dazu aber später mehr).
Die Nachricht des YoloModules wird über den Bus auch an den IoT Hub in Azure geschickt. Sollte zum Zeitpunkt der Nachricht keine Konnektivität bestehen, werden die Nachrichten für eine gewisse Zeit persistiert (Default 2h=7200s). Bei bestehender Verbindung werden die Nachrichten an IoT Hub gesendet und dort (zumindest in meiner Lösung) in Azure Time Series übertragen – dazu auch später mehr.
Außerdem speichert YoloModule das Frame, auf dem etwas detektiert wurde, in einem lokalen Azure Blob Storage (auf dem Device). Das Charmante an dieser Lösung ist, dass das Blob Storage Modul automatisch die Bilder mit seinem Cloud-Pendant synchronisiert, wenn das IoT Edge Device online ist.
Derzeit aber gibt es lediglich die IoT Lösung in VSCode. Um diese auch auf einem Jetson Nano laufen lassen zu können, muss die Lösung als “Konfiguration” dem Azure IoT Hub bekannt gemacht werden und die Module in Form von Docker Images in einer Registry hinterlegt sein. Und das funktioniert so.
IoT Edge Deployment Config
In der IoT Edge Solution findest du ein File namens deployment.arm64v8.template.json. Dieses File ist die Konfiguration für den IoT Hub. Sie beschreibt, für welche CPU/HW, welche Module und Default-Settings angewendet werden. Wenn du meinen Code im GitHub Repo anschaust, wirst du auch noch deployment files für andere System finden können. Da mein Jetson eine ARM64 Architektur ist, spreche ich insbesondere über diese Konfiguration.
In dem Deployment File findest du einen Abschnitt “modules”. Dieser beinhaltet alle Module, die ich für diese IoT Edge Solution vorgesehen habe.
Natürlich kann ich damit nun unterschiedliche Konfigurationen vorhalten. Wie du im Bild erkennen kannst, habe ich eine Deployment-Konfig zu Debuggingzwecken. Hier sind die Settings (z.B. im Bild unter CreateOptions -> Env) verändert, sodass ich lokal meine Tests besser nachvollziehen kann. Es könnte auch eine Konfiguration denkbar sein, die zum Beispiel das SpeechModul nicht enthält, falls das Device über keine Ausgabe verfügt. Diese Deploymente-Konfigs sind also schon sehr nützlich, wenn es darum geht, auf Geräte zugeschnittene Lösungen zu erzeugen.
Build & Push – Docker Images / Config
Um nun eine Konfig zu verwenden, müssen die verwendeten Module als Images in eine Registry geladen werden. Unter VSCode geht das sehr einfach:
– Rechtsklick auf die Deployment-Konfiguration
– Im Context-Menü auf “Build and Push IoT Edge Solution”
Danach läuft das Bauen der Docker Images los und bei Erfolg der Push in die hinterlegte Container Registry. Ich für meinen Fall habe eine Azure Container Registry angelegt , worin ich meine eigenen Images horte.
Wenn das Bauen und die Pushes fertig sind, wird die Deployment Konfig fertig gestellt. Dazu werden die Variablen in den deployment templates (z.B. deployment.debug.arm64v8.template.json) durch feste Werte ersetzt und das daraus resultierende Konfigurationsfile in Verzeichnis “config” angelegt. Beispiel:
Das YoloModule wird beschrieben mit “version”: “1.5.$BUILD_VERSION_YOLO”. Dies ist später die Versionsnummer des IoT Edge Modules im IoT Hub. Anhand dieser Versionsnummer kann die IoT Edge Runtime feststellen, ob ein Update eines Modules notwendig ist. In meiner Lösung habe ich für diesen Zweck eine Buildnummer eingeführt, sodass ich das zentral in der “.env”-Datei steuern kann. Dort habe ich auch weitere Variablen deklariert, sodass ich andere Teile der Konfig ebenfalls anpassen kann.
Wenn der Task “Build IoT Solution” läuft, wird im obigen Beispiel der Wert des Json-Attributes “version” zu “1.5.118”. Die Variable $CONTAINER_VIDEO_SOURCE unter “settings -> createOptions -> env” wird ersetzt durch “/dev/video0”
In dem Deployment-Template File (Bild “Modul Definition von “YoloModule” mit Variablen“) kannst du aber noch eine andere Form von Variablen erkennen: ${MODULES.YoloModule.debug-arm64v8}. Über diese Variablen-Syntax wird die Zuordnung zu den verwendeten Modulen erzeugt.
Modules.<Modul-Ordnername>.<Architektur>
Dazu wird ein Lookup im modules.json unter dem Ordner des angegebenen Modules unter “modules” gemacht. In diesem File ist aufgelistet, welches Dockerfile unter welcher Architektur zum Bauen des Images genommen werden soll.
In meinem Beispiel bedeutet dies, dass “image” den Wert “iotcontainertt.azurecr.io/yolomodule:latest-debug-arm64v8” erhält. Damit ist auch klar, wie IoT Hub nachher die Zuordnung Konfiguration – Docker Image in Registry macht.
IoT Edge Config Deployment
Nun, nachdem alle Images in der Registry hinterlegt sind und die IoT Edge Konfiguration erstellt wurde, kann die Lösung nun auf das entsprechende Gerät deployt werden. Dafür gibt es zwei generelle Lösungen (sicherlich fallen mir noch ein bis zwei mehr ein; möchte hier aber nicht darauf eingehen) – Azure CLI oder VSCode (Tippen oder Klicken)
Wenn du, wie am Anfang des Blogs empfohlen, auf dem Jetson Nano IoT Edge eingerichtet hast, dann hast du bereits eine Device Identity, die im IoT Hub registriert ist. Dieses Device kannst du hier wieder finden (VSCode Extension ):
Mein Gerät heißt “TotoNano”, welches als Zielgerät dienen wird. In der Liste oben im Bild sind noch viele weitere Geräte registriert. Die Einträge mit den Icons, wie sie mein TotoNano Device davor hat, sind IoT Edge – Devices, die anderen Microcontroller basierte IoT Devices.
Für das Deployment der IoT Edge Solution benötigst du das generierte Deployment File, welches beim Schritt “Build and Push IoT Edge Solution” erzeugt wurde. Da wir uns bereits in VSCode befinden und derzeit kein automatisiertes Deployment vorgesehen ist, machen wir das naheliegende. Dazu klickst du auf das generierte Deployment mit der rechten Maustaste, worauf sich das Kontextmenü öffnet. Dort klickst du auf “Create Deployment for Single Device”.
Nach dem Auswählen des Zielgerätes wird das Deployment angestoßen. Um nachvollziehen zu können was passiert, habe ich nachfolgend den Follow meines Jounals abgebildet.
journalctl -f -u iotedge | grep -v mgmt
Mit diesem Kommando kann ich das Journal permanent ausgeben (-f follow). Dabei lasse ich mir nur alle Log-Einträge für die Unit (-u) iotedge anzeigen. Mich haben aber die Management-Logs (“[mgmt]”) genervt, weshalb ich sie mit grep -v ausgeschlossen habe.
Du kannst hier sehen, wie die IoT Edge Runtime beginnt, die einzelnen Module als Docker Images herunter zu laden (gelb markiert). Nach dem “Pull” hält IoT Edge das entsprechende Modul an und startet es mit dem neuen Image neu. Der Azure Blobstorage bleibt in diesem ScreenShot unberührt, da ich bei dem Deployment die Versionsnummer des Storage-Moduls nicht hochgezählt habe und bereits ein Deployment lief.
Nach dem vollständigen Deployment läuft die IoT Edge Lösung nun selbstständig. Du kannst die Internetverbindung trennen, oder sie beibehalten. Die Lösung berührt das nicht. Falls du übrigens noch etwas im Deployment korrigierst, ohne Code oder ähnliches anzufassen, dann reicht es völlig aus, die Versionsnummer des Moduls zu ändern (hoch oder runter spielt dabei sogar keine Rolle) und mit Rechtsklick auf das Deployment-Template im erscheinenden Kontextmenü den Punkt “Generate IoT Edge Deployment Manifest” anklickst. Daraufhin wird dein neues Deployment File im Ordner config neu erstellt und du kannst wie vorher das Deployment neu anstoßen.
Zur Erinnerung: Du solltest einen Speaker und eine WebCam per USB angeschlossen haben, damit du ein Ergebnis bekommst. Nichts desto trotz kannst du schon mal einen Blick darauf werfen, was dein Jetson Nano sieht.
Mache dazu den Browser auf und gib die IP des Nano mit dem Port 8080 an. Daraufhin sollte sich ein Video öffnen, das zum Einen den Livestream zeigt; zum Anderen auch die erkannten Objekte.
Was im Stream erkannt wird, bekommt zum einen eine ID und wird mit einem Punkt markiert. Die Markierung beschreibt das Zentrum des erkannten Objekts (Centroid). Daneben ist in Klammern die Bewegungsrichtung festgehalten (Betrag der aktuellen Werte (x,y) und dem Mittel über die gesamten x bzw. y Werte der vorherigen Centroids).
Vielleicht ist dir in den Videos zusätzlich aufgefallen, dass die Centroids eine Weile “stehen” bleiben, wenn die Objekte nicht mehr im Bild sind. Das ist die Zeit, in der es vorkommen darf, dass ein Objekt mal in einigen Frames “verloren geht”. Damit wird das Tracking robuster. Hinzu kommt, dass ich nicht immer jedes Frame per ML-Modell analysieren muss und damit Ressourcen einspare. Abschließend habe ich hier noch ein Video, dass dir noch etwas Einblick in die Performance des Jetson Nano gibt, während es beschäftigt ist.
Jetzt hast du vieles vom System kennengelernt und vielleicht schon das eine oder andere ausprobiert. Dann lass es mich gern wissen und schreibe einen Kommentar.
Jetzt fehlt also nur noch das, was die Lösung zu einer macht. … Code! Und der kommt mit einem nächsten Artikel. Sei gespannt!