跳到主要内容ESP32-CAM 实现实时视频监控(内网与外网方案) | 极客日志C++AI
ESP32-CAM 实现实时视频监控(内网与外网方案)
综述由AI生成ESP32-CAM 模块支持低成本视频监控方案。基于 Arduino IDE 的环境搭建步骤,演示了两种视频流获取方式:一是利用内置 CameraWebServer 示例实现局域网直接访问;二是通过自定义 TCP 协议配合 Python 服务端,实现跨公网的视频数据传输与实时显示。内容涵盖硬件选型、引脚配置、代码逻辑解析及烧录调试细节,适合物联网开发者快速上手。
217728380114 浏览 ESP32-CAM 实时视频监控实战
ESP32-CAM 是一款小巧的摄像头模组,支持独立工作。尺寸仅 2740.54.5mm,非常适合物联网场景,比如家庭监控、工业无线控制或定位系统。

核心特性
- 处理器:低功耗双核 32 位 CPU,主频 240MHz,运算能力达 600 DMIPS
- 存储:内置 520 KB SRAM,外置 8MB PSRAM
- 接口:支持 UART/SPI/I2C/PWM/ADC/DAC 等
- 摄像头:支持 OV2640/OV7670,内置闪光灯
- 网络:支持 STA/AP/STA+AP 模式,Smart Config/AirKiss 配网
- 系统:内嵌 LwIP 和 FreeRTOS

开发环境搭建
Arduino IDE 配置
首先下载并安装官方 Arduino IDE。由于 ESP32 不在默认库中,需要手动添加开发板管理器。
- 打开 Arduino IDE,进入 工具 > 开发板 > 开发板管理。搜索
ESP32 并安装 ESP32 Wrover Module。
- 在 文件 > 首选项 中,将以下 URL 填入'附加开发板管理器网址':
https://dl.espressif.com/dl/package_esp32_index.json

方案一:内网视频流查看
利用 ESP32 自带的 Web Server 功能,可以直接在内网浏览器访问视频流。
代码修改
在 Arduino IDE 中选择 文件 > 示例 > ESP32 > Camera > CameraWebServer。
主要修改两点:
- 替换为你的 WiFi SSID 和密码。
- 确认摄像头型号宏定义。对于常见的 AI Thinker 版本,需取消注释
#define CAMERA_MODEL_AI_THINKER。

完整代码
#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);
}
运行结果
编译上传后,按下开发板上的 RST 键重启。打开串口监视器查看输出的 IP 地址,在浏览器输入该地址即可看到实时画面。
方案二:外网视频流传输
若需在外网查看,需要将视频数据推送到服务器。这里采用 TCP 协议,ESP32 作为客户端发送,Python 脚本作为服务端接收并显示。
ESP32 端代码
这段代码负责采集图像并通过 TCP 分包发送给服务器。注意 maxcache 设置为 1430,这是为了避免 MTU 问题导致的数据包丢失。
#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 maxcache 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 leng = fb->len;
int timess = leng / maxcache;
int extra = leng % maxcache;
for (int j = 0; j < timess; j++) {
client.write(fb->buf, maxcache);
for (int i = 0; i < maxcache; i++) fb->buf++;
}
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 服务端代码
服务端监听指定端口,接收分包数据,重组为 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):len(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('Listening 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()
固件烧录指南
使用专用下载器
连接下载器与 ESP32-CAM,通过 USB 连接电脑。安装驱动后,在 Arduino IDE 中选择对应的开发板和串口号,点击编译验证,随后点击上传按钮。
使用 USB-TTL (CH340)
如果没有专用下载器,可以使用 USB-TTL 模块。接线方式如下:
- USB-TTL VCC -> ESP32-CAM 5V
- USB-TTL GND -> ESP32-CAM GND
- USB-TTL RXD -> ESP32-CAM TXD
- USB-TTL TXD -> ESP32-CAM RXD
注意:下载时需将 GPIO1 拉低到 GND 以进入下载模式。
相关免费在线工具
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online