4. 智能家居安防监控

任务目标

在ESP32-S3开发板上接入OV2640摄像头,实现基于帧差法的运动检测。当检测到运动时,点亮LED并触发蜂鸣器报警,同时通过WiFi将报警消息推送到手机(使用Bark推送服务,iOS/Android均可接收)。

硬件与依赖

  • ESP32-S3开发板(含PSRAM,推荐ESP32-S3-DevKitC-1)
  • OV2640摄像头模块(FPC排线连接)
  • LED(GPIO 4)与蜂鸣器(GPIO 5),各串联220Ω电阻
  • Arduino IDE 2.x,安装 esp32 开发板包(≥2.0.6)
  • 库:WiFi.h(内置)、esp_camera.h(内置)

实现原理

核心采用帧差法:连续捕获两帧灰度图像,计算像素差值,超过阈值(20)的像素点计数。若差异像素占比超过画面总像素的0.5%,判定为运动。检测到运动后置位报警标志,持续报警10秒(避免频繁误触发),同时通过HTTPS请求调用Bark服务推送消息到手机。

关键完整代码

#include "esp_camera.h"
#include <WiFi.h>
#include <HTTPClient.h>

// WiFi配置
const char* WIFI_SSID = "your_wifi";
const char* WIFI_PASS = "your_password";
// Bark推送配置(在手机安装Bark应用后,从App获取此URL)
const char* BARK_URL = "https://api.day.app/YOUR_BARK_KEY/";

// 引脚定义
#define LED_PIN     4
#define BUZZER_PIN  5
#define PWDN_GPIO_NUM   -1
#define RESET_GPIO_NUM  -1
#define XCLK_GPIO_NUM   10
#define SIOD_GPIO_NUM   40
#define SIOC_GPIO_NUM   39
#define Y9_GPIO_NUM     48
#define Y8_GPIO_NUM     11
#define Y7_GPIO_NUM     12
#define Y6_GPIO_NUM     14
#define Y5_GPIO_NUM     16
#define Y4_GPIO_NUM     18
#define Y3_GPIO_NUM     17
#define Y2_GPIO_NUM     15
#define VSYNC_GPIO_NUM  38
#define HREF_GPIO_NUM   47
#define PCLK_GPIO_NUM   13

// 运动检测参数
#define DIFF_THRESHOLD   20
#define MOTION_RATIO     0.005
#define ALARM_DURATION_MS 10000

static camera_config_t camera_config = {
    .pin_pwdn  = PWDN_GPIO_NUM,
    .pin_reset = RESET_GPIO_NUM,
    .pin_xclk = XCLK_GPIO_NUM,
    .pin_sccb_sda = SIOD_GPIO_NUM,
    .pin_sccb_scl = SIOC_GPIO_NUM,
    .pin_d7 = Y9_GPIO_NUM,
    .pin_d6 = Y8_GPIO_NUM,
    .pin_d5 = Y7_GPIO_NUM,
    .pin_d4 = Y6_GPIO_NUM,
    .pin_d3 = Y5_GPIO_NUM,
    .pin_d2 = Y4_GPIO_NUM,
    .pin_d1 = Y3_GPIO_NUM,
    .pin_d0 = Y2_GPIO_NUM,
    .pin_vsync = VSYNC_GPIO_NUM,
    .pin_href = HREF_GPIO_NUM,
    .pin_pclk = PCLK_GPIO_NUM,
    .xclk_freq_hz = 20000000,
    .ledc_timer = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,
    .pixel_format = PIXFORMAT_GRAYSCALE,
    .frame_size = FRAMESIZE_QVGA,  // 320x240
    .jpeg_quality = 12,
    .fb_count = 2
};

static bool initCamera() {
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        Serial.printf("摄像头初始化失败: 0x%x\n", err);
        return false;
    }
    return true;
}

static void sendAlarm() {
    HTTPClient http;
    String url = String(BARK_URL) + "智能家居报警/检测到入侵!";
    http.begin(url);
    int code = http.GET();
    if (code > 0) {
        Serial.printf("推送成功,HTTP %d\n", code);
    } else {
        Serial.printf("推送失败: %s\n", http.errorToString(code).c_str());
    }
    http.end();
}

static bool detectMotion() {
    camera_fb_t* fb1 = esp_camera_fb_get();
    if (!fb1) return false;
    delay(50);  // 间隔50ms捕获第二帧
    camera_fb_t* fb2 = esp_camera_fb_get();
    if (!fb2) {
        esp_camera_fb_return(fb1);
        return false;
    }

    size_t len = fb1->len;
    if (len != fb2->len) {
        esp_camera_fb_return(fb1);
        esp_camera_fb_return(fb2);
        return false;
    }

    int diffCount = 0;
    uint8_t* p1 = fb1->buf;
    uint8_t* p2 = fb2->buf;
    for (size_t i = 0; i < len; i++) {
        int diff = abs((int)p1[i] - (int)p2[i]);
        if (diff > DIFF_THRESHOLD) diffCount++;
    }

    esp_camera_fb_return(fb1);
    esp_camera_fb_return(fb2);

    float ratio = (float)diffCount / len;
    Serial.printf("差异像素占比: %.3f%%\n", ratio * 100);
    return (ratio > MOTION_RATIO);
}

void setup() {
    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);
    pinMode(BUZZER_PIN, OUTPUT);
    digitalWrite(LED_PIN, LOW);
    digitalWrite(BUZZER_PIN, LOW);

    WiFi.begin(WIFI_SSID, WIFI_PASS);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nWiFi已连接");

    if (!initCamera()) {
        Serial.println("摄像头初始化失败,系统停止");
        while (1) delay(1000);
    }
    Serial.println("摄像头就绪,开始监控");
}

void loop() {
    if (detectMotion()) {
        Serial.println("检测到运动!触发报警");
        digitalWrite(LED_PIN, HIGH);
        digitalWrite(BUZZER_PIN, HIGH);
        sendAlarm();
        delay(ALARM_DURATION_MS);
        digitalWrite(LED_PIN, LOW);
        digitalWrite(BUZZER_PIN, LOW);
    }
    delay(200);
}

运行与验证

  1. 将代码中的 WIFI_SSID、WIFI_PASS 替换为实际WiFi信息;BARK_URL 中的 YOUR_BARK_KEY 替换为手机Bark应用分配的密钥。
  2. Arduino IDE中选择开发板 ESP32S3 Dev Module,开启 PSRAM(Tools → PSRAM → Enabled),选择对应串口后上传。
  3. 打开串口监视器(115200波特率),观察日志:
WiFi已连接
摄像头就绪,开始监控
差异像素占比: 0.012%
差异像素占比: 0.008%

静止场景下占比应长期低于0.5%。

  1. 在摄像头前挥手或走动,串口应输出:
差异像素占比: 3.245%
检测到运动!触发报警
推送成功,HTTP 200

同时LED点亮、蜂鸣器鸣响约10秒,手机Bark应用收到报警通知。

通过条件:静止时无报警;人为运动时LED/蜂鸣器动作且手机收到推送;串口日志中差异占比与报警信息对应。

常见问题

  • 摄像头初始化失败:检查FPC排线是否插紧,确认开发板PSRAM已开启。
  • 推送失败:确认Bark URL格式正确,手机与ESP32处于同一网络或ESP32可访问外网。
  • 误报频繁:适当提高 MOTION_RATIO(如0.01)或增大 DIFF_THRESHOLD。
  • 检测不到运动:降低 DIFF_THRESHOLD 至15,或缩短两帧间隔(减少delay至30ms)。