跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++AI算法

ESP32-CAM 实时监控方案:从局域网到外网部署

ESP32-CAM 模块支持低成本物联网视频监控。演示了基于 Arduino IDE 配置开发环境,利用内置示例代码实现局域网视频流查看。针对外网访问需求,提供了自定义 TCP 客户端与服务端方案,通过 Python 接收并解码 ESP32 发送的 JPEG 数据流,实现公网远程监控。内容涵盖引脚定义、串口调试、烧录步骤及网络通信协议解析。

道系青年发布于 2026/3/26更新于 2026/6/1628 浏览
ESP32-CAM 实时监控方案:从局域网到外网部署

ESP32-CAM 实时视频监控实战

ESP32-CAM 是一款小巧的摄像头模组,支持独立工作,尺寸仅 2740.54.5mm。它集成了低功耗双核 32 位 CPU、PSRAM 以及 OV2640/OV7670 摄像头,非常适合家庭智能设备、工业无线控制及物联网监控场景。

ESP32-CAM 模块实物图

硬件特性与接口

该模块主频高达 240MHz,内置 520KB SRAM 并支持外置 8MB PSRAM。通信方面支持 UART/SPI/I2C/PWM/ADC/DAC 等丰富接口,内嵌 LwIP 和 FreeRTOS,支持 STA/AP/STA+AP 多种工作模式及 Smart Config 配网。

引脚定义图

开发环境搭建

安装 Arduino IDE

前往官网下载对应操作系统的 IDE 并安装。

配置 ESP32 开发板支持

Arduino IDE 默认不包含 ESP32 核心库,需手动添加。

  1. 打开 工具 > 开发板 > 开发板管理器。
  2. 搜索 ESP32 并安装 ESP32 Wrover Module 或相关包。
  3. 在 文件 > 首选项 中,将以下 URL 填入'附加开发板管理器网址':
    https://dl.espressif.com/dl/package_esp32_index.json
    

IDE 配置界面

局域网视频流查看

利用 ESP32 自带的 WebServer 功能,可以快速实现局域网内的视频预览。

调用示例代码

在 Arduino IDE 中选择 文件 > 示例 > ESP32 > Camera > CameraWebServer。这个示例已经封装好了相机初始化和 HTTP 服务逻辑。

示例代码路径

关键参数修改

上传前需根据实际网络环境和硬件型号调整代码:

  1. WiFi 信息:修改 ssid 和 password 为你的路由器账号密码。
  2. 摄像头型号:确保宏定义正确。对于常见的 AI-Thinker 版本,取消注释 #define CAMERA_MODEL_AI_THINKER。
// 选择摄像头模型
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
// #define CAMERA_MODEL_WROVER_KIT // Has PSRAM
// ... 其他型号注释掉

型号配置

编译与烧录

点击左上角的编译验证按钮,成功后点击上传。上传完成后,按下开发板上的 RST 按键重启。

打开串口监视器(波特率 115200),查看输出的 IP 地址。在浏览器输入该 IP 即可看到实时画面。

运行结果

浏览器预览

完整代码参考

以下是经过格式化的标准示例代码,重点在于初始化配置和缓冲区管理:

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

// 注意:UXGA 分辨率需要 PSRAM
// 确保选择了带有 PSRAM 的模块,如 ESP32 Wrover

// 选择摄像头型号
// #define CAMERA_MODEL_WROVER_KIT 
// #define CAMERA_MODEL_ESP_EYE 
// #define CAMERA_MODEL_M5STACK_PSRAM 
// #define CAMERA_MODEL_M5STACK_V2_PSRAM 
// #define CAMERA_MODEL_M5STACK_WIDE 
// #define CAMERA_MODEL_M5STACK_ESP32CAM 
#define CAMERA_MODEL_AI_THINKER
// #define CAMERA_MODEL_TTGO_T_JOURNAL 

#include "camera_pins.h"

const char* ssid = "TP-LINK_1760";
const char* password = "987654321";

void startCameraServer();

void setup() {
    Serial.begin(115200);
    Serial.setDebugOutput(true);
    Serial.println();

    camera_config_t config;
    config.ledc_channel = LEDC_CHANNEL_0;
    config.ledc_timer = LEDC_TIMER_0;
    config.pin_d0 = Y2_GPIO_NUM;
    config.pin_d1 = Y3_GPIO_NUM;
    config.pin_d2 = Y4_GPIO_NUM;
    config.pin_d3 = Y5_GPIO_NUM;
    config.pin_d4 = Y6_GPIO_NUM;
    config.pin_d5 = Y7_GPIO_NUM;
    config.pin_d6 = Y8_GPIO_NUM;
    config.pin_d7 = Y9_GPIO_NUM;
    config.pin_xclk = XCLK_GPIO_NUM;
    config.pin_pclk = PCLK_GPIO_NUM;
    config.pin_vsync = VSYNC_GPIO_NUM;
    config.pin_href = HREF_GPIO_NUM;
    config.pin_sscb_sda = SIOD_GPIO_NUM;
    config.pin_sscb_scl = SIOC_GPIO_NUM;
    config.pin_pwdn = PWDN_GPIO_NUM;
    config.pin_reset = RESET_GPIO_NUM;
    config.xclk_freq_hz = 20000000;
    config.pixel_format = PIXFORMAT_JPEG;

    // 如果检测到 PSRAM,使用高分辨率和大帧缓冲
    if (psramFound()) {
        config.frame_size = FRAMESIZE_UXGA;
        config.jpeg_quality = 10;
        config.fb_count = 2;
    } else {
        config.frame_size = FRAMESIZE_SVGA;
        config.jpeg_quality = 12;
        config.fb_count = 1;
    }

#if defined(CAMERA_MODEL_ESP_EYE)
    pinMode(13, INPUT_PULLUP);
    pinMode(14, INPUT_PULLUP);
#endif

    // 初始化相机
    esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed with error 0x%x", err);
        return;
    }

    sensor_t * s = esp_camera_sensor_get();
    // 部分传感器默认翻转且色彩饱和度高,需校正
    if (s->id.PID == OV3660_PID) {
        s->set_vflip(s, 1);
        s->set_brightness(s, 1);
        s->set_saturation(s, -2);
    }

    // 降低初始帧率以提高启动速度
    s->set_framesize(s, FRAMESIZE_QVGA);

#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
    s->set_vflip(s, 1);
    s->set_hmirror(s, 1);
#endif

    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected");

    startCameraServer();
    Serial.print("Camera Ready! Use 'http://");
    Serial.print(WiFi.localIP());
    Serial.println("' to connect");
}

void loop() {
    delay(10000);
}

外网视频流传输方案

若需在外网访问,简单的 WebServer 可能受限于 NAT 穿透。更灵活的方式是通过 TCP 协议将视频数据推送到公网服务器,由服务端接收并展示。

架构说明

  • ESP32 端:作为 TCP Client,负责采集图像分包发送。
  • 服务器端:运行 Python 程序,作为 TCP Server 接收数据流并解码显示。

ESP32 端代码逻辑

这段代码的核心在于 TCP 分包发送。JPEG 图片较大,直接发送容易丢包,因此我们设定了最大缓存大小(MTU),并在数据包前后加上标记(Frame Begin / Frame Over)以便服务端识别边界。

#include <Arduino.h>
#include <WiFi.h>
#include "esp_camera.h"
#include <vector>

const char* ssid = "TP-LINK_1760";
const char* password = "987654321";

// 目标服务器 IP 和端口
const IPAddress serverIP(192, 168, 1, 104);
uint16_t serverPort = 18080;

#define MAX_CACHE 1430
WiFiClient client;

// AI-Thinker 摄像头引脚定义
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22

static camera_config_t camera_config = {
    .pin_pwdn = PWDN_GPIO_NUM,
    .pin_reset = RESET_GPIO_NUM,
    .pin_xclk = XCLK_GPIO_NUM,
    .pin_sscb_sda = SIOD_GPIO_NUM,
    .pin_sscb_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_JPEG,
    .frame_size = FRAMESIZE_QVGA, // 根据带宽调整分辨率
    .jpeg_quality = 24,
    .fb_count = 1,
};

void wifi_init() {
    WiFi.mode(WIFI_STA);
    WiFi.setSleep(false); // 关闭休眠提高响应
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("WiFi Connected!");
    Serial.print("IP Address:");
    Serial.println(WiFi.localIP());
}

esp_err_t camera_init() {
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        Serial.println("Camera Init Failed");
        return err;
    }
    sensor_t * s = esp_camera_sensor_get();
    if (s->id.PID == OV2640_PID) {
        // 可选:校正翻转和亮度
    }
    Serial.println("Camera Init OK!");
    return ESP_OK;
}

void setup() {
    Serial.begin(115200);
    wifi_init();
    camera_init();
}

void loop() {
    Serial.println("Try To Connect TCP Server!");
    if (client.connect(serverIP, serverPort)) {
        Serial.println("Connect Tcp Server Success!");
        while (1) {
            camera_fb_t * fb = esp_camera_fb_get();
            uint8_t* temp = fb->buf; // 保存指针,防止内存泄漏

            if (!fb) {
                Serial.println("Camera Capture Failed");
            } else {
                client.print("Frame Begin"); // 起始标志
                int len = fb->len;
                int times = len / MAX_CACHE;
                int extra = len % MAX_CACHE;

                for (int j = 0; j < times; j++) {
                    client.write(fb->buf, MAX_CACHE);
                    fb->buf += MAX_CACHE;
                }
                if (extra > 0) {
                    client.write(fb->buf, extra);
                }
                client.print("Frame Over"); // 结束标志

                Serial.print("This Frame Length:");
                Serial.print(fb->len);
                Serial.println(". Succes To Send Image For TCP!");

                // 归还缓冲区供驱动复用
                fb->buf = temp;
                esp_camera_fb_return(fb);
            }
            delay(20);
        }
    } else {
        Serial.println("Connect To Tcp Server Failed! After 10 Seconds Try Again!");
        client.stop();
        delay(10000);
    }
}

服务器端 Python 程序

服务端使用 Socket 监听,接收到数据后通过 OpenCV 进行解码显示。关键点在于解析 Frame Begin 和 Frame Over 标记,重组完整的 JPEG 字节流。

import socket
import threading
import time
import numpy as np
import cv2

begin_data = b'Frame Begin'
end_data = b'Frame Over'

def handle_sock(sock, addr):
    temp_data = b''
    t1 = int(round(time.time() * 1000))
    
    while True:
        data = sock.recv(1430)
        if not data:
            break
            
        # 检测起始标志
        if data[0:len(begin_data)] == begin_data:
            # 去除起始标志
            data = data[len(begin_data):]
            
            # 循环接收直到遇到结束标志
            while data[-len(end_data):] != end_data:
                temp_data = temp_data + data
                data = sock.recv(1430)
            
            # 处理最后一包,去除结束标志
            temp_data = temp_data + data[0:(len(data)-len(end_data))]
            
            # 解码显示
            receive_data = np.frombuffer(temp_data, dtype='uint8')
            r_img = cv2.imdecode(receive_data, cv2.IMREAD_COLOR)
            
            t2 = int(round(time.time() * 1000))
            fps = 1000 // (t2 - t1)
            cv2.putText(r_img, "FPS: " + str(fps), (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
            cv2.imshow('server_frame', r_img)
            
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            
            t1 = t2
            print("接收到的数据包大小:" + str(len(temp_data)))
            temp_data = b''

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('192.168.1.104', 18080))
server.listen(5)
print('Server started on 192.168.1.104:18080')

while True:
    sock, addr = server.accept()
    print('Connect--{}'.format(addr))
    client_thread = threading.Thread(target=handle_sock, args=(sock, addr))
    client_thread.start()

总结

通过上述步骤,我们实现了从本地局域网到远程外网的视频流传输。局域网方案简单快捷,适合调试;外网方案则提供了更大的灵活性,但需要注意网络带宽和安全性。在实际应用中,建议根据具体需求调整分辨率和帧率以平衡画质与流畅度。

目录

  1. ESP32-CAM 实时视频监控实战
  2. 硬件特性与接口
  3. 开发环境搭建
  4. 安装 Arduino IDE
  5. 配置 ESP32 开发板支持
  6. 局域网视频流查看
  7. 调用示例代码
  8. 关键参数修改
  9. 编译与烧录
  10. 完整代码参考
  11. 外网视频流传输方案
  12. 架构说明
  13. ESP32 端代码逻辑
  14. 服务器端 Python 程序
  15. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ STL 源码解析:基于红黑树实现 map 和 set
  • Harness Engineering:给 AI 套上缰绳的工程学
  • ESP-SR 模型选型指南:如何为你的项目选择最佳语音模型
  • QWEN-AUDIO 语音合成支持 20+ 情感指令与多音色演绎
  • ESLint 从原理到实践:构建高质量 JavaScript/TypeScript 代码
  • ESLint 从原理到实践:构建高质量 JS/TS 代码
  • ESLint 从原理到实践:JavaScript/TypeScript 代码规范指南
  • Windows 本地部署闲鱼 AI 自动回复系统实战指南
  • ES6 模板字符串核心用法与特性详解
  • SVM 核心数据结构详解
  • ES6 扩展运算符(...)在对象与数组中的实战用法
  • Open WebUI:自托管 AI 平台功能特性与部署指南
  • Python 面向对象进阶:封装、继承、多态与元类详解
  • ROS2 MoveIt2 机械臂控制入门与实战
  • Midjourney官网地址是哪个?有没有中文官网?
  • 顺序表与链表详解:结构、实现与算法分析
  • OpenClaw 爆火分析:AI Agent 如何从技术圈走向大众场景
  • 前端现代化:从传统到现代的技术演进
  • Stable Diffusion 3.5 中文云端使用教程
  • OpenClaw Linux 部署指南:模型接入与飞书机器人配置

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online