Files
christian c11fe9ab04 Vogelalbum 1.0.0
Eine Kamera am Vogelhaus, ein Rechner der erkennt wer da war — und eine
Sammlung, die sich von allein fuellt.

Zwei neuronale Netze: eines findet den Vogel im Bild, eines bestimmt die
Art. 148 heimische Arten, auf Deutschland eingeschraenkt und neu normiert —
ohne diese Einschraenkung wird aus einer Kohlmeise eine nordamerikanische
Schwesterart. Daraus entsteht ein Sammelalbum: nummerierte Seiten,
gefundene Arten in Farbe, fehlende als Schatten, dazu Erstfang, Punkte,
Stufen und 15 Abzeichen.

Laeuft vollstaendig auf eigener Hardware. Kein Cloud-Dienst, kein Konto,
keine Telemetrie; die Bilder verlassen das eigene Netz nicht.

Zwei getrennt deploybare Teile: die Sammlung als Django-Anwendung, die
Erkennung als eigener Dienst. Der Erkennungs-Dienst fragt selbst an und
braucht deshalb weder eine erreichbare Adresse noch einen offenen Port —
er laeuft hinter einem gewoehnlichen Router.

Dabei: Firmware fuer Raspberry Pi und ESP32-S3, Anbindung an Home
Assistant ueber MQTT, Oberflaeche auf Deutsch und Englisch, Einstellungen
je Kamera aus dem Browser, und ein Weg fuer Fotos von unterwegs.

AGPL-3.0.
2026-10-15 14:30:02 +00:00

6.7 KiB

Betrieb

← zurück zum Überblick · alle Kapitel

Live zuschauen, mehrere Kameras, der Detector auf einem eigenen Host und das Deployen der Web-App. Also alles, was nach dem ersten erkannten Vogel kommt.

Live zuschauen

Eine Kamera liefert einen Stream. Wer direkt bei ihr zuschaut, nimmt ihn der Erkennung weg — auf einem Pi Zero W ist das keine Einstellung, sondern Physik: die Funkstrecke trägt rund 2 Mbit/s, ein zweiter Client sättigt sie.

Der Detector liest den Stream aber ohnehin rund um die Uhr. Also verteilt er ihn weiter:

python -m detector watch --source http://kamera:8080/stream --live-port 8131

Damit schauen beliebig viele Leute zu, und die Kamera merkt nichts davon — sie hat weiterhin genau einen Zuschauer. Solange niemand hinsieht, wird auch nichts umkodiert: bei MJPEG-Quellen gehen die JPEG-Bytes unverändert durch, und das Kodieren für andere Quellen passiert im HTTP-Thread, einmal je Bild statt einmal je Zuschauer. Die Erkennung wird davon nicht langsamer.

Adresse wozu
http://<detector>:8131/live MJPEG, für mehrere Zuschauer
http://<detector>:8131/bild.jpg Einzelbild
http://<detector>:8131/ Seite mit Livebild

In der Web-App

Eingetragen wird das im Django-Admin bei der Kamera: Live-Adresse auf http://<detector>:8131/live, dann Live-Bild zeigen ankreuzen. Danach gibt es die Seite /live/ und einen Eintrag in der Navigation. Ohne Freigabe existiert beides nicht — kein Link, keine Seite, 404.

Das Bild lädt der Browser direkt beim Verteiler; die Web-App reicht nichts durch. Sonst hinge an jedem Zuschauer eine offene Verbindung im Webserver.

Für eine öffentliche Instanz: VOGELALBUM_LIVE_ERLAUBT=0. Dann ist Live nicht einschaltbar, auch nicht mit einem Fehlklick im Admin, und die Admin-Liste zeigt „gesperrt". Ein Livebild aus einem Privatgarten soll nicht an einem Häkchen hängen — für einen Showcase ist die Sammlung ohnehin das Interessantere.

Der Verteiler selbst kennt keine Anmeldung. Er gehört ins Heimnetz.

Mehrere Kameras

Jede Kamera bekommt einen eigenen Token. Der Token identifiziert sie — die Web-App muss nie wissen, wo sie steht, und ein kompromittierter Standort lässt sich einzeln stilllegen, ohne die anderen anzufassen.

manage.py camera add futterhaus --name "Futterhaus Garten" --location "Garten"
manage.py camera add nistkasten --name "Nistkasten Apfelbaum"
manage.py camera list
manage.py camera rotate futterhaus     # Token erneuern
manage.py camera disable nistkasten

Der Token wird genau einmal ausgegeben; gespeichert ist nur sein SHA-256. Bei 256 Bit Entropie braucht es kein Salt, und wer die Datenbank in die Hand bekommt, kann damit trotzdem nichts einliefern.

Pro Kamera läuft eine eigene detector watch-Instanz. Sie können auf demselben Host laufen; rechne mit rund 40 % eines Kerns je Kamera bei --classify-every 2 und 4 fps.

Die Sammlung ist dabei gemeinsam: Findet die Gartenkamera die erste Amsel, ist sie im Dex freigeschaltet — die Nistkastenkamera bekommt für dieselbe Art keinen zweiten Erstfang. Das ist Absicht, es ist die Sammlung eines Menschen und nicht die einer Kamera. Die Sichtungsliste lässt sich trotzdem nach Kamera filtern.

manage.py camera list markiert Kameras, die sich über eine Stunde nicht gemeldet haben — der schnellste Weg zu „warum kommt da nichts mehr".

Betrieb

Detector auf einem eigenen Host

Ein frischer Debian- oder Ubuntu-Container, dann:

sudo ./scripts/setup_detector_host.sh

Das Skript legt den Dienstbenutzer vogelalbum an, klont nach /opt/vogelalbum, baut das venv, holt die Modelle, schreibt /etc/vogelalbum.env und installiert die systemd-Unit. Idempotent — ein zweiter Lauf aktualisiert nur.

Danach /etc/vogelalbum.env ausfüllen und systemctl enable --now vogelalbum-detector.

Dimensionierung

CPU 1 Kern je Kamera (bei --classify-every 2 und ~4 fps rund 40 % Last)
RAM 512 MB je Instanz, 1 GB für Luft
Platte 4 GB fürs System, plus ~150 kB je gespeicherter Sichtung

Netz: ausgehend zur Kamera und zur Web-App. Eingehend nichts.

Achtung, wenn Kamera und Erkennungs-Host in verschiedenen Netzsegmenten liegen: Die Route muss stehen. Bei uns hängt die Kamera im WLAN-Segment und der Server im LAN — ein Scan des falschen Segments findet sie schlicht nicht.

Zweite Kamera: eigene /etc/vogelalbum-<name>.env, die Unit als vogelalbum-detector@.service ablegen, dann systemctl enable --now vogelalbum-detector@nistkasten.

Die Web-App deployen

Nur die Web-App wird deployt. Der Detector bleibt im Heimnetz — er braucht Sichtverbindung zur Kamera.

  1. DNS auf den Host zeigen lassen.
  2. Anwendung aus dem Repo anlegen, Build über docker-compose.yml. ⚠️ Der Eingang ist medien auf Port 8080, nicht web auf 8000. Das compose startet zwei Dienste: Gunicorn und einen nginx davor, der die Vogelfotos vom Volume ausliefert. Zeigt der Reverse Proxy direkt auf web, lädt die Seite — aber jede Karte der Sammlung hat ein kaputtes Bild, denn Django liefert unter DJANGO_DEBUG=0 keine Medien aus. Begründung ausführlich in deploy/nginx-media.conf.
  3. Persistentes Volume auf /data mounten. Das ist der Schritt, den man vergisst — ohne ihn löscht jeder Deploy Sammlung und Fotos.
  4. Umgebungsvariablen setzen (siehe Einstellungen).
  5. Denselben Token auf dem Erkennungs-Host eintragen und VOGELALBUM_API auf die öffentliche Adresse setzen.
  6. Auf einer öffentlichen Instanz VOGELALBUM_LIVE_ERLAUBT=0 setzen — steht im docker-compose.yml schon drin. Ohne die Angabe wäre das Livebild aus dem eigenen Garten erlaubt, denn der Standard im Code ist 1.

Der erste Start dauert ein paar Minuten: Der Entrypoint holt einmalig die Referenzfotos. Danach kostet der Schritt nichts mehr. migrate, collectstatic und seed_dex erledigt er ebenfalls — neue Arten im Katalog landen also mit dem nächsten Deploy automatisch in der Datenbank.

📘 Schritt für Schritt mit Coolify, inklusive Umzug einer bestehenden Sammlung: deploy/COOLIFY.md. Dort steht auch, wie Datenbank und Fotos als 1:1-Kopie auf das Volume kommen — Kamera-Tokens, Handbestimmungen und Abzeichen bleiben dabei erhalten, und die Kameras zu Hause liefern mit ihrem bisherigen Token weiter.

Einbinden auf einer anderen Seite

/widget/ ist dafür da — keine Navigation, transparenter Hintergrund:

<iframe src="https://vogelalbum.example.org/widget/"
        style="width:100%;height:110px;border:0" loading="lazy"></iframe>

Wer lieber selbst gestaltet, nimmt /api/stats/ — Level, Artenzahl und die letzte Sichtung als JSON.