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:
Er schreibt eine Zeile in den Seriellen Monitor, sodass Sie genau sehen, was die Kamera gesehen hat
Er speichert ein großes Farbfoto auf der microSD-Karte
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.
※ 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:
ESP32 S3 - Telegram Bot, um zusätzlich eine Textnachricht zu senden oder ein Licht einzuschalten
Der Erkennungscode bleibt gleich. Nur der letzte Schritt ändert sich.
Was Sie bauen werden:
Eine Kamera, die selbst auf Bewegung achtet, ohne PIR-Sensor
Eine Meldung im Seriellen Monitor jedes Mal, wenn die Kamera etwas sich bewegen sieht
Ein großes Foto, das auf der microSD-Karte gespeichert wird, wenn sich etwas ändert
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
Eine Kamera, die auch Dinge sieht, die ein PIR-Sensor nicht sehen kann, zum Beispiel einen kalten Gegenstand
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.
Die Kamera nimmt ein kleines Schwarz-Weiß-Bild auf, nur 160 × 120 Pixel.
In Schwarz-Weiß ist ein Pixel eine Zahl zwischen 0 (schwarz) und 255 (weiß).
Der Code vergleicht jedes Pixel mit demselben Pixel des vorherigen Bildes.
Wenn die beiden Zahlen weit auseinander liegen, hat sich dieses Pixel verändert.
Wenn sich viele Pixel verändert haben, hat sich etwas bewegt.
Warum Schwarz-Weiß, und warum so klein?
Wahl
Grund
Schwarz-Weiß
Ein Pixel ist eine Zahl, daher ist der Vergleich einfach und schnell
160 x 120
19200 Zahlen passen problemlos in den Speicher, und das Board kann sie viele Male pro Sekunde vergleichen
Kein JPEG
Ein 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.
In diesem Projekt gibt es keine Verdrahtung. Sie stecken nur die microSD-Karte in den Steckplatz.
Dieses Bild wurde mit Fritzing erstellt. Klicken Sie, um das Bild zu vergrößern.
Ziehen Sie das USB-Kabel ab.
Stellen Sie sicher, dass das Kameramodul mit seinem Flachbandkabel verbunden ist.
Schieben Sie die microSD-Karte in den Steckplatz, bis Sie ein leises Klicken hören.
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 Tools
Diese Auswahl treffen
Board
ESP32S3 Dev Module
Flash Size
16MB (128Mb)
PSRAM
OPI PSRAM
Partition Scheme
Huge APP (3MB No OTA/1MB SPIFFS)
CPU Frequency
240MHz (WiFi)
USB Mode
Hardware CDC and JTAG
Upload Mode
UART0 / Hardware CDC
Upload Speed
921600
USB CDC On Boot
Enabled
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.
Beispiel
Was es hinzufügt
Geeignet für
*i. Einfach
Vergleicht zwei Bilder, zählt veränderte Pixel
Die Idee verstehen. Einen ruhigen Raum in Innenräumen.
*i. Lichtsicher
Alles aus Beispiel 1, PLUS Lampen, Wolken und die Abendsonne
Einen Raum mit Fenstern oder einer Lampe
*i. Licht- und windsicher
Alles aus Beispiel 2, PLUS Blätter, Vorhänge und wackelnde Dinge
Drauß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:
Schutz
Beispiel 1
Beispiel 2
Beispiel 3
Sieht eine sich bewegende Person
Ja
Ja
Ja
Entfernt die Lichtänderung (averageShift)
Nein
Ja
Ja
Lehnt eine Änderung des ganzen Bildes ab (eine Lampe)
Nein
Ja
Ja
Wartet nach einer Lichtänderung (SETTLE_TIME)
Nein
Ja
Ja
Gruppen von Blöcken, die sich berühren (Wind)
Nein
Nein
Ja
Muss in mehreren Bildern gesehen werden (CONFIRM_FRAMES)
Nein
Nein
Ja
Kann den oberen Bildteil ignorieren
Nein
Nein
Ja
※ 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
Name
Startwert
Bedeutung
PIXEL_THRESHOLD
30
Wie stark sich ein Pixel unterscheiden muss (0 bis 255)
MOTION_PERCENT
2
Wie viel vom Bild sich ändern muss, in Prozent
QUIET_TIME
5000
Millisekunden 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 darkerif (difference > PIXEL_THRESHOLD) changed++; }return changed;}
/* * 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 40camera_config_t cameraConfig; // we keep the settings, because we change them lateruint8_t *lastPicture = NULL; // the picture we saw the time beforesize_t pictureSize = 0;int photoNumber = 1;unsignedlong lastPhotoTime = 0;// Returns the name of the camera sensor that is installed on the boardconstchar *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 softerbreak;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 watchingbool 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 fastif (!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);returnfalse; } sensor_t *s = esp_camera_sensor_get();Serial.printf("Camera sensor: %s\n", getSensorName(s->id.PID)); applyCameraSettings();returntrue;}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.");returnfalse; }if (SD_MMC.cardType() == CARD_NONE) {Serial.println("No SD card inside the slot");returnfalse; }Serial.printf("SD card size: %llu MB\n", SD_MMC.cardSize() / (1024 * 1024));returntrue;}String makeFileName(int number) {charname[32]; sprintf(name, "/change_%05d.jpg", number);returnString(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 changedint 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 darkerif (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 modevoid 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 itif (lastPicture) { free(lastPicture); lastPicture = NULL; }}voidsetup() {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);}voidloop() { 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 itif (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
Name
Startwert
Bedeutung
PIXEL_THRESHOLD
30
Wie stark sich ein Pixel unterscheiden muss (0 bis 255)
MOTION_PERCENT
2
Wie viel vom Bild sich ändern muss, in Prozent
LIGHT_CHANGE_PERCENT
50
Darüber ist es eine Lampe und keine Person
SETTLE_TIME
1000
Millisekunden Wartezeit nach einer Lichtänderung
QUIET_TIME
5000
Millisekunden 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 againreturn;}
※ 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 40camera_config_t cameraConfig; // we keep the settings, because we change them lateruint8_t *lastPicture = NULL; // the picture we saw the time beforesize_t pictureSize = 0;int photoNumber = 1;unsignedlong lastPhotoTime = 0;// Returns the name of the camera sensor that is installed on the boardconstchar *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 softerbreak;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 watchingbool 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 fastif (!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);returnfalse; } sensor_t *s = esp_camera_sensor_get();Serial.printf("Camera sensor: %s\n", getSensorName(s->id.PID)); applyCameraSettings();returntrue;}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.");returnfalse; }if (SD_MMC.cardType() == CARD_NONE) {Serial.println("No SD card inside the slot");returnfalse; }Serial.printf("SD card size: %llu MB\n", SD_MMC.cardSize() / (1024 * 1024));returntrue;}String makeFileName(int number) {charname[32]; sprintf(name, "/change_%05d.jpg", number);returnString(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 darkerif (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 modevoid 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 itif (lastPicture) { free(lastPicture); lastPicture = NULL; }}voidsetup() {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);}voidloop() { 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 itif (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 againreturn; }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:
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:
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
Name
Startwert
Bedeutung
Gelöstes Problem
PIXEL_THRESHOLD
30
Wie stark sich ein Pixel unterscheiden muss
Kamerarauschen
BLOCK_PERCENT
20
Wie viel von einem Block sich ändern muss
Kleines Rauschen innerhalb eines Blocks
MIN_GROUP_BLOCKS
4
Wie viele Blöcke sich berühren müssen
Wind und Blätter
LIGHT_CHANGE_BLOCKS
40
Darüber ist es eine Lichtänderung
Lampen und Wolken
CONFIRM_FRAMES
2
Wie viele Bilder hintereinander
Wackeln und Flackern
IGNORE_TOP_ROWS
0
Wie viele Blockzeilen oben ignoriert werden
Ein Baum im oberen Bereich
SETTLE_TIME
1000
Wartezeit nach einer Lichtänderung
Neubelichtung der Kamera
QUIET_TIME
5000
Wartezeit vor dem nächsten Foto
Zu viele Fotos
SHOW_MAP
true
Die Karte im Seriellen Monitor ausgeben
Hilft 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 40camera_config_t cameraConfig; // we keep the settings, because we change them lateruint8_t *lastPicture = NULL; // the picture we saw the time beforesize_t pictureSize = 0;bool blockChanged[BLOCK_ROWS][BLOCK_COLS];int photoNumber = 1;int confirmCount = 0; // how many pictures in a row showed movementunsignedlong lastPhotoTime = 0;// Returns the name of the camera sensor that is installed on the boardconstchar *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 softerbreak;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 watchingbool 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 fastif (!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);returnfalse; } sensor_t *s = esp_camera_sensor_get();Serial.printf("Camera sensor: %s\n", getSensorName(s->id.PID)); applyCameraSettings();returntrue;}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.");returnfalse; }if (SD_MMC.cardType() == CARD_NONE) {Serial.println("No SD card inside the slot");returnfalse; }Serial.printf("SD card size: %llu MB\n", SD_MMC.cardSize() / (1024 * 1024));returntrue;}String makeFileName(int number) {charname[32]; sprintf(name, "/move_%05d.jpg", number);returnString(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 itint top = 0;intsize = 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, rightint 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 modevoid 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 itif (lastPicture) { free(lastPicture); lastPicture = NULL; } confirmCount = 0;}voidsetup() {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);}voidloop() { 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 itif (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 personif (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 againreturn; }// 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 rowif (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);}
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.
Ö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
···
8Serial.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.
Problem
Was Sie ändern sollten
Fotos, obwohl nichts im Raum ist
PIXEL_THRESHOLD erhöhen (30 auf 40 oder 50)
Eine Lampe löst die Kamera immer noch aus
LIGHT_CHANGE_BLOCKS verringern (40 auf 25)
Blätter und Wind lösen sie immer noch aus
MIN_GROUP_BLOCKS erhöhen (4 auf 6 oder 8)
Ein Vorhang löst sie immer noch aus
CONFIRM_FRAMES erhöhen (2 auf 4)
Ein Baum im oberen Bereich
IGNORE_TOP_ROWS erhöhen (0 auf 2)
Eine Person geht vorbei und nichts passiert
MIN_GROUP_BLOCKS verringern (4 auf 2)
Nur sehr große Bewegungen werden erkannt
BLOCK_PERCENT verringern (20 auf 10)
Viele Fotos von derselben Person
QUIET_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
Situation
Beispiel 1
Beispiel 2
Beispiel 3
Eine Person geht vorbei
Ja
Ja
Ja
Eine Lampe geht an oder aus
Nein
Ja
Ja
Eine Wolke zieht vor der Sonne vorbei
Nein
Ja
Ja
Blätter bewegen sich im Wind
Nein
Nein
Meistens
Ein Vorhang bewegt sich
Nein
Nein
Meistens
Regen oder Schnee fällt
Nein
Nein
Meistens
Ein Schatten wandert über eine Wand
Nein
Nein
Nein
Eine flackernde Lampe oder ein Fernseher
Nein
Nein
Nein
Das Board selbst wackelt
Nein
Nein
Nein
※ 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-Bewegungssensorund 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:
Sicherheitskamera ohne zusätzliche Teile: Die günstigste Art, einen Raum zu überwachen.
Paket- und Lieferkamera: Ein PIR-Sensor sieht kein kaltes Paket auf dem Boden, diese Kamera aber schon.
Auto- und Einfahrtsbeobachter: Ein Auto ist kalt, daher kann nur eine Kamera seine Ankunft sehen.
Schaufensterbeobachter: Ein PIR-Sensor funktioniert nicht durch Glas, eine Kamera schon.
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.
Vogelhauskamera: Kleine Tiere sind für einen PIR-Sensor oft zu klein.
Wasserstands- und Leckbeobachter: Speichern Sie ein Foto, wenn sich das Bild eines Bodens oder eines Tanks verändert.
Experiment im Klassenzimmer: Zeigen Sie Schülern, wie ein Computer Bewegung mit einfachen Zahlen "sieht".
Tür- und Torbeobachter: Funktioniert auch, wenn sich die Tür hinter Glas befindet.
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
Einsteiger: Ändern Sie MOTION_PERCENT von 2 auf 5 und sehen Sie, wie viel größer eine Bewegung sein muss.
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.
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.
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!