最近在项目开发中,我尝试使用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 开发板选择与配置
- 插入XIAO ESP32S3开发板,打开Arduino IDE。
- 在
工具 -> 开发板
中选择XIAO_ESP32S3
。 - 工具配置选项见下图:
-
三、实现颜色识别的完整代码
以下代码实现了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 0x52static 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