Zum Hauptinhalt springen
  1. Projekte und Blogs/

Hüten von Diensten und Reihern: Microservices in freier Wildbahn

·1620 Wörter·
Ai Microservices Architecture Rabbitmq Object Detection Wildlife Defense Heron
Oliver Hihn
Autor
Oliver Hihn
Getrieben von Neugier, angetrieben von Code. Ich entwerfe und baue individuelle Technik mit Leidenschaft und Zweck.
Reiher-Projekt - Dieser Artikel ist Teil einer Serie.
Teil 2: Dieser Artikel

Willkommen zurück bei Heron Watch 🕵️‍♂️🐦
#

Im letzten Beitrag habe ich meinen etwas absurden, aber durchaus ernst gemeinten Versuch vorgestellt, meinen Gartenteich vor einem gefiederten Fischdieb zu schützen. Kurz gesagt: KI erkennt Graureiher + Sprinkleranlage = niemand fasst die Koi an.

👈 Zuvor in der Reiher-Saga…

Objekterkennung vs. Graureiher
·559 Wörter
Ki Computer Vision Objekterkennung Yolo Hobbyprojekt Wildtiere Graureiher Iot

Diesmal gehen wir allerdings unter die Haube.

Dieser Blogeintrag dreht sich ganz um die Architektur – das System von Microservices, das hinter den Kulissen zusammenarbeitet, um den Reiher in Aktion zu erwischen. Es ist herrlich über-engineered und zugleich eine großartige Demonstration von verteiltem Systemdesign in einem realen (ok, Hinterhof-)Anwendungsfall.

Legen wir los.


🧩 Der Gesamtüberblick
#

Hier ist die gesamte Architektur auf hoher Ebene:

flowchart TB %% ─────────────────────────────────────────── %% 1. Capture FrameCapture["frame-capture-svc"] FrameCapture -->|publishes JPEGs| raw_frames %% ─────────────────────────────────────────── %% 2. RabbitMQ queues subgraph RabbitMQ["RabbitMQ Broker"] direction TB raw_frames["raw_frames"] detections["detections"] predicted_frames["predicted_frames"] end %% ─────────────────────────────────────────── %% 3. Orchestration Orchestrator["orchestrator-svc"] raw_frames --> Orchestrator Orchestrator -->|MQTT trigger| MQTT MQTT["MQTT Broker
(Home Assistant)"] %% ─────────────────────────────────────────── %% 4. Inference Orchestrator -->|POST image| Inference Inference["inference-svc"] Inference -->|JSON results| Orchestrator Orchestrator -->|publishes detections| detections Orchestrator -->|publishes predicted JPEGs| predicted_frames %% ─────────────────────────────────────────── %% 5. Clip extraction ClipExtractor["clip-extractor-svc"] detections --> ClipExtractor %% ─────────────────────────────────────────── %% 6. Clip buffer & storage ClipBuffer["clip-buffer-svc"] ClipBuffer -->|maintains ring| Buffer subgraph Storage["Storage"] direction TB Buffer["/buffer (ring segments)"] Clips["/clips (saved MP4s)"] end Buffer --> ClipExtractor ClipExtractor -->|writes clips| Clips Clips --> Dashboard %% ─────────────────────────────────────────── %% 7. Dashboard & user subgraph DashboardGrp["Dashboard"] direction TB predicted_frames --> Dashboard Dashboard["dashboard-svc"] Dashboard -->|serves live + clips| User User["User / Browser"] end

Das mag wie ein unübersichtliches Pfeilgewirr aussehen, aber keine Sorge. Wir zoomen in jeden Abschnitt hinein und erkunden die Services, die diese Hinterhof-Wachhund-Magie möglich machen.


📨 Warum RabbitMQ?
#

Bevor wir uns den Service-Details widmen, lassen Sie uns über Message Broker sprechen. In einer Microservice-Architektur müssen verschiedene Komponenten effizient kommunizieren, ohne sich laut über den Raum zu rufen. Genau hier kommt RabbitMQ ins Spiel.

RabbitMQ ist ein leichtgewichtiger, bewährter Message Broker, der asynchrone Kommunikation zwischen Services ermöglicht. Ein Service kann eine Nachricht (z. B. einen neuen Frame) veröffentlichen, und ein anderer kann sie abholen und etwas Nützliches damit anstellen (z. B. Inferenz durchführen, speichern, ignorieren).

In diesem Projekt ist RabbitMQ die Verbindung zwischen:

  • Dem Kameraframe-Grabber
  • Der KI-Inferenz-Engine
  • Der Erkennungs-Pipeline
  • Der Clip-Extraktions-Logik
  • Der Dashboard-UI
  • Und allem anderen, das eventbasierte Kommunikation benötigt

RabbitMQ unterstützt Features wie persistente Queues, Routing-Keys und Fanout-Exchanges, was es ideal für diese Art von lose gekoppelter, ereignisgetriebener Architektur macht.

🛠️ Hätte ich stattdessen MQTT verwenden können? Ja, absolut. Tatsächlich könnte für kleinere Projekte oder reinen Smart-Home-Einsatz MQTT allein ausreichen – vor allem mit Lösungen wie MQTT Image. Aber ich sah hier eine großartige Gelegenheit, RabbitMQ als echten Message Broker mit Queues und Fanout zu demonstrieren und nicht nur als simples Pub/Sub-Rohr.

🚀 Was ist mit Kafka + MinIO? Wenn dies ein großmaßstäbliches Überwachungssystem mit tausenden Kameras und mehreren TB Video-Puffer wäre, würde ich zu Apache Kafka greifen und die Aufnahmen auf MinIO oder S3 speichern. Aber das hier ist ein Hinterhof-Projekt mit genau einer IP-Kamera und einem Vogel.


📸 frame-capture-svc: Das wachsame Auge
#

Dieser Service hat eine Aufgabe – und erfüllt sie mit ungetrübtem Fokus (oder… Objektivfokus?). Er verbindet sich mit einer RTSP-fähigen IP-Kamera, extrahiert JPEG-Schnappschüsse bei konfigurierter FPS und wirft sie in die raw_frames-Queue von RabbitMQ, damit der Rest der Pipeline sie verarbeitet.

Denken Sie an ihn als den Wachturm des Systems – stets wachsam, stets im Stream.

So ist es in der docker-compose.yml konfiguriert:

frame-capture-svc:
  build: ./frame-capture-svc
  environment:
    - RTSP_URL=rtsp://user:password@ip_address:554/live/ch1
    - RABBIT_HOST=rabbitmq
    - FPS=2
  volumes:
    - ./frame-capture-svc/frames:/app/frames
  depends_on:
    - rabbitmq

🔍 Einige Details
#

  • RTSP_URL zeigt auf den IP-Kamerastream.
  • RABBIT_HOST gibt an, wo RabbitMQ läuft.
  • FPS=2 bedeutet, dass wir zwei Bilder pro Sekunde erfassen – mehr als genug für unseren gefiederten Einbrecher.
  • Das Volume-Mapping dient nur dem Debugging – so kann man sehen, welche Bilder tatsächlich erfasst wurden.

🧠 inference-svc: Das Gehirn
#

Sobald ein Frame erfasst ist, leitet der orchestrator-svc (dazu gleich mehr) ihn an den inference-svc weiter, der ein YOLOv8 ONNX-Modell kapselt. Hier findet die Objekterkennung statt.

Der Service liefert ein sauberes JSON mit erkannten Objekten, Klassen, Bounding Boxes und Konfidenzwerten zurück. Er ist mit FastAPI + Uvicorn + ONNX für eine flotte Performance aufgebaut.

inference-svc:
  build: ./inference-svc
  volumes:
    - ./inference-svc/model:/app/model
  environment:
    - RABBIT_HOST=rabbitmq
    - MODEL_PATH=/app/model/best.onnx
    - CONF_THRESHOLD=0.6
  depends_on:
    - rabbitmq

🔍 Einige Details
#

  • MODEL_PATH verweist auf die ONNX-Modell-Datei.
  • CONF_THRESHOLD ist der Konfidenzschwellenwert für Erkennungen (0,6 bedeutet 60 % Sicherheit).
  • RABBIT_HOST – na klar, den RabbitMQ-Host brauchen wir auch hier.
  • Das Volume-Mapping ist der Speicherort der Modell-Datei.
  • Das Modell wird einmal geladen und für alle Anfragen wiederverwendet, was effizient ist.

Warum ein separater Service? Weil:

  • wir die Inferenz unabhängig skalieren wollen (bei Bedarf weitere Instanzen)
  • die Modell-Logik isoliert und testbar bleibt
  • wir Modelle einfach austauschen können (z. B. YOLOv4, YOLOv7, YOLOv9 etc.)

🕹 orchestrator-svc: Der Puppenspieler
#

Das Herz des Systems.

Er orchestriert alles, indem er:

  • Frames aus der raw_frames-Queue konsumiert
  • die Frames an den inference-svc zur Erkennung schickt
  • die vorhergesagten Frames in die predicted_frames-Queue veröffentlicht
  • die Erkennungsergebnisse überprüft und wenn eine interessante Klasse erkannt wird:
    • die Erkennungsergebnisse in die detections-Queue veröffentlicht
    • einen MQTT-Trigger an Home Assistant (oder ein anderes System) sendet

Betrachten Sie ihn als Dirigenten in unserem reiherjagenden Orchester.

orchestrator-svc:
  build: ./orchestrator-svc
  environment:
    - RABBIT_HOST=rabbitmq
    - INFERENCE_URL=http://inference-svc:8000/detect
    - DETECTION_CLASSES=heron # or list: heron,person,dog
    - MQTT_HOST=homeassistant.fritz.box
    - MQTT_PORT=1883
    - MQTT_USERNAME=admin
    - MQTT_PASSWORD=redacted
  depends_on:
    - rabbitmq

🔍 Einige Details
#

  • RABBIT_HOST ist der RabbitMQ-Broker.
  • INFERENCE_URL ist die URL des Inferenz-Services.
  • DETECTION_CLASSES ist eine kommagetrennte Liste der zu erkennenden Klassen (z. B. heron,person,dog).
  • MQTT_HOST ist der MQTT-Broker (bei mir Home Assistant).
  • MQTT_PORT, MQTT_USERNAME und MQTT_PASSWORD sind für die MQTT-Verbindung.
  • depends_on stellt sicher, dass RabbitMQ läuft, bevor der Orchestrator startet.
  • Der Orchestrator ist der einzige Service, der den MQTT-Broker kennt, weshalb nur er diese Zugangsdaten besitzt.

🌀 clip-buffer-svc: Der Ringmeister
#

Dieser Service zeichnet fortlaufend kleine Segmente (z. B. 5 Sekunden HLS-Chunks) auf und hält sie in einem Ringpuffer in /buffer vor.

Die Idee dahinter: Schon bevor ein Reiher erkannt wird, liegt das Filmmaterial bereits in einer Dauerschleife bereit. So verpassen wir nicht den „Anflug“-Teil der Aktion.

Ringpuffer + schneller Zugriff = saftige Pre-Roll-Clips.

  clip-buffer-svc:
    build: ./clip-buffer-svc
    environment:
      - RTSP_URL=rtsp://user:password@ip_address:554/live/ch1
    volumes:
      - buffer:/buffer
    depends_on:
      - rabbitmq

🔍 Einige Details
#

Hier gibt es nicht viel zu sagen:

  • RTSP_URL ist wie zuvor die RTSP-URL der Kamera.
  • /buffer ist ein Docker-Volume, das die Ringsegmente speichert.
  • Es nutzt FFmpeg zur Verarbeitung des Videostreams.
  • Jedes Segment erhält den Timestamp seiner Aufnahme als Namen, sodass man später das richtige leicht findet.

✂️ clip-extractor-svc: Der Editor
#

Wenn der orchestrator einen Reiher erkennt, veröffentlicht er eine Nachricht in der detections-Queue. Der clip-extractor-svc konsumiert Nachrichten aus dieser Queue und holt sich die relevanten Segmente aus dem Ringpuffer.

Er verwendet einen 10-Sekunden-Pre-Roll und einen 10-Sekunden-Post-Roll, um die gesamte Aktion aufzunehmen. Nach der Erkennung wartet er kurz, damit der Reiher verschwunden ist, bevor die Aufnahme beendet wird. Anschließend fügt er die Segmente mit FFmpeg zu einer einzigen MP4-Datei zusammen und speichert sie in /clips. Nach dem Speichern wird für das Dashboard ein Thumbnail generiert.

flowchart TD detections["RabbitMQ: detections"] --> ClipExtractor["clip-extractor-svc"] Buffer["/buffer (ring segments)"] --> ClipExtractor ClipExtractor --> Clips["/clips (saved MP4s)"]
clip-extractor-svc:
  build: ./clip-extractor-svc
  environment:
    - RABBIT_HOST=rabbitmq
    - BUFFER_DIR=/buffer
    - OUTPUT_DIR=/clips
  volumes:
    - buffer:/buffer
    - clips:/clips
  depends_on:
    - rabbitmq
    - clip-buffer-svc

🔍 Einige Details
#

  • RABBIT_HOST ist der RabbitMQ-Broker.
  • BUFFER_DIR ist der Pfad zum Ringpuffer im Container.
  • OUTPUT_DIR ist der Pfad, um die Clips im Container zu speichern.
  • Die volumes mounten die Buffer- und Clips-Verzeichnisse.
  • depends_on stellt sicher, dass der Clip-Buffer läuft, bevor der Clip-Extractor startet.

Ja, der Reiher steht jetzt in seinen eigenen Überwachungsclips im Rampenlicht.


📊 dashboard-svc: Das Kontrollzentrum
#

Ein Flask + Socket.IO Dashboard, das:

  • Live-Frames streamt
  • die neuesten Erkennungen zeigt
  • Zugriff auf gespeicherte Clips bietet
  • Logs und Status der Services anzeigt

Perfekt, um live mitzuerleben, wie der Reiher leicht in seiner Pläne gestört wird.

dashboard-svc:
  build: ./dashboard-svc
  ports:
    - "5000:5000"
  environment:
    - RABBIT_HOST=rabbitmq
    - CLIPS_DIR=/clips
  volumes:
    - clips:/clips
  depends_on:
    - rabbitmq

🔍 Einige Details
#

  • RABBIT_HOST ist natürlich erneut der RabbitMQ-Broker.
  • CLIPS_DIR ist der Pfad zu den gespeicherten Clips im Container.
  • Die volumes mounten das Clips-Verzeichnis.
  • Die ports legen das Dashboard auf Port 5000 frei.
  • depends_on stellt sicher, dass RabbitMQ läuft, bevor das Dashboard startet.

🧩 Erkenntnisse zum Systemdesign
#

Sie fragen sich, wie das Setup funktioniert? Jeder Service läuft in seinem eigenen Docker-Container, definiert in einer docker-compose.yml. Die Kommunikation erfolgt über RabbitMQ, und das Ganze ist modular aufgebaut – tauschen Sie ein Modell aus, fügen Sie weitere Kameras hinzu oder aktualisieren Sie einen Service, ohne den Rest anzufassen.

Umgebungsvariablen ermöglichen die Konfiguration ohne Codeänderungen – über eine .env-Datei oder direkt in docker-compose.

Während der Entwicklung sind keine Container nötig. Einfach die Services lokal mit Standardwerten starten. Nur für den RTSP-Stream benötigt man eine echte oder virtuelle Kamera, aber ich arbeite an einem gemockten RTSP-Setup mit einem Beispielvideo und zwei leichten Containern.


🧑‍💻 Benutzererfahrung
#

Das alles erreicht den Benutzer – mich – schließlich über ein Web-Dashboard. Von hier aus kann ich Erkennungen überprüfen, das System debuggen oder einfach bei einer frischen Tasse Kaffee das Drama der Tierüberwachung genießen.

Dashboard-Screenshot
Das Dashboard in Aktion: Live-Frames und Erkennungs-Logs.

🧠 Warum Microservices?
#

Sie fragen sich vielleicht: „Warum nicht einfach einen Monolithen schreiben?“

Gute Frage. Darum bin ich voll auf Microservices gesetzt:

  • Modularität: Jeder Service ist einfach und fokussiert
  • Skalierbarkeit: Führen Sie inference-svc bei Bedarf auf einer stärkeren Maschine aus
  • Fehlerisolation: Ein Ausfall betrifft nicht das gesamte System
  • Asynchronität: RabbitMQ entkoppelt alles sauber

Außerdem… weil es Spaß macht.


Bleiben Sie dran für zukünftige Beiträge dieser Serie:

  • Annotieren mit Stil (und Geschwindigkeit)
  • Tipps für Live-Deployments
  • Dashboard-UI- & Integrations-Tricks

Bis dahin: frühzeitig erkennen, verantwortungsvoll spritzen.

Fun Fact: Reiher hassen plötzliche laute Geräusche. Offenbar leben sie nicht nach YOLO – sie zucken zusammen.

Reiher-Projekt - Dieser Artikel ist Teil einer Serie.
Teil 2: Dieser Artikel