← 返回实战项目

AI 摄像头:ESP32-CAM 抓图 + 端侧识别 + 推送手机

最后更新 2026-07-01
⏱ 约 16 分钟 🟢 软件/低风险
你将学到
  • 做出一台能抓图、在板上做简单识别(运动/颜色/人脸检测)、有情况就把画面推到手机或服务器的 AI 摄像头
  • 用可跑的 ESP-IDF esp_camera 代码走通「初始化摄像头 → 抓一帧 → 处理这帧」的完整链路,而不是只刷官方网页示例
  • 认清 ESP32-CAM 的三个成品级命门:PSRAM 必需、IO0 下载模式、摄像头排线,把它们在接线阶段一次躲干净
  • 想清端侧能做什么(运动/颜色/人脸检测)、做不到什么(认出是谁、是什么),复杂识别怎么往边缘 AI 或云端接
🛒 器材清单
器材数量参考
ESP32-CAM 开发板(AI-Thinker,板载 OV2640 摄像头 + PSRAM)1约 20-40 元(以商城实际为准)
USB 转 TTL 下载器(FTDI / CP2102,3.3V/5V 可切)1约 5-12 元(以商城实际为准)
杜邦线(母对母若干)1 套约 3-8 元(以商城实际为准)

价格随渠道波动,以购买页实时为准。

桌上放一块比火柴盒还小的板子,一头一颗黄豆大的摄像头对着门口。你人在公司,没人动它时它悄悄省电;一旦画面里有东西动了,它抓下那一帧,"叮"地推到你手机上——门口来人了。整套物料,不到五十块钱。

这就是本项目要带你做出来的:一台会"看"、还会"判断该不该报"的 AI 摄像头。 它不是让你再刷一遍官方网页直播示例——那个 ESP32-CAM 入门已带你跑通过,你知道它能出画面了。project 这一层要往前推一大步:自己写代码抓帧、在板上做点简单识别、有情况才推出去,串成一件能挂门口天天用的成品。

这一篇不重复讲 ESP32-CAM 是什么、三个坑为什么坑——那些 l4-esp32cam 讲透了,这里默认你跑通过官方画面。我们只干一件事:把摄像头接进你自己的 ESP-IDF 代码里,让它从"能被看"变成"会自己看、自己判断"。


第一步:想清成品长什么样,再定选型

动手前把"成品行为"钉死,选型才有依据。我们的 AI 摄像头就三个行为:

  • 抓帧:设备能主动拍下当前画面,拿到一块可处理的图像内存,而不是被动等浏览器来看。
  • 识别:板上对这帧做轻量判断——最典型的是运动检测(前后两帧差异大就是"有动静"),也可以是找某种颜色、或简单人脸检测。
  • 告警/推送:判断出"有情况",就把这一帧或一条消息推到手机 / 服务器;没情况就安静省电。

行为定了,选型每一步就都有理由。

ESP32-CAM 还是 S3 带摄像头板?

这是本项目唯一要做的选择题,答案取决于你的识别野心:

ESP32-CAM(AI-Thinker) ESP32-S3 摄像头板(如 S3-EYE 类)
芯片算力 原始 ESP32,双核 240MHz S3,带向量指令,跑轻量神经网络快得多
PSRAM 板载约 4MB,够抓图 通常 8MB,跑模型更宽裕
端侧 AI 只适合运动/颜色/简单人脸检测 能跑量化后的小模型(人脸识别、简单分类)
价格 更便宜,二十几块 贵一截
供电脾气 娇气,推流易 Brownout 相对稳,多带正规 USB 口

一句话决策:先做运动/颜色告警这类"察觉变化"的项目,ESP32-CAM 足够、也最省钱,本文主线就用它;想在板上真跑识别模型(认人脸、分类物体),直接上 S3 摄像头板。 两者的 esp_camera 代码几乎一模一样,只差引脚定义那张表,换板迁移几乎零成本。

顺带说清一件常被搞混的事:AI-Thinker ESP32-CAM 用的是最原始的 ESP32 芯片(不是 ESP32-S3),没有 S3 那套加速轻量神经网络的向量指令。所以本文主线代码写成跨芯片通用——ESP32、ESP32-S3 都能跑,只在换板时改那张引脚表;真要跑端侧模型时 S3 的算力优势才显出来。

为什么必须是带 PSRAM 的板子

这是 ESP32-CAM 成品级的第一条命门,比选脚还重要:一帧图像太大,芯片内置那点 RAM 装不下。 一张 QVGA(320×240)的 JPEG 就要几十 KB,稍高分辨率上百 KB,而 ESP32 内置 SRAM 拢共几百 KB 还要留给系统和 Wi-Fi。所以帧缓冲必须放进 PSRAM(板载的那颗外部大内存)。

好在 AI-Thinker 这类标准 ESP32-CAM 都自带 PSRAM,你只要在代码里把帧缓冲位置指定成 CAMERA_FB_IN_PSRAM(下面代码里有)。买板时务必确认"带 PSRAM"——极少数阉割版没有,那种只能出最低分辨率,稍微一调高就初始化失败,是新手最冤的翻车点。

下载器:为什么要单独买

l4-esp32cam 讲过,最常见的 ESP32-CAM 没有 USB 口,一排光秃秃的引脚。要刷代码,得靠一个 USB 转 TTL 下载器(FTDI 或 CP2102 都行)在电脑和板子之间转一道。买的时候认准一点:电压能切到能提供 5V——ESP32-CAM 推流瞬间电流不小,下载器供电偏弱是它反复重启的头号原因。


第二步:接线——排线、下载模式、供电三关

ESP32-CAM 的接线和你之前做台灯、环境站不一样:它没面包板那套自由发挥,就固定这几根线,但一根都不能错

摄像头排线:多半已焊好,别去乱抠

OV2640 摄像头通过一条软排线(FPC)连到板上的座子。绝大多数 ESP32-CAM 出厂就插好了,你不需要动它。真要注意的只有:如果你收到货是分开的、或者调试中出现初始化失败,翻开排线座的黑色卡扣,把排线蓝色面朝向卡扣、金属触点朝下平推到底,再压回卡扣。排线插反、没插到底、金属面朝错,都会让摄像头初始化失败——这是花屏和 Camera init failed 的一大来源。

下载器接线:TX/RX 交叉,别接反

下载器和 ESP32-CAM 就这几根线:

USB 转 TTL 下载器            ESP32-CAM
   5V   ─────────────────────  5V
   GND  ─────────────────────  GND
   TX   ─────────────────────  U0R (RX)   ← 交叉
   RX   ─────────────────────  U0T (TX)   ← 交叉

下载模式(烧录时才接):
   IO0  ─────────────────────  GND
  • 供电走 5V:ESP32-CAM 的 5V 脚接下载器 5V。它板载有稳压,别去接 3.3V 脚硬灌,容易供不上。
  • TX/RX 必须交叉:下载器的 TX 接板子的 U0R(接收),下载器的 RX 接板子的 U0T(发送)。一方的"发"接另一方的"收",接反了刷不进也没日志——这是仅次于供电的高频翻车点。
🚧 避坑

烧录前把 IO0 接到 GND,进下载模式;上电(或按 RST)后板子才等着你烧。烧完,把 IO0 和 GND 断开,再按一下 RST,它才会去跑你刚烧的程序。很多人"刷不进"或"刷进去却不动",就是这根线没处理对——烧时接上、跑时拔掉。这跟 l4-esp32cam 里说的完全一致,只是这次你会反复烧好几版代码,每次都别忘了这个动作。

供电这一关,决定稳不稳

摄像头一开工、Wi-Fi 一推数据,电流会冲高。供电稍弱,板子就在启动或推流瞬间突然重启,串口刷 Brownout detector was triggered——看着像程序崩了,其实是电不够。对策:用能给 5V 的下载器、换一根短而粗的好 USB 线;实在压不住就单独给板子一路稳的 5V。供电这关过不去,下面代码写得再对也跑不稳,先解决它。


第三步:分步把 ESP-IDF 代码写出来

我们不一次甩一大段,而是分三步长出来,每步都能单独烧进去看效果——出问题你才知道是哪步坏的。

前置:把官方 esp32-camera 组件加进工程。在工程目录下用组件管理器拉一次即可:

idf.py add-dependency "espressif/esp32-camera"

它会把 esp_camera.h 那套接口拉进来,下面代码就是围着它写的。

步 1:初始化摄像头,抓下第一帧

第一步只验证一件事:摄像头配对了、PSRAM 用上了、能拿到一帧数据。 这段是整个项目的地基,把 AI-Thinker 的引脚表、时钟、帧缓冲位置一次配好:

#include "esp_camera.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char *TAG = "ai-cam";

// AI-Thinker ESP32-CAM 的引脚定义(换 S3 板就换这一整张表,代码逻辑不动)
#define PWDN_GPIO   32
#define RESET_GPIO  -1
#define XCLK_GPIO    0
#define SIOD_GPIO   26   // SCCB 数据
#define SIOC_GPIO   27   // SCCB 时钟
#define Y9_GPIO     35
#define Y8_GPIO     34
#define Y7_GPIO     39
#define Y6_GPIO     36
#define Y5_GPIO     21
#define Y4_GPIO     19
#define Y3_GPIO     18
#define Y2_GPIO      5
#define VSYNC_GPIO  25
#define HREF_GPIO   23
#define PCLK_GPIO   22

static esp_err_t camera_init(void)
{
    camera_config_t config = {
        .pin_pwdn     = PWDN_GPIO,
        .pin_reset    = RESET_GPIO,
        .pin_xclk     = XCLK_GPIO,
        .pin_sccb_sda = SIOD_GPIO,
        .pin_sccb_scl = SIOC_GPIO,
        .pin_d7 = Y9_GPIO, .pin_d6 = Y8_GPIO, .pin_d5 = Y7_GPIO, .pin_d4 = Y6_GPIO,
        .pin_d3 = Y5_GPIO, .pin_d2 = Y4_GPIO, .pin_d1 = Y3_GPIO, .pin_d0 = Y2_GPIO,
        .pin_vsync = VSYNC_GPIO, .pin_href = HREF_GPIO, .pin_pclk = PCLK_GPIO,

        .xclk_freq_hz = 20000000,          // 20MHz 时钟,标准值
        .ledc_timer   = LEDC_TIMER_0,      // 摄像头时钟借用 LEDC 生成
        .ledc_channel = LEDC_CHANNEL_0,

        .pixel_format = PIXFORMAT_JPEG,    // 抓 JPEG,省内存、好传
        .frame_size   = FRAMESIZE_QVGA,    // 320x240,入门先用小的稳
        .jpeg_quality = 12,                // 数字越小越清晰、越占内存,10~15 之间挑
        .fb_count     = 2,                 // 双缓冲,抓帧更顺
        .fb_location  = CAMERA_FB_IN_PSRAM, // 帧缓冲放 PSRAM——命门,没它必崩
        .grab_mode    = CAMERA_GRAB_WHEN_EMPTY,
    };

    esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "摄像头初始化失败 0x%x:查排线/PSRAM/供电", err);
    }
    return err;
}

void app_main(void)
{
    if (camera_init() != ESP_OK) return;

    // 抓一帧看看
    camera_fb_t *fb = esp_camera_fb_get();     // 拿到一帧(frame buffer)
    if (!fb) {
        ESP_LOGE(TAG, "抓帧失败:多半是内存不足或摄像头没就绪");
        return;
    }
    ESP_LOGI(TAG, "抓到一帧!格式=%d 宽=%d 高=%d 大小=%u 字节",
             fb->format, fb->width, fb->height, (unsigned)fb->len);
    esp_camera_fb_return(fb);                   // 用完必须归还,否则下一帧没缓冲
}

烧进去 idf.py build flash monitor(别忘了烧前 IO0 接 GND、烧后拔掉再复位)。你应该看到串口打出一行 抓到一帧!... 大小=xxxxx 字节,大小通常几 KB 到几十 KB。看到这个,说明摄像头引脚、PSRAM、供电三件事全对——这一步的意义就是把最容易崩的地基先隔离验证掉。

esp_camera_fb_get() 拿帧、esp_camera_fb_return(fb) 还帧,必须成对。忘了 return,缓冲池很快被占满,下一次 get 直接返回 NULL、程序就"卡"在抓不到帧——这是自己写摄像头代码最常见的一个坑,比台灯那个"忘了 update_duty"还隐蔽。从项目一开始就把 get/return 当一对来写。

步 2:做最实用的端侧识别——运动检测

有了稳定抓帧,加"识别"。端侧算力有限,l4-esp32cam 讲过它的边界:能做运动/颜色/简单人脸检测,认不出"是谁、是什么"。 这里选最实用、最省算力的运动检测——不需要"认识"任何东西,纯比前后两帧的差异。

思路:JPEG 不好直接逐像素比,所以这一步把像素格式换成灰度、分辨率压到很小(比如 96×96),拿两帧灰度图逐点相减、差异超阈值的点够多,就判定"动了"。注意:做识别时把 pixel_format 改成 PIXFORMAT_GRAYSCALEframe_size 设小(识别不需要高清,小图省内存又快):

// 把步 1 的 config 改两处:
//   .pixel_format = PIXFORMAT_GRAYSCALE,   // 灰度,一个像素一字节,好比对
//   .frame_size   = FRAMESIZE_96X96,       // 96x96,运动检测够用

#define MOTION_PIX_THRESH  40    // 单个像素亮度差超过它,算"这个点变了"
#define MOTION_CNT_THRESH  200   // 变了的点超过这么多,算"画面动了"

static uint8_t *g_prev = NULL;   // 上一帧灰度图,放堆上
static size_t   g_prev_len = 0;

// 返回 true 表示检测到运动
static bool detect_motion(camera_fb_t *fb)
{
    if (g_prev == NULL || g_prev_len != fb->len) {   // 第一帧或尺寸变了,先存下当基准
        free(g_prev);
        g_prev = malloc(fb->len);
        if (!g_prev) return false;
        memcpy(g_prev, fb->buf, fb->len);
        g_prev_len = fb->len;
        return false;                                 // 第一帧没得比,不算动
    }

    int changed = 0;
    for (size_t i = 0; i < fb->len; i++) {
        int diff = (int)fb->buf[i] - (int)g_prev[i];
        if (diff < 0) diff = -diff;
        if (diff > MOTION_PIX_THRESH) changed++;
    }
    memcpy(g_prev, fb->buf, fb->len);                 // 更新基准帧

    return changed > MOTION_CNT_THRESH;
}

void app_main(void)
{
    if (camera_init() != ESP_OK) return;

    while (1) {
        camera_fb_t *fb = esp_camera_fb_get();
        if (fb) {
            if (detect_motion(fb)) {
                ESP_LOGW(TAG, ">>> 检测到运动!<<<");
            }
            esp_camera_fb_return(fb);      // 无论动没动,帧都要还
        }
        vTaskDelay(pdMS_TO_TICKS(200));    // 每 200ms 看一眼,5 帧/秒够用还省电
    }
}

你应该看到:画面静止时串口安静;你手在镜头前一晃,立刻刷出 >>> 检测到运动!<<<。两个阈值调灵敏度——MOTION_PIX_THRESH 调低更敏感(连光线微变都报),MOTION_CNT_THRESH 调低更容易触发。屋里光照抖动老误报,就把这两个都往上抬一点。

想做颜色检测(比如"画面里出现红色物体就触发"),把格式换成 PIXFORMAT_RGB565,逐像素判断 R 分量是否明显大于 G/B,思路和这里一模一样,只是比的从"前后帧差异"变成"当前帧里某颜色的像素数"。简单人脸检测要接官方的人脸检测组件,那是把小模型跑在板上,S3 板更从容——想走这条深路,看把 AI 跑在板子上

步 3:有情况才推——把画面/告警送到手机

最后一步,把"识别到了"变成"你手机能收到":检测到运动,就把这一帧 JPEG POST 到你的服务器 / 消息推送接口(自建告警服务,或任意能收图的 HTTP 端点)。

先把一件容易埋雷的事讲清:运动检测和推图,用的是两种不同的像素格式,不能图省事拿一路数据既检测又推送。 步 2 的 detect_motion逐字节比灰度像素——它只对 PIXFORMAT_GRAYSCALE 的原始像素有意义;而推给手机存证要的是清晰 JPEG。JPEG 是压缩后的字节流,逐字节相减不对应任何像素,拿它跑 detect_motion 等于白检测。

所以正确做法是检测归检测、推送归推送,两种格式各抓各的

  • 运动检测:仍沿用步 2 的配置——PIXFORMAT_GRAYSCALE + 低分辨率(96×96),跑 detect_motion。这一路省算力、判断快。
  • 推图存证:检测到运动后,要一张清晰 JPEG,就得单独抓一帧 JPEG——不能拿检测用的那帧灰度小图直接推。

板上摄像头同一时刻只有一个像素格式,做不到"一帧同时是灰度又是 JPEG"。想在检测到运动后推清晰 JPEG,实际有两条路:(A)切换像素格式重抓一帧——esp_camera_deinit() 后用 JPEG 配置 esp_camera_init() 重新初始化再抓,代价是切换有开销、这一瞬间检测会短暂中断;(B)用两台/两路缓冲——高阶玩法,硬件和内存都更吃紧。入门先走 A:平时用灰度小图连续检测,一旦判定"动了",切到 JPEG 抓一帧推出去,推完再切回灰度继续盯。 下面代码演示这条 A 路。

先看 POST 那段(它只管把一帧 JPEG 发出去,跟检测无关):

#include "esp_http_client.h"

#define ALERT_URL  "http://你的服务器IP:端口/alert"   // 收图的端点,换成你自己的

// 把一帧 JPEG 通过 HTTP POST 发出去
static void push_frame(camera_fb_t *fb)
{
    esp_http_client_config_t cfg = {
        .url    = ALERT_URL,
        .method = HTTP_METHOD_POST,
        .timeout_ms = 8000,
    };
    esp_http_client_handle_t client = esp_http_client_init(&cfg);
    esp_http_client_set_header(client, "Content-Type", "image/jpeg");
    esp_http_client_set_post_field(client, (const char *)fb->buf, fb->len);

    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "已推送告警图,服务器返回 %d",
                 esp_http_client_get_status_code(client));
    } else {
        ESP_LOGE(TAG, "推送失败:%s", esp_err_to_name(err));
    }
    esp_http_client_cleanup(client);
}

主循环把两路串起来:平时用灰度小图连续检测;一旦判定"动了"、且过了冷却期,就切到 JPEG 抓一帧推出去,推完切回灰度继续盯。 冷却是必须的——不然人一直在镜头前,它一秒推十张,把你手机和服务器都刷爆。切格式用两个小助手包住 deinit + init,读起来清楚:

#include "esp_timer.h"

#define ALERT_COOLDOWN_MS  10000   // 两次告警至少隔 10 秒

// 用灰度 96x96 初始化——检测模式(复用步 1 的引脚,只改这两项)
static esp_err_t camera_init_gray(void)
{
    camera_config_t config = { /* …引脚同步 1… */
        .pixel_format = PIXFORMAT_GRAYSCALE,
        .frame_size   = FRAMESIZE_96X96,
        .fb_count     = 2,
        .fb_location  = CAMERA_FB_IN_PSRAM,
        .grab_mode    = CAMERA_GRAB_WHEN_EMPTY,
        /* xclk / ledc 等同步 1 */
    };
    return esp_camera_init(&config);
}

// 用 JPEG QVGA 初始化——推图模式
static esp_err_t camera_init_jpeg(void)
{
    camera_config_t config = { /* …引脚同步 1… */
        .pixel_format = PIXFORMAT_JPEG,
        .frame_size   = FRAMESIZE_QVGA,
        .jpeg_quality = 12,
        .fb_count     = 2,
        .fb_location  = CAMERA_FB_IN_PSRAM,
        .grab_mode    = CAMERA_GRAB_WHEN_EMPTY,
        /* xclk / ledc 等同步 1 */
    };
    return esp_camera_init(&config);
}

// 检测到运动时:切 JPEG → 抓一帧清晰图推出去 → 切回灰度继续检测
static void snap_jpeg_and_push(void)
{
    esp_camera_deinit();                       // 先卸掉灰度那套
    if (camera_init_jpeg() != ESP_OK) {        // 换成 JPEG 配置
        ESP_LOGE(TAG, "切 JPEG 失败,跳过这次推送");
        camera_init_gray();                    // 尽量切回去别卡死
        return;
    }
    // 切格式后头一两帧常是脏的,丢掉再取一张干净的
    camera_fb_t *warm = esp_camera_fb_get();
    if (warm) esp_camera_fb_return(warm);

    camera_fb_t *jpg = esp_camera_fb_get();
    if (jpg) {
        push_frame(jpg);                       // 推的是真 JPEG,不是灰度差分那路
        esp_camera_fb_return(jpg);
    }
    esp_camera_deinit();                        // 推完卸掉 JPEG
    camera_init_gray();                         // 切回灰度,继续盯
}

void app_main(void)
{
    // 联网部分照搬 l3-wifi:wifi_init_sta() 连上路由器后再进循环
    if (camera_init_gray() != ESP_OK) return;   // 开机先进检测模式(灰度)

    int64_t last_alert = -(int64_t)ALERT_COOLDOWN_MS * 1000;   // 微秒
    while (1) {
        camera_fb_t *fb = esp_camera_fb_get();  // 这是灰度小图
        if (fb) {
            bool moved = detect_motion(fb);     // 只对灰度像素做差分,成立
            esp_camera_fb_return(fb);           // 灰度帧用完先还,别占着去切格式

            int64_t now = esp_timer_get_time();
            if (moved && (now - last_alert) > (int64_t)ALERT_COOLDOWN_MS * 1000) {
                ESP_LOGW(TAG, ">>> 有情况,切 JPEG 抓一帧推送 <<<");
                snap_jpeg_and_push();           // 切 JPEG → 推 → 切回灰度
                last_alert = now;
            }
        }
        vTaskDelay(pdMS_TO_TICKS(200));         // 每 200ms 看一眼,5 帧/秒够用还省电
    }
}

你应该看到:镜头前有动静 → 串口打 有情况,切 JPEG 抓一帧推送 并显示服务器返回码 → 你服务器 / 手机端收到那张清晰 JPEG(不是灰度差分用的那张小图)。冷却生效时,连续晃动只会隔 10 秒推一次,不再刷屏。

切格式那一下有开销(deinit + init 大概几十到上百毫秒),这期间检测会短暂停一下——对"门口有人动一下"这类告警完全够用,别为了追帧率把它复杂化。要真做到检测不中断,得上双路缓冲那套高阶玩法,等手感稳了再啃。关键是记住:检测那路永远是灰度、推送那路永远是 JPEG,别让一路数据串岗。

联网这块(wifi_init_sta、事件组、证书)一个字没重复讲,因为它和连上 Wi-Fi 一模一样;把图 POST 上去、内存/超时更敏感这件事,l4-esp32cam 讲透了——图比文字大得多,POST 时的内存和上传耗时会更突出,Key 别硬编码进代码,那节的注意事项这里一字不差地适用。到这里,你没写多少全新的东西:抓帧是 esp_camera 的、联网是 l3-wifi 的、POST 是 esp_http_client 的,你干的是把"抓帧 → 识别 → 冷却 → 推送"这四件事组织成一条自主链路——这个"组织",就是 project 比 guide 多出来的那层功夫。


第四步:调试——对不上就查这张表

分步烧的好处是,哪步崩你已经缩小了范围。真出岔子,照这张表从"电和排线"往"逻辑"查:

现象 最可能的原因 怎么办
串口刷 Brownout detector was triggered、反复重启 供电不足 换能给 5V 的下载器、换短而粗的好 USB 线;先过供电这关
摄像头初始化失败 0x… / Camera init failed 排线没插好 / PSRAM 缺失 / 引脚表选错板 重插摄像头排线(蓝面朝卡扣、插到底);确认买的是带 PSRAM 的板;核对引脚表对应你这款
初始化过了但抓帧总返回 NULL 忘了 fb_return,缓冲被占满;或分辨率太高内存不够 确认每次 get 后都有 return;把 frame_size 调小、fb_count 降到 1 试试
画面/推出去的图花屏、雪花 供电不稳;jpeg_quality 太激进;排线接触不良 先稳供电;jpeg_quality 数字往大调(如 15);重插排线
编译报 esp_camera.h: No such file 没加 esp32-camera 组件 idf.py add-dependency "espressif/esp32-camera" 再编
刷不进 / 一连 Failed to connect 没进下载模式,或 TX/RX 接反 IO0 接 GND 后再上电;检查 TX 接 U0R、RX 接 U0T(交叉)
刷进去了但不跑你的程序 烧完没断开 IO0 拔掉 IO0 与 GND 的连线,按一下 RST
运动检测一直误报 阈值太敏感 / 环境光在抖 抬高 MOTION_PIX_THRESHMOTION_CNT_THRESH;避开忽明忽暗的光源
触发了但推送失败 URL/端口不对、没联网、服务器没起 核对 ALERT_URL;确认板子连上 Wi-Fi;先用能收 POST 的测试端点验证
一推图就重启 推流瞬间电流冲高,供电扛不住 还是供电——加强 5V,或把分辨率/质量降一档
💡 提示

排查总原则:先怀疑供电,再查排线和下载模式,最后才看代码。 ESP32-CAM 一多半的"灵异问题"根子都在电不够和排线没插好。一次只改一处再烧,同时改三个地方还是不对,你根本分不清是哪个改动的锅。


第五步:从"能报警的 demo"做成"真管用的东西"

到这它已经是台能自主抓图、识别、告警的摄像头了。但"能用"和"真管用"之间还差几步——正好通向后面的阶梯,先给你指条路。

让识别更聪明:接边缘 AI 模型

运动检测只会说"有东西动了",不会说"动的是人还是猫还是被风吹动的窗帘"。想让板子自己认出"是不是人",得把一个量化后的小检测模型跑在板上——这就是边缘 AI。ESP32-CAM 算力紧,跑得吃力;S3 摄像头板带向量指令,跑量化小模型从容得多。这条路怎么走、模型怎么塞进这颗小芯片,看把 AI 跑在板子上的边缘智能——它讲清了端侧模型的门槛和取舍。

让判断更准:把图甩给云端理解

要认出"这是张三""画面里有个包裹""有人翻墙"这种复杂内容,端侧扛不住,思路和硬件调大模型一样:板子只管省着拍、按需传,理解交给云端多模态大模型。 运动触发后把那帧 JPEG POST 给云端视觉接口,拿回一段文字结果再决定报不报——你在步 3 已经把"POST 一帧图上去"这条链路打通了,换个接收端就是它。完整的压缩、流式上传、成本控制属于更深的话题,L4 这一级往下走有体系化的实战。

做成真正的成品

面包板加飞线只能算原型。想挂门口天天用甚至送人,得走产品化:稳定 5V 供电(别再靠下载器吊着)、电路挪到洞洞板或小 PCB、配个带镜头孔的外壳、固件量产烧录。这些是更后面阶梯的活儿,多做几个项目、手感稳了再来啃——这台 AI 摄像头正是做第一件"带镜头实体产品"的好对象。


小结 · 你做出了什么、下一步去哪

  • 你做出了一台会自己抓帧、在板上做运动检测、有情况才把画面推到手机/服务器的 AI 摄像头,从选型、排线/下载模式/供电三关接线、可跑的 ESP-IDF esp_camera 代码到花屏/初始化失败/内存排查,走完了一件成品的全流程。
  • 你第一次把硬件的"眼睛"接进自己的代码,用 esp_camera_fb_get / return 抓帧、灰度差分做端侧识别、冷却 + esp_http_client 推送,把"抓帧 → 识别 → 判断 → 推送"组织成一条自主链路——这个"组织零件"的功夫,就是 project 比 guide 多出来的那层。
  • 你摸清了几个成品级命门:帧缓冲必须放 PSRAM、get/return 必须成对、识别用小灰度图省算力、告警要加冷却别刷屏、图比文字大所以内存和上传更敏感。

下一步:想让它认得出"是什么",看把 AI 跑在板子上的边缘智能;想把摄像头和其他传感器揉一起做决策,回 L4 这一级往下走。回看全部实战项目见项目总览。这台会看又会判断的小摄像头,是你从"控制引脚"跨到"感知世界"最有分量的一件成品——留着它。

📄 来源 / 自校链接

本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明