本项目仅作为学习记录,不定时更新。
对于ESP32-CAM,我们使用Arduino来开发,首先需要准备一些硬件:
由于我采用的是烧录底座,所以只需要一根micro-usb线即可。
在使用Arduino之前,我们需要下载ESP32的库,其中也包含了ESP32-CAM,若还未配置完成,可以参照这篇博客进行配置。
成功配置后,就可以在工具->开发板中找到“AI Thinker ESP32-CAM”。由于安信可官方所提供的例程并不能在成功烧录后显示ip地址,所以我们使用大神yoursunny所提供的库,下载链接。
下载了.zip包之后,不需要解压,在项目->加载库->添加.ZIP库中添加即可。
基于下载的库,使用ESP32-CAM获取视频流的代码如下:
#include <esp32cam.h>
#include <WebServer.h>
#include <WiFi.h>
const char* WIFI_SSID = "******"; // 改成自己的wifi名称
const char* WIFI_PASS = "******"; // 改成自己的wifi密码
WebServer server(80);
static auto loRes = esp32cam::Resolution::find(320, 240);
static auto hiRes = esp32cam::Resolution::find(800, 600);
void handleBmp()
{
if (!esp32cam::Camera.changeResolution(loRes)) {
Serial.println("SET-LO-RES FAIL");
}
auto frame = esp32cam::capture();
if (frame == nullptr) {
Serial.println("CAPTURE FAIL");
server.send(503, "", "");
return;
}
Serial.printf("CAPTURE OK %dx%d %db\n", frame->getWidth(), frame->getHeight(),
static_cast<int>(frame->size()));
if (!frame->toBmp()) {
Serial.println("CONVERT FAIL");
server.send(503, "", "");
return;
}
Serial.printf("CONVERT OK %dx%d %db\n", frame->getWidth(), frame->getHeight(),
static_cast<int>(frame->size()));
server.setContentLength(frame->size());
server.send(200, "image/bmp");
WiFiClient client = server.client();
frame->writeTo(client);
}
void serveJpg()
{
auto frame = esp32cam::capture();
if (frame == nullptr) {
Serial.println("CAPTURE FAIL");
server.send(503, "", "");
return;
}
Serial.printf("CAPTURE OK %dx%d %db\n", frame->getWidth(), frame->getHeight(),
static_cast<int>(frame->size()));
server.setContentLength(frame->size());
server.send(200, "image/jpeg");
WiFiClient client = server.client();
frame->writeTo(client);
}
void handleJpgLo()
{
if (!esp32cam::Camera.changeResolution(loRes)) {
Serial.println("SET-LO-RES FAIL");
}
serveJpg();
}
void handleJpgHi()
{
if (!esp32cam::Camera.changeResolution(hiRes)) {
Serial.println("SET-HI-RES FAIL");
}
serveJpg();
}
void handleJpg()
{
server.sendHeader("Location", "/cam-hi.jpg");
server.send(302, "", "");
}
void handleMjpeg()
{
if (!esp32cam::Camera.changeResolution(hiRes)) {
Serial.println("SET-HI-RES FAIL");
}
Serial.println("STREAM BEGIN");
WiFiClient client = server.client();
auto startTime = millis();
int res = esp32cam::Camera.streamMjpeg(client);
if (res <= 0) {
Serial.printf("STREAM ERROR %d\n", res);
return;
}
auto duration = millis() - startTime;
Serial.printf("STREAM END %dfrm %0.2ffps\n", res, 1000.0 * res / duration);
}
void setup()
{
Serial.begin(115200);
Serial.println();
{
using namespace esp32cam;
Config cfg;
cfg.setPins(pins::AiThinker);
cfg.setResolution(hiRes);
cfg.setBufferCount(2);
cfg.setJpeg(80);
bool ok = Camera.begin(cfg);
Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
}
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.print("http://");
Serial.println(WiFi.localIP());
Serial.println(" /cam.bmp");
Serial.println(" /cam-lo.jpg");
Serial.println(" /cam-hi.jpg");
Serial.println(" /cam.mjpeg");
server.on("/cam.bmp", handleBmp);
server.on("/cam-lo.jpg", handleJpgLo);
server.on("/cam-hi.jpg", handleJpgHi);
server.on("/cam.jpg", handleJpg);
server.on("/cam.mjpeg", handleMjpeg);
server.begin();
}
void loop()
{
server.handleClient();
}
在VSCode中,我使用windows+python进行开发,python版本为3.9。在这里,VSCode以及python的下载和配置就略过了,大家可以自行根据其他博客进行安装。对于opencv在VSCode里的下载和安装,我们进行详细的介绍。
首先需要使用pip下载opencv,依次在终端里使用如下命令进行安装:
pip install opencv-python
pip install opencv-contrib-python
下载后到下载路径找cv2这个文件夹
将该文件夹下的cv2.cp39-win_amd64.pyd文件
复制到你想做opencv项目的新文件夹里。至此,opencv的配置完成。
为了获取ESP32-CAM的视频流,我们需要将电脑和ESP32-CAM连接同一个wifi热点,在Arduino中烧录好代码后,打开串口监视器,波特率选择115200,则可以看到ESP32-CAM的ip地址。比如我的ip地址如下:
然后我们使用复制过cv2.cp39-win_amd64.pyd文件的文件夹新建一个python文件。并且输入代码:
import urllib
import cv2
import numpy as np
url='http://192.168.43.103/cam-hi.jpg'// 改成自己的ip地址+/cam-hi.jpg
while True:
imgResp=urllib.request.urlopen(url)
imgNp=np.array(bytearray(imgResp.read()),dtype=np.uint8)
img=cv2.imdecode(imgNp,-1)
# all the opencv processing is done here
cv2.imshow('test',img)
if ord('q')==cv2.waitKey(10):
exit(0)
然后运行,即可在test窗口看到我们的图片,此时打开Arduino的串口监视器,可以看到读取速度约为10-15帧,具体情况和当时网速有关。
有一些朋友可能会遇到这种情况
这是因为新版的ESP32的库版本兼容问题,现在2.0.2版的esp32开发板即可解决。在arduino软件-文件-首选项里面,附加开放项管理地址改成https://github.com/espressif/arduino-esp32/releases/download/2.0.2/package_esp32_dev_index.json,然后点开工具,开发板,开发板管理器,搜esp32,下载2.0.2解决。
本问题的解决方式由@qq_39641794提供
2022.4.17 更新
首先我们需要初始化TF卡。初始化TF卡的完整代码如下:
// Init SD Card
void sd_init()
{
//The argument ("/sdcard",true) means closing LED light on the board
if (!SD_MMC.begin("/sdcard",true)) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
}
else if (cardType == CARD_SD) {
Serial.println("SDSC");
}
else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
}
else {
Serial.println("UNKNOWN");
}
//Get the size of SD card, unit: MB
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
Serial.printf("SD 卡容量大小: %lluMB\n", cardSize);
}
这一段代码要放在setup函数前面。**注意,ESP32-CAM模块的TF卡槽仅支持4G容量,超过4G均无法成功识别。**这段方法写好后,工程文件开头的#include要包含如下几个头文件,并声明几个变量,用以作为文件名序号:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "cJSON.h"
#include "FS.h"
#include "esp_camera.h"
...
char path[] = "/1.jpg";
int order = 1;
而在setup()函数中,在上面获取视频流的代码保持不变的情况下,需要添加一行代码:
void setup(){
...
sd_init();//初始化TF卡
delay(5000);
...
}
}
在loop()函数中,添加保存的代码。完整的loop()函数代码如下:
void loop()
{
server.handleClient();
camera_fb_t * fb = esp_camera_fb_get();
sprintf(path,"/%d.jpg",order);
if (fb == NULL)
{
Serial.println( "Get picture failed");
}
else
{
fs::FS &fs = SD_MMC;
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if (!file)
{
Serial.println("Failed to Create a File!");
}
else
{
file.write(fb->buf , fb->len);
}
esp_camera_fb_return(fb);
order += 1;
}
}
将代码上传到板子中并运行,然后取下TF卡,插入读卡器,并将读卡器插在电脑上,我们就可以看到保存在TF卡中的照片了。效果如下图所示:
至此,我们就完成了WiFi图像传输和本地保存两个功能。
注:此次更新的代码均是在上文中获取视频流代码的基础上修改,读者可以对比位置,在不修改获取视频流代码的情况下,将保存TF卡的代码添加到正确的位置即可,整个代码即可同时实现两个功能。
本次更新由本人同学,用户@Zhuwany进行。