ESP32-S3入门第九天:摄像头入门与应用
ESP32-S3 入门第九天:摄像头模块使用与图像采集
- 一、今日目标
- 二、摄像头模块选型与硬件基础
- 1. 兼容的摄像头模块
- 2. ESP32-S3 摄像头接口特性
- 三、硬件连接(以 OV2640 为例)
- 1. 引脚定义与接线图
- 2. 接线注意事项
- 四、开发环境配置
- 1. 安装摄像头库
- 2. 开发板配置
- 五、基础实验:摄像头初始化与图像采集
- 1. 测试摄像头是否正常工作
- 2. 实验现象
- 六、进阶实验:Web 服务器实时预览
- 1. 实验原理
- 2. 代码实现
- 3. 实验操作与现象
- 七、实战项目:图像捕获与 UART 传输
- 1. 项目功能
- 2. 硬件新增组件
- 3. 核心代码片段
- 4. 上位机接收
- 八、常见问题与解决方案
- 九、第九天总结与第十天预告
- 1. 今日成果
- 2. 第十天预告:图像识别基础
一、今日目标
- 了解 ESP32-S3 支持的摄像头模块及硬件特性
- 掌握摄像头模块的接线方法与驱动配置
- 实现基础图像采集、本地预览与 WiFi 传输功能
- 结合往期知识(WiFi/UART)完成图像数据应用
二、摄像头模块选型与硬件基础
1. 兼容的摄像头模块
ESP32-S3 凭借强大的处理能力和专用 DVP 接口,支持多种摄像头模块:
型号 | 分辨率 | 接口 | 优势 | 适合场景 |
---|---|---|---|---|
OV2640 | 200 万像素(1600×1200) | DVP | 性价比高、功耗低 | 基础图像采集 |
OV5640 | 500 万像素(2592×1944) | DVP | 高分辨率 | 细节拍摄 |
GC0308 | 30 万像素(640×480) | DVP | 体积小、帧率高 | 实时监控 |
推荐新手首选: OV2640
驱动成熟、资料丰富、价格低廉(约 20 元),且对 ESP32-S3 兼容性最佳。
2. ESP32-S3 摄像头接口特性
- 专用 DVP(Digital Video Port)图像接口
- 支持 8/16 位数据宽度
- 最高支持 500 万像素摄像头
- 内置 JPEG 编码器,可直接输出压缩图像(节省带宽)
- 需占用较多 GPIO(10-12 个),需避免与其他外设冲突
三、硬件连接(以 OV2640 为例)
1. 引脚定义与接线图
OV2640 与 ESP32-S3 的 DVP 接口连接(需严格对应):
OV2640 引脚 | 功能 | ESP32-S3 引脚 | 备注 |
---|---|---|---|
VCC | 电源 | 3.3V | 禁止接 5V,会烧毁模块 |
GND | 接地 | GND | 必须与开发板共地 |
SCL | I2C 时钟 | GPIO22 | 用于配置摄像头参数 |
SDA | I2C 数据 | GPIO21 | 用于配置摄像头参数 |
XCLK | 系统时钟 | GPIO15 | 摄像头工作时钟输入 |
PCLK | 像素时钟 | GPIO12 | 像素数据同步时钟 |
VSYS | 场同步 | GPIO14 | 帧同步信号 |
HREF | 行同步 | GPIO27 | 行同步信号 |
D0-D7 | 8 位数据 | GPIO35-39, 41-42 | 图像数据总线(共 8 根) |
RST | 复位 | GPIO13 | 低电平复位(可选接) |
PWDN | 电源使能 | GPIO16 | 低电平使能(可选接) |
2. 接线注意事项
- 摄像头模块需 3.3V 稳定供电,建议外接电源(若开发板 USB 供电不足)
- 数据排线尽量短(<10cm),避免信号干扰导致图像花屏
- 确保 D0-D7 数据线顺序正确,接错会导致图像错乱
- 先焊接排针再接线,避免引脚接触不良
四、开发环境配置
1. 安装摄像头库
- 打开 Arduino IDE,点击「工具」→「管理库」
- 搜索「ESP32 Camera」,安装「ESP32 Camera by Espressif Systems」
- 安装依赖库「ESP32」(确保版本≥2.0.0)
2. 开发板配置
在「工具」菜单中正确配置:
- 开发板:ESP32S3 Dev Module
- Camera Model:OV2640(根据实际模块选择)
- PSRAM:Enabled(必须启用,用于图像缓存)
- Flash Size:根据开发板选择(≥4MB)
- USB Mode:USB-OTG (HID + MSC + CDC)
五、基础实验:摄像头初始化与图像采集
1. 测试摄像头是否正常工作
#include "esp_camera.h"
#include <WiFi.h>// WiFi配置(用于后续图像传输)
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";// OV2640摄像头引脚配置
#define PWDN_GPIO_NUM 16
#define RESET_GPIO_NUM 13
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 21
#define SIOC_GPIO_NUM 22
#define Y9_GPIO_NUM 39
#define Y8_GPIO_NUM 35
#define Y7_GPIO_NUM 34
#define Y6_GPIO_NUM 5
#define Y5_GPIO_NUM 36
#define Y4_GPIO_NUM 37
#define Y3_GPIO_NUM 38
#define Y2_GPIO_NUM 42
#define VSYNC_GPIO_NUM 14
#define HREF_GPIO_NUM 27
#define PCLK_GPIO_NUM 12void 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; // 20MHz时钟config.pixel_format = PIXFORMAT_JPEG; // 输出JPEG格式// 根据PSRAM大小配置分辨率if(psramFound()){config.frame_size = FRAMESIZE_VGA; // 640x480(带PSRAM可支持)config.jpeg_quality = 12; // 0-63,数字越小质量越高config.fb_count = 2; // 双缓冲(提高帧率)} else {config.frame_size = FRAMESIZE_QVGA; // 320x240(无PSRAM)config.jpeg_quality = 12;config.fb_count = 1;}// 初始化摄像头esp_err_t err = esp_camera_init(&config);if (err != ESP_OK) {Serial.printf("摄像头初始化失败:0x%x", err);return;}Serial.println("摄像头初始化成功!");// 连接WiFiWiFi.begin(ssid, password);while (WiFi.status() != WL_CONNECTED) {delay(500);Serial.print(".");}Serial.println("");Serial.println("WiFi连接成功");Serial.println("IP地址: " + WiFi.localIP().toString());
}void loop() {// 获取一帧图像camera_fb_t * fb = esp_camera_fb_get();if (!fb) {Serial.println("获取图像失败");delay(1000);return;}// 打印图像信息Serial.printf("图像尺寸: %zu bytes, 分辨率: %dx%d\n", fb->len, fb->width, fb->height);// 释放图像缓冲区esp_camera_fb_return(fb);delay(2000); // 每2秒拍摄一次
}
2. 实验现象
- 若摄像头初始化成功,串口会打印 “摄像头初始化成功!”
- 成功获取图像后,会显示图像大小和分辨率(如 “图像尺寸: 35648 bytes, 分辨率: 640x480”)
- 若初始化失败,会输出错误代码(可查阅 ESP32 摄像头库文档排查原因)
六、进阶实验:Web 服务器实时预览
1. 实验原理
通过 ESP32-S3 创建 Web 服务器,将摄像头采集的 JPEG 图像以 MJPEG 流的形式发送到浏览器,实现实时预览(帧率约 5-10fps)。
2. 代码实现
#include "esp_camera.h"
#include <WiFi.h>
#include <WebServer.h>// WiFi配置
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";// Web服务器(端口80)
WebServer server(80);// 摄像头引脚配置(同前一个实验)
#define PWDN_GPIO_NUM 16
#define RESET_GPIO_NUM 13
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 21
#define SIOC_GPIO_NUM 22
#define Y9_GPIO_NUM 39
#define Y8_GPIO_NUM 35
#define Y7_GPIO_NUM 34
#define Y6_GPIO_NUM 5
#define Y5_GPIO_NUM 36
#define Y4_GPIO_NUM 37
#define Y3_GPIO_NUM 38
#define Y2_GPIO_NUM 42
#define VSYNC_GPIO_NUM 14
#define HREF_GPIO_NUM 27
#define PCLK_GPIO_NUM 12// 生成HTML页面(用于浏览器显示)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head><title>ESP32-S3 Camera</title><style>body { text-align:center; }.cam-container { margin:0 auto; width:640px; }img { width:100%; }</style>
</head>
<body><h1>ESP32-S3 摄像头实时预览</h1><div class="cam-container"><img src="/stream" /> <!-- 显示MJPEG流 --></div>
</body>
</html>
)rawliteral";// 处理MJPEG流请求
void handleStream() {server.sendHeader("Access-Control-Allow-Origin", "*");server.sendHeader("Cache-Control", "no-cache");server.sendHeader("Pragma", "no-cache");server.sendHeader("Connection", "close");server.send(200, "multipart/x-mixed-replace; boundary=frame", "");while (true) {// 获取图像帧camera_fb_t * fb = esp_camera_fb_get();if (!fb) {Serial.println("获取图像失败");return;}// 发送MJPEG帧边界server.sendContent("--frame\r\n");// 发送图像类型和大小server.sendHeader("Content-Type", "image/jpeg");server.sendHeader("Content-Length", String(fb->len));server.sendContent("\r\n");// 发送图像数据server.sendContent((const char*)fb->buf, fb->len);server.sendContent("\r\n");// 释放缓冲区esp_camera_fb_return(fb);// 检查客户端是否断开连接if (!server.client().connected()) {break;}}
}// 初始化摄像头(同前)
void initCamera() {// 摄像头配置代码与基础实验相同camera_config_t config;// ... 省略配置代码 ...esp_err_t err = esp_camera_init(&config);if (err != ESP_OK) {Serial.printf("摄像头初始化失败:0x%x", err);while(1);}
}void setup() {Serial.begin(115200);initCamera();// 连接WiFiWiFi.begin(ssid, password);while (WiFi.status() != WL_CONNECTED) {delay(500);Serial.print(".");}Serial.println("");Serial.println("WiFi连接成功,IP地址: " + WiFi.localIP().toString());// 路由配置server.on("/", [](){server.send_P(200, "text/html", index_html);});server.on("/stream", handleStream);// 启动服务器server.begin();Serial.println("Web服务器已启动");
}void loop() {server.handleClient(); // 处理客户端请求
}
3. 实验操作与现象
- 上传代码后,在串口监视器获取 ESP32-S3 的 IP 地址(如 192.168.1.100)
- 在电脑或手机浏览器中输入该 IP 地址
- 浏览器将显示摄像头实时画面(640×480 分辨率)
- 移动摄像头,画面会实时更新(延迟约 0.5-1 秒)
七、实战项目:图像捕获与 UART 传输
1. 项目功能
按下按钮时拍摄一张照片,通过 UART2 将 JPEG 图像数据发送到上位机(如电脑),并在 OLED 屏幕显示拍摄状态。
2. 硬件新增组件
- 轻触按钮(连接 GPIO04,使用 INPUT_PULLUP 模式)
- 0.96 寸 OLED 屏幕(复用 I2C 接口,GPIO21/SDA, GPIO22/SCL)
3. 核心代码片段
#include "esp_camera.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>// OLED初始化
Adafruit_SSD1306 oled(128, 64, &Wire, -1);// 按钮引脚
#define BUTTON_PIN 4void setup() {// ... 省略摄像头和WiFi初始化 ...// 初始化OLEDoled.begin(SSD1306_SWITCHCAPVCC, 0x3C);oled.clearDisplay();oled.setTextColor(SSD1306_WHITE);oled.setCursor(0, 0);oled.println("按按钮拍照");oled.display();// 初始化按钮pinMode(BUTTON_PIN, INPUT_PULLUP);
}void loop() {// 检测按钮按下(低电平)if (digitalRead(BUTTON_PIN) == LOW) {delay(50); // 消抖if (digitalRead(BUTTON_PIN) == LOW) {oled.clearDisplay();oled.setCursor(0, 0);oled.println("正在拍照...");oled.display();// 拍摄照片camera_fb_t * fb = esp_camera_fb_get();if (fb) {oled.setCursor(0, 16);oled.println("拍照成功!");oled.setCursor(0, 32);oled.print("大小: ");oled.print(fb->len/1024);oled.println("KB");oled.display();// 通过UART2发送图像数据Serial2.write(0xFF); // 帧头Serial2.write(0xD8);Serial2.write(fb->buf, fb->len); // 图像数据Serial2.write(0xFF); // 帧尾Serial2.write(0xD9);esp_camera_fb_return(fb); // 释放缓冲区} else {oled.setCursor(0, 16);oled.println("拍照失败");oled.display();}delay(2000); // 防止重复触发}}
}
4. 上位机接收
使用串口助手(如 SSCOM)接收图像数据:
- 配置波特率 115200,选择 UART2 对应的 COM 口
- 开启 “十六进制显示”,观察是否有 FF D8 开头、FF D9 结尾的 JPEG 数据
- 将接收的数据保存为 “.jpg” 文件,即可查看拍摄的照片
八、常见问题与解决方案
问题现象 | 可能原因 | 解决方法 |
---|---|---|
摄像头初始化失败(错误代码 0x20004) | PSRAM 未启用或故障 | 1. 在开发板配置中开启 PSRAM 2. 检查 PSRAM 焊接是否良好 |
图像花屏或条纹 | 1. 接线错误 2. 时钟频率过高 | 1. 重新核对 D0-D7 引脚顺序 2. 将 xclk_freq_hz 降低到 16MHz |
无法获取图像(返回 NULL) | 1. 摄像头未供电 2. 复位引脚配置错误 | 1. 确认 VCC 接 3.3V 且 GND 连接正确 2. 检查 PWDN 和 RESET 引脚配置 |
Web 预览卡顿或断开 | 1. WiFi 信号弱 2. 分辨率过高 | 1. 靠近路由器或降低分辨率 2. 将 frame_size 改为 FRAMESIZE_QVGA |
图像偏色 | 白平衡未校准 | 1. 在代码中添加白平衡配置 2. 使用 esp_camera_set_whitebal () 函数 |
ESP32官方–摄像头应用方案
九、第九天总结与第十天预告
1. 今日成果
- ✅ 掌握 ESP32-S3 与 OV2640 摄像头的硬件连接方法
- ✅ 学会配置摄像头参数并实现基础图像采集
- ✅ 实现 Web 服务器实时预览和 UART 图像传输
- ✅ 结合按钮、OLED 等外设完成综合应用
2. 第十天预告:图像识别基础
明天将利用 ESP32-S3 的 NPU(神经网络处理单元),实现:
- 简单的图像识别(如人脸检测、颜色识别)
- TensorFlow Lite 模型部署
- 识别结果与硬件联动(如检测到特定物体点亮 LED)
通过这部分内容,你将初步了解边缘计算在微控制器上的应用,为更复杂的 AIoT 项目打下基础。