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.
161 lines
7.0 KiB
Markdown
161 lines
7.0 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
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/`](../esp32cam/README.md)), 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.
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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](../docs/kamera.md#hängt-die-kamera-richtig-detector-karte).
|
||
|
||
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.
|