ESP32 S3 Kamera - Bewegung ohne Sensor erkennen

Sie brauchen keinen Bewegungssensor, um Bewegung zu sehen. Die Kamera ist bereits ein Sensor. In diesem Tutorial übernimmt der ESP32 S3 die Bildverarbeitung: Er nimmt ständig kleine Schwarz-Weiß-Bilder auf, vergleicht jedes neue Bild mit dem vorherigen und ermittelt, ob sich etwas vor der Kamera bewegt hat.

Wenn er eine Bewegung findet, tut er zwei Dinge:

Es wird kein zusätzliches Bauteil benötigt. Sie brauchen nur das ESP32 S3 Kamera-Board und eine microSD-Karte. In diesem Projekt gibt es keinen PIR-Sensor, keine Verdrahtung und kein Breadboard.

ESP32 S3 Kamera - Bewegung ohne Sensor erkennen

※ Notiz:

Dieses Tutorial behandelt nur den Teil der Erkennung. Sobald Ihre Kamera eine Bewegung findet, können Sie die Nachricht überallhin senden. Nehmen Sie die Funktion takeAndSavePhoto() und ersetzen Sie sie durch den Sendeteil eines anderen Tutorials:

Der Erkennungscode bleibt gleich. Nur der letzte Schritt ändert sich.

Was Sie bauen werden:

  1. Eine Kamera, die selbst auf Bewegung achtet, ohne PIR-Sensor
  2. Eine Meldung im Seriellen Monitor jedes Mal, wenn die Kamera etwas sich bewegen sieht
  3. Ein großes Foto, das auf der microSD-Karte gespeichert wird, wenn sich etwas ändert
  4. Einstellungen oben im Code, die Sie ändern können, um die Kamera empfindlicher oder weniger empfindlich zu machen und eine Person von einer Lampe oder von Blättern im Wind zu unterscheiden
  5. Eine Kamera, die auch Dinge sieht, die ein PIR-Sensor nicht sehen kann, zum Beispiel einen kalten Gegenstand

Erforderliche Hardware

1×ESP32 S3 N16R8 OV5640 Camera
1×(Alternativ) ESP32 S3 N16R8 OV2640 Camera
1×microSD Card
1×USB-Kabel Typ-A zu Typ-C (für USB-A PC)
1×USB-Kabel Typ-C zu Typ-C (für USB-C PC)
1×(Optional) DC-Stromanschluss
1×(Empfohlen) Screw Terminal Expansion Board for ESP32 S3
1×(Empfohlen) Breakout Expansion Board for ESP32 S3
1×(Empfohlen) Power Splitter for ESP32 S3

Oder Sie können die folgenden Kits kaufen:

1×DIYables Sensor-Kit (18 Sensoren/Displays)
Offenlegung: Einige der in diesem Abschnitt bereitgestellten Links sind Amazon-Affiliate-Links. Wir können eine Provision für Käufe erhalten, die über diese Links getätigt werden, ohne zusätzliche Kosten für Sie. Wir schätzen Ihre Unterstützung.

※ Notiz:

Das ESP32 S3 Kamera-Board hat 40 Pins, aber der Schraubklemmenblock und das Breakout-Board haben 44 Pins. Sie funktionieren trotzdem zusammen. Setzen Sie das Kamera-Board in die Mitte des 44-Pin-Boards, sodass auf der linken Seite 2 Pins frei bleiben und auf der rechten Seite 2 Pins frei bleiben.

Mit dem Schraubklemmenblock können Sie Drähte mit einem Schraubendreher anschließen, ganz ohne Löten. Das Breakout-Board bietet Ihnen einfache Stiftleisten für ein Breadboard.

Wie es funktioniert

Die Idee ist einfach. Ein Bild aufnehmen. Noch ein Bild aufnehmen. Beide vergleichen.

  1. Die Kamera nimmt ein kleines Schwarz-Weiß-Bild auf, nur 160 × 120 Pixel.
  2. In Schwarz-Weiß ist ein Pixel eine Zahl zwischen 0 (schwarz) und 255 (weiß).
  3. Der Code vergleicht jedes Pixel mit demselben Pixel des vorherigen Bildes.
  4. Wenn die beiden Zahlen weit auseinander liegen, hat sich dieses Pixel verändert.
  5. Wenn sich viele Pixel verändert haben, hat sich etwas bewegt.

Warum Schwarz-Weiß, und warum so klein?

WahlGrund
Schwarz-WeißEin Pixel ist eine Zahl, daher ist der Vergleich einfach und schnell
160 x 12019200 Zahlen passen problemlos in den Speicher, und das Board kann sie viele Male pro Sekunde vergleichen
Kein JPEGEin JPEG ist komprimiert, daher können Sie es nicht Pixel für Pixel vergleichen

Das kleine Bild dient nur zum Beobachten. Wenn sich etwas bewegt, schaltet der Code die Kamera auf ein großes Farbfoto um, speichert es und schaltet zurück.

Bewegungssensor oder kein Sensor?

Kein Sensor (dieses Tutorial)PIR-Bewegungssensor
Zusätzliche TeileKeineEin Sensor und drei Drähte
Sieht einen kalten GegenstandJa, zum Beispiel ein Paket oder ein AutoNein, er sieht nur warme Körper
Sieht durch GlasJaNein
Funktioniert im DunkelnNein, die Kamera braucht LichtJa
FehlalarmeWenn sich das Licht ändert oder bei WindWenn sich warme Luft bewegt
StromverbrauchHöher, die Kamera hört nie aufGeringer

Die beiden Wege sind in unterschiedlichen Dingen gut. Siehe auch das Tutorial ESP32 S3 Kamera - Foto auf SD-Karte speichern, wenn Bewegung erkannt wird.

Verdrahtungsdiagramm

In diesem Projekt gibt es keine Verdrahtung. Sie stecken nur die microSD-Karte in den Steckplatz.

ESP32 S3 Software-Bewegungserkennung Schaltplan

Dieses Bild wurde mit Fritzing erstellt. Klicken Sie, um das Bild zu vergrößern.

  1. Ziehen Sie das USB-Kabel ab.
  2. Stellen Sie sicher, dass das Kameramodul mit seinem Flachbandkabel verbunden ist.
  3. Schieben Sie die microSD-Karte in den Steckplatz, bis Sie ein leises Klicken hören.
  4. Stecken Sie das USB-Kabel ein.

Sicherheitshinweise

Nehmen Sie die Karte niemals heraus, während das Board ein Foto schreibt. Ziehen Sie immer zuerst das USB-Kabel ab, warten Sie zwei Sekunden und nehmen Sie dann die Karte heraus.

Arduino IDE Einstellungen für die ESP32 S3 Kamera

Die Kamera funktioniert nicht mit den Standardeinstellungen der Arduino IDE. Sie müssen einige Punkte im Menü Tools ändern, bevor Sie den Code hochladen.

Menüpunkt in ToolsDiese Auswahl treffen
BoardESP32S3 Dev Module
Flash Size16MB (128Mb)
PSRAMOPI PSRAM
Partition SchemeHuge APP (3MB No OTA/1MB SPIFFS)
CPU Frequency240MHz (WiFi)
USB ModeHardware CDC and JTAG
Upload ModeUART0 / Hardware CDC
Upload Speed921600
USB CDC On BootEnabled

Drei Beispiele, Schritt für Schritt

Dieses Tutorial enthält drei Programme. Alle erledigen dieselbe Aufgabe, aber jedes ist besser als das vorherige. Beginnen Sie mit Beispiel 1, bringen Sie es zum Laufen und gehen Sie dann weiter.

BeispielWas es hinzufügtGeeignet für
*i. EinfachVergleicht zwei Bilder, zählt veränderte PixelDie Idee verstehen. Einen ruhigen Raum in Innenräumen.
*i. LichtsicherAlles aus Beispiel 1, PLUS Lampen, Wolken und die AbendsonneEinen Raum mit Fenstern oder einer Lampe
*i. Licht- und windsicherAlles aus Beispiel 2, PLUS Blätter, Vorhänge und wackelnde DingeDraußen, einen Garten, ein Fenster

※ Notiz:

Jedes Beispiel speichert ein großes Foto auf der microSD-Karte. Der Unterschied liegt nur darin, wann es entscheidet, dass sich etwas bewegt hat.

Jedes Beispiel behält alles aus dem vorherigen bei. Beispiel 3 ist nicht nur windsicher, es ist auch lichtsicher:

SchutzBeispiel 1Beispiel 2Beispiel 3
Sieht eine sich bewegende PersonJaJaJa
Entfernt die Lichtänderung (averageShift)NeinJaJa
Lehnt eine Änderung des ganzen Bildes ab (eine Lampe)NeinJaJa
Wartet nach einer Lichtänderung (SETTLE_TIME)NeinJaJa
Gruppen von Blöcken, die sich berühren (Wind)NeinNeinJa
Muss in mehreren Bildern gesehen werden (CONFIRM_FRAMES)NeinNeinJa
Kann den oberen Bildteil ignorierenNeinNeinJa

※ Notiz:

Wenn Sie nur das beste Beispiel möchten, verwenden Sie Beispiel 3. Beispiel 1 und Beispiel 2 sind dazu da, dass Sie sehen, warum jeder Teil nötig ist. Laden Sie sie der Reihe nach hoch, bringen Sie sie absichtlich an ihre Grenzen, und die Zahlen in Beispiel 3 werden verständlich.

ESP32 S3 Code - Beispiel 1: Einfach

Dies ist die kürzeste Version und am einfachsten zu lesen. Sie tut genau drei Dinge: ein kleines Schwarz-Weiß-Bild aufnehmen, es mit dem vorherigen Bild vergleichen und zählen, wie viele Pixel sich verändert haben.

Die Zahlen, die Sie ändern können

NameStartwertBedeutung
PIXEL_THRESHOLD30Wie stark sich ein Pixel unterscheiden muss (0 bis 255)
MOTION_PERCENT2Wie viel vom Bild sich ändern muss, in Prozent
QUIET_TIME5000Millisekunden Wartezeit vor dem nächsten Foto
  • Zählen Sie die Pixel, die sich verändert haben.
int countChangedPixels(uint8_t *now, uint8_t *before, size_t size) { int changed = 0; for (size_t i = 0; i < size; i++) { int difference = (int)now[i] - (int)before[i]; if (difference < 0) difference = -difference; // we do not care if it is brighter or darker if (difference > PIXEL_THRESHOLD) changed++; } return changed; }
  • Vergleichen Sie die Anzahl mit Ihrem Grenzwert.
int changed = countChangedPixels(fb->buf, lastPicture, pictureSize); int limit = (pictureSize * MOTION_PERCENT) / 100; if (changed > limit) { Serial.println("Movement seen"); }
/* * Dieser ESP32 S3 Code wurde von newbiely.de entwickelt * Dieser ESP32 S3 Code wird der Öffentlichkeit ohne jegliche Einschränkung zur Verfügung gestellt. * Für vollständige Anleitungen und Schaltpläne besuchen Sie bitte: * https://newbiely.de/tutorials/esp32-s3/esp32-s3-camera-detect-motion-without-a-sensor */ #include "esp_camera.h" #include "FS.h" #include "SD_MMC.h" // How different one pixel must be before we call it "changed" #define PIXEL_THRESHOLD 30 // How many pixels must change before we call it "motion". // The small picture has 160 x 120 = 19200 pixels, so 2% is about 384 pixels. #define MOTION_PERCENT 2 #define QUIET_TIME 5000 // wait 5 seconds before the next photo // Camera pins of the ESP32-S3-N16R8 camera board. // The same pins work for the OV2640, OV3660 and OV5640 versions. #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 15 #define SIOD_GPIO_NUM 4 #define SIOC_GPIO_NUM 5 #define Y9_GPIO_NUM 16 #define Y8_GPIO_NUM 17 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 12 #define Y5_GPIO_NUM 10 #define Y4_GPIO_NUM 8 #define Y3_GPIO_NUM 9 #define Y2_GPIO_NUM 11 #define VSYNC_GPIO_NUM 6 #define HREF_GPIO_NUM 7 #define PCLK_GPIO_NUM 13 // Pins of the microSD card slot on the board #define SD_CLK_GPIO_NUM 39 #define SD_CMD_GPIO_NUM 38 #define SD_D0_GPIO_NUM 40 camera_config_t cameraConfig; // we keep the settings, because we change them later uint8_t *lastPicture = NULL; // the picture we saw the time before size_t pictureSize = 0; int photoNumber = 1; unsigned long lastPhotoTime = 0; // Returns the name of the camera sensor that is installed on the board const char *getSensorName(int pid) { switch (pid) { case OV2640_PID: return "OV2640 (2 MP, max UXGA 1600x1200)"; case OV3660_PID: return "OV3660 (3 MP, max QXGA 2048x1536)"; case OV5640_PID: return "OV5640 (5 MP, max QSXGA 2592x1944)"; default: return "Unknown sensor"; } } // Each sensor needs slightly different settings. // We must call this again every time the camera restarts. void applyCameraSettings() { sensor_t *s = esp_camera_sensor_get(); switch (s->id.PID) { case OV2640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; case OV3660_PID: s->set_vflip(s, 1); // the OV3660 image is upside down by default s->set_brightness(s, 1); // make the image a little brighter s->set_saturation(s, -2); // make the colors a little softer break; case OV5640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; default: break; } } // Starts the camera in the small black and white mode, used for watching bool initCamera() { cameraConfig.ledc_channel = LEDC_CHANNEL_0; cameraConfig.ledc_timer = LEDC_TIMER_0; cameraConfig.pin_d0 = Y2_GPIO_NUM; cameraConfig.pin_d1 = Y3_GPIO_NUM; cameraConfig.pin_d2 = Y4_GPIO_NUM; cameraConfig.pin_d3 = Y5_GPIO_NUM; cameraConfig.pin_d4 = Y6_GPIO_NUM; cameraConfig.pin_d5 = Y7_GPIO_NUM; cameraConfig.pin_d6 = Y8_GPIO_NUM; cameraConfig.pin_d7 = Y9_GPIO_NUM; cameraConfig.pin_xclk = XCLK_GPIO_NUM; cameraConfig.pin_pclk = PCLK_GPIO_NUM; cameraConfig.pin_vsync = VSYNC_GPIO_NUM; cameraConfig.pin_href = HREF_GPIO_NUM; cameraConfig.pin_sccb_sda = SIOD_GPIO_NUM; cameraConfig.pin_sccb_scl = SIOC_GPIO_NUM; cameraConfig.pin_pwdn = PWDN_GPIO_NUM; cameraConfig.pin_reset = RESET_GPIO_NUM; cameraConfig.xclk_freq_hz = 20000000; cameraConfig.jpeg_quality = 10; cameraConfig.fb_count = 1; cameraConfig.fb_location = psramFound() ? CAMERA_FB_IN_PSRAM : CAMERA_FB_IN_DRAM; cameraConfig.grab_mode = CAMERA_GRAB_WHEN_EMPTY; // GRAYSCALE means black and white. One pixel is one byte, so it is easy to compare. cameraConfig.pixel_format = PIXFORMAT_GRAYSCALE; cameraConfig.frame_size = FRAMESIZE_QQVGA; // 160x120 - small and fast if (!psramFound()) Serial.println("WARNING: PSRAM not found. Please set PSRAM to OPI PSRAM in the Tools menu."); esp_err_t err = esp_camera_init(&cameraConfig); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x\n", err); return false; } sensor_t *s = esp_camera_sensor_get(); Serial.printf("Camera sensor: %s\n", getSensorName(s->id.PID)); applyCameraSettings(); return true; } bool initSDCard() { SD_MMC.setPins(SD_CLK_GPIO_NUM, SD_CMD_GPIO_NUM, SD_D0_GPIO_NUM); // true means 1-bit mode. The camera uses the other pins, so we cannot use 4-bit mode. if (!SD_MMC.begin("/sdcard", true)) { Serial.println("SD card not found. Please check the card and push it in again."); return false; } if (SD_MMC.cardType() == CARD_NONE) { Serial.println("No SD card inside the slot"); return false; } Serial.printf("SD card size: %llu MB\n", SD_MMC.cardSize() / (1024 * 1024)); return true; } String makeFileName(int number) { char name[32]; sprintf(name, "/change_%05d.jpg", number); return String(name); } void findFirstFreeNumber() { while (SD_MMC.exists(makeFileName(photoNumber))) photoNumber++; Serial.printf("The next photo will be change_%05d.jpg\n", photoNumber); } // Compares the new picture with the old one and returns how many pixels changed int countChangedPixels(uint8_t *now, uint8_t *before, size_t size) { int changed = 0; for (size_t i = 0; i < size; i++) { int difference = (int)now[i] - (int)before[i]; if (difference < 0) difference = -difference; // we do not care if it is brighter or darker if (difference > PIXEL_THRESHOLD) changed++; } return changed; } // Changes the camera to the big colour mode, takes one photo, saves it, // and changes back to the small black and white mode void takeAndSavePhoto() { cameraConfig.pixel_format = PIXFORMAT_JPEG; cameraConfig.frame_size = FRAMESIZE_SVGA; if (esp_camera_reconfigure(&cameraConfig) != ESP_OK) { Serial.println("Cannot change the camera to photo mode"); return; } applyCameraSettings(); delay(200); camera_fb_t *fb = esp_camera_fb_get(); if (fb) { String fileName = makeFileName(photoNumber); File file = SD_MMC.open(fileName.c_str(), FILE_WRITE); if (file) { file.write(fb->buf, fb->len); file.close(); Serial.printf("Saved %s (%u bytes)\n", fileName.c_str(), fb->len); photoNumber++; } else { Serial.printf("Cannot write the file %s\n", fileName.c_str()); } esp_camera_fb_return(fb); } else { Serial.println("Photo capture failed"); } // Back to watching mode cameraConfig.pixel_format = PIXFORMAT_GRAYSCALE; cameraConfig.frame_size = FRAMESIZE_QQVGA; esp_camera_reconfigure(&cameraConfig); applyCameraSettings(); delay(200); // The old picture belongs to the other mode, so we forget it if (lastPicture) { free(lastPicture); lastPicture = NULL; } } void setup() { Serial.begin(115200); delay(1000); if (!initCamera()) { Serial.println("Stopped"); while (true) delay(1000); } if (!initSDCard()) { Serial.println("Stopped"); while (true) delay(1000); } findFirstFreeNumber(); Serial.printf("Watching for movement. A photo is saved when more than %d%% of the picture changes.\n", MOTION_PERCENT); } void loop() { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Frame capture failed"); delay(500); return; } // The very first picture has nothing to compare with, so we only keep it if (lastPicture == NULL) { pictureSize = fb->len; lastPicture = (uint8_t *)malloc(pictureSize); if (lastPicture) memcpy(lastPicture, fb->buf, pictureSize); esp_camera_fb_return(fb); return; } int changed = countChangedPixels(fb->buf, lastPicture, pictureSize); int limit = (pictureSize * MOTION_PERCENT) / 100; if (changed > limit) { Serial.printf("Movement seen: %d pixels changed (the limit is %d)\n", changed, limit); if (millis() - lastPhotoTime >= QUIET_TIME) { esp_camera_fb_return(fb); takeAndSavePhoto(); lastPhotoTime = millis(); return; } } memcpy(lastPicture, fb->buf, pictureSize); // remember this picture for the next time esp_camera_fb_return(fb); }

※ Notiz:

Probieren Sie dieses Beispiel zuerst in Innenräumen bei geschlossenen Vorhängen aus. In einem ruhigen Raum funktioniert es sehr gut. Schalten Sie dann eine Lampe ein und beobachten Sie, was passiert: Es speichert ein Foto eines leeren Raums. Genau dieses Problem behebt Beispiel 2.

ESP32 S3 Code - Beispiel 2: Lichtsicher

Wenn jemand eine Lampe einschaltet oder eine Wolke vor der Sonne vorbeizieht, ändert sich jedes Pixel gleichzeitig. Beispiel 1 meldet dann "Bewegung!" und speichert ein Foto eines leeren Raums.

Die Kamera verschlimmert das selbst. Sie regelt ständig ihre eigene Helligkeit, sodass sich selbst bei gleichmäßigem Licht ab und zu das ganze Bild verschiebt.

Die Zahlen, die Sie ändern können

NameStartwertBedeutung
PIXEL_THRESHOLD30Wie stark sich ein Pixel unterscheiden muss (0 bis 255)
MOTION_PERCENT2Wie viel vom Bild sich ändern muss, in Prozent
LIGHT_CHANGE_PERCENT50Darüber ist es eine Lampe und keine Person
SETTLE_TIME1000Millisekunden Wartezeit nach einer Lichtänderung
QUIET_TIME5000Millisekunden Wartezeit vor dem nächsten Foto

1. Die Lichtänderung entfernen

Eine Lampe verschiebt alle Pixel um ungefähr denselben Betrag. Eine Person verändert nur einige davon. Deshalb misst der Code zuerst, wie stark sich das ganze Bild verschoben hat, und zieht diesen Betrag dann von jedem Pixel ab:

int averageShift(uint8_t *now, uint8_t *before, size_t size) { long total = 0; for (size_t i = 0; i < size; i++) total += (int)now[i] - (int)before[i]; return (int)(total / (long)size); }

Und dann, innerhalb des Vergleichs:

int difference = ((int)now[i] - (int)before[i]) - shift;

※ Notiz:

Das ist die wichtige Idee. Wenn der ganze Raum um 20 Stufen heller wird, ist shift etwa 20, und nach dem Abziehen sieht jedes Pixel wieder unverändert aus. Eine Person, die durch das Bild geht, verändert ihre eigenen Pixel aber immer noch um viel mehr als 20, daher wird sie weiterhin erkannt.

2. Zu große Änderungen ablehnen

Eine Person füllt nie die Hälfte des Bildes aus. Wenn sich mehr als LIGHT_CHANGE_PERCENT der Pixel verändert haben, muss es das Licht sein:

if (changed > lightLimit) { Serial.printf("The light changed (%d pixels). No photo.\n", changed); delay(SETTLE_TIME); // let the camera set its brightness again return; }

※ Notiz:

Nach einer Lichtänderung wartet der Code eine Sekunde. Die Kamera braucht diese Zeit, um ihre eigene Helligkeit einzustellen, und ohne die Wartezeit würden auch die nächsten zwei oder drei Bilder verändert aussehen.

/* * Dieser ESP32 S3 Code wurde von newbiely.de entwickelt * Dieser ESP32 S3 Code wird der Öffentlichkeit ohne jegliche Einschränkung zur Verfügung gestellt. * Für vollständige Anleitungen und Schaltpläne besuchen Sie bitte: * https://newbiely.de/tutorials/esp32-s3/esp32-s3-camera-detect-motion-without-a-sensor */ #include "esp_camera.h" #include "FS.h" #include "SD_MMC.h" // How different one pixel must be before we call it "changed" #define PIXEL_THRESHOLD 30 // How many pixels must change before we call it "motion". // The small picture has 160 x 120 = 19200 pixels, so 2% is about 384 pixels. #define MOTION_PERCENT 2 // When MORE than this much of the picture changes, it is a light change and // not a person. A lamp, a cloud or a passing car light moves the whole picture. #define LIGHT_CHANGE_PERCENT 50 #define SETTLE_TIME 1000 // after a light change, give the camera 1 second #define QUIET_TIME 5000 // wait 5 seconds before the next photo // Camera pins of the ESP32-S3-N16R8 camera board. // The same pins work for the OV2640, OV3660 and OV5640 versions. #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 15 #define SIOD_GPIO_NUM 4 #define SIOC_GPIO_NUM 5 #define Y9_GPIO_NUM 16 #define Y8_GPIO_NUM 17 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 12 #define Y5_GPIO_NUM 10 #define Y4_GPIO_NUM 8 #define Y3_GPIO_NUM 9 #define Y2_GPIO_NUM 11 #define VSYNC_GPIO_NUM 6 #define HREF_GPIO_NUM 7 #define PCLK_GPIO_NUM 13 // Pins of the microSD card slot on the board #define SD_CLK_GPIO_NUM 39 #define SD_CMD_GPIO_NUM 38 #define SD_D0_GPIO_NUM 40 camera_config_t cameraConfig; // we keep the settings, because we change them later uint8_t *lastPicture = NULL; // the picture we saw the time before size_t pictureSize = 0; int photoNumber = 1; unsigned long lastPhotoTime = 0; // Returns the name of the camera sensor that is installed on the board const char *getSensorName(int pid) { switch (pid) { case OV2640_PID: return "OV2640 (2 MP, max UXGA 1600x1200)"; case OV3660_PID: return "OV3660 (3 MP, max QXGA 2048x1536)"; case OV5640_PID: return "OV5640 (5 MP, max QSXGA 2592x1944)"; default: return "Unknown sensor"; } } // Each sensor needs slightly different settings. // We must call this again every time the camera restarts. void applyCameraSettings() { sensor_t *s = esp_camera_sensor_get(); switch (s->id.PID) { case OV2640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; case OV3660_PID: s->set_vflip(s, 1); // the OV3660 image is upside down by default s->set_brightness(s, 1); // make the image a little brighter s->set_saturation(s, -2); // make the colors a little softer break; case OV5640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; default: break; } } // Starts the camera in the small black and white mode, used for watching bool initCamera() { cameraConfig.ledc_channel = LEDC_CHANNEL_0; cameraConfig.ledc_timer = LEDC_TIMER_0; cameraConfig.pin_d0 = Y2_GPIO_NUM; cameraConfig.pin_d1 = Y3_GPIO_NUM; cameraConfig.pin_d2 = Y4_GPIO_NUM; cameraConfig.pin_d3 = Y5_GPIO_NUM; cameraConfig.pin_d4 = Y6_GPIO_NUM; cameraConfig.pin_d5 = Y7_GPIO_NUM; cameraConfig.pin_d6 = Y8_GPIO_NUM; cameraConfig.pin_d7 = Y9_GPIO_NUM; cameraConfig.pin_xclk = XCLK_GPIO_NUM; cameraConfig.pin_pclk = PCLK_GPIO_NUM; cameraConfig.pin_vsync = VSYNC_GPIO_NUM; cameraConfig.pin_href = HREF_GPIO_NUM; cameraConfig.pin_sccb_sda = SIOD_GPIO_NUM; cameraConfig.pin_sccb_scl = SIOC_GPIO_NUM; cameraConfig.pin_pwdn = PWDN_GPIO_NUM; cameraConfig.pin_reset = RESET_GPIO_NUM; cameraConfig.xclk_freq_hz = 20000000; cameraConfig.jpeg_quality = 10; cameraConfig.fb_count = 1; cameraConfig.fb_location = psramFound() ? CAMERA_FB_IN_PSRAM : CAMERA_FB_IN_DRAM; cameraConfig.grab_mode = CAMERA_GRAB_WHEN_EMPTY; // GRAYSCALE means black and white. One pixel is one byte, so it is easy to compare. cameraConfig.pixel_format = PIXFORMAT_GRAYSCALE; cameraConfig.frame_size = FRAMESIZE_QQVGA; // 160x120 - small and fast if (!psramFound()) Serial.println("WARNING: PSRAM not found. Please set PSRAM to OPI PSRAM in the Tools menu."); esp_err_t err = esp_camera_init(&cameraConfig); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x\n", err); return false; } sensor_t *s = esp_camera_sensor_get(); Serial.printf("Camera sensor: %s\n", getSensorName(s->id.PID)); applyCameraSettings(); return true; } bool initSDCard() { SD_MMC.setPins(SD_CLK_GPIO_NUM, SD_CMD_GPIO_NUM, SD_D0_GPIO_NUM); // true means 1-bit mode. The camera uses the other pins, so we cannot use 4-bit mode. if (!SD_MMC.begin("/sdcard", true)) { Serial.println("SD card not found. Please check the card and push it in again."); return false; } if (SD_MMC.cardType() == CARD_NONE) { Serial.println("No SD card inside the slot"); return false; } Serial.printf("SD card size: %llu MB\n", SD_MMC.cardSize() / (1024 * 1024)); return true; } String makeFileName(int number) { char name[32]; sprintf(name, "/change_%05d.jpg", number); return String(name); } void findFirstFreeNumber() { while (SD_MMC.exists(makeFileName(photoNumber))) photoNumber++; Serial.printf("The next photo will be change_%05d.jpg\n", photoNumber); } // Works out how much the WHOLE picture became brighter or darker. // A lamp, a cloud or the evening sun moves every pixel by about the same amount. int averageShift(uint8_t *now, uint8_t *before, size_t size) { long total = 0; for (size_t i = 0; i < size; i++) total += (int)now[i] - (int)before[i]; return (int)(total / (long)size); } // Compares the new picture with the old one and returns how many pixels changed. // We take the light change away first, so only real movement is counted. int countChangedPixels(uint8_t *now, uint8_t *before, size_t size, int shift) { int changed = 0; for (size_t i = 0; i < size; i++) { int difference = ((int)now[i] - (int)before[i]) - shift; if (difference < 0) difference = -difference; // we do not care if it is brighter or darker if (difference > PIXEL_THRESHOLD) changed++; } return changed; } // Changes the camera to the big colour mode, takes one photo, saves it, // and changes back to the small black and white mode void takeAndSavePhoto() { cameraConfig.pixel_format = PIXFORMAT_JPEG; cameraConfig.frame_size = FRAMESIZE_SVGA; if (esp_camera_reconfigure(&cameraConfig) != ESP_OK) { Serial.println("Cannot change the camera to photo mode"); return; } applyCameraSettings(); delay(200); camera_fb_t *fb = esp_camera_fb_get(); if (fb) { String fileName = makeFileName(photoNumber); File file = SD_MMC.open(fileName.c_str(), FILE_WRITE); if (file) { file.write(fb->buf, fb->len); file.close(); Serial.printf("Saved %s (%u bytes)\n", fileName.c_str(), fb->len); photoNumber++; } else { Serial.printf("Cannot write the file %s\n", fileName.c_str()); } esp_camera_fb_return(fb); } else { Serial.println("Photo capture failed"); } // Back to watching mode cameraConfig.pixel_format = PIXFORMAT_GRAYSCALE; cameraConfig.frame_size = FRAMESIZE_QQVGA; esp_camera_reconfigure(&cameraConfig); applyCameraSettings(); delay(200); // The old picture belongs to the other mode, so we forget it if (lastPicture) { free(lastPicture); lastPicture = NULL; } } void setup() { Serial.begin(115200); delay(1000); if (!initCamera()) { Serial.println("Stopped"); while (true) delay(1000); } if (!initSDCard()) { Serial.println("Stopped"); while (true) delay(1000); } findFirstFreeNumber(); Serial.printf("Watching for movement. A photo is saved when more than %d%% of the picture changes.\n", MOTION_PERCENT); } void loop() { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Frame capture failed"); delay(500); return; } // The very first picture has nothing to compare with, so we only keep it if (lastPicture == NULL) { pictureSize = fb->len; lastPicture = (uint8_t *)malloc(pictureSize); if (lastPicture) memcpy(lastPicture, fb->buf, pictureSize); esp_camera_fb_return(fb); return; } int shift = averageShift(fb->buf, lastPicture, pictureSize); int changed = countChangedPixels(fb->buf, lastPicture, pictureSize, shift); int limit = (pictureSize * MOTION_PERCENT) / 100; int lightLimit = (pictureSize * LIGHT_CHANGE_PERCENT) / 100; // Almost the whole picture changed. A person is never that big, // so this is a light change, not a movement. if (changed > lightLimit) { Serial.printf("The light changed (%d pixels). No photo.\n", changed); memcpy(lastPicture, fb->buf, pictureSize); esp_camera_fb_return(fb); delay(SETTLE_TIME); // let the camera set its brightness again return; } if (changed > limit) { Serial.printf("Movement seen: %d pixels changed (the limit is %d, light shift %d)\n", changed, limit, shift); if (millis() - lastPhotoTime >= QUIET_TIME) { esp_camera_fb_return(fb); takeAndSavePhoto(); lastPhotoTime = millis(); return; } } memcpy(lastPicture, fb->buf, pictureSize); // remember this picture for the next time esp_camera_fb_return(fb); }

※ Notiz:

Richten Sie die Kamera nun an einem windigen Tag auf ein Fenster mit einem Baum davor. Die Blätter bewegen sich, und die Kamera speichert ein Foto nach dem anderen von nichts. Genau dieses Problem behebt Beispiel 3.

ESP32 S3 Code - Beispiel 3: Licht- und windsicher

Wind ist ein anderes Problem als Licht. Blätter, ein Vorhang oder eine Fahne verändern Pixel überall im Bild, in kleinen Flecken. Eine Person verändert eine Gruppe von Pixeln, die nebeneinander liegen.

Durch das Zählen von Pixeln lassen sich die beiden nicht unterscheiden, weil beide eine ähnliche Zahl ergeben. Wenn man betrachtet, wo sie sich verändert haben, schon.

Das Bild wird zu Blöcken

Der Code teilt das kleine Bild in 8 × 8 = 64 Blöcke. Jeder Block ist 20 × 15 Pixel groß. Ein Block gilt als "verändert", wenn sich genügend seiner eigenen Pixel verändert haben:

int neededPerBlock = (BLOCK_WIDTH * BLOCK_HEIGHT * BLOCK_PERCENT) / 100; bool changed = (count >= neededPerBlock); blockChanged[by][bx] = changed;

Betrachten Sie nun den Unterschied auf der Karte. Wind:

..#..... .....#.. #....... ...#.... ......#. ..#..... ........ .#......

Eine Person:

........ ...##... ..###... ..###... ...##... ...#.... ........ ........

Beide haben etwa 8 veränderte Blöcke. Aber die Wind-Blöcke stehen allein, und die Personen-Blöcke berühren sich.

Die größte zusammenhängende Gruppe finden

Der Code beginnt bei einem veränderten Block, geht zu jedem veränderten Block daneben und zählt dann, wie groß diese Gruppe ist. Er behält die größte Gruppe, die er findet:

int group = biggestGroup(); if (group >= MIN_GROUP_BLOCKS) { Serial.println("Something moved"); } else { Serial.println("Ignored. Probably wind."); }

※ Notiz:

Diese eine Idee beseitigt die meisten Windprobleme. Blätter bilden fast nie 4 Blöcke, die sich berühren, eine vorbeigehende Person aber immer.

Im nächsten Bild erneut prüfen

Blätter wackeln hin und her, daher sehen sie in einem Bild verändert und im nächsten normal aus. Eine Person bewegt sich weiter. Deshalb verlangt der Code die Bewegung in mehreren Bildern hintereinander:

if (group >= MIN_GROUP_BLOCKS) confirmCount++; else confirmCount = 0; if (confirmCount >= CONFIRM_FRAMES) { takeAndSavePhoto(); }

Den oberen Bildteil ignorieren

Bäume, Fahnen und der Himmel befinden sich meist im oberen Bereich. IGNORE_TOP_ROWS verwirft entsprechend viele Blockzeilen:

#define IGNORE_TOP_ROWS 2 // ignore the top 2 rows of blocks

※ Notiz:

Bei 8 Blockzeilen verwirft IGNORE_TOP_ROWS 2 das obere Viertel des Bildes. Verwenden Sie es, wenn die Störung immer an derselben Stelle auftritt.

Alle Zahlen, die Sie ändern können

NameStartwertBedeutungGelöstes Problem
PIXEL_THRESHOLD30Wie stark sich ein Pixel unterscheiden mussKamerarauschen
BLOCK_PERCENT20Wie viel von einem Block sich ändern mussKleines Rauschen innerhalb eines Blocks
MIN_GROUP_BLOCKS4Wie viele Blöcke sich berühren müssenWind und Blätter
LIGHT_CHANGE_BLOCKS40Darüber ist es eine LichtänderungLampen und Wolken
CONFIRM_FRAMES2Wie viele Bilder hintereinanderWackeln und Flackern
IGNORE_TOP_ROWS0Wie viele Blockzeilen oben ignoriert werdenEin Baum im oberen Bereich
SETTLE_TIME1000Wartezeit nach einer LichtänderungNeubelichtung der Kamera
QUIET_TIME5000Wartezeit vor dem nächsten FotoZu viele Fotos
SHOW_MAPtrueDie Karte im Seriellen Monitor ausgebenHilft Ihnen beim Einstellen der Zahlen
/* * Dieser ESP32 S3 Code wurde von newbiely.de entwickelt * Dieser ESP32 S3 Code wird der Öffentlichkeit ohne jegliche Einschränkung zur Verfügung gestellt. * Für vollständige Anleitungen und Schaltpläne besuchen Sie bitte: * https://newbiely.de/tutorials/esp32-s3/esp32-s3-camera-detect-motion-without-a-sensor */ #include "esp_camera.h" #include "FS.h" #include "SD_MMC.h" // ============================================================ // SETTINGS YOU CAN CHANGE // Start with these numbers, then change ONE at a time and // watch the map in the Serial Monitor. // ============================================================ // How different one pixel must be before we call it "changed" (0 to 255). // Bigger = less sensitive to camera noise. #define PIXEL_THRESHOLD 30 // How much of ONE block must change before the block counts as changed (in %). // Bigger = a block must be really busy before it counts. #define BLOCK_PERCENT 20 // How many blocks must be changed AND touching each other. // This is the number that stops wind. Bigger = only bigger objects count. #define MIN_GROUP_BLOCKS 4 // When more blocks than this change at once, it is a light change, not a person. #define LIGHT_CHANGE_BLOCKS 40 // The movement must be seen in this many pictures in a row before a photo is taken. // Bigger = leaves that shake back and forth are ignored. #define CONFIRM_FRAMES 2 // Ignore this many block rows at the TOP of the picture. // Use it when there are trees, a sky or a flag in the upper part. #define IGNORE_TOP_ROWS 0 #define SETTLE_TIME 1000 // after a light change, give the camera 1 second #define QUIET_TIME 5000 // wait 5 seconds before the next photo #define SHOW_MAP true // print the map of changed blocks // ============================================================ // The picture is cut into blocks. 160 x 120 becomes 8 x 8 blocks // of 20 x 15 pixels each. // ============================================================ #define IMG_WIDTH 160 #define IMG_HEIGHT 120 #define BLOCK_COLS 8 #define BLOCK_ROWS 8 #define BLOCK_WIDTH (IMG_WIDTH / BLOCK_COLS) // 20 #define BLOCK_HEIGHT (IMG_HEIGHT / BLOCK_ROWS) // 15 // Camera pins of the ESP32-S3-N16R8 camera board. // The same pins work for the OV2640, OV3660 and OV5640 versions. #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 15 #define SIOD_GPIO_NUM 4 #define SIOC_GPIO_NUM 5 #define Y9_GPIO_NUM 16 #define Y8_GPIO_NUM 17 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 12 #define Y5_GPIO_NUM 10 #define Y4_GPIO_NUM 8 #define Y3_GPIO_NUM 9 #define Y2_GPIO_NUM 11 #define VSYNC_GPIO_NUM 6 #define HREF_GPIO_NUM 7 #define PCLK_GPIO_NUM 13 // Pins of the microSD card slot on the board #define SD_CLK_GPIO_NUM 39 #define SD_CMD_GPIO_NUM 38 #define SD_D0_GPIO_NUM 40 camera_config_t cameraConfig; // we keep the settings, because we change them later uint8_t *lastPicture = NULL; // the picture we saw the time before size_t pictureSize = 0; bool blockChanged[BLOCK_ROWS][BLOCK_COLS]; int photoNumber = 1; int confirmCount = 0; // how many pictures in a row showed movement unsigned long lastPhotoTime = 0; // Returns the name of the camera sensor that is installed on the board const char *getSensorName(int pid) { switch (pid) { case OV2640_PID: return "OV2640 (2 MP, max UXGA 1600x1200)"; case OV3660_PID: return "OV3660 (3 MP, max QXGA 2048x1536)"; case OV5640_PID: return "OV5640 (5 MP, max QSXGA 2592x1944)"; default: return "Unknown sensor"; } } // Each sensor needs slightly different settings. // We must call this again every time the camera restarts. void applyCameraSettings() { sensor_t *s = esp_camera_sensor_get(); switch (s->id.PID) { case OV2640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; case OV3660_PID: s->set_vflip(s, 1); // the OV3660 image is upside down by default s->set_brightness(s, 1); // make the image a little brighter s->set_saturation(s, -2); // make the colors a little softer break; case OV5640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; default: break; } } // Starts the camera in the small black and white mode, used for watching bool initCamera() { cameraConfig.ledc_channel = LEDC_CHANNEL_0; cameraConfig.ledc_timer = LEDC_TIMER_0; cameraConfig.pin_d0 = Y2_GPIO_NUM; cameraConfig.pin_d1 = Y3_GPIO_NUM; cameraConfig.pin_d2 = Y4_GPIO_NUM; cameraConfig.pin_d3 = Y5_GPIO_NUM; cameraConfig.pin_d4 = Y6_GPIO_NUM; cameraConfig.pin_d5 = Y7_GPIO_NUM; cameraConfig.pin_d6 = Y8_GPIO_NUM; cameraConfig.pin_d7 = Y9_GPIO_NUM; cameraConfig.pin_xclk = XCLK_GPIO_NUM; cameraConfig.pin_pclk = PCLK_GPIO_NUM; cameraConfig.pin_vsync = VSYNC_GPIO_NUM; cameraConfig.pin_href = HREF_GPIO_NUM; cameraConfig.pin_sccb_sda = SIOD_GPIO_NUM; cameraConfig.pin_sccb_scl = SIOC_GPIO_NUM; cameraConfig.pin_pwdn = PWDN_GPIO_NUM; cameraConfig.pin_reset = RESET_GPIO_NUM; cameraConfig.xclk_freq_hz = 20000000; cameraConfig.jpeg_quality = 10; cameraConfig.fb_count = 1; cameraConfig.fb_location = psramFound() ? CAMERA_FB_IN_PSRAM : CAMERA_FB_IN_DRAM; cameraConfig.grab_mode = CAMERA_GRAB_WHEN_EMPTY; // GRAYSCALE means black and white. One pixel is one byte, so it is easy to compare. cameraConfig.pixel_format = PIXFORMAT_GRAYSCALE; cameraConfig.frame_size = FRAMESIZE_QQVGA; // 160x120 - small and fast if (!psramFound()) Serial.println("WARNING: PSRAM not found. Please set PSRAM to OPI PSRAM in the Tools menu."); esp_err_t err = esp_camera_init(&cameraConfig); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x\n", err); return false; } sensor_t *s = esp_camera_sensor_get(); Serial.printf("Camera sensor: %s\n", getSensorName(s->id.PID)); applyCameraSettings(); return true; } bool initSDCard() { SD_MMC.setPins(SD_CLK_GPIO_NUM, SD_CMD_GPIO_NUM, SD_D0_GPIO_NUM); // true means 1-bit mode. The camera uses the other pins, so we cannot use 4-bit mode. if (!SD_MMC.begin("/sdcard", true)) { Serial.println("SD card not found. Please check the card and push it in again."); return false; } if (SD_MMC.cardType() == CARD_NONE) { Serial.println("No SD card inside the slot"); return false; } Serial.printf("SD card size: %llu MB\n", SD_MMC.cardSize() / (1024 * 1024)); return true; } String makeFileName(int number) { char name[32]; sprintf(name, "/move_%05d.jpg", number); return String(name); } void findFirstFreeNumber() { while (SD_MMC.exists(makeFileName(photoNumber))) photoNumber++; Serial.printf("The next photo will be move_%05d.jpg\n", photoNumber); } // Works out how much the WHOLE picture became brighter or darker. // A lamp, a cloud or the evening sun moves every pixel by about the same amount. int averageShift(uint8_t *now, uint8_t *before, size_t size) { long total = 0; for (size_t i = 0; i < size; i++) total += (int)now[i] - (int)before[i]; return (int)(total / (long)size); } // Looks at every block and marks the ones that changed. // Returns how many blocks changed in the whole picture. int markChangedBlocks(uint8_t *now, uint8_t *before, int shift) { int pixelsPerBlock = BLOCK_WIDTH * BLOCK_HEIGHT; int neededPerBlock = (pixelsPerBlock * BLOCK_PERCENT) / 100; int changedBlocks = 0; for (int by = 0; by < BLOCK_ROWS; by++) { for (int bx = 0; bx < BLOCK_COLS; bx++) { int count = 0; for (int y = 0; y < BLOCK_HEIGHT; y++) { int rowStart = (by * BLOCK_HEIGHT + y) * IMG_WIDTH + bx * BLOCK_WIDTH; for (int x = 0; x < BLOCK_WIDTH; x++) { int difference = ((int)now[rowStart + x] - (int)before[rowStart + x]) - shift; if (difference < 0) difference = -difference; if (difference > PIXEL_THRESHOLD) count++; } } bool changed = (count >= neededPerBlock) && (by >= IGNORE_TOP_ROWS); blockChanged[by][bx] = changed; if (changed) changedBlocks++; } } return changedBlocks; } // Finds the biggest group of changed blocks that TOUCH each other. // Leaves in the wind change single blocks here and there. // A person changes a group of blocks next to each other. int biggestGroup() { bool visited[BLOCK_ROWS][BLOCK_COLS]; memset(visited, 0, sizeof(visited)); int best = 0; int stackY[BLOCK_ROWS * BLOCK_COLS]; int stackX[BLOCK_ROWS * BLOCK_COLS]; for (int startY = 0; startY < BLOCK_ROWS; startY++) { for (int startX = 0; startX < BLOCK_COLS; startX++) { if (!blockChanged[startY][startX] || visited[startY][startX]) continue; // Start a new group here and walk to every block that touches it int top = 0; int size = 0; stackY[top] = startY; stackX[top] = startX; top++; visited[startY][startX] = true; while (top > 0) { top--; int y = stackY[top]; int x = stackX[top]; size++; // Look at the four neighbours: up, down, left, right int nextY[4] = {y - 1, y + 1, y, y}; int nextX[4] = {x, x, x - 1, x + 1}; for (int i = 0; i < 4; i++) { int ny = nextY[i]; int nx = nextX[i]; if (ny < 0 || ny >= BLOCK_ROWS || nx < 0 || nx >= BLOCK_COLS) continue; if (blockChanged[ny][nx] && !visited[ny][nx]) { visited[ny][nx] = true; stackY[top] = ny; stackX[top] = nx; top++; } } } if (size > best) best = size; } } return best; } // Draws a small map in the Serial Monitor. A # is a changed block. void printMap() { for (int by = 0; by < BLOCK_ROWS; by++) { String line = " "; for (int bx = 0; bx < BLOCK_COLS; bx++) line += blockChanged[by][bx] ? '#' : '.'; Serial.println(line); } } // Changes the camera to the big colour mode, takes one photo, saves it, // and changes back to the small black and white mode void takeAndSavePhoto() { cameraConfig.pixel_format = PIXFORMAT_JPEG; cameraConfig.frame_size = FRAMESIZE_SVGA; if (esp_camera_reconfigure(&cameraConfig) != ESP_OK) { Serial.println("Cannot change the camera to photo mode"); return; } applyCameraSettings(); delay(200); camera_fb_t *fb = esp_camera_fb_get(); if (fb) { String fileName = makeFileName(photoNumber); File file = SD_MMC.open(fileName.c_str(), FILE_WRITE); if (file) { file.write(fb->buf, fb->len); file.close(); Serial.printf("Saved %s (%u bytes)\n", fileName.c_str(), fb->len); photoNumber++; } else { Serial.printf("Cannot write the file %s\n", fileName.c_str()); } esp_camera_fb_return(fb); } else { Serial.println("Photo capture failed"); } // Back to watching mode cameraConfig.pixel_format = PIXFORMAT_GRAYSCALE; cameraConfig.frame_size = FRAMESIZE_QQVGA; esp_camera_reconfigure(&cameraConfig); applyCameraSettings(); delay(200); // The old picture belongs to the other mode, so we forget it if (lastPicture) { free(lastPicture); lastPicture = NULL; } confirmCount = 0; } void setup() { Serial.begin(115200); delay(1000); if (!initCamera()) { Serial.println("Stopped"); while (true) delay(1000); } if (!initSDCard()) { Serial.println("Stopped"); while (true) delay(1000); } findFirstFreeNumber(); Serial.printf("Watching. A photo is saved when %d touching blocks change in %d pictures in a row.\n", MIN_GROUP_BLOCKS, CONFIRM_FRAMES); } void loop() { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Frame capture failed"); delay(500); return; } // The very first picture has nothing to compare with, so we only keep it if (lastPicture == NULL) { pictureSize = fb->len; lastPicture = (uint8_t *)malloc(pictureSize); if (lastPicture) memcpy(lastPicture, fb->buf, pictureSize); esp_camera_fb_return(fb); return; } int shift = averageShift(fb->buf, lastPicture, pictureSize); int changedBlocks = markChangedBlocks(fb->buf, lastPicture, shift); // 1. Too many blocks at once means the light changed, not a person if (changedBlocks > LIGHT_CHANGE_BLOCKS) { Serial.printf("The light changed (%d blocks). No photo.\n", changedBlocks); memcpy(lastPicture, fb->buf, pictureSize); esp_camera_fb_return(fb); confirmCount = 0; delay(SETTLE_TIME); // let the camera set its brightness again return; } // 2. Wind moves single blocks here and there. A person moves a GROUP of blocks. int group = biggestGroup(); if (group >= MIN_GROUP_BLOCKS) { confirmCount++; Serial.printf("Something moved: %d blocks changed, biggest group %d (%d of %d pictures)\n", changedBlocks, group, confirmCount, CONFIRM_FRAMES); if (SHOW_MAP) printMap(); } else { if (confirmCount > 0 && group > 0) Serial.printf("Ignored: biggest group only %d blocks. Probably wind.\n", group); confirmCount = 0; } // 3. The movement must be there in several pictures in a row if (confirmCount >= CONFIRM_FRAMES) { if (millis() - lastPhotoTime >= QUIET_TIME) { esp_camera_fb_return(fb); takeAndSavePhoto(); lastPhotoTime = millis(); return; } confirmCount = 0; } memcpy(lastPicture, fb->buf, pictureSize); // remember this picture for the next time esp_camera_fb_return(fb); }

Schnelle Schritte

  • Neu beim ESP32 S3? Arbeiten Sie zuerst unsere Anleitung Erste Schritte mit dem ESP32 S3 durch.
  • Formatieren Sie Ihre microSD-Karte auf Ihrem Computer als FAT32.
  • Ziehen Sie das USB-Kabel ab und stecken Sie dann die Karte in den Steckplatz.
  • Kopieren Sie Beispiel 1 und fügen Sie es in die Arduino IDE ein.
  • Ändern Sie die Einstellungen im Menü Tools. Siehe die Tabelle oben.
  • Kompilieren Sie den Code und laden Sie ihn auf das ESP32 S3 Board hoch, indem Sie in der Arduino IDE auf die Schaltfläche Upload klicken.
Arduino IDE Code hochladen
  • Öffnen Sie den Seriellen Monitor in der Arduino IDE.
So öffnen Sie den Seriellen Monitor in der Arduino IDE
  • Drücken Sie einmal die Taste RESET.
  • Bewegen Sie Ihre Hand vor der Kamera. Ein Foto wird gespeichert.
  • Schalten Sie nun eine Lampe ein und aus. Beispiel 1 speichert Fotos von nichts.
  • Laden Sie Beispiel 2 hoch. Probieren Sie die Lampe erneut. Jetzt wird nichts gespeichert.
  • Stellen Sie die Kamera an ein Fenster mit einem Baum oder vor einen sich bewegenden Vorhang. Beispiel 2 speichert Fotos von nichts.
  • Laden Sie Beispiel 3 hoch. Beobachten Sie die Karte im Seriellen Monitor und ändern Sie die Zahlen, bis Ruhe herrscht.
  • Profi-Tipp: Stellen Sie das Board auf etwas Stabiles. Wenn sich das Board bewegt, ändert sich das ganze Bild, und jedes kleine Wackeln sieht wie eine Bewegung aus.

Serieller Monitor

Beispiel 3 zeichnet eine kleine Karte, sodass Sie genau sehen, was die Kamera gesehen hat. Die folgenden Messwerte wurden im Jahr 2026 aufgezeichnet:

∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev Module' on 'COM15')
New Line
9600 baud
[2026-09-28 13:00:01] Camera sensor: OV5640 (5 MP, max QSXGA 2592x1944) [2026-09-28 13:00:01] SD card size: 15193 MB [2026-09-28 13:00:01] The next photo will be move_00001.jpg [2026-09-28 13:00:01] Watching. A photo is saved when 4 touching blocks change in 2 pictures in a row. [2026-09-28 13:00:38] Ignored: biggest group only 2 blocks. Probably wind. [2026-09-28 13:00:42] Something moved: 9 blocks changed, biggest group 7 (1 of 2 pictures) [2026-09-28 13:00:42] ........ [2026-09-28 13:00:42] ...##... [2026-09-28 13:00:42] ..###... [2026-09-28 13:00:42] ...##... [2026-09-28 13:00:42] ........ [2026-09-28 13:00:42] ........ [2026-09-28 13:00:42] ........ [2026-09-28 13:00:42] ........ [2026-09-28 13:00:42] Something moved: 11 blocks changed, biggest group 8 (2 of 2 pictures) [2026-09-28 13:00:43] Saved /move_00001.jpg (51353 bytes) [2026-09-28 13:01:09] The light changed (58 blocks). No photo.
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

※ Notiz:

Die Karte ist Ihr bestes Werkzeug. Beobachten Sie sie eine Minute lang, während sich im Raum nichts bewegt. Wenn Sie ständig #-Zeichen sehen, sind Ihre Zahlen zu empfindlich.

So stellen Sie die Zahlen ein

Ändern Sie eine Zahl nach der anderen und beobachten Sie die Karte nach jeder Änderung.

ProblemWas Sie ändern sollten
Fotos, obwohl nichts im Raum istPIXEL_THRESHOLD erhöhen (30 auf 40 oder 50)
Eine Lampe löst die Kamera immer noch ausLIGHT_CHANGE_BLOCKS verringern (40 auf 25)
Blätter und Wind lösen sie immer noch ausMIN_GROUP_BLOCKS erhöhen (4 auf 6 oder 8)
Ein Vorhang löst sie immer noch ausCONFIRM_FRAMES erhöhen (2 auf 4)
Ein Baum im oberen BereichIGNORE_TOP_ROWS erhöhen (0 auf 2)
Eine Person geht vorbei und nichts passiertMIN_GROUP_BLOCKS verringern (4 auf 2)
Nur sehr große Bewegungen werden erkanntBLOCK_PERCENT verringern (20 auf 10)
Viele Fotos von derselben PersonQUIET_TIME erhöhen

※ Notiz:

Möchten Sie das Lichtproblem vollständig beseitigen? Schalten Sie die automatische Helligkeit der Kamera mit s->set_gain_ctrl(s, 0) und s->set_exposure_ctrl(s, 0) aus. Dann ändert sich das Bild nie von selbst. Der Nachteil ist, dass sich die Kamera nicht mehr anpassen kann, daher funktioniert sie nur in einem Raum, in dem das Licht immer gleich bleibt.

Was sie immer noch täuscht

SituationBeispiel 1Beispiel 2Beispiel 3
Eine Person geht vorbeiJaJaJa
Eine Lampe geht an oder ausNeinJaJa
Eine Wolke zieht vor der Sonne vorbeiNeinJaJa
Blätter bewegen sich im WindNeinNeinMeistens
Ein Vorhang bewegt sichNeinNeinMeistens
Regen oder Schnee fälltNeinNeinMeistens
Ein Schatten wandert über eine WandNeinNeinNein
Eine flackernde Lampe oder ein FernseherNeinNeinNein
Das Board selbst wackeltNeinNeinNein

※ Notiz:

Keine Kamera ist perfekt. Selbst eine teure Ladenkamera hat diese Probleme. Die richtige Lösung für einen schwierigen Ort ist oft, beide Wege zusammen zu verwenden: einen PIR-Bewegungssensor und diesen Code, und nur dann ein Foto zu speichern, wenn beide übereinstimmen.

Fehlerbehebung

Es werden ständig Fotos gespeichert, sogar in einem leeren Raum.

  • Erhöhen Sie PIXEL_THRESHOLD, zum Beispiel auf 40.
  • Stellen Sie sicher, dass sich das Board nicht bewegen oder wackeln kann.
  • Ein Raum mit einer flackernden Lampe oder einem Fernseher verändert sich ständig. Richten Sie die Kamera von ihnen weg.

Es wird nie etwas erkannt.

  • Verringern Sie MOTION_PERCENT, zum Beispiel auf 1.
  • Der Raum ist möglicherweise zu dunkel. Diese Methode braucht Licht, weil die Kamera die Änderung sehen muss.

Der Serielle Monitor zeigt cam_hal: FB-OVF.

Die Bildgröße wurde irgendwo mit s->set_framesize() geändert. Verwenden Sie stattdessen esp_camera_reconfigure(), wie es der Code tut.

Die Fotos werden gespeichert, aber die Überwachung stoppt nach dem ersten Foto.

Die Kamera ist nicht in den kleinen Schwarz-Weiß-Modus zurückgekehrt. Prüfen Sie, ob das zweite esp_camera_reconfigure() nach dem Foto aufgerufen wird.

Der Serielle Monitor zeigt SD card not found.

Formatieren Sie die Karte als FAT32, verwenden Sie eine Karte mit 32 GB oder weniger und stecken Sie die Karte erneut ein.

Der Serielle Monitor zeigt nach den Startmeldungen nichts an.

Wenn Ihr USB-Kabel im Anschluss mit dem Namen UART steckt, stellen Sie USB CDC On Boot auf Disabled. Wenn es im Anschluss mit dem Namen USB steckt, stellen Sie es auf Enabled.

Anwendungen

Diese Kamera braucht nichts außer sich selbst, daher passt sie überallhin und kostet nichts extra. Hier sind praktische Projekte, die Sie mit diesem Code bauen können:

  1. Sicherheitskamera ohne zusätzliche Teile: Die günstigste Art, einen Raum zu überwachen.
  2. Paket- und Lieferkamera: Ein PIR-Sensor sieht kein kaltes Paket auf dem Boden, diese Kamera aber schon.
  3. Auto- und Einfahrtsbeobachter: Ein Auto ist kalt, daher kann nur eine Kamera seine Ankunft sehen.
  4. Schaufensterbeobachter: Ein PIR-Sensor funktioniert nicht durch Glas, eine Kamera schon.
  5. Fehlerdetektor für Maschinen und 3D-Drucker: Speichern Sie ein Foto in dem Moment, in dem sich das Bild auf ungewöhnliche Weise verändert.
  6. Vogelhauskamera: Kleine Tiere sind für einen PIR-Sensor oft zu klein.
  7. Wasserstands- und Leckbeobachter: Speichern Sie ein Foto, wenn sich das Bild eines Bodens oder eines Tanks verändert.
  8. Experiment im Klassenzimmer: Zeigen Sie Schülern, wie ein Computer Bewegung mit einfachen Zahlen "sieht".
  9. Tür- und Torbeobachter: Funktioniert auch, wenn sich die Tür hinter Glas befindet.
  10. Ergänzung zu einer PIR-Kamera: Betreiben Sie beide Wege gleichzeitig und vergleichen Sie, welcher in Ihrem Raum besser ist.

Video Tutorial

Wir erwägen die Erstellung von Video-Tutorials. Wenn Sie Video-Tutorials für wichtig halten, abonnieren Sie bitte unseren YouTube-Kanal , um uns zu motivieren, die Videos zu erstellen.

Sehen Sie sich unten die Schritt-für-Schritt-Videoanleitung für dieses ESP32 S3 Projekt an.

Herausforderungen

  1. Einsteiger: Ändern Sie MOTION_PERCENT von 2 auf 5 und sehen Sie, wie viel größer eine Bewegung sein muss.
  2. Fortgeschrittene: Geben Sie jede Sekunde die Anzahl der veränderten Pixel aus, auch wenn es keine Bewegung gibt, und beobachten Sie, wie sie sich in Ihrem Raum im Laufe des Tages verhält.
  3. Profis: Betrachten Sie nur die Mitte des Bildes und ignorieren Sie die Ränder. Dann löst ein Baum, der sich am Bildrand bewegt, die Kamera nicht aus.

※ UNSERE NACHRICHTEN

  • Sie können gerne den Link zu diesem Tutorial teilen. Bitte verwenden Sie jedoch unsere Inhalte nicht auf anderen Websites. Wir haben viel Mühe und Zeit in die Erstellung der Inhalte investiert, bitte respektieren Sie unsere Arbeit!