BFFT Techblog April: “Nearly” Real-Time Traffic-Analysis
16.04.2018

BFFT Techblog: “Nearly” Real-Time Traffic-Analysis

Motivation

In meinem letzten Blog-Artikel H2O, when should i go to work?” habe ich beschrieben, wie sich mittels H2O ein Random-Forest-Modell erzeugen lässt, um damit die Anzahl der Fahrzeuge auf einem Bild zu zählen.

In der Datenanalyse ist man oftmals damit beschäftigt, im ersten Schritt in R oder Python ein prototypisches Modell umzusetzen. Möchte man dieses Modell in Produktion einsetzen, ist es häufig notwendig, es zum Beispiel in Java zu implementieren. Mit H2O gestaltet sich dieser Schritt als sehr einfach. Im Folgenden werde ich deshalb zeigen, wie sich das im Rahmen einer Real-Time-Verarbeitung umsetzen lässt.

Model-Extraktion

Wir haben das Modell in R wie folgt erzeugt:

Nun ist es einfach, das Modell als POJO (Plain old Java object)1 aus H2O zu extrahieren

und es im aktuellen Arbeitsverzeichnis abzulegen. Die generierte Java-Datei besitzt nur eine Abhängigkeit zu H2O. Somit ist es ideal dafür geeignet, um in ein Framework wie Apache Spark2 eingebettet zu werden.

Die Datei beinhaltet 200×3=600200×3=600 Klassen, das entspricht einer Klasse für jeden Baum und jeweils eine Klasse für jede Kategorie jedes Baums. Des Weiteren haben wir unser Modell für Bilder der Größe 80×11080×110 Pixel trainiert, somit ergeben sich 8800 Features, d.h. eines für jedes Pixel. Ein Beispiel sieht wie folgt aus:

Das ist relativ viel Quellcode, aber es bietet ein sehr schönes Beispiel für die häufige Verwendung des Conditional-Operators, der genutzt wird, um einen Entscheidungsbaum nachzubilden. Nun können wir die Java-Klasse kompilieren lassen und Prognosen unter Verwendung der predictCSV-Klasse ausführen, die von H2OH2O geliefert wird3 4. Alternativ lässt sich die generierte Java-Klasse wie folgt aufrufen:

Hierbei ist DRF_model_R_1478847643061_1 der Name des heruntergeladenen POJO, und data stellt ein Array der Features dar, die für die Prognose benötigt werden. Wir integrieren dies in eine Prediction-Klasse, die die Features als Eingabe erhält.

Daten-Verarbeitungs-Pipeline

Die Daten-Verarbeitungs-Pipeline lässt sich wie folgt visualisieren:

Die einzelnen Schritte daraus werden im Folgenden näher beschrieben.

Feature Generation

Die Webcam-Bilder von BayernInfo sehen wie folgt aus:

Um die Features zu erhalten, d.h. die Bilder herunterzuladen, die für die Vorhersage benötigt werden, können wir die folgende Java-Implementierung verwenden

Da die Webcam-Bilder auf der BayernInfo-Seite nur alle 60 Sekunden aktualisiert werden, lässt sich keine Real-Time-Verarbeitung im Millisekunden-Zeitbereich umsetzen. Somit wird der obige Code nur jede Minute aufgerufen, um damit die Anzahl der Fahrzeuge auf dem Bild zu bestimmen. Somit kann man lediglich eine (angenäherte) Echtzeitverarbeitung erreichen5. Hier konvertieren wir zusätzlich das gegebene Bild in ein Byte-Array. Wir senden dieses Byte-Array an das Apache Kafka Topic in.webcam.data. Apache Kafka ist ein Open-Source Stream-Processing-Framework, welches in Scala und Java implementiert wurde 6 7.

Ich möchte kurz darstellen, wie sich Apache Kafka lokal einsetzen lässt8:

  1. Download der aktuellen Version von Apache Kafka 0.10.2.0, die ich verwendet habe9.
  2. Um Kafka verwenden zu können, muss zunächst Zookeeper im bin/windows-Verzeichnis von Kafka gestartet werden: zookeeper-server-start.bat ../../config/zookeeper.properties
  3. Starte Kafka kafka-server-start ../../config/server.properties.
  4. Erstelle das Topic “in.webcam.data” mit dem folgendem Befehl: kafka-topics.bat –create –topic in.webcam.data –zookeeper localhost:2181 –partitions 1 –replication-factor 1

Ich gehe lediglich so vor, um meine Beispielanwendung zu zeigen. Jedoch ist klar, dass Apache Kafka in einer Produktionsumgebung auf einem Cluster laufen sollte. Des Weiteren empfiehlt es sich z.B. Apache Thrift10 zu verwenden, um komplexere Datenstrukturen zu einem Topic zu senden oder solche zu empfangen.

Ähnlich wie bereits in meinem vorherigen Blog-Post beschrieben, wird die folgende Verarbeitung auf jedem Bild durchgeführt: Schneide den Header ab, verwende nur die linke Seite des Bildes, führe eine grau-Skalierung durch und zerteile es in Teilbilder. Hierfür habe ich eine ImageDivider-Klasse geschrieben, die auf Github zusammen mit dem restlichen Quellcode zu finden ist.

Spark-Streaming-Job

Der Hauptteil des Spark-Streaming-Jobs sieht wie folgt aus:

Die Einzelschritte lassen sich folgendermaßen erklären:

  1. Abrufen der Bilder vom Topic in.webcam.data alle 60 Sekunden. Dies lässt sich in der Kafka-Consumer-Konfiguration einstellen, siehe getConsumerConfig (TrafficAnalysisApplication-Klasse) auf Github.
  2. Zerteile die Bilder unter Verwendung der ImageDivider-Klasse.
  3. Führe die Prognose auf alle Teilbilder aus, d.h. schätze, ob 0, 1 oder 2 Fahrzeuge darauf zu erkennen sind.
  4. Wir erhalten 16 Teilbilder, dabei erhalten alle den gleichen Key, d.h. den Zeitstempel, zu dem das Bild empfangen wurde. Somit können wir mittels reduceByKey die Anzahl der Fahrzeuge auf dem gesamten Bild abschätzen.
  5. Speichere das Ergebnis unter dem Elastisearch-Index analysis/log.

Der Spark-Streaming-Job kann nun mit dem spark-submit-Kommando auf einem Spark-Cluster ausgeführt werden11. Das Ergebnis wird zu Elasticsearch mit meiner CountData-Klasse gesendet, siehe Github.

“Elasticsearch ist eine verteilte, auf JSON basierende Such- und Analytik-Engine”12. Die aktuelle Version (5.2.2) kann unter https://www.elastic.co/downloads/elasticsearch heruntergeladen werden. Es arbeitet hervorragend mit dem json-Datenformat, deshalb verwenden wir das Jackson-Projekt13, um die Daten in dieses Format zu transformieren. Zur Visualisierung der Daten wird innerhalb des ELK-Stacks Kibana verwendet. Download der aktuellen Version unter: https://www.elastic.co/downloads/kibana

Nun können wir in Kibana ein Line-Chart-Diagramm erstellen (siehe Github README), welches im Folgenden dargestellt ist:

Hier sehen wir die geschätzte Anzahl der Autos je Zeiteinheit. Es ist möglich, einen Auto-Refresh auf “1 Minute” zu setzen, damit sich das Dashboard automatisch aktualisiert.

Zusammenfassung

Es wurde gezeigt, wie wir einen anfänglichen Prototypen in R nehmen und in Produktion bringen können, um eine Real-Time-Streaming-Anwendung zu erstellen. Wir können diese Art von Verkehrsanalyse für weitere Webcams in Bayern vornehmen. Somit ist es möglich, einen Service zu erstellen, der den wahrscheinlichen Verkehr auf einer geplanten Route abschätzen kann. Weiterhin ist es denkbar, eine Art von Anomalie- oder Ausreißer-Erkennung auf diesen Daten durchzuführen, um beispielsweise Verkehrsunfälle oder ähnliche abnorme Ereignisse zu identifizieren. Darüber hinaus können wir fortgeschrittene Werkzeuge wie OpenCV14 oder TensorFlow15 verwenden, um die Erkennung von Fahrzeugen auf den Bildern und damit das Zählen zu verbessern.

1.  https://de.wikipedia.org/wiki/Plain_old_Java_object

2. http://spark.apache.org/

3. http://docs.h2o.ai/h2o/latest-stable/h2o-docs/faq.html

4. http://projects.rajivshah.com/blog/2016/08/22/H2O_prod/

5. https://de.wikipedia.org/wiki/Real-time_computing#Near_real-time

6. https://de.wikipedia.org/wiki/Apache_Kafka

7. https://kafka.apache.org/

8. https://kafka.apache.org/quickstart

9. https://kafka.apache.org/downloads

10. https://thrift.apache.org/

11. http://spark.apache.org/docs/latest/submitting-applications.html

12. https://www.elastic.co/de/products

13. https://github.com/FasterXML/jackson

14. http://opencv.org/

15. https://www.tensorflow.org/


Autor: Andreas W. (Entwicklung Konzepte & Tooling)
Kontakt: techblog@bfft.de
Bildquellen: Andreas W.

Themenvorschau Mai 2017: „Divide and Conquer NURBS into Polylines“ – Wie man parametrische Non-Uniform Rational B-Splines in Polylines aus Knoten und Kanten mittels eines effizienten Divide and Conquer Ansatzes umwandeln kann.


Jobs bei BFFT: