使用XiaoESP32S3在Arduino环境中实现颜色识别

最近在项目开发中,我尝试使用XIAO ESP32S3搭载OV2640摄像头,完成对颜色色块的实时识别。本篇博客将系统性地介绍如何在Arduino环境中使用XIAO ESP32S3实现颜色识别,并分享完整代码。


请添加图片描述

一、硬件准备

1.1 必备硬件

  • XIAO ESP32S3 开发板(带PSRAM)
  • OV2640摄像头模块 (广角 80°)
  • USB数据线
    在这里插入图片描述

二、开发环境搭建

2.1 安装Arduino IDE

  • 下载并安装Arduino IDE(推荐版本:2.2.1)。
  • 打开文件 -> 首选项,在“附加开发板管理器网址”中填写以下地址:https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
    点击确定后,到工具 -> 开发板 -> 开发板管理器中搜索esp32,选择esp32 by Espressif Systems进行安装。

2.2 开发板选择与配置

  1. 插入XIAO ESP32S3开发板,打开Arduino IDE。
  2. 工具 -> 开发板中选择XIAO_ESP32S3
  3. 工具配置选项见下图:
    -在这里插入图片描述

三、实现颜色识别的完整代码

以下代码实现了XIAO ESP32S3对红、黄、绿、蓝、紫五种颜色色块的实时识别,并通过IIC接口传输检测结果。
项目结构:

project/
├── src/
│   ├── app_httpd.cpp
│   ├── camera_setting.c
│   ├── color_detection.cpp
│   ├── iic_data_send.cpp
│   └── XIAO_ESP32S3_color_WIFI_5s.ino
├── include/
│   ├── camera_index.h
│   ├── camera_setting.h
│   ├── color_detection.hpp
│   └── iic_data_send.hpp
├── data/
│   └── partitions.csv

3.1 主程序:颜色识别与WiFi热点功能

XIAO_ESP32S3_color_WIFI_5s.ino

#include "camera_setting.h"
#include "color_detection.hpp"
#include "iic_data_send.hpp"
#include "esp_camera.h"
#include <WiFi.h>

#define CAMERA_MODEL_XIAO_ESP32S3 // 使用XIAO ESP32S3摄像头
static QueueHandle_t xQueueAIFrame = NULL;
static QueueHandle_t xQueueIICData = NULL;

const char* ssid = "ESP123-3";  // 自定义WiFi名称
const char* password = "12345678";  // 自定义WiFi密码

void startCameraServer();

void setup() {
Serial.begin(115200);
Serial.println();

// 创建图像传输队列
xQueueAIFrame = xQueueCreate(2, sizeof(camera_fb_t *)); 
// 创建IIC数据传输队列
xQueueIICData = xQueueCreate(2, sizeof(color_data_t *) * SEND_CLOLOR_NUM);

// 注册摄像头任务
register_camera(PIXFORMAT_RGB565, FRAMESIZE_240X240, 4, xQueueAIFrame);
// 注册颜色检测任务
register_color_detection(xQueueAIFrame, NULL, xQueueIICData, NULL, true);
// 注册IIC数据传输任务
register_iic_data_send(xQueueIICData, NULL);

// 开启WiFi热点
WiFi.softAP(ssid, password);
startCameraServer();

Serial.print("Camera Ready! Use 'http://");
Serial.print(WiFi.localIP());
Serial.println("' to connect");
}

void loop() {
delay(1000);
}

这里我添加了wifi热点模式,连接对应的热点名称(ESP123-3,可以看到摄像头画面,但是因为串口一直在输出,所以画面会很卡顿,请耐心!)

3.2 颜色检测模块

color_detection.cpp
颜色识别算法的主要逻辑是:从摄像头获取图像帧,对图像进行颜色检测,找到每种颜色的最大色块,记录其中心点坐标、宽度和长度信息,最后将识别结果通过队列传递给其他任务。通过这种方式,可以实现实时的颜色识别功能。

#include "color_detection.hpp"
#include "esp_log.h"
#include "esp_camera.h"
#include "dl_image.hpp"
#include "fb_gfx.h"
#include "color_detector.hpp"


using namespace std;
using namespace dl;

static const char *TAG = "color_detection";

static QueueHandle_t xQueueFrameI = NULL;
static QueueHandle_t xQueueEvent = NULL;
static QueueHandle_t xQueueFrameO = NULL;
static QueueHandle_t xQueueResult = NULL;

static bool gReturnFB = true;
static int g_max_color_area = 0;
color_data_t color_data[5];

/* 颜色阈值 可在此处调整HSV */
vector<color_info_t> std_color_info = {
    {{151, 15, 70, 255, 90, 255}, 64, "red"},
    {{23, 34, 70, 255, 90, 255}, 64, "yellow"},
    {{45, 75, 70, 255, 90, 255}, 64, "green"},
    {{97, 117, 70, 255, 90, 255}, 64, "blue"},
    {{130, 155, 70, 255, 90, 255}, 64, "purple"}
};

static uint8_t state_value;

/* 获取颜色检测的结果 */
static void get_color_detection_result(uint16_t *image_ptr, int image_height, int image_width, vector<color_detect_result_t> &results, uint16_t color)
{
  int g_max_color_column_index = 0;
  /* 寻找同色最大色块 */
  for (int i = 0; i < results.size(); ++i)
  {
    if (results[i].area > g_max_color_area)
    {
      g_max_color_area= results[i].area;
      g_max_color_column_index = i;
    }
    
    switch (color)
    {
      case COLOR_RED:

        color_data[0].center_x = (uint8_t)results[g_max_color_column_index].center[0];
        color_data[0].center_y = (uint8_t)results[g_max_color_column_index].center[1];
        /* right_down_x - left_up_x  */
        color_data[0].width = (uint8_t)(results[g_max_color_column_index].box[2] - results[g_max_color_column_index].box[0]);
        /* right_down_y - left_up_y  */
        color_data[0].length = (uint8_t)(results[g_max_color_column_index].box[3] - results[g_max_color_column_index].box[1]);
        break;

      case COLOR_YELLOW:

        color_data[1].center_x = (uint8_t)results[g_max_color_column_index].center[0];
        color_data[1].center_y = (uint8_t)results[g_max_color_column_index].center[1];
        /* right_down_x - left_up_x  */
        color_data[1].width = (uint8_t)(results[g_max_color_column_index].box[2] - results[g_max_color_column_index].box[0]);
        /* right_down_y - left_up_y  */
        color_data[1].length = (uint8_t)(results[g_max_color_column_index].box[3] - results[g_max_color_column_index].box[1]);
        break;

      case COLOR_GREEN:

        color_data[2].center_x = (uint8_t)results[g_max_color_column_index].center[0];
        color_data[2].center_y = (uint8_t)results[g_max_color_column_index].center[1];
        /* right_down_x - left_up_x  */
        color_data[2].width = (uint8_t)(results[g_max_color_column_index].box[2] - results[g_max_color_column_index].box[0]);
        /* right_down_y - left_up_y  */
        color_data[2].length = (uint8_t)(results[g_max_color_column_index].box[3] - results[g_max_color_column_index].box[1]);
        break;

      case COLOR_BLUE:

        color_data[3].center_x = (uint8_t)results[g_max_color_column_index].center[0];
        color_data[3].center_y = (uint8_t)results[g_max_color_column_index].center[1];
        /* right_down_x - left_up_x  */
        color_data[3].width = (uint8_t)(results[g_max_color_column_index].box[2] - results[g_max_color_column_index].box[0]);
        /* right_down_y - left_up_y  */
        color_data[3].length = (uint8_t)(results[g_max_color_column_index].box[3] - results[g_max_color_column_index].box[1]);
        break;

      case COLOR_PURPLE:

        color_data[4].center_x = (uint8_t)results[g_max_color_column_index].center[0];
        color_data[4].center_y = (uint8_t)results[g_max_color_column_index].center[1];
        /* right_down_x - left_up_x  */
        color_data[4].width = (uint8_t)(results[g_max_color_column_index].box[2] - results[g_max_color_column_index].box[0]);
        /* right_down_y - left_up_y  */
        color_data[4].length = (uint8_t)(results[g_max_color_column_index].box[3] - results[g_max_color_column_index].box[1]);
        break;

      default:
        break;
    }    
  }
}

static void task_process_handler(void *arg)
{
  camera_fb_t *frame = NULL;
  ColorDetector detector;
  /* 注册颜色信息 */
  for (int i = 0; i < std_color_info.size(); ++i)
  {
    detector.register_color(std_color_info[i].color_thresh, std_color_info[i].area_thresh, std_color_info[i].name);
  }
  vector<uint16_t> draw_colors = {
    COLOR_RED,
    COLOR_YELLOW,
    COLOR_GREEN,
    COLOR_BLUE,
    COLOR_PURPLE,
  };
  int draw_colors_num = draw_colors.size();
  while (true)
  {
    // printf("center_x:%d\r\n", color_data[3].center_x);
    if (xQueueReceive(xQueueFrameI, &frame, portMAX_DELAY))
    {
      std::vector<std::vector<color_detect_result_t>> &results = detector.detect((uint16_t *)frame->buf, {(int)frame->height, (int)frame->width, 3});
      for(int i = 0; i < COLOR_NUM; ++i)
      {
        if(results[i].size() == 0)
        {
          
          color_data[i].center_x = 0;
          color_data[i].center_y = 0;
          color_data[i].width = 0;
          color_data[i].length = 0;
        }else{
          printf("Color:[%d] \r\n", i);
        }
      }
      
      for (int i = 0; i < results.size(); ++i)
      {
        get_color_detection_result((uint16_t *)frame->buf, (int)frame->height, (int)frame->width, results[i], draw_colors[i % draw_colors_num]);
      }
    }
    if (xQueueFrameO)
    {
      xQueueSend(xQueueFrameO, &frame, portMAX_DELAY);
    }
    else if (gReturnFB)
    {
      esp_camera_fb_return(frame);
    }
    else
    {
      free(frame);
    }
    if (xQueueResult)
    {
      xQueueSend(xQueueResult, &color_data, portMAX_DELAY);             
    }          
  }
}

static void task_event_handler(void *arg)
{
    while (true)
    {
    }
}

void register_color_detection(const QueueHandle_t frame_i,
                                   const QueueHandle_t event,
                                   const QueueHandle_t result,
                                   const QueueHandle_t frame_o,
                                   const bool camera_fb_return)
{
  xQueueFrameI = frame_i;
  xQueueFrameO = frame_o;
  xQueueEvent = event;
  xQueueResult = result;
  gReturnFB = camera_fb_return;

  xTaskCreatePinnedToCore(task_process_handler, TAG, 4 * 1024, NULL, 5, NULL, 1);
  // xTaskCreatePinnedToCore(task_event_handler, TAG, 4 * 1024, NULL, 5, NULL, 0);
}

颜色阈值 可在此处调整HSV
vector<color_info_t> std_color_info = {
{{151, 15, 70, 255, 90, 255}, 64, “red”},
{{23, 34, 70, 255, 90, 255}, 64, “yellow”},
{{45, 75, 70, 255, 90, 255}, 64, “green”},
{{97, 117, 70, 255, 90, 255}, 64, “blue”},
{{130, 155, 70, 255, 90, 255}, 64, “purple”}
};根据摄像头拍摄的画面,调整颜色的HSV阈值,上面的参数分别代表, H_min, H_max, S_min, S_max, V_min, V_max

3.3 在运行窗口打印出检测数值

通过IIC接口,可以将检测结果实时传输到其他模块
iic_data_send.cpp

#include "iic_data_send.hpp"
#include "Wire.h"

#define I2C_SLAVE_ADDRESS 0x52

static QueueHandle_t xQueueResultI = NULL;
static QueueHandle_t xQueueResultO = NULL;

static const char *TAG = "iic_data_send";
static const int sdaPin = 5;
static const int sclPin = 6;
static const uint32_t i2cFrequency = 100000;

send_color_data_t send_color_data[5];

static uint8_t rec = 0xFF;
static uint8_t send_data[4] = {0};

static void iic_receive(int len)
{
  while(Wire.available())
  {
    rec = Wire.read();
  }  
}

static void iic_request()
{
    /* 发送红色色块数据 */
    if(rec == 0x00) 
    {
        send_data[0] = send_color_data[0].center_x;
        send_data[1] = send_color_data[0].center_y;
        send_data[2] = send_color_data[0].width;
        send_data[3] = send_color_data[0].length;

    }
    /* 发送绿色色块数据 */
    else if(rec == 0x01)
    {
        send_data[0] = send_color_data[1].center_x;
        send_data[1] = send_color_data[1].center_y;
        send_data[2] = send_color_data[1].width;
        send_data[3] = send_color_data[1].length;

    }
    /* 发送蓝色色块数据 */
    else if(rec == 0x02)
    {
        send_data[0] = send_color_data[2].center_x;
        send_data[1] = send_color_data[2].center_y;
        send_data[2] = send_color_data[2].width;
        send_data[3] = send_color_data[2].length;

    }
    /* 发送黄色色块数据 */
    else if(rec == 0x03)
    {
        send_data[0] = send_color_data[3].center_x;
        send_data[1] = send_color_data[3].center_y;
        send_data[2] = send_color_data[3].width;
        send_data[3] = send_color_data[3].length;
      
    }
    /* 发送紫色色块数据 */
    else if(rec == 0x04)
    {
        send_data[0] = send_color_data[0].id;
        send_data[1] = send_color_data[1].id;
        send_data[2] = send_color_data[2].id;
        send_data[3] = send_color_data[3].id;      
    }
    /* 打包发送色块数据 */
    Wire.slaveWrite(send_data, sizeof(send_data));
}

static void task_process_handler(void *arg)
{
  /* IIC初始化 */
  Wire.begin((uint8_t)I2C_SLAVE_ADDRESS, sdaPin, sclPin, i2cFrequency);
  /* 注册接收数据的回调函数 */
  Wire.onReceive(iic_receive);
  /* 注册请求数据的回调函数 */
  Wire.onRequest(iic_request);

  while (true)
  {

    if (xQueueReceive(xQueueResultI, &send_color_data, portMAX_DELAY))
    {

        printf("center_x: red:%d yellow:%d green:%d  blue:%d  purple:%d\r\n", send_color_data[0].center_x, \
                                                                     send_color_data[1].center_x, \
                                                                     send_color_data[2].center_x, \
                                                                     send_color_data[3].center_x, \
                                                                     send_color_data[4].center_x);
    }
  }
}

void register_iic_data_send(const QueueHandle_t result_i,
                            const QueueHandle_t result_o)
{
  xQueueResultI = result_i;
  xQueueResultO = result_o;

  xTaskCreatePinnedToCore(task_process_handler, TAG, 5 * 1024, NULL, 5, NULL, 1);
}

四、效果展示

颜色识别:当摄像头检测到红、黄、绿、蓝、紫五种颜色的色块时,可以通过串口打印检测到的色块中心坐标及尺寸。
Web 端实时监控:连接热点“ESP32S3-3”浏览器打开“192.168.4.1”查看实时视频流
在这里插入图片描述
完整程序链接:通过网盘分享的文件:XIAO_ESP32S3_color_WIFI_5s.zip
链接: https://pan.baidu.com/s/1LIpI19jnzQ_ENh_wjTlozw 提取码: lyx4

ESP32-S3是一款由Espressif Systems开发的基于Arm Cortex-M4的单片机,它集成了Wi-Fi和蓝牙功能,并且拥有丰富的GPIO资源。要在Arduino环境使用ESP32S3控制无刷电机,你需要遵循以下步骤: 1. **安装库支持**: - 首先,确保已经安装了适用于ESP32 S3Arduino IDE插件,如`ESP32 Boards Manager`或`ESP32-S3 Library`。 2. **硬件连接**: - 将电机的电源、地线、以及三相脉冲信号(通常为PWM信号)分别连接到ESP32 S3的GPIO脚上。通常使用PWM输出控制电机速度。 3. **编写代码**: - 使用Arduino IDE创建一个新的项目,选择ESP32 S3作为板型。 - 引入电机库,例如`Adafruit_Motor_HBridge`或自定义PID控制库,以便控制电机转动。 - 编写电机初始化函数,设置GPIO为输出模式,并配置为PWM输出。 - 创建控制函数,通过改变PWM信号的占空比(0-255)来控制电机的速度和方向。 示例代码片段: ```cpp #include <Adafruit_Motor_HBridge.h> Adafruit_Motor_HBridge motor(MOTOR_A, MOTOR_B); //假设MOTOR_A和MOTOR_B是对应的电机连接 void setup() { motor.setSpeed(0); // 初始化GPIO为PWM pinMode(PWM_PIN, OUTPUT); } void loop() { int speed = map(analogRead PotentiometerPin, 0, 1023, 0, 255); //读取并映射ADC值到0-255范围 motor.setSpeed(speed); } ``` 4. **注意**: - 可能需要对电机的电压和电流规格进行适配,避免过载。 - 使用PID或其他反馈控制算法来确保精确的速度控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值