Aus Raspberry Pi Geek 04/2021

Jetson Nano: KI-Einstieg zum moderaten Preis (Seite 2)

Im einfachsten Fall folgt der Aufruf dem Muster detectnet /dev/video0 oder detectnet /dev/video0 rtp://Anzeigerechner:5000. Das erste Beispiel liest von der ersten direkt angeschlossenen USB-Kamera und stellt die Ergebnisse lokal auf dem Bildschirm dar. Beim Headless-Betrieb schickt der zweite Aufruf die Daten an einen RTP-Empfänger; Abbildung 1 zeigt den entsprechenden Output. Das als Standardeinstellung vorgegebene, vortrainierte Netz heißt SSD-Mobilenet-v2 und verwendet Tensorflow als Engine.

Abbildung 1: Ein Beispiel für Objekterkennung mit dem Jetson Nano.

Abbildung 1: Ein Beispiel für Objekterkennung mit dem Jetson Nano.

Um in Echtzeit an die Daten der IP-Kamera zu gelangen, verwendete der Autor Gstreamer [4], ein sehr flexibles Toolkit zur Manipulation von Audio-Video-Strömen aus allen möglichen Quellen und in vielen Formaten. Neben Kommandozeilenwerkzeugen umfasst das Paket auch Bibliotheken mit Schnittstellen zu allen gängigen Programmiersprachen. Gstreamer erwies sich als momentan einziges Toolkit, das man auf dem Jetson Nano mit voller Hardware-Unterstützung verwenden kann [5], was dem Autor beim Umwandeln der Videodateien einige graue Haare wachsen ließ.

Für den einfachen Testaufbau kommt das Programm Gst-launch-1.0 zum Einsatz, dem man beim Aufruf eine Reihe von Queues mitgibt – etwa eine Datei als Eingang, eine Umwandlungsmethode und eine andere Datei als Ausgang. Elemente einer solchen Queue trennt jeweils ein Ausrufezeichen. Bei einer Datei mit mehreren Strömen (etwa Video- und Tonspur) kann beziehungsweise muss man diese getrennt behandeln, um sie gegebenenfalls wieder in einem Container zusammenzufassen. Da die IP-Kamera nur eine Videospur bietet, genügt eine Queue, die Folgendes abwickeln muss:

  • das Auslesen des MJPEG-Stroms per HTTP (mit Benutzerauthentisierung),
  • das Auspacken des Videostroms aus dem HTTP-Strom,
  • das Dekodieren des MJPEG-4-Formats,
  • dessen Umwandeln ins H.264-Format, das Detectnet benötigt, und
  • das Senden dieses Stroms per RTP an Localhost.

Der Jetson Nano übernimmt in diesem Fall wegen der entsprechenden Hardware-Unterstützung der GPU auch das Konvertieren nach H.264. Der Aufruf von Gst-launch-1.0 sieht so aus wie in den ersten sechs Zeilen von Listing 2. Durch Veränderung des Parameters bitrate (Zeile 4) lässt sich die Bildqualität beeinflussen; omxh264enc verwendet hier den Hardware-beschleunigten Encoder für H.264. Anschließend kommt Detectnet zum Einsatz (letzte Zeile). Schließlich startet der Anwender auf dem darstellenden Rechner noch den Player mit der SDP-Datei aus Listing 1; nach einem Moment des Einschwingens erscheint das Bild aus Abbildung 1.

Listing 2

Gst-launch-1.0

$ gst-launch-1.0 -v souphttpsrc
 location=http://edimax-cam/snapshot.cgi \
  user-id=camera user-pw=camera do-timestamp=true ! multipartdemux ! \
  image/jpeg,width=1280,height=960 ! jpegdec ! videoconvert ! \
  omxh264enc profile=high preset-level=1 bitrate=900000 ! \
  'video/x-h264,stream-format=(string)byte-stream' ! rtph264pay ! \
  udpsink host=127.0.0.1 port=1234
$ detectnet --input-codec=h264 rtp://@:1234 rtp://Anzeigerechner:5000

Mit etwas zusätzlichem Python-Code (Listing 3) lässt sich das ganze nun als Alarmanlage verwenden. Im Unterschied zur Kommandozeilen-Pipeline kann der Autor hier etwas effizienter arbeiten, da im Code nur die Bilder aus dem MJPEG-Videostrom zur Verfügung stehen müssen. Die liefert in Python das CV2-Modul, dem man eine GST-Queue mitgibt, die in appsink endet (Zeile 11). Das neuronale Netz initialisiert das Modul jetson.utils aus dem entsprechenden Repository [2]. Die Hauptschleife liest das jeweils nächste Bild, wandelt es in ein Format um, mit dem Tensorflow etwas anfangen kann, und ruft die Objekterkennung auf. Erkennt das Netz Objekte, arbeitet die Schleife sie ab. Bei einer Erkennungswahrscheinlichkeit von mehr als 70 Prozent gibt das Skript eine Meldung aus und speichert das Bild samt eingeblendeten Boxen um die erkannten Objekte und Zeitstempel ab.

Listing 3

Objekterkennung von der IP-Kamera in Python

import jetson.inference
import jetson.utils
import sys
import cv2
import time
frame_width = 1280
frame_height = 960
net = jetson.inference.detectNet("ssd-mobilenet-v2", sys.argv, 0.5)
cap = cv2.VideoCapture("souphttpsrc location=http://edimax-cam/snapshot.cgi user-id=camera user-pw=camera do-timestamp=true ! multipartdemux ! image/jpeg,width=1280,height=960 ! jpegdec ! videoconvert ! appsink")
font = jetson.utils.cudaFont(size=jetson.utils.adaptFontSize(frame_width))
while True:
  ret,frame = cap.read()
  if ret == False:
    break
  else:
    frame_rgba = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
    cuda_frame = jetson.utils.cudaFromNumpy(frame_rgba)
    detections = net.Detect(cuda_frame, overlay="box,labels,conf")
    for detection in detections:
      detname =  net.GetClassDesc(detection.ClassID)
      if detection.Confidence > 0.7:
        print(detname+" "+str(detection.Confidence))
        font.OverlayText(cuda_frame, frame_width, frame_height, "{:f}% {:s}".format(detection.Confidence * 100, detname), 10, 10, font.White, font.Gray40)
        jetson.utils.cudaDeviceSynchronize()
        filename = "out-"+str(time.time())+".jpg"
        jetson.utils.saveImageRGBA(filename, cuda_frame, frame_width, frame_height)

Videos umwandeln

Der Autor verwendet Tvheadend [6] als Videorekorder für Fernsehprogramme. Die Software schreibt die rohen MPEG2-Ströme aus der DVB-C-Quelle auf die Festplatte – nicht besonders platzsparend, dafür genügt aber ein RasPi 2 für die Arbeit. Das Umwandeln einer solchen Aufnahme ins weitaus kompaktere H.265-Format dauert jedoch selbst mit leistungsfähigen CPUs länger.

Dabein leistet die GPU im Jetson Nano allerdings im Verhältnis zum Stromverbrauch des Geräts Erstaunliches. Eine 70-Minuten-Aufnahme in SD (720 x 576) wandelte der Winzling in gut 5 Minuten in eine H.265-Datei um. Zum Platzsparen auf der Festplatte leistet der Jetson Nano also gute und effiziente Dienste. Der Aufruf von Gstreamer ist dabei allerdings etwas trickreich, da es Audio- und Videostrom zu behandeln gilt. Im Ganzen sieht das dann so aus wie in Listing 4.

Listing 4

Video rekodieren

$ gst-launch-1.0 filesrc \
  location=aufnahme.mkv ! matroskademux name=demux \
  matroskamux name=mux ! filesink location=ziel.mkv \
  demux.audio_0 ! \ mpegaudioparse ! queue ! mux. \
  demux.video_0 ! mpegvideoparse ! omxmpeg2videodec ! \
  queue ! \ omxh265enc preset-level=3 bitrate=750000 \
  EnableTwopassCBR=true control-rate=variable ! \
  queue ! mux.

Die erste Queue (beginnend mit filesrc) liest die Rohdaten aus der Datei aufname.mkv. Den entsprechenden Matroska-Container zerlegt matroskademux in seine Einzelteile. Die Anweisung name=demux benennt den Ausgang der Queue (in der Gstreamer-Sprache eine Sink), sodass er sich später referenzieren lässt. Die dritte Zeile erzeugt das Ziel der Aktion – wiederum einen Matroska-Container, der in der Datei ziel.mkv landet.

Nun geht es darum, etwas aus den beiden Strömen zu machen, die aus dem Quellcontainer kommen. Diese Ströme lassen sich mit Name.audio_index ansprechen. Bei mehreren Tonspuren in einer Aufnahme kann es also auch audio_1 geben; in unserem Beispiel existiert aber nur die Spur audio_0. Die leitet der Aufruf durch Mpegaudioparse, um das MPEG-Format zu verstehen, und dann gleich in den Zielcontainer, da hier keine Umwandlung stattfinden soll (Zeile 4).

Erst der Code ab Zeile 5 bringt die GPU zum Arbeiten. Der Videostrom läuft ebenfalls durch einen MPEG-Parser und wird dann via Omxmpeg2videodec mit Hardware-Beschleunigung dekodiert. Nun folgt ein Puffer in Form von queue, bevor es direkt mit der Kodierung mittels Omxh265enc weitergeht. Dessen Parameter beziehen sich auf die Qualität des Videos: preset-level 3 sorgt für hohe Qualität, und die Bitrate steuert die entstehende Datenmenge, was ebenfalls Einfluss auf das Bild hat. Die letzten beiden Parameter aktivieren einen zweifachen Durchlauf und eine variable Kontrollrate. Bevor das Ergebnis an den Ziel-Container geht, findet sich wiederum ein Puffer in der Queue, da es sonst zu Verklemmungen kommt.

Fazit

Der Nvidia Jetson Nano 2GB ist etwas teurer als ein Raspberry Pi 4, dafür aber auch leistungsfähiger. Der Einstieg ins Machine Learning kommt damit günstiger als beim Einbau einer entsprechenden Grafikkarte in einen PC. Besonders der Stromverbrauch von 5 bis 10 Watt unterbietet den einer aktuellen Grafikkarte deutlich. Auch für das Umwandeln von Videos eignet sich der Jetson Nano bestens. Die zugehörige Distribution enthält zudem Hardware-Beschleunigungsmodule für Chrome, sodass sich der Rechenzwerg vermutlich auch als Desktop eignet – das hat der Autor aber nicht untersucht. (jlu)

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
RASPBERRY PI GEEK KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Raspberry Pi Geek bei Google Play Readly Logo
Nach oben