Firebeetle2 ESP32-S3 新手笔记2-带你搭建网页摄像头
本帖最后由 伊娃老师 于 2023-10-19 17:48 编辑## 引言
本项目最终目标是实现Firebeetle 2 ESP32-S3 的实时摄像头小车制作,上一个分享内容是:
[用网页服务器控制板载LED](https://mc.dfrobot.com.cn/thread-316962-1-1.html)
接着将继续实现搭建一个简单的摄像头服务器。我将分享:
初始化摄像头,配置 Wi-Fi 连接以及设置一个异步 Web 服务器,以便从浏览器中查看摄像头图像。
## 所需材料
- Firebeetle2 ESP32-S3
- OV2640 摄像头模块
- 计算机(用于编程)
- Arduino IDE
## 准备工作
(https://wiki.dfrobot.com.cn/_SKU_DFR0975_FireBeetle_2_Board_ESP32_S3#target_4)
(https://wiki.dfrobot.com.cn/_SKU_DFR0975_FireBeetle_2_Board_ESP32_S3_Advanced_Tutorial#target_12)
## 注意事项
Wiki已说明CameraWebServer示例用法,可跟随操作实践。
本次分享案例仅显示摄像头画面。
需要安装ESPAsyncWebServer库,可参考上期分享:[用网页服务器控制板载LED](https://mc.dfrobot.com.cn/thread-316962-1-1.html)
## 完整代码
复制贴上代码后,修改无线网络ID、密码,
并在串口监视器查看IP,即可使用浏览器查看摄像头画面。
查看的设备需与Firebeetle连接同一个无线网络。
```
#include <WiFi.h> // 包含WiFi库
#include <ESPAsyncWebServer.h>// 包含异步Web服务器库
#include "esp_camera.h" // 包含ESP相机库
#include "DFRobot_AXP313A.h" // 包含DFRobot AXP313A电源管理模块库
DFRobot_AXP313A axp;// 创建一个DFRobot AXP313A对象,用于电源管理
const char *ssid = "eva"; // 你的Wi-Fi网络名称
const char *password = "12345678";// 你的Wi-Fi密码
// 相机引脚定义
const int XCLK_GPIO_NUM = 45;
const int PWDN_GPIO_NUM = -1;
const int RESET_GPIO_NUM = -1;
const int SIOD_GPIO_NUM = 1;
const int SIOC_GPIO_NUM = 2;
const int Y9_GPIO_NUM = 48;
const int Y8_GPIO_NUM = 46;
const int Y7_GPIO_NUM = 8;
const int Y6_GPIO_NUM = 7;
const int Y5_GPIO_NUM = 4;
const int Y4_GPIO_NUM = 41;
const int Y3_GPIO_NUM = 40;
const int Y2_GPIO_NUM = 39;
const int VSYNC_GPIO_NUM = 6;
const int HREF_GPIO_NUM = 42;
const int PCLK_GPIO_NUM = 5;
AsyncWebServer server(80);// 创建一个异步Web服务器对象,监听端口80
void configInitCamera() {
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_VGA;
config.jpeg_quality = 30;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 60;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config);// 初始化相机
if (err != ESP_OK) {
Serial.printf("摄像头初始化失败,错误代码 0x%x", err);
delay(1000);
ESP.restart();// 重启ESP32
}
}
const char *index_html = R"(
<!DOCTYPE html>
<html>
<head>
<title>ESP32 摄像头</title>
</head>
<body>
<img id="cameraImage" width="640" height="480">
<script>
function updateCameraImage() {
var imageElement = document.getElementById("cameraImage");
imageElement.src = "/image?" + new Date().getTime();
setTimeout(updateCameraImage, 200); // 0.2秒刷新一次
}
updateCameraImage();
</script>
</body>
</html>
)";
void setup() {
Serial.begin(115200);// 启动串口通信
Serial.println("开始");
while (axp.begin() != 0) {// 初始化DFRobot AXP313A电源管理模块
Serial.println("初始化错误。重试中...");
delay(1000);
}
axp.enableCameraPower(axp.eOV2640);// 启用摄像头电源
configInitCamera();// 初始化摄像头配置
WiFi.mode(WIFI_STA); // 将WiFi设置为站点模式
WiFi.begin(ssid, password);// 连接WiFi网络
WiFi.setSleep(false); //不进入Wi-Fi模块的休眠模式
while (WiFi.status() != WL_CONNECTED) {// 等待WiFi连接
Serial.print(".");
delay(500);
}
Serial.println(WiFi.localIP());// 打印本地IP地址
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", index_html);// 处理根目录请求并发送HTML页面
});
server.on("/image", HTTP_GET, [](AsyncWebServerRequest *request) {
camera_fb_t *fb = esp_camera_fb_get();
if (fb) {
AsyncWebServerResponse *response = request->beginResponse_P(200, "image/jpeg", fb->buf, fb->len);
request->send(response); // 发送摄像头图像数据
esp_camera_fb_return(fb);// 释放图像缓冲
}
});
server.begin();// 启动Web服务器
}
void loop() {
// 这里可以放置与摄像头相关的代码
}
```
## 代码解析
### 引入所需的库
这些库的导入是为了提供所需的功能。
WiFi库用于连接到Wi-Fi网络,
ESPAsyncWebServer库用于创建Web服务器,
esp_camera库用于摄像头控制,
DFRobot_AXP313A库用于电源管理。
```
#include <WiFi.h> // 包含WiFi库
#include <ESPAsyncWebServer.h>// 包含异步Web服务器库
#include "esp_camera.h" // 包含ESP相机库
#include "DFRobot_AXP313A.h" // 包含DFRobot AXP313A电源管理模块库
```
### 创建变量与定义
在这一部分,创建了一些变量和对象以便在后续的代码中使用。
axp对象用于电源管理
ssid和password是Wi-Fi连接所需的网络名称和密码,
而后面的一系列常量用于定义摄像头引脚的设置。
```
DFRobot_AXP313A axp;// 创建一个DFRobot AXP313A对象,用于电源管理
const char *ssid = "eva"; // 你的Wi-Fi网络名称
const char *password = "12345678";// 你的Wi-Fi密码
// 相机引脚定义
const int XCLK_GPIO_NUM = 45;
const int PWDN_GPIO_NUM = -1;
const int RESET_GPIO_NUM = -1;
const int SIOD_GPIO_NUM = 1;
const int SIOC_GPIO_NUM = 2;
const int Y9_GPIO_NUM = 48;
const int Y8_GPIO_NUM = 46;
const int Y7_GPIO_NUM = 8;
const int Y6_GPIO_NUM = 7;
const int Y5_GPIO_NUM = 4;
const int Y4_GPIO_NUM = 41;
const int Y3_GPIO_NUM = 40;
const int Y2_GPIO_NUM = 39;
const int VSYNC_GPIO_NUM = 6;
const int HREF_GPIO_NUM = 42;
const int PCLK_GPIO_NUM = 5;
AsyncWebServer server(80);// 创建一个异步Web服务器对象,监听端口80
```
### 配置摄像头
这个函数configInitCamera用于配置和初始化摄像头的参数。摄像头的各项参数被设置为config对象中,包括像素格式、分辨率、JPEG质量等。
```
void configInitCamera() {
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_VGA;
config.jpeg_quality = 30;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 60;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config);// 初始化相机
if (err != ESP_OK) {
Serial.printf("摄像头初始化失败,错误代码 0x%x", err);
delay(1000);
ESP.restart();// 重启ESP32
}
}
```
关于图像质量,可以看这一段代码,
config.jpeg_quality 参数被设置为 30 和 60。这是一个表示 JPEG 图像质量的值,取值范围从 0 到 63,其中 0 表示最低质量,而 63 表示最高质量。较低的质量值会导致更高的压缩,因此图像质量较低,而较高的质量值会导致更低的压缩,从而获得更高的图像质量。
简单说,数字越小,压缩越少,画面质量越高。
而FRAMESIZE可以参考的设置如下:
- FRAMESIZE_UXGA (1600 x 1200)
- FRAMESIZE_QVGA (320 x 240)
- FRAMESIZE_CIF (352 x 288)
- FRAMESIZE_VGA (640 x 480)
- FRAMESIZE_SVGA (800 x 600)
- FRAMESIZE_XGA (1024 x 768)
- FRAMESIZE_SXGA (1280 x 1024)
```
if (psramFound()) {
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 30; // 这里设置 JPEG 质量
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 60; // 这里设置 JPEG 质量
config.fb_count = 1;
}
```
### 配置HTML页面
这段代码定义了一个包含HTML页面的字符串,该页面包括一个显示摄像头图像的图像元素以及JavaScript代码用于定时刷新图像。
- `<!DOCTYPE html>`:这是HTML文档的声明,它指定文档使用的HTML版本。在这里,我们使用HTML5。
- `<html>`:HTML文档的根元素,包含了整个HTML页面。
- `<head>`:这是头部部分,通常包含了文档的元数据,如标题、字符集设置等。在这里,我们设置了页面的标题为 "ESP32 摄像头"。
- `<body>`:这是HTML文档的主体部分,包含了要在页面上显示的内容。
- `<img id="cameraImage" width="640" height="480">:这是一个图像元素,用于显示摄像头图像。`id="cameraImage"` 用于给图像元素一个唯一的标识符,以便在JavaScript中引用它。`width` 和 `height` 属性设置了图像元素的宽度和高度。
- `<script>`:这是JavaScript代码的起始标签,用于在页面加载时执行JavaScript代码。
- `function updateCameraImage()`:这是一个JavaScript函数,用于更新摄像头图像。它通过 `getElementById` 方法获取了 `cameraImage` 元素,然后设置其 `src` 属性为 `/image`,后跟一个时间戳以确保每次都获取新的图像。最后,它使用 `setTimeout` 函数每 0.2 秒刷新一次图像。
- `updateCameraImage();`:这行代码调用 `updateCameraImage` 函数,以便在页面加载后立即开始刷新摄像头图像。
```
const char *index_html = R"(
<!DOCTYPE html>
<html>
<head>
<title>ESP32 摄像头</title>
</head>
<body>
<img id="cameraImage" width="640" height="480">
<script>
function updateCameraImage() {
var imageElement = document.getElementById("cameraImage");
imageElement.src = "/image?" + new Date().getTime();
setTimeout(updateCameraImage, 200); // 0.2秒刷新一次
}
updateCameraImage();
</script>
</body>
</html>
)";
```
### 设置setup()函数
在setup()函数中,串口通信被初始化,DFRobot AXP313A电源管理模块被初始化,摄像头配置被设置,Wi-Fi连接被建立,并Web服务器被启动。
```
void setup() {
Serial.begin(115200);// 启动串口通信
// ...
while (axp.begin() != 0) {
// 初始化DFRobot AXP313A电源管理模块
// ...
}
axp.enableCameraPower(axp.eOV2640);// 启用摄像头电源
configInitCamera();// 初始化摄像头配置
WiFi.mode(WIFI_STA); // 将WiFi设置为站点模式
WiFi.begin(ssid, password);// 连接WiFi网络
WiFi.setSleep(false); // 不进入Wi-Fi模块的休眠模式
// ...
```
### Web服务器路由设置
在这部分中,设置了两个Web服务器路由。第一个路由处理根目录请求,向浏览器发送HTML页面。第二个路由用于获取摄像头图像数据,并将其发送到浏览器。
- `server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {`:这是一个Web服务器路由设置,它定义了当浏览器请求根目录("/")时应该执行的操作。`HTTP_GET` 表示这是一个HTTP GET请求的处理。在这个示例中,注释 `// 处理根目录请求并发送HTML页面` 提示这是用于处理根目录请求并发送HTML页面的部分代码。实际的HTML页面发送操作被注释掉,因此在此示例中,请求根目录时并未执行具体操作。
- `server.on("/image", HTTP_GET, [](AsyncWebServerRequest *request) {`:这是另一个Web服务器路由设置,它定义了当浏览器请求 "/image" 路径时应该执行的操作。同样,`HTTP_GET` 表示这是一个HTTP GET请求的处理。在这个示例中,当浏览器请求 "/image" 时,会执行以下操作:
- `camera_fb_t *fb = esp_camera_fb_get();`:这行代码用于获取摄像头帧缓冲。`esp_camera_fb_get()` 函数用于从摄像头获取当前帧的图像数据,并将其存储在 `fb` 变量中。
- `if (fb) {`:这是一个条件语句,检查是否成功获取摄像头图像帧。如果成功获取,就会进入下面的操作块。
- `AsyncWebServerResponse *response = request->beginResponse_P(200, "image/jpeg", fb->buf, fb->len);`:这行代码创建一个Web服务器响应对象,其中包含了图像数据。响应的HTTP状态码为 `200`,表示成功。`"image/jpeg"` 指定了响应的内容类型为JPEG图像。`fb->buf` 包含了图像数据的指针,而 `fb->len` 包含了图像数据的长度。
- `request->send(response);`:这行代码用于将创建的响应发送给浏览器,从而向浏览器提供了摄像头图像数据。
- `esp_camera_fb_return(fb);`:这行代码用于释放摄像头图像缓冲,以便将其用于下一帧。
通过这个Web服务器路由设置,当浏览器请求 "/image" 时,摄像头图像数据将被获取并发送给浏览器,从而允许用户查看实时摄像头图像。这对于创建一个实时视频流非常有用。
```
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// 处理根目录请求并发送HTML页面
// ...
});
server.on("/image", HTTP_GET, [](AsyncWebServerRequest *request) {
camera_fb_t *fb = esp_camera_fb_get();
if (fb) {
AsyncWebServerResponse *response = request->beginResponse_P(200, "image/jpeg", fb->buf, fb->len);
request->send(response); // 发送摄像头图像数据
esp_camera_fb_return(fb);// 释放图像缓冲
}
});
```
页:
[1]