Einmal 42 und zurück > Piep-Show im Vogelhaus
Piep-Show im Vogelhaus
Die heimischen Vögel sind im Frühjahr immer wieder imposant zu beobachten. Schon früh morgens um 5 beginnen sie mit ihrem Gesang (andere nennen es unerträgliches Geplärr) und flattern den ganzen Tag von Busch zu Baum zu Hecke zu Zaun und zurück. Da es bei uns in der Umgebung relativ wenig Nistmöglichkeiten gibt, kam die Idee auf, ein Vogelhaus im Garten anzubringen. Natürlich ein richtiges Maker-Vogelhaus mit Kamera für die Piep-Show. Herzstück des Vogelhauses ist ein ESP32-CAM Modul, das WLAN, eine Kamera im Megapixel-Bereich und einen programmierbaren Mikrocontroller mitbringt.
Da die Kamera auch bei schwachen Lichtverhältnissen noch Bilder liefern soll, wurde aus der Kamera der Infrarotfilter entfernt. Dazu muss man nur die Linse rausschrauben und die Glasscheibe, die man im inneren der Linse sieht, entfernen. Mit der Aktion büßt man zwar die Farben ein, kann aber im Infrarot-Bereich sehen. Dazu wurden noch zwei Infrarot-Leichtdioden im Vogelhaus montiert, welche bei ca. 900nm unsichtbar für den Vogel das Vogelhaus erleuchten.
Das Kamera-Modul wurde danach von Tom professionell in der doppelten Decke des Vogelhauses montiert.
Danach galt es, auf das Vogelhaus Software zu bekommen. Wir haben uns in diesem Fall mit dem von Espressif mitgelieferten Beispielcode im Arduino Studio begnügt. Damit kann man sowohl Einzelbilder als auch Videos aufnehmen. Außerdem lassen sich massenhaft Einstellungen an der Kamera vornehmen. Das ESP32-CAM Modul bucht sich dabei nach dem Start automatisch in unser WLAN ein und stellt eine Web-Oberfläche zur Verfügung.
Bei den ersten Tests stellte sich jedoch heraus, dass es manchmal vorkam, dass entweder die Kamera nicht sauber erkannt wurde oder das WLAN nach einer Zeit nicht mehr funktionierte. Daher haben wir in der setup- und in der main-Funktion einige Anpassungen gemacht.
#define LED_GREEN 12 void startCameraServer(); void setup() { Serial.begin(115200); Serial.setDebugOutput(true); Serial.println(); // Grüne LED zur Statusanzeige am Vogelhaus pinMode(LED_GREEN, OUTPUT); digitalWrite(LED_GREEN, 1); // Kamera konfigurieren camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; //init with high specs to pre-allocate larger buffers if(psramFound()){ config.frame_size = FRAMESIZE_UXGA; config.jpeg_quality = 10; config.fb_count = 2; } else { config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 12; config.fb_count = 1; } #if defined(CAMERA_MODEL_ESP_EYE) pinMode(13, INPUT_PULLUP); pinMode(14, INPUT_PULLUP); #endif // Kamera initialisieren esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { // Fehler bei der Initialisierung: Neu starten! Serial.printf("Camera init failed with error 0x%x", err); delay(2000); ESP.restart(); return; } // Bild umdrehen (wegen Kameramontage) sensor_t * s = esp_camera_sensor_get(); s->set_framesize(s, FRAMESIZE_SVGA); s->set_vflip(s, 1); s->set_hmirror(s, 1); // Versuchen, WLAN zu verbinden while(WiFi.status() != WL_CONNECTED) { int iTriesT=0; WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); digitalWrite(LED_GREEN, iTriesT++ & 1); if(iTriesT>30) break; } // Wenn wir hier ankommen, ist 30*500ms keine Verbindung zu // Stande gekommen. Neustart! if(WiFi.status() != WL_CONNECTED) { Serial.println("WiFi Connect failed!"); WiFi.disconnect(true); delay(500); } } Serial.println("WiFi connected"); // Server starten startCameraServer(); Serial.print("Camera Ready! Use 'http://"); Serial.print(WiFi.localIP()); Serial.println("' to connect"); digitalWrite(LED_GREEN,1); delay(10000); } void loop() { // Sobald die WLAN-Verbindung zusammenbricht, neu starten. delay(1000); if(WiFi.status() != WL_CONNECTED) ESP.restart(); digitalWrite(LED_GREEN,0); }
Ab diesem Punkt konnten wir schon stabil Bilder und Videos aus dem Vogelhaus betrachten. Jetzt galt es noch, die Daten ins Internet zu streamen. Dafür haben wir ein kleines PHP-Skript gebaut, das ein Bild holt und es mit zusätzlichen Informationen versieht:
<?php /* File: capture.php */ $source='http://10.10.1.9/capture'; $dt = new DateTime(); $date = $dt->format("Y")."-".$dt->format("m")."-".$dt->format("d"); @mkdir ("raw_".$date); @mkdir ("ovl_".$date); $raw_image_file = "raw_".$date."/"."raw_".$date."_".$dt->format("H")."-".$dt->format("i")."-".$dt->format("s")."_".$dt->format("U").".jpg"; $ovl_image_file = "ovl_".$date."/"."ovl_".$date."_".$dt->format("H")."-".$dt->format("i")."-".$dt->format("s")."_".$dt->format("U").".jpg"; $tod = $dt->format("H")*3600 + $dt->format("i")*60 + $dt->format("s"); // Wetterinformationen holen $weather_json = file_get_contents( "http://api.openweathermap.org/data/2.5/weather?q=Dorsten&mode=json&units=metric&appid=<apikey>&lang=de" ); $weather = json_decode($weather_json); // Bild vom Vogelhaus holen $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $source); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // seconds curl_setopt($ch, CURLOPT_TIMEOUT, 10); //timeout in seconds $image_string = curl_exec($ch); $curl_errno = curl_errno($ch); curl_close($ch); if($curl_errno>0) throw new Exception("Connection to Vogelhaus failed!"); // Erstmal das Rohbild schreiben file_put_contents($raw_image_file, $image_string); // Bild laden, um es zu bearbeiten $image=imagecreatefromstring($image_string); if(!$image) throw new Exception("Cannot create image from string!"); // Darstellung der Tageszeit am oberen Rand des Bildes $x = $tod / 86400 * 390 + 205; $color = imagecolorallocate($image, 0x30,0xFF,0x10); imagepolygon($image, [ 200,10, 600,10, 600,20, 200,20, 200,10],5, $color); imagefilledellipse($image, $x,15, 8,8, $color); imagestring($image, 3, 610, 9, sprintf("%02d:%02d:%02d", $dt->format("H"), $dt->format("i"), $dt->format("s")), $color); imagestring($image, 3, 120, 9, sprintf("%02d.%02d.%04d", $dt->format("d"), $dt->format("m"), $dt->format("Y")), $color); // Darstellung der Wetterinformationen oben rechts imagestring($image, 3, 670, 9, sprintf(" Temp: %3d %cC", $weather->main->temp,176 ), $color); imagestring($image, 3, 670, 21, sprintf(" Wind: %3.1f m/s", $weather->wind->speed ), $color); imagestring($image, 3, 670, 34, sprintf(" RH: %3d %%", $weather->main->humidity ), $color); imagestring($image, 3, 670, 47, sprintf("Druck: %4d mbar", $weather->main->pressure ), $color); imagestring($image, 3, 670, 47, sprintf("Druck: %4d mbar", $weather->main->pressure ), $color); imagestring($image, 3, 670, 66, sprintf("%s", utf8_decode($weather->weather[0]->description) ), $color); imagestringup($image, 2 , 780, 580, "https://vogelhaus.familie-schumann.info", $color); // Bilddatei schreiben imagejpeg($image, $ovl_image_file,90); // Datei per SSH auf den Server schieben exec('scp '.$ovl_image_file.' vogelhaus@webserver:~/vogelhaus/'.$ovl_image_file);
Dieses Skript wird nun 4x pro Minute aufgerufen. Das erfolgt per CRON-Job durch ein kleines Hilfsskript, das jede Minute aufgerufen wird:
#!/bin/bash php capture.php sleep 15 php capture.php sleep 15 php capture.php sleep 15 php capture.php
So landet im Schnitt alle 15 Sekunden ein neues Bild, das nur noch angezeigt werden muss.
In unserer Anwendung auf https://vogelhaus.familie-schumann.info erfolgt die Anzeige über React als Frontend mit PHP als Backend. Die Klassifikation der Bilder (Erkennung des Vogels) erfolgt über TensorFlow, das nach knapp 2000 Bildern Training eine Trefferquote von 99,9% hat.