最近在项目开发中,我尝试使用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 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