1.项目需求与参考
1.1、项目需求
- 开发一个创新的智能家居方案,通过使用XIAO ESP32S3 Sense开发板结合其他技术栈,为助力宠物在数字化的今天更好的安家乐居提供一套完整的方案。
- 利用摄像头配合图像识别AI,能够准确实时识别宠物。
- 利用XIAO ESP32S3 Sense的扩展模块实现智能宠物系统。
1.2、项目参考技术栈
- 微控制器:XIAO以及其它开源硬件如Arduino、ESP32等;
- 边缘计算硬件:Raspberry Pi,Nvidia Jetson等;
- 智能家居系统:Home Assistant;
- 嵌入式TinyML AI工具:Edge Impulse,SenseCraft AI 等;
- 云平台:Ubidots、Blynk、Tasmota,易微联等云平台;
2、功能模块
2.1、主控模块
本次主控核心板采用的是Seeed的XIAO ESP32S3 Sense开发板,下图2.1.1和2.1.2分别是开发板的正反端示意图:
图2.1.1
图2.1.2
该图是XIAO ESP32S3 Sense开发板的引脚图,可以看出XIAO开发板的一个最大的特点就是“小(xiao)”,在一个拇指大小的开发板上集成了14个引脚,片上8MPSRAM和8MB闪存板载SD卡插槽,支持32GB FAT。并且拥有Xtensa LX7双核32位处理器,工作频率高达240MHz。
图2.1.3
2.2、OV2640模块
2.2.1、OV2640摄像的安装
首先在XIAO ESP32S3的左下角下有一个用来连接WiFi的连接口,需要我们将一起的天线安装在上面以此来获得更好的信号。然后将摄像头与XIAO开发板上的B2B连接器对其再安装上去。在摄像头上有一个专门的SD卡的卡槽,用来放置我们的SD卡,拍下来的照片就会保存在这个SD卡中。
2.2.2、OV2640摄像模块
OV2640摄像模块支持输出最大为200万像素的图像 (1600x1200分辨率), 支持使用VGA时序输出图像数据,输出图像的数据格式支持YUV(422/420)、YCbCr422、RGB565以及JPEG格式,若直接输出JPEG格式的图像时可大大减少数据量, 方便网络传输。它还可以对采集得的图像进行补偿,支持伽玛曲线、白平衡、饱和度、色度等基础处理。根据不同的分辨率配置, 传感器输出图像数据的帧率从15-60帧可调,工作时功率在125mW-140mW之间。图2.2.1为OV2640的引脚分布图,其采用BGA封装,它的前端是采光窗口,引脚都在背面引出。
图2.2.1
其功能框图如下图2.2.2所示:
图2.2.2
- 控制寄存器
标号处的是OV2640的控制寄存器,它根据这些寄存器配置的参数来运行,而这些参数是由外部控制器通过SIO_C和SIO_D引脚写入的, SIO_C与SIO_D使用的通讯协议SCCB跟I2C十分类似,在STM32中我们完全可以直接用I2C硬件外设来控制。
- 通信、控制信号及时钟
标号处包含了OV2640的通信、控制信号及外部时钟,其中PCLK、HREF及VSYNC分别是像素同步时钟、行同步信号以及帧同步信号, 这与液晶屏控制中的信号是很类似的。RESETB引脚为低电平时,用于复位整个传感器芯片,PWDN用于控制芯片进入低功耗模式。 注意最后的一个XCLK引脚,它跟PCLK是完全不同的,XCLK是用于驱动整个传感器芯片的时钟信号,是外部输入到OV2640的信号; 而PCLK是OV2640输出数据时的同步信号,它是由OV2640输出的信号。XCLK可以外接晶振或由外部控制器提供, 若要类比XCLK之于OV2640就相当于HSE时钟输入引脚与STM32芯片的关系,PCLK引脚可类比STM32的I2C外设的SCL引脚。
- 感光矩阵
标号处的是感光矩阵,光信号在这里转化成电信号,经过各种处理,这些信号存储成由一个个像素点表示的数字图像。
- 数据输出信号
标号处包含了DSP处理单元,它会根据控制寄存器的配置做一些基本的图像处理运算。这部分还包含了图像格式转换单元及压缩单元, 转换出的数据最终通过Y0-Y9引脚输出,一般来说我们使用8根据数据线来传输,这时仅使用Y2-Y9引脚, OV2640与外部器件的连接方式见图 2.2.3。
图2.2.3
3、流程框图与思路
图3.1.1
思路大致如下:
- 首先通过OV2640摄像模块,在Arduino IDE上下载拍摄功能的代码;
- 将摄像头对准要进行识别的玩偶宠物,把拍摄得到的图片保存在SD卡中;
- SD卡中的图片上传到Roboflow平台上进行标注,如果效果不好可以再次对目标物进行拍摄,获得更多的数据集,以此来提高精度;
- 把得到的URL代码在飞桨AI studio中进行迭代训练,得到训练好的模型文件;
- 将训练好的模型文件上传到SenseCraft中,就可以对玩偶宠物进行目标识别了。
4、主要代码
利用XIAO ESP32S3 Sense开发板上的OV2640摄像头需要在Arduino IDE上编译并下载如下代码,这里展示主要代码部分:
#include "esp_camera.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#define CAMERA_MODEL_XIAO_ESP32S3 // Has PSRAM
#include "camera_pins.h"
unsigned long lastCaptureTime = 0; // Last shooting time
int imageCount = 1; // File Counter
bool camera_sign = false; // Check camera status
bool sd_sign = false; // Check sd status
bool commandRecv = false; // flag used for indicating receipt of commands from serial port
bool captureFlag = false;
// Save pictures to SD card
void photo_save(const char * fileName) {
// Take a photo
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Failed to get camera frame buffer");
return;
}
// Save photo to file
writeFile(SD, fileName, fb->buf, fb->len);
// Release image buffer
esp_camera_fb_return(fb);
Serial.println("Photo saved to file");
}
// SD card write file
void writeFile(fs::FS &fs, const char * path, uint8_t * data, size_t len){
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
if(file.write(data, len) == len){
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
void setup() {
Serial.begin(115200);
while(!Serial); // When the serial monitor is turned on, the program starts to execute
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.frame_size = FRAMESIZE_UXGA;
config.pixel_format = PIXFORMAT_JPEG; // for streaming
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.jpeg_quality = 12;
config.fb_count = 1;
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality
// for larger pre-allocated frame buffer.
if(config.pixel_format == PIXFORMAT_JPEG){
if(psramFound()){
config.jpeg_quality = 10;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
// Limit the frame size when PSRAM is not available
config.frame_size = FRAMESIZE_SVGA;
config.fb_location = CAMERA_FB_IN_DRAM;
}
} else {
// Best option for face detection/recognition
config.frame_size = FRAMESIZE_240X240;
#if CONFIG_IDF_TARGET_ESP32S3
config.fb_count = 2;
#endif
}
// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
camera_sign = true; // Camera initialization check passes
// Initialize SD card
if(!SD.begin(21)){
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
// Determine if the type of SD card is available
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");
}
sd_sign = true; // sd initialization check passes
Serial.println("XIAO ESP32S3 Sense Camera Image Capture");
Serial.println("Send 'capture' to initiates an image capture\n");
}
void loop() {
// Camera & SD available, start taking pictures
if(camera_sign && sd_sign){
String command;
// Read incoming commands from serial monitor
while (Serial.available()) {
char c = Serial.read();
if ((c != '\n') && (c != '\r')) {
command.concat(c);
}
else if (c == '\n') {
commandRecv = true;
command.toLowerCase();
}
}
//If command = "capture", take a picture and save it to the SD card
if (commandRecv && command == "capture") {
commandRecv = false;
Serial.println("\nPicture Capture Command is sent");
char filename[32];
sprintf(filename, "/image%d.jpg", imageCount);
photo_save(filename);
Serial.printf("Saved picture:%s\n", filename);
Serial.println("");
imageCount++;
}
}
}
其中,分别定义了两个函数,一个是将捕获的图像保存到SD卡的函数photo_save()
,另一个是写入文件的函数writeFile( )。
// Save pictures to SD card
void photo_save(const char * fileName) {
// Take a photo
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Failed to get camera frame buffer");
return;
}
// Save photo to file
writeFile(SD, fileName, fb->buf, fb->len);
// Release image buffer
esp_camera_fb_return(fb);
Serial.println("Photo saved to file");
}
if(camera_sign && sd_sign){
// Get the current time
unsigned long now = millis();
//If it has been more than 1 minute since the last shot, take a picture and save it to the SD card
if ((now - lastCaptureTime) >= 60000) {
char filename[32];
sprintf(filename, "/image%d.jpg", imageCount);
photo_save(filename);
Serial.printf("Saved picture:%s\n", filename);
Serial.println("Photos will begin in one minute, please be ready.");
imageCount++;
lastCaptureTime = now;
}
}
5、遇到的主要问题
- 在刚上手XIAO时,需要使用Arduino IDE对其进行编程,刚开始下载安装ESP32的资源包时出现问题;
- Arduino IDE编译代码时速度较慢,编译效率不太高;
- 与ESP32S3的连接有时会出现问题,出现连接不稳定的问题;
6、改进与收获
本次寒假一起练任务还可以进一步的优化:
- 可以联合其他智能外设进行联动,如蓝牙音箱等;
- 可以增加一些其他声光提醒功能;
- 可以将训练得到的模型进一步的优化,使识别效果和精度都提高;
这次寒假任务得收获:
- 进一步了解和学习了ESP32得相关知识和内容,对Arduino IDE编程得到了一定的提升;
- 对OV2640摄像模块有了一定的了解,并且结合XIAO ESP32S3一起简单的实现了一个AI智能的目标识别;
- 通过使用Roboflow与AI Studio平台对目标识别的标注与训练有了相应的了解;
- 与其他小伙伴一起学习交流解决问题并且学习到了新的知识。
7、Hackster链接
Doll pet recognition system based on Seeed XIAO ESP32S3 Sens - Hackster.io