项目名称:智能物体检测与监控系统
项目介绍:
智能物体检测与监控系统是基于ESP32开发板和Edge Impulse SDK开发的一款智能监控设备。该系统具有实时物体检测、网络连接、时间同步、OLED显示、按键控制等功能。用户可以通过该系统监控特定区域内的物体,并实时获取检测结果。
本次识别的物体是橙子和一个星星玩具。首先利用Seeed XIAO ESP32S3 Sense对橙子和星星拍照采样,拍了大概100张左右的照片,然后把这些图片上传至edge impulse 平台进行模型训练,之后将导出的Arduino项目代码上传至开发板,与自己的代码整合后烧录进开发板
主要功能和特点:
- 物体检测:利用Edge Impulse SDK进行实时物体检测,通过相机捕获图像并用Edge Impulse训练的模型进行处理,最终在OLED显示屏上显示检测结果。
- 网络连接:通过WiFi连接到无线网络,实现远程监控和数据传输。
- 时间同步:利用NTPClient库同步设备时间,确保系统具有准确的时间信息。
- 显示功能:通过OLED显示屏显示网络连接状态、电池状态、菜单选项等信息,提供用户友好的操作界面。
- 按键控制:通过按键控制菜单的导航和选择,实现用户与系统的交互。
- 摄像头服务器:启动摄像头服务器,允许用户通过网络流式传输摄像头的视频,实现远程监控功能。
- 其他功能:包括电池状态显示、信号强度显示等。
项目背景:
随着物联网技术的发展,智能监控设备在家庭安全、工业生产等领域得到了广泛应用。本项目旨在利用低成本的硬件平台和开源的软件工具,构建一款功能丰富、易于部署的智能监控系统,满足用户对安全监控的需求。
未来展望:
在未来,可以进一步优化系统的性能和稳定性,增加更多的监控功能和智能算法,如行人识别、车辆识别等,提高系统的智能化水平。同时,可以考虑将系统与云平台集成,实现远程管理和数据存储,拓展其应用场景和商业化价值。
简单的硬件介绍:
Seeed XIAO ESP32S3 Sense开发板:作为系统的主控制器,负责控制各个外设的操作和数据传输。
Seeed Studio XIAO ESP32S3 Sense 是一款功能强大、易于使用的开发板,专为智能语音和视觉 AI 应用而设计。它集成了摄像头传感器、数字麦克风和 SD 卡支持,并具有强大的嵌入式 ML 计算能力。
主要特点:
- 搭载 Espressif ESP32-S3R8 芯片,支持 2.4GHz Wi-Fi 和低功耗蓝牙 BLE 5.0 双模
- 内置 8MB PSRAM 和 8MB Flash
- 拥有 1600 x 1200 分辨率的 OV2640 摄像头传感器
- 支持数字麦克风阵列
- 支持 microSD 卡扩展
- 内置锂电池充电管理功能
应用领域:
- 智能家居
- 可穿戴设备
- 工业控制
- 环境监测
- 机器视觉
示例项目:
- 智能家居摄像头
- 语音控制机器人
- 人脸识别门禁系统
- 环境监测系统
- 机器学习图像分类
优势:
- 小巧玲珑,尺寸仅有拇指大小
- 功能强大,支持多种功能
- 易于使用,支持 Arduino 和 MicroPython 开发环境
总结:
Seeed Studio XIAO ESP32S3 Sense 是一款功能强大、易于使用的开发板,是您开始学习智能语音和视觉 AI 的绝佳工具
OLED12864 是一种常见的单色OLED显示屏,具有以下特点:
- 分辨率为128 x 64像素
- 显示效果清晰
- 功耗低
- 体积小巧
OLED12864由一个OLED显示屏和一个驱动芯片组成。OLED显示屏由像素矩阵组成,每个像素由一个有机发光二极管组成。驱动芯片负责控制OLED显示屏的显示内容。
OLED12864可以显示文本、图形和图像。它可以用于各种应用,包括:
- 电子设备的显示屏
- 仪表仪表的显示屏
- 工业控制设备的显示屏
OLED12864的接口类型包括:
I2C
SPI
8080并行接口
微动按键:
造型小巧,适合用于交互设计的场景充当按键
实现功能:
开机界面:
获取实时时间:
主功能菜单:
查看实时网络信息:
物体识别界面:
局域网摄像头功能:
重置本机网络界面:
主要代码说明:
这里做了一个状态机
利用中断里改变全局变量enter的值来改变要在屏幕上输出的内容,是哪个界面。
switch (enter) {
case 0:
if (clc == 1) {
display.clearDisplay();
display_signal();
display_battery();
String currentTime = timeClient.getFormattedTime();
currentTime.remove(5, 3);
display.setTextSize(2);
display.setCursor(30, 30);
display.print(currentTime);
display.display();
clc = false;
}
//使用更新时间
if (timeClient.update()) {
display.clearDisplay();
display_signal();
display_battery();
displayout("Online", 46, 4);
String currentTime = timeClient.getFormattedTime();
currentTime.remove(5, 3);
if (currentTime != previousTime) {
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(30, 30);
display.print(currentTime);
display_signal();
displayout("Online", 46, 4);
display_battery();
display.display();
previousTime = currentTime;
}
}
break;
case 1:
//stopCameraServer();
WiFi.setSleep(true);
display.clearDisplay();
//display.display();
displayout("Menu", 46, 4);
display_signal();
display_battery();
interface_menu(&menu_1);
select_icon(x);
display.display();
break;
case 2:
display_signal();
display_battery();
//select_icon(x);
display.display();
下面利用全局变量x来筛选选择了哪一个功能
switch (x) {
case 13:
display.clearDisplay();
//display.display();
displayout("Network", 46, 4);
display_signal();
display_battery();
interface_menu(&menu_1_1);
Internet();
display.display();
break;
case 23:
display.clearDisplay();
display_signal();
display_battery();
interface_menu(&menu_1_2);
display.display();
// 检查是否需要进行检测
if (Dection_status) {
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) {
menu_1_2.fourth_text="buffer error";
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) {
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) {
return;
}
#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;
}
// Display only the label on the screen
displayout_s(bb.label, 10, 10); // Adjust position as needed
}
if (!bb_found) {
menu_1_2.fourth_text="no object";
}
#else
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
// Display only the label on the screen
displayout_s(result.classification[ix].label, 10, 10); // Adjust position as needed
menu_1_2.fourth.text=result.classification[ix].label;
}
#endif
free(snapshot_buf);
display.display();
}
break;
case 33:
display.clearDisplay();
//display.display();
display_signal();
display_battery();
interface_menu(&menu_1_3);
display.display();
WiFi.setSleep(false);
startCameraServer();
while(Web_status)
{
delay(10000);
}
break;
case 43:
display.clearDisplay();
//display.display();
display_signal();
display_battery();
interface_menu(&menu_1_4);
display.display();
WiFi.begin(ssid, password);
//Get_net(&ESP32_NET);
break;
}
这里利用状态机筛选功能把Edge impulse 的训练出来的模型用于物体识别
case 23:
display.clearDisplay();
display_signal();
display_battery();
interface_menu(&menu_1_2);
display.display();
// 检查是否需要进行检测
if (Dection_status) {
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) {
menu_1_2.fourth_text="buffer error";
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) {
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) {
return;
}
#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;
}
// Display only the label on the screen
displayout_s(bb.label, 10, 10); // Adjust position as needed
}
if (!bb_found) {
menu_1_2.fourth_text="no object";
}
#else
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
// Display only the label on the screen
displayout_s(result.classification[ix].label, 10, 10); // Adjust position as needed
menu_1_2.fourth.text=result.classification[ix].label;
}
#endif
free(snapshot_buf);
display.display();
}
break;
这里的结构体声明并定义了每个界面输出的内容
/* 输出函数 */
extern void displayout(char *pt, int x, int y);
extern void displayout_s(const String pt, int x, int y);
extern const char *ssid;
extern const char *password;
/* 定义屏幕显示内容结构体 */
struct interface {
unsigned int first_text_x;
unsigned int first_text_y;
char *first_text;
unsigned int second_text_x;
unsigned int second_text_y;
char *second_text;
unsigned int third_text_x;
unsigned int third_text_y;
char *third_text;
unsigned int fourth_text_x;
unsigned int fourth_text_y;
char *fourth_text;
};
/* 定义显示菜单的函数 */
void interface_menu(struct interface *menu) {
displayout(menu->first_text, menu->first_text_x, menu->first_text_y);
displayout(menu->second_text, menu->second_text_x, menu->second_text_y);
displayout(menu->third_text, menu->third_text_x, menu->third_text_y);
displayout(menu->fourth_text, menu->fourth_text_x, menu->fourth_text_y);
}
/*输出网络信息*/
void Internet() {
displayout_s(WiFi.localIP().toString().c_str(), 50, 17);
displayout_s(WiFi.dnsIP().toString().c_str(), 50, 27);
displayout_s(WiFi.gatewayIP().toString().c_str(), 50, 37);
displayout_s("xfp23", 50, 47);
}
/* 定义菜单项结构体 */
struct interface menu_1 = {
.first_text_x = TEXT_X,
.first_text_y = 17,
.first_text = "NetWork",
.second_text_x = TEXT_X,
.second_text_y = 27,
.second_text = "Face dection",
.third_text_x = TEXT_X,
.third_text_y = 37,
.third_text = "LAN camera",
.fourth_text_x = TEXT_X,
.fourth_text_y = 47,
.fourth_text = "Restart network",
};
struct interface menu_1_1 = {
.first_text_x = 5,
.first_text_y = 17,
.first_text = "IP: ",
.second_text_x = 5,
.second_text_y = 27,
.second_text = "Gateway: ",
.third_text_x = 5,
.third_text_y = 37,
.third_text = "DNS: ",
.fourth_text_x = 5,
.fourth_text_y = 47,
.fourth_text = "SSID: ",
};
struct interface menu_1_2 = {
.first_text_x = 46,
.first_text_y = 4,
.first_text = "Dection",
.second_text_x = TEXT_X,
.second_text_y = 17,
.second_text = "Object dection",
.third_text_x = TEXT_X,
.third_text_y = 27,
.third_text = "Dection resultS:",
.fourth_text_x = TEXT_X,
.fourth_text_y = 47,
.fourth_text = " "
};
struct interface menu_1_3 = {
.first_text_x = 46,
.first_text_y = 4,
.first_text = "IPC",
.second_text_x = TEXT_X,
.second_text_y = 17,
.second_text = "Enter the IP address view",
.third_text_x = TEXT_X,
.third_text_y = 27,
.third_text = " ",
.fourth_text_x = TEXT_X,
.fourth_text_y = 47,
.fourth_text = " "
};
struct interface menu_1_4 = {
.first_text_x = 46,
.first_text_y = 4,
.first_text = "REBOOT",
.second_text_x = TEXT_X,
.second_text_y = 17,
.second_text = "Network reconnecting ......",
.third_text_x = TEXT_X,
.third_text_y = 27,
.third_text = " ",
.fourth_text_x = TEXT_X,
.fourth_text_y = 47,
.fourth_text = " "
};
按键的消抖:
const unsigned int JITTERTIME=200;//消抖时间
volatile unsigned long int Enter_key_LASTTIME=0;
volatile unsigned long int Return_key_LASTTIME=0;
volatile unsigned long int Menu_selection_key_LASTTIME=0;
volatile unsigned long int Current_time=0;
Current_time=millis();
if(Current_time-Enter_key_LASTTIME>=JITTERTIME)
{
Enter_key_LASTTIME=Current_time;
if (enter >= 0 && enter <= 2) {
enter += 1;
}
Current_time=millis();
if(Current_time-Return_key_LASTTIME>=JITTERTIME){
Return_key_LASTTIME=Current_time;
Current_time=millis();
if(Current_time-Menu_selection_key_LASTTIME>=JITTERTIME){
Menu_selection_key_LASTTIME=Current_time;
这里是返回键的服务函数:
extern volatile unsigned short int enter;
extern volatile unsigned short int x;
extern volatile unsigned long int Return_key_LASTTIME;
extern const unsigned int JITTERTIME;
extern volatile unsigned long int Current_time;
extern volatile bool Web_status;
extern volatile bool Dection_status;
extern bool clc;
extern void return_down() {
Current_time=millis();
if(Current_time-Return_key_LASTTIME>=JITTERTIME){
Return_key_LASTTIME=Current_time;
if (enter > 0 && enter <= 2) {
enter -= 1;
} else {
enter = 0;
clc=true;
x = 13;
}
if(x==33&&Web_status==true)
{
Web_status=false;
}
if(x==22&&Dection_status==true)
{
Dection_status=false;
}
}
}
这里是确认键的服务函数:
extern volatile unsigned short int enter;
extern volatile unsigned short int x;
extern volatile unsigned long int Enter_key_LASTTIME;
extern volatile unsigned long int Current_time;
extern const unsigned int JITTERTIME;
extern volatile bool Dection_status;
/*确认键设置*/
extern void enter_down() {
Current_time=millis();
if(Current_time-Enter_key_LASTTIME>=JITTERTIME)
{
Enter_key_LASTTIME=Current_time;
if (enter >= 0 && enter <= 2) {
enter += 1;
}
if(x==22&&Dection_status==false)
{
Dection_status=true;
}
}
}
这里是选择键的服务函数:
extern void displayout(char *pt,int x,int y);
/*绘制选择图标*/
extern void select_icon(int cur) {
int sur = cur + 5;
int a = cur, b = sur;
for (int x = 0; x < 5; x++) {
for (int y = cur; y < sur; y++) {
if (cur <= (a + b) / 2 && sur >= (a + b) / 2) {
displayout(".", x, y);
}
}
sur -= 1;
cur += 1;
}
}
这里是等待连接WiFi的函数:
#include <WiFi.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
extern Adafruit_SSD1306 display;
extern void display_signal();
extern void display_battery();
extern void displayout(char *pt, int x, int y);
extern void wait_to_wifi() {
static unsigned int point_x = 20, point_y = 30; // 声明为静态变量
display_battery();
display_signal();
while (WiFi.status() != WL_CONNECTED) {
display_battery();
displayout("Connecting to WiFi", 11, 20);
point_x += 5;
displayout(".", point_x, point_y);
display.display();
delay(500);
if(point_x>=60)
{
display.clearDisplay();
display.display();
point_x=20;
}
}
}
遇到的问题和解决方法:
1.按键消抖问题:在项目进行的过程中,由于不能在中断函数里不能出现返回值和使用延时,思考了很久怎么消抖的问题。
解决方法:利用了单片机的定时器消抖。
2.物体识别率过低问题:在edge impulse平台训练的AI模型识别率过低
解决方法:重新利用开发板拍照,在纯白的背景下拍照。
3.单片机运行问题:在项目进行过程中,把编译成功的程序烧录进开发板,开发板无法正常运行
解决方法:重构了代码,修复了部分函数返回空指针的bug,开启了ardiuno 的OPI PSRAM
未来的计划:
未来计划是进一步优化系统性能和稳定性,增加更多的监控功能和智能算法,如行人识别、车辆识别等,提高系统的智能化水平。同时,可以考虑将系统与云平台集成,实现远程管理和数据存储,拓展其应用场景和商业化价值。建议对系统的用户界面进行优化,提供更友好、直观的操作界面,提升用户体验。