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.
201 lines
8.3 KiB
Markdown
201 lines
8.3 KiB
Markdown
# Die Kamera anschließen
|
||
|
||
<sub>[← zurück zum Überblick](../README.md) · [alle Kapitel](README.md)</sub>
|
||
|
||
Vogelalbum braucht von der Kamera nur einen Stream. Hier steht, welche Formen
|
||
davon gelesen werden, wie man die Schwellwerte einstellt und — die Frage, an
|
||
der ein Aufbau wirklich scheitert — **wie groß ein Vogel im Bild sein muss**.
|
||
|
||
Der Aufbau der Kameraseite selbst steht getrennt: [Raspberry
|
||
Pi](../pi/README.md) oder [ESP32-S3](../esp32cam/README.md).
|
||
|
||
**Noch keine Kamera aufgebaut?** Es gibt zwei fertige Kameraseiten, beide mit
|
||
denselben Pfaden — der Detector merkt den Unterschied nicht:
|
||
|
||
- **[pi/README.md](../pi/README.md)** — Raspberry Pi mit Kameramodul, MJPEG-Server
|
||
als Dienst (`pi/vogelkamera.py`). Die bessere Optik.
|
||
- **[esp32cam/README.md](../esp32cam/README.md)** — ESP32-S3 mit Kameramodul
|
||
(`esp32cam/`). Ein Bruchteil des Preises, externe Antenne möglich, und
|
||
`/status` liefert Bildrate, Funkstärke und Temperatur, ohne den Stream
|
||
anzufassen.
|
||
|
||
Beide Anleitungen nennen die drei Dinge, die man dabei einmal falsch macht —
|
||
zweiter Zuschauer, Hitze im Kasten, Bildrate am Licht statt an der CPU.
|
||
|
||
Der Detector spricht MJPEG, RTSP, Einzelbild-URLs und Ordner:
|
||
|
||
```bash
|
||
# MJPEG (mjpg-streamer, motion, picamera2-Beispielserver)
|
||
python -m detector watch --source "http://kamera.local:8080/stream"
|
||
|
||
# Einzelbild im Sekundentakt — genügsamer, für einen Nistkasten meist genug
|
||
python -m detector watch --source "http://kamera.local/snapshot.jpg" --interval 1
|
||
|
||
# RTSP (braucht ffmpeg auf dem Host)
|
||
python -m detector watch --source "rtsp://kamera.local:8554/cam"
|
||
|
||
# Ordner mit Bildern, zum Ausprobieren ohne Kamera
|
||
python -m detector watch --source ./testbilder
|
||
```
|
||
|
||
Mit Anbindung an die Web-App:
|
||
|
||
```bash
|
||
python -m detector watch \
|
||
--source "http://kamera.local:8080/stream" \
|
||
--api http://127.0.0.1:8000 \
|
||
--token "$VOGELALBUM_TOKEN" \
|
||
--save-to /var/lib/vogelalbum/rohdaten
|
||
```
|
||
|
||
`--save-to` schreibt Bild und JSON zusätzlich auf Platte. Das kostet fast
|
||
nichts und ist später das Trainingsmaterial fürs eigene Modell.
|
||
|
||
### Schwellwerte einstellen
|
||
|
||
Die Voreinstellungen passen zu *unserer* Kamera. An einer anderen gehören sie
|
||
gemessen:
|
||
|
||
```bash
|
||
python -m detector tune --source "http://kamera.local:8080/stream"
|
||
```
|
||
|
||
Zeigt live den Anteil bewegter Pixel und die Bildhelligkeit. `--motion-ratio`
|
||
gehört knapp über den Ruhepegel — hoch genug, dass Wind und Lichtwechsel nicht
|
||
auslösen, niedrig genug für eine Blaumeise am Bildrand. Liegt der Ruhepegel
|
||
ohnehin hoch, ist `--gate detector` die bessere Wahl.
|
||
|
||
Das ist aber nur die Bewegungsschwelle. Ob die Kamera überhaupt an der
|
||
richtigen Stelle hängt, beantwortet [`detector karte`](#hängt-die-kamera-richtig-detector-karte)
|
||
— und zwar bevor man Tage auf den ersten Vogel wartet.
|
||
|
||
### Abschaltung bei Dunkelheit
|
||
|
||
Eine Kamera ohne Infrarot liefert nachts ein schwarzes Bild. Es durch zwei
|
||
neuronale Netze zu schicken kostet Strom und bringt nichts, deshalb pausiert
|
||
der Detector von selbst.
|
||
|
||
Entschieden wird über die **Bildhelligkeit**, nicht über Sonnenauf- und
|
||
-untergang. Das braucht keine Koordinaten und misst genau das, worauf es
|
||
ankommt: ob man etwas sieht. Ein Futterhaus unter einer Straßenlaterne bleibt
|
||
nutzbar, ein zugewachsenes ist schon vor Sonnenuntergang zu dunkel.
|
||
|
||
Zwischen Abschalt- und Einschaltschwelle liegt bewusst Abstand — ohne diese
|
||
Hysterese flattert die Entscheidung in der Dämmerung im Sekundentakt.
|
||
|
||
Eine Infrarotkamera liefert nachts ein graues, aber helles Bild und läuft
|
||
damit von selbst weiter. `--nachtsicht` ist nur nötig, wenn die Automatik
|
||
danebenliegt.
|
||
|
||
Gemessen im Betrieb: statt rund 40 % eines Kerns bleiben nachts 9 %, und die
|
||
gehen fast vollständig für das Dekodieren des MJPEG-Stroms drauf.
|
||
|
||
### Dauerprotokoll
|
||
|
||
Sechs Minuten zuschauen beantwortet die Frage „übersehen wir Vögel?" nur für
|
||
diese sechs Minuten. Über Tage beantwortet sie ein Protokoll:
|
||
|
||
```bash
|
||
python -m detector watch --source ... --protokoll /var/lib/vogelalbum/protokoll.csv
|
||
```
|
||
|
||
Geschrieben werden zwei Sorten Zeilen:
|
||
|
||
* **jede Erkennung ab `--protokoll-ab`** (Standard 0.08, weit unter der
|
||
Betriebsschwelle von 0.4) — auch die, die für eine Sichtung nicht gereicht
|
||
hat. Genau daran sieht man, ob die Schwelle zu hoch steht oder die Vögel
|
||
schlicht zu klein im Bild sind.
|
||
* **ein Lebenszeichen je Viertelstunde**, immer, auch wenn nichts passiert
|
||
ist. Diese Zeilen sind die wichtigeren: Ein Protokoll, das nur bei Erfolg
|
||
schreibt, sieht bei einem Ausfall genauso aus wie bei einem ruhigen Tag.
|
||
|
||
### Wie groß muss ein Vogel im Bild sein
|
||
|
||
Gemessen, indem ein echter Vogelausschnitt in verschiedenen Größen in ein
|
||
Bild derselben Kamera montiert wurde:
|
||
|
||
| Anteil am Bild | Detektor-Score | |
|
||
|---|---|---|
|
||
| 15 % | 0.93 | ✅ |
|
||
| 10 % | 0.77 | ✅ |
|
||
| 5 % | 0.77 | ✅ |
|
||
| 3 % | 0.23 | ❌ unter der Schwelle |
|
||
| 1 % | 0.07 | ❌ |
|
||
|
||
Die Grenze liegt bei rund **5 % der Bildfläche** — bei 1280×720 also etwa
|
||
260×175 Pixel.
|
||
|
||
Aufschlussreich ist, was dabei *nicht* zusammenbricht: Die Artbestimmung
|
||
liegt selbst bei 1 % Fläche noch bei 100 %. Der Engpass ist also nicht, die
|
||
Art zu erkennen, sondern überhaupt zu merken, dass da ein Vogel ist. Für den
|
||
Aufbau heißt das: **näher heran schlägt höhere Auflösung.**
|
||
|
||
Die 5 % sind allerdings **kein fester Wert, sondern hängen am Hintergrund.**
|
||
Dieselbe Messung nach einem Umsetzen der Kamera — statt ruhigem Grund nun ein
|
||
Baum im Wind hinter dem Futter — ergab erst ab **8 %** eine zuverlässige
|
||
Erkennung. Deshalb misst man an der eigenen Kamera nach, statt eine Zahl aus
|
||
diesem README zu übernehmen. Genau dafür gibt es das nächste Kommando.
|
||
|
||
### Hängt die Kamera richtig? (`detector karte`)
|
||
|
||
Die Größe ist nur die halbe Antwort. Die andere ist, **wo** im Bild der Vogel
|
||
sitzt — und da gibt es Zonen, in denen der Detektor auch einen großen Vogel
|
||
nicht findet.
|
||
|
||
```bash
|
||
python -m detector karte --source http://vogelhaus.local:8080/stream \
|
||
--vogel testdata/amsel.jpg --anteil 0.08 --ziel karte.jpg
|
||
```
|
||
|
||
Ein echtes Vogelfoto wird mit weicher Maske über ein Raster von Positionen in
|
||
aktuelle Bilder der eigenen Kamera montiert und jedes Mal durch den Detektor
|
||
geschickt. Heraus kommt eine Ampel über dem eigenen Kamerabild — grün erkannt,
|
||
rot nicht — plus dieselbe Karte als Tabelle und ein Satz, was zu tun ist.
|
||
|
||
**Das beantwortet vor dem ersten echten Besuch, ob die Kamera richtig hängt.**
|
||
Ohne diese Karte wartet man Tage auf eine leere Sammlung und weiß danach immer
|
||
noch nicht, ob keine Vögel kamen oder ob sie an der falschen Stelle im Bild
|
||
saßen.
|
||
|
||
Am Vogelhaus in Herzogenrath sah das so aus:
|
||
|
||
```
|
||
Fuss y x=0.18 x=0.32 x=0.46 x=0.60 x=0.74 x=0.88
|
||
0.55 OK 0.84 -- 0.44 OK 0.67 OK 0.67 OK 0.44 -- 0.44
|
||
0.65 OK 0.77 -- 0.44 OK 0.80 OK 0.56 -- 0.28 OK 0.62
|
||
0.75 OK 0.67 -- 0.38 OK 0.80 OK 0.72 -- 0.38 OK 0.56
|
||
0.85 OK 0.84 OK 0.67 OK 0.77 OK 0.84 OK 0.56 OK 0.72
|
||
0.95 -- 0.38 -- 0.16 -- 0.38 OK 0.62 -- 0.23 -- 0.20
|
||
```
|
||
|
||
Die unterste Reihe fällt fast geschlossen durch — und genau dort steht ein
|
||
Vogel, der auf dem Futterbrett landet, wenn die Kamera flach darüber hinweg
|
||
schaut. Derselbe Vogel in derselben Größe zehn Prozent höher im Bild: 0.84.
|
||
|
||
Daraus die Bauregel, die für jedes Futterhaus gilt: **die Landefläche gehört
|
||
in die untere Bildmitte, nicht an den unteren Bildrand.** Kamera so weit nach
|
||
unten neigen, bis unter dem Futter noch Bild übrig ist.
|
||
|
||
### Nachsehen, was angekommen ist
|
||
|
||
Nach den ersten Tagen ist die Frage nicht „läuft es", sondern „sehen wir Vögel,
|
||
und taugen die Bilder etwas". Dafür gibt es eine Auswertung der Rohdaten:
|
||
|
||
```bash
|
||
python -m detector report /var/lib/vogelalbum/rohdaten
|
||
python -m detector report /var/lib/vogelalbum/rohdaten --kontaktbogen bogen.jpg
|
||
```
|
||
|
||
Sie misst je Sichtung nach, statt zu schätzen: **Schärfe** (Varianz der
|
||
Laplace-Antwort, unter ~50 sichtbar verwackelt), **Fläche** des Vogels im Bild
|
||
und **Helligkeit**.
|
||
|
||
Die Fläche ist dabei der wichtigste Wert. Ein Vogel, der unter 3 % des Bildes
|
||
einnimmt, ist ein Fleck — daran ändert kein Schwellwert etwas, da muss die
|
||
Kamera näher heran oder enger eingestellt werden. Die Auswertung sagt das
|
||
ausdrücklich, wenn es auf mehr als ein Drittel der Sichtungen zutrifft.
|
||
|
||
Der Kontaktbogen legt alle Ausschnitte als ein Bild nebeneinander. Ob die
|
||
Qualität reicht, sieht man damit in zehn Sekunden statt in sechzig einzeln
|
||
geöffneten Dateien.
|