跳到主要内容ESP32-CAM 实时监控方案:从局域网到外网部署 | 极客日志C++AI算法
ESP32-CAM 实时监控方案:从局域网到外网部署
综述由AI生成ESP32-CAM 模块支持低成本物联网视频监控。演示了基于 Arduino IDE 配置开发环境,利用内置示例代码实现局域网视频流查看。针对外网访问需求,提供了自定义 TCP 客户端与服务端方案,通过 Python 接收并解码 ESP32 发送的 JPEG 数据流,实现公网远程监控。内容涵盖引脚定义、串口调试、烧录步骤及网络通信协议解析。
道系青年5 浏览 ESP32-CAM 实时视频监控实战
ESP32-CAM 是一款小巧的摄像头模组,支持独立工作,尺寸仅 2740.54.5mm。它集成了低功耗双核 32 位 CPU、PSRAM 以及 OV2640/OV7670 摄像头,非常适合家庭智能设备、工业无线控制及物联网监控场景。

硬件特性与接口
该模块主频高达 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 核心库,需手动添加。
- 打开 工具 > 开发板 > 开发板管理器。
- 搜索
ESP32 并安装 ESP32 Wrover Module 或相关包。
- 在 文件 > 首选项 中,将以下 URL 填入'附加开发板管理器网址':
https://dl.espressif.com/dl/package_esp32_index.json

局域网视频流查看
利用 ESP32 自带的 WebServer 功能,可以快速实现局域网内的视频预览。
调用示例代码
在 Arduino IDE 中选择 文件 > 示例 > ESP32 > Camera > CameraWebServer。这个示例已经封装好了相机初始化和 HTTP 服务逻辑。

关键参数修改
上传前需根据实际网络环境和硬件型号调整代码:
- WiFi 信息:修改
ssid 和 password 为你的路由器账号密码。
- :确保宏定义正确。对于常见的 AI-Thinker 版本,取消注释 。
摄像头型号
#define CAMERA_MODEL_AI_THINKER
#define CAMERA_MODEL_AI_THINKER
编译与烧录
点击左上角的编译验证按钮,成功后点击上传。上传完成后,按下开发板上的 RST 按键重启。
打开串口监视器(波特率 115200),查看输出的 IP 地址。在浏览器输入该 IP 即可看到实时画面。
完整代码参考
以下是经过格式化的标准示例代码,重点在于初始化配置和缓冲区管理:
#include "esp_camera.h"
#include <WiFi.h>
#define CAMERA_MODEL_AI_THINKER
#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;
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";
const IPAddress serverIP(192, 168, 1, 104);
uint16_t serverPort = 18080;
#define MAX_CACHE 1430
WiFiClient client;
#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()
总结
通过上述步骤,我们实现了从本地局域网到远程外网的视频流传输。局域网方案简单快捷,适合调试;外网方案则提供了更大的灵活性,但需要注意网络带宽和安全性。在实际应用中,建议根据具体需求调整分辨率和帧率以平衡画质与流畅度。
相关免费在线工具
- 加密/解密文本
使用加密算法(如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