Clasificación de Imágenes XIAO ESP32S3-Sense
Aquí vamos a construir un proyecto tinyML con XIAO ESP32S3-Sense para clasificar perros y gatos en fotos. Comencemos.
Cómo funciona
Estamos ejecutando el modelo de aprendizaje automático en nuestro XIAO y alimentándolo con el flujo de la cámara. Luego el XIAO infiere el resultado y predice con la ayuda de la red neuronal a bordo que implementamos. Construyamos uno.

Cosas necesarias
- XIAO ESP32-Sense
- Tarjeta MicroSD <32GB
- Cable Type-C
- Arduino IDE
- Cuenta de Edge Impulse
- Usa arduino-esp32 versión 2.x ya que no es compatible con 3.x.
- Habilita PSRAM para activar el módulo/función de la cámara.
Paso 1. Recolectar las Imágenes de Gatos y Perros
El primer paso de un proyecto de aprendizaje automático es recolectar el conjunto de datos y aquí necesitamos recolectar imágenes de perros y gatos. Aquí, podemos recolectar las imágenes de dos maneras.
- Recolectar imágenes directamente desde el XIAO-ESP32S3 Sense y guardar en la tarjeta SD luego subirlas a Edge Impulse
- Recolectar directamente las imágenes vía teléfono móvil, internet o conjunto de datos abierto luego subirlas a Edge Impulse.
1.1 Método 1: Recolectar Imágenes vía XIAO-ESP32S3 Sense
Aquí estamos recolectando imágenes usando el módulo de cámara sense y guardándolas en la tarjeta SD, luego más tarde las subiremos a EdgeImpulse.
1.1.1 Conectar Cámara
Si estás comprando el XIAO ESP32S3 Sense, entonces también deberías incluir una placa de expansión. Esta placa de expansión tiene un sensor de cámara OV2640 de 1600*1200, ranura para tarjeta SD integrada y micrófono digital.
Al instalar la placa de expansión con XIAO ESP32S3 Sense, puedes usar las funciones en la placa de expansión.
Instalar la placa de expansión es muy simple, solo necesitas alinear el conector en la placa de expansión con el conector B2B en el XIAO ESP32S3, presionarlo fuerte y escuchar un "clic", la instalación está completa.

Ahora tenemos una nueva cámara potente totalmente compatible con XIAO ESP32S3 Sense, la OV5640, en nuestros estantes, y si la compras, puedes reemplazar la cámara para usarla.

- Paso 1. Descarga e instala la versión estable de Arduino IDE según tu sistema operativo.
1.1.2 Instalar XIAO-ESP32S3 Sense en Arduino
-
Paso 1. Añade el paquete de placa ESP32 a tu Arduino IDE.
Navega a Archivo > Preferencias, y llena "URLs Adicionales del Gestor de Placas" con la url de abajo: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
Navega a Herramientas > Placa > Gestor de Placas..., escribe la palabra clave esp32 en la caja de búsqueda, selecciona la última versión de esp32, e instálala.
El paquete a bordo para XIAO ESP32S3 requiere al menos la versión 2.0.8 para estar disponible.
- Paso 2. Selecciona tu placa y puerto.
En la parte superior del Arduino IDE, puedes seleccionar el puerto directamente. Es probable que sea COM3 o superior (COM1 y COM2 usualmente están reservados para puertos serie de hardware).
También, busca xiao en la placa de desarrollo a la izquierda. selecciona XIAO_ESP32S3.

Con esta preparación, puedes comenzar a escribir programas para XIAO ESP32S3 para compilar y subir.
1.1.3 Subir sketch que puede tomar imágenes y guardar en tarjeta SD
Descarga el archivo sketch desde aquí, y descomprímelo en tu computadora y haz clic en "take_photos_command.ino" para abrir el sketch. Luego selecciona el puerto y placa correctos para XIAO para subir.

Mientras tomas fotos, asegúrate de apuntar a la imagen u objeto que necesitamos recolectar como conjunto de datos.

Dado que es una clasificación de imágenes necesitamos recolectar tantas imágenes como sea posible para hacer el sistema más estable. Así que recolecta más imágenes lindas de perros y gatos.
1.2 Método 2: Subir imágenes a EdgeImpulse Directamente
Para este método, necesitamos recopilar imágenes de internet o a través del teléfono. Afortunadamente, tenemos servicios como Kaggle que nos proporcionan conjuntos de datos para imágenes de perros y gatos, por favor ver aquí.
Una vez que hayas recopilado el conjunto de datos, abre tu proyecto de EdgeImpulse y ve a Data acquisition y selecciona + Add data y luego elige la opción Upload data.

Desde la página, selecciona los modos de carga como "Select a folder", luego selecciona los archivos de la opción de abajo. Asegúrate de seleccionar "Automaticlly split between training and testing" y Label como "Infer from file name"

Una vez que hayas terminado la carga, puedes ver la ventana como se muestra a continuación.

Paso 2. Diseñar el Impulso y Entrenar nuestra Red Neuronal
Después de recopilar datos para tu proyecto, ahora puedes crear tu Impulso. Un Impulso completo consistirá en 3 bloques principales: bloque de entrada, bloque de procesamiento y un bloque de aprendizaje.
- Para diseñar un impulso, selecciona Create an impulse bajo Impulse design.
- Bloque de entrada: El bloque de entrada indica el tipo de datos de entrada con los que estás entrenando tu modelo. Esto puede ser series temporales (audio, vibración, movimientos) o imágenes. Aquí seleccionamos los datos de imagen como bloque de entrada
- Bloques de procesamiento: Un bloque de procesamiento es básicamente un extractor de características. Consiste en operaciones DSP (Procesamiento Digital de Señales) que se utilizan para extraer características sobre las que nuestro modelo aprende. Estas operaciones varían dependiendo del tipo de datos utilizados en tu proyecto. Aquí estamos usando la imagen pre-construida de EdgeImpulse como bloque de procesamiento.
- Bloques de aprendizaje: Después de agregar tu bloque de procesamiento, ahora es momento de agregar un bloque de aprendizaje para hacer tu impulso completo. Un bloque de aprendizaje es simplemente una red neuronal que está entrenada para aprender sobre tus datos. Aquí estamos usando Transfer Learning (Images) que ajustará finamente un modelo de clasificación de imágenes pre-entrenado en tus datos. Buen rendimiento incluso con conjuntos de datos de imágenes relativamente pequeños.
- Después de diseñar el impulso, haz clic en save impulse para continuar.

Paso 2.1: Extracción de características usando bloque de procesamiento
Aquí utilizaremos operaciones DSP (Procesamiento Digital de Señales) que se usan para extraer características sobre las que nuestro modelo aprende. Estas operaciones varían dependiendo del tipo de datos utilizados en tu proyecto. Primero necesitamos establecer los parámetros como se menciona a continuación.

Una vez que establecemos los parámetros, necesitamos generar las características, haz clic en generate features para iniciar la operación.

Una vez que generamos las características, necesitamos configurar la red neuronal que está entrenada para aprender sobre tus datos. Aquí estamos usando Transfer Learning (Images) que ajustará finamente un modelo de clasificación de imágenes pre-entrenado en tus datos. Buen rendimiento incluso con conjuntos de datos de imágenes relativamente pequeños. Selecciona la red neuronal como se menciona a continuación y haz clic en Start Training. Tomará algo de tiempo y necesitas esperar.

Una vez que el entrenamiento esté terminado, estamos listos para descargar nuestra biblioteca tinyML que se creó. Ve a la opción Deployment y selecciona Arduino library como despliegue y haz clic en Build para generar la biblioteca.

La biblioteca se descargará automáticamente. Y después de eso, agrégala al IDE de Arduino. Abre tu IDE de Arduino, haz clic en Sketch > Include Library > Add .ZIP Library.

Elige el archivo zip que acabas de descargar, y si la biblioteca se instala correctamente, verás Library added to your libraries en la ventana de notificación. Lo que significa que la biblioteca se instaló exitosamente.

Paso 3. Ejecutar el sketch de ejemplo
Copia el código de inferencia de abajo y pégalo en tu Arduino IDE.
/* Edge Impulse Arduino examples
* Copyright (c) 2022 EdgeImpulse Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/* Includes ---------------------------------------------------------------- */
#include <XIAO_esp32S3_CatDog2_inferencing.h>
#include "edge-impulse-sdk/dsp/image/image.hpp"
#include "esp_camera.h"
// Select camera model - find more camera models in camera_pins.h file here
// https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Camera/CameraWebServer/camera_pins.h
#define CAMERA_MODEL_XIAO_ESP32S3 // Has PSRAM
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 10
#define SIOD_GPIO_NUM 40
#define SIOC_GPIO_NUM 39
#define Y9_GPIO_NUM 48
#define Y8_GPIO_NUM 11
#define Y7_GPIO_NUM 12
#define Y6_GPIO_NUM 14
#define Y5_GPIO_NUM 16
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 17
#define Y2_GPIO_NUM 15
#define VSYNC_GPIO_NUM 38
#define HREF_GPIO_NUM 47
#define PCLK_GPIO_NUM 13
#define LED_GPIO_NUM 21
/* Constant defines -------------------------------------------------------- */
#define EI_CAMERA_RAW_FRAME_BUFFER_COLS 320
#define EI_CAMERA_RAW_FRAME_BUFFER_ROWS 240
#define EI_CAMERA_FRAME_BYTE_SIZE 3
/* Private variables ------------------------------------------------------- */
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
static bool is_initialised = false;
uint8_t *snapshot_buf; //points to the output of the capture
static camera_config_t camera_config = {
.pin_pwdn = PWDN_GPIO_NUM,
.pin_reset = RESET_GPIO_NUM,
.pin_xclk = XCLK_GPIO_NUM,
.pin_sscb_sda = SIOD_GPIO_NUM,
.pin_sscb_scl = SIOC_GPIO_NUM,
.pin_d7 = Y9_GPIO_NUM,
.pin_d6 = Y8_GPIO_NUM,
.pin_d5 = Y7_GPIO_NUM,
.pin_d4 = Y6_GPIO_NUM,
.pin_d3 = Y5_GPIO_NUM,
.pin_d2 = Y4_GPIO_NUM,
.pin_d1 = Y3_GPIO_NUM,
.pin_d0 = Y2_GPIO_NUM,
.pin_vsync = VSYNC_GPIO_NUM,
.pin_href = HREF_GPIO_NUM,
.pin_pclk = PCLK_GPIO_NUM,
//XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental)
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG, //YUV422,GRAYSCALE,RGB565,JPEG
.frame_size = FRAMESIZE_QVGA, //QQVGA-UXGA Do not use sizes above QVGA when not JPEG
.jpeg_quality = 12, //0-63 lower number means higher quality
.fb_count = 1, //if more than one, i2s runs in continuous mode. Use only with JPEG
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};
/* Function definitions ------------------------------------------------------- */
bool ei_camera_init(void);
void ei_camera_deinit(void);
bool ei_camera_capture(uint32_t img_width, uint32_t img_height, uint8_t *out_buf) ;
/**
* @brief Arduino setup function
*/
void setup()
{
// put your setup code here, to run once:
Serial.begin(115200);
//comment out the below line to start inference immediately after upload
while (!Serial);
Serial.println("Edge Impulse Inferencing Demo");
if (ei_camera_init() == false) {
ei_printf("Failed to initialize Camera!\r\n");
}
else {
ei_printf("Camera initialized\r\n");
}
ei_printf("\nStarting continious inference in 2 seconds...\n");
ei_sleep(2000);
}
/**
* @brief Get data and run inferencing
*
* @param[in] debug Get debug info if true
*/
void loop()
{
// instead of wait_ms, we'll wait on the signal, this allows threads to cancel us...
if (ei_sleep(5) != EI_IMPULSE_OK) {
return;
}
snapshot_buf = (uint8_t*)malloc(EI_CAMERA_RAW_FRAME_BUFFER_COLS * EI_CAMERA_RAW_FRAME_BUFFER_ROWS * EI_CAMERA_FRAME_BYTE_SIZE);
// check if allocation was successful
if(snapshot_buf == nullptr) {
ei_printf("ERR: Failed to allocate snapshot buffer!\n");
return;
}
ei::signal_t signal;
signal.total_length = EI_CLASSIFIER_INPUT_WIDTH * EI_CLASSIFIER_INPUT_HEIGHT;
signal.get_data = &ei_camera_get_data;
if (ei_camera_capture((size_t)EI_CLASSIFIER_INPUT_WIDTH, (size_t)EI_CLASSIFIER_INPUT_HEIGHT, snapshot_buf) == false) {
ei_printf("Failed to capture image\r\n");
free(snapshot_buf);
return;
}
// Run the classifier
ei_impulse_result_t result = { 0 };
EI_IMPULSE_ERROR err = run_classifier(&signal, &result, debug_nn);
if (err != EI_IMPULSE_OK) {
ei_printf("ERR: Failed to run classifier (%d)\n", err);
return;
}
// print the predictions
ei_printf("Predictions (DSP: %d ms., Classification: %d ms., Anomaly: %d ms.): \n",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
#if EI_CLASSIFIER_OBJECT_DETECTION == 1
bool bb_found = result.bounding_boxes[0].value > 0;
for (size_t ix = 0; ix < result.bounding_boxes_count; ix++) {
auto bb = result.bounding_boxes[ix];
if (bb.value == 0) {
continue;
}
ei_printf(" %s (%f) [ x: %u, y: %u, width: %u, height: %u ]\n", bb.label, bb.value, bb.x, bb.y, bb.width, bb.height);
}
if (!bb_found) {
ei_printf(" No objects found\n");
}
#else
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: %.5f\n", result.classification[ix].label,
result.classification[ix].value);
}
#endif
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(" anomaly score: %.3f\n", result.anomaly);
#endif
free(snapshot_buf);
}
/**
* @brief Setup image sensor & start streaming
*
* @retval false if initialisation failed
*/
bool ei_camera_init(void) {
if (is_initialised) return true;
#if defined(CAMERA_MODEL_ESP_EYE)
pinMode(13, INPUT_PULLUP);
pinMode(14, INPUT_PULLUP);
#endif
//initialize the camera
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x\n", err);
return false;
}
sensor_t * s = esp_camera_sensor_get();
// initial sensors are flipped vertically and colors are a bit saturated
if (s->id.PID == OV3660_PID) {
s->set_vflip(s, 1); // flip it back
s->set_brightness(s, 1); // up the brightness just a bit
s->set_saturation(s, 0); // lower the saturation
}
#if defined(CAMERA_MODEL_M5STACK_WIDE)
s->set_vflip(s, 1);
s->set_hmirror(s, 1);
#elif defined(CAMERA_MODEL_ESP_EYE)
s->set_vflip(s, 1);
s->set_hmirror(s, 1);
s->set_awb_gain(s, 1);
#endif
is_initialised = true;
return true;
}
/**
* @brief Stop streaming of sensor data
*/
void ei_camera_deinit(void) {
//deinitialize the camera
esp_err_t err = esp_camera_deinit();
if (err != ESP_OK)
{
ei_printf("Camera deinit failed\n");
return;
}
is_initialised = false;
return;
}
/**
* @brief Capture, rescale and crop image
*
* @param[in] img_width width of output image
* @param[in] img_height height of output image
* @param[in] out_buf pointer to store output image, NULL may be used
* if ei_camera_frame_buffer is to be used for capture and resize/cropping.
*
* @retval false if not initialised, image captured, rescaled or cropped failed
*
*/
bool ei_camera_capture(uint32_t img_width, uint32_t img_height, uint8_t *out_buf) {
bool do_resize = false;
if (!is_initialised) {
ei_printf("ERR: Camera is not initialized\r\n");
return false;
}
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
ei_printf("Camera capture failed\n");
return false;
}
bool converted = fmt2rgb888(fb->buf, fb->len, PIXFORMAT_JPEG, snapshot_buf);
esp_camera_fb_return(fb);
if(!converted){
ei_printf("Conversion failed\n");
return false;
}
if ((img_width != EI_CAMERA_RAW_FRAME_BUFFER_COLS)
|| (img_height != EI_CAMERA_RAW_FRAME_BUFFER_ROWS)) {
do_resize = true;
}
if (do_resize) {
ei::image::processing::crop_and_interpolate_rgb888(
out_buf,
EI_CAMERA_RAW_FRAME_BUFFER_COLS,
EI_CAMERA_RAW_FRAME_BUFFER_ROWS,
out_buf,
img_width,
img_height);
}
return true;
}
static int ei_camera_get_data(size_t offset, size_t length, float *out_ptr)
{
// we already have a RGB888 buffer, so recalculate offset into pixel index
size_t pixel_ix = offset * 3;
size_t pixels_left = length;
size_t out_ptr_ix = 0;
while (pixels_left != 0) {
out_ptr[out_ptr_ix] = (snapshot_buf[pixel_ix] << 16) + (snapshot_buf[pixel_ix + 1] << 8) + snapshot_buf[pixel_ix + 2];
// go to the next pixel
out_ptr_ix++;
pixel_ix+=3;
pixels_left--;
}
// and done!
return 0;
}
#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_CAMERA
#error "Invalid model for current sensor"
#endif
Asegúrate de reemplazar el #include <XIAO_esp32S3_CatDog2_inferencing.h>
con la biblioteca que generaste. Sube el código y apunta a las imágenes de Gato o Perro o a las reales, podemos ver los resultados en el Monitor Serie.
¡Felicidades 🙌 por completar el proyecto de clasificación de imágenes tinyML!
Recursos
[PDF] Hoja de datos ESP32-S3
Para Seeed Studio XIAO ESP32S3
Para Seeed Studio XIAO ESP32S3 Sense
-
[DXF] Dimensiones Seeed Studio XIAO ESP32S3 Sense en DXF (superior)
-
[DXF] Dimensiones Seeed Studio XIAO ESP32S3 Sense en DXF (inferior)
Otros
¡El material de código abierto restante se está compilando, así que mantente atento!
Soporte Técnico y Discusión de Productos
¡Gracias por elegir nuestros productos! Estamos aquí para brindarle diferentes tipos de soporte para asegurar que su experiencia con nuestros productos sea lo más fluida posible. Ofrecemos varios canales de comunicación para satisfacer diferentes preferencias y necesidades.