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.
7.0 KiB
Die Kameraseite
Vogelalbum braucht von der Kamera nur eines: einen Stream, den der Detector lesen kann. Alles andere passiert woanders.
Der Pi erkennt nichts. Ein Pi Zero W schafft das Streamen, aber keine Inferenz — die beiden Modelle brauchen rund 30 ms auf einem N100 und ein Vielfaches davon auf einem Zero. Deshalb streamt der Pi, und der Detector läuft auf einer Maschine mit Rechenleistung im selben Netz.
Was du brauchst
- Raspberry Pi mit Kameraanschluss — ein Zero W reicht und ist der sparsamste Weg. Ein Pi 4/5 kann zusätzlich den Detector mitlaufen lassen.
- Ein Kameramodul. Getestet mit dem OV5647 (das ältere 5-MP-Modul).
- Raspberry Pi OS (Bookworm oder neuer), WLAN eingerichtet.
Einrichten
sudo cp vogelkamera.py /opt/vogelkamera_server.py
sudo cp vogelkamera.service /etc/systemd/system/
sudo systemctl enable --now vogelkamera
journalctl -u vogelkamera -f
🔑 Kein zusätzliches Paket. Der Server ruft rpicam-vid auf, das bei
Raspberry Pi OS ohnehin dabei ist, und liest dessen MJPEG-Ausgabe mit. Die
Arbeit passiert im C-Programm; Python schiebt nur Bytes weiter. Auf einem
Zero W ist das der Unterschied zwischen läuft und läuft nicht.
⚠️ Bis zum 25.09.2026 stand hier python3-picamera2 — für eine Fassung,
die eleganter aussah und nie auf einem Pi gestartet worden war. Auf dem
Gerät am Vogelhaus lief die ganze Zeit etwas anderes, und picamera2 war dort
nicht einmal installiert. Auf den Punkt gebracht: im Repo lag Code, der nie
getestet wurde.
Seitdem steht in diesem Ordner, was tatsächlich läuft — und zwar seit dem 28.08.2026 ununterbrochen.
Danach:
| Adresse | wozu |
|---|---|
http://<pi>:8080/stream |
der MJPEG-Stream — das kommt in VOGELALBUM_SOURCE |
http://<pi>:8080/ |
Vorschauseite im Browser |
http://<pi>:8080/status |
Bildrate, Funkstärke, Temperatur, Laufzeit als JSON |
http://<pi>:8080/temp |
CPU-Temperatur |
http://<pi>:8080/gesund |
200, sobald Bilder ankommen |
Die drei unteren fassen den Stream nicht an — das ist der richtige Weg,
eine laufende Kamera zu prüfen. /status liefert dieselben Felder wie die
ESP32-Kameraseite (../esp32cam/), ein Werkzeug muss
also nicht wissen, was am anderen Ende hängt.
Hängt die Kamera über Kopf: --vflip und --hflip stehen im Skript bereits
im Aufruf von rpicam-vid (capture_loop). Beide zusammen ergeben die
180°-Drehung, einzeln spiegeln sie.
Drei Dinge, die man einmal falsch macht
Nur ein Zuschauer gleichzeitig
Ein Pi Zero W liefert über WLAN rund 2 Mbit/s. Ein zweiter Client teilt sich diese Strecke — und der Detector fällt in Timeouts, ohne dass irgendwo ein Fehler auftaucht. Die Bildrate halbiert sich einfach.
Das ist bei der Entwicklung zweimal passiert: einmal durch ein curl auf den
Stream, mit dem eine Störung diagnostiziert werden sollte, und einmal, weil
die Vorschauseite im Browser offen stand. Gemessen: 0,9 statt 1,9 Bilder pro
Sekunde, solange ein zweites Fenster mitlas.
Deshalb weist vogelkamera.py einen zweiten Zuschauer mit 503 ab, statt
beide schlecht zu bedienen. Wer selbst zuschauen will, hält kurz den Detector
an — oder nimmt /snapshot, das kostet nur ein Bild.
⚠️ Bis zum 09.10.2026 stand das hier und stimmte nicht. Die Fassung, die auf dem Gerät läuft, hat den zweiten Zuschauer nur gezählt und beide bedient; abgewiesen hat ihn nur die ESP32-Kameraseite. Aufgefallen ist es erst beim Nachziehen der Tests, die seit dem 25.09. auf die alte Fassung zeigten. Seitdem tut der Code, was hier steht.
Ein Stream, der 15 Sekunden kein Bild liefert, wird beendet. Das klingt nebensächlich, ist aber die Bedingung dafür, dass die Absage überhaupt funktionieren darf: Ohne neues Bild schreibt der Server nichts und merkt deshalb nicht, dass der Zuschauer schon weg ist — der eine Platz bliebe für immer besetzt und die Kamera wäre durch ihre eigene Schutzsperre dauerhaft stumm. Der Detector verbindet von selbst neu.
Die Temperatur im Blick behalten
Ein Vogelhaus in der Sonne ist ein geschlossener Kasten. Gemessen an einem Septembernachmittag: 72 °C auf einem Pi Zero W. Ab etwa 80 °C drosselt der Pi von selbst, und die Bildrate fällt — ohne Fehlermeldung.
curl http://<pi>:8080/temp
Diese Abfrage kostet den Stream nichts, im Gegensatz zu einem Blick auf das
Bild. ping taugt hier nicht als Gesundheitsprüfung: ICMP fällt bei
gesättigtem WLAN als Erstes aus, während TCP weiterläuft — eine Entwarnung per
ping ist Glück, keine Messung.
Das vollständige Bild gibt /status, ebenfalls ohne den Stream anzufassen:
curl -s http://<pi>:8080/status
{"kamera":"bereit","bilder":1751409,"fps":3.84,"letztes_bild_s":0.2,
"fehler":"0x0","laufzeit_s":401238,"zuschauer":true,"rssi":-58,
"temperatur_c":55.1,"speicher_frei":218103808}
| Feld | Gesund | Woran es sonst liegt |
|---|---|---|
fps |
stabil über Stunden | fällt → zweiter Zuschauer, Funkstrecke, Hitze |
rssi |
über −70 dBm | darunter näher ran oder Antenne ausrichten |
temperatur_c |
unter 70 °C | geschlossener Kasten in der Sonne drosselt |
letztes_bild_s |
unter 1 s | steht → Kamera liefert nicht mehr |
zuschauer |
true im Normalbetrieb |
false, obwohl der Detector läuft → er kommt nicht durch |
bilder und fps zählen, was die Kamera liefert, nicht was ein Zuschauer
abholt. Ein fps über null bei zuschauer: false heißt also: die Kamera
arbeitet, es holt nur niemand ab.
Wer den Pi in einen geschlossenen Kasten baut: Schattenseite wählen, Öffnung für Luft lassen, und die Kamera nicht direkt neben den SoC setzen.
Die Bildrate hängt am Licht, nicht an der CPU
Über WLAN ist die Strecke bandbreitenbegrenzt. Ein helles, detailreiches Bild ergibt größere JPEGs und damit weniger Bilder pro Sekunde. Gemessen über neun Tage am selben Aufbau: nachts 6,2 fps (fast schwarzes Bild), tagsüber 3,7–4,2 fps.
Reicht die Bildrate nicht, ist --qualitaet der Hebel — nicht die Auflösung.
Die Auflösung braucht man, damit der Vogel groß genug im Bild ist.
Wie groß muss der Vogel im Bild sein?
Das ist die Frage, an der ein Aufbau scheitert, und sie lässt sich vorher beantworten — ohne auf einen echten Vogel zu warten:
python -m detector karte --source http://<pi>:8080/stream --ziel karte.jpg
Der Detector montiert ein Vogelfoto über ein Raster von Positionen in aktuelle Bilder deiner Kamera und zeigt, wo er anspringt. Details im Kamera-Kapitel.
Die zwei Regeln, die dabei herausgekommen sind:
- Ein Vogel braucht 5–8 % der Bildfläche. Vor ruhigem Hintergrund reichen 5 %, vor einem Baum im Wind sind es 8 %.
- Die Landefläche gehört in die untere Bildmitte, nicht an den unteren Bildrand. Dort unten findet der Detektor auch einen großen Vogel nicht zuverlässig. Eine Kamera, die flach über das Futterbrett hinwegschaut, legt jeden Gast genau in diese tote Zone.