2024年寒假练 - 基于Seeed XIAO ESP32S3 Sense开发板实现的玩偶宠物识别系统
该项目使用了Seeed XIAO ESP32S3 Sense开发板,实现了识别玩偶宠物的设计,它的主要功能为:基于XIAO ESP32S3 Sense开发板,此开发板还具有扩展板,有一个1600*1200 OV2640摄像头传感器、板载SD卡插槽和数字麦克风。通过摄像头将识别目标拍摄下来,将图片标准并训练好相关的模型,再通过ESP32S3进行目标识别。。
标签
ESP32S3
2024寒假一起练
物体识别
Alkaid
更新2024-03-28
南昌大学
77

1.项目需求与参考

1.1、项目需求

  1. 开发一个创新的智能家居方案,通过使用XIAO ESP32S3 Sense开发板结合其他技术栈,为助力宠物在数字化的今天更好的安家乐居提供一套完整的方案
  2.  利用摄像头配合图像识别AI,能够准确实时识别宠物。
  3. 利用XIAO ESP32S3 Sense的扩展模块实现智能宠物系统。

1.2、项目参考技术栈

  1. 微控制器:XIAO以及其它开源硬件如Arduino、ESP32等;
  2. 边缘计算硬件:Raspberry Pi,Nvidia Jetson等;
  3. 智能家居系统:Home Assistant;
  4. 嵌入式TinyML AI工具:Edge Impulse,SenseCraft AI 等;
  5. 云平台:Ubidots、Blynk、Tasmota,易微联等云平台;

2、功能模块

2.1、主控模块

本次主控核心板采用的是Seeed的XIAO ESP32S3 Sense开发板,下图2.1.1和2.1.2分别是开发板的正反端示意图:

image.png

图2.1.1

image.png

图2.1.2


该图是XIAO ESP32S3 Sense开发板的引脚图,可以看出XIAO开发板的一个最大的特点就是“小(xiao)”,在一个拇指大小的开发板上集成了14个引脚,片上8MPSRAM和8MB闪存板载SD卡插槽,支持32GB FAT。并且拥有Xtensa LX7双核32位处理器,工作频率高达240MHz。

image.png

图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封装,它的前端是采光窗口,引脚都在背面引出。

image.png

图2.2.1


其功能框图如下图2.2.2所示:

image.png

图2.2.2


  1. 控制寄存器

标号处的是OV2640的控制寄存器,它根据这些寄存器配置的参数来运行,而这些参数是由外部控制器通过SIO_C和SIO_D引脚写入的, SIO_C与SIO_D使用的通讯协议SCCB跟I2C十分类似,在STM32中我们完全可以直接用I2C硬件外设来控制

  1. 通信、控制信号及时钟

标号处包含了OV2640的通信、控制信号及外部时钟,其中PCLK、HREF及VSYNC分别是像素同步时钟、行同步信号以及帧同步信号, 这与液晶屏控制中的信号是很类似的。RESETB引脚为低电平时,用于复位整个传感器芯片,PWDN用于控制芯片进入低功耗模式。 注意最后的一个XCLK引脚,它跟PCLK是完全不同的,XCLK是用于驱动整个传感器芯片的时钟信号,是外部输入到OV2640的信号; 而PCLK是OV2640输出数据时的同步信号,它是由OV2640输出的信号。XCLK可以外接晶振或由外部控制器提供, 若要类比XCLK之于OV2640就相当于HSE时钟输入引脚与STM32芯片的关系,PCLK引脚可类比STM32的I2C外设的SCL引脚。

  1. 感光矩阵

标号处的是感光矩阵,光信号在这里转化成电信号,经过各种处理,这些信号存储成由一个个像素点表示的数字图像。

  1. 数据输出信号

标号处包含了DSP处理单元,它会根据控制寄存器的配置做一些基本的图像处理运算。这部分还包含了图像格式转换单元及压缩单元, 转换出的数据最终通过Y0-Y9引脚输出,一般来说我们使用8根据数据线来传输,这时仅使用Y2-Y9引脚, OV2640与外部器件的连接方式见图 2.2.3。

image.png

图2.2.3


3、流程框图与思路

image.png

图3.1.1

思路大致如下:

  1. 首先通过OV2640摄像模块,在Arduino IDE上下载拍摄功能的代码;
  2. 将摄像头对准要进行识别的玩偶宠物,把拍摄得到的图片保存在SD卡中;
  3. SD卡中的图片上传到Roboflow平台上进行标注,如果效果不好可以再次对目标物进行拍摄,获得更多的数据集,以此来提高精度;
  4. 把得到的URL代码在飞桨AI studio中进行迭代训练,得到训练好的模型文件;
  5. 将训练好的模型文件上传到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、改进与收获

本次寒假一起练任务还可以进一步的优化:

  1. 可以联合其他智能外设进行联动,如蓝牙音箱等;
  2. 可以增加一些其他声光提醒功能;
  3. 可以将训练得到的模型进一步的优化,使识别效果和精度都提高;

这次寒假任务得收获:

  1. 进一步了解和学习了ESP32得相关知识和内容,对Arduino IDE编程得到了一定的提升;
  2. 对OV2640摄像模块有了一定的了解,并且结合XIAO ESP32S3一起简单的实现了一个AI智能的目标识别;
  3. 通过使用Roboflow与AI Studio平台对目标识别的标注与训练有了相应的了解;
  4. 与其他小伙伴一起学习交流解决问题并且学习到了新的知识。

7、Hackster链接

Doll pet recognition system based on Seeed XIAO ESP32S3 Sens - Hackster.io

附件下载
XIAO.zip
主要代码
团队介绍
南昌大学
团队成员
Alkaid
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号