AI逗猫设备
项目背景
家中饲养的猫咪在白天长时间独自在家,由于工作繁忙,我们无法在白天陪伴它。猫咪独自在家时,缺乏足够的互动和运动,这不仅可能导致它感到孤独和焦虑,还可能影响它的身心健康。为了改善猫咪的独处时光,我们考虑设计一个自动逗猫器,以在白天为猫咪提供陪伴和娱乐。自动逗猫器可以模拟人类的互动,通过移动的激光来吸引猫咪的注意力,激发它的捕猎本能,促使它进行追逐和跳跃等身体活动。这样的设备不仅有助于猫咪保持活力和健康,还能给它带来乐趣,减少因缺乏陪伴而产生的压力和不良情绪。通过这种方式,我们可以在繁忙的工作生活中,为心爱的宠物创造一个更加快乐和活跃的家居环境。
材料清单
表 1 材料清单
序号 | 名称 | 数量 |
硬件类 | ||
1 | Xiao ESP32C3 | 1 |
2 | Grove Vision AI V2 | 1 |
3 | 基于 XIAO 的多功能扩展板 | 1 |
4 | 激光模块 | 1 |
5 | 舵机 | 2 |
6 | 舵机云台 | 1 |
软件类及网络工具包 | ||
7 | Arduino IDE | |
8 | SenseCraft | |
9 | Fusion 360 | |
其他 | ||
10 | 3D 打印机 | |
11 | 电烙铁 |
硬件介绍
- Seeed Studio XIAO ESP32C3 概述
图 1 XIAO ESP32C3
Seeed Studio XIAO ESP32C3是一款基于express的物联网迷你开发板 ESP32-C3 WiFi/蓝牙双模芯片ESP32-C3是一款32位RISC-V CPU,包含一个FPU(浮点单元),用于32位单精度运算,具有强大的计算能力。 它具有出色的射频性能,支持IEEE 802.11 b/g/n WiFi和蓝牙5 (LE)协议。该电路板附带一个外部天线,以增加无线应用程序的信号强度。 它还具有小巧精致的外形,并结合了单面表面贴装设计。 它配备了丰富的接口,具有11个数字I/O,可作为PWM引脚和4个模拟I/O,可作为ADC引脚。支持**UART、I2C、SPI等4种串行接口。在电路板上还有一个小的复位按钮和一个引导加载模式按钮。XIAO ESP32C3与Grove Shield for Seeeduino XIAO和Seeeduino XIAO Expansion board完全兼容。除Seeeduino XIAO Expansion board外,该板上的SWD弹簧触点不兼容。
基于以上特点,XIAO ESP32C3定位为高性能、低功耗、高性价比的物联网微型开发板,适用于低功耗物联网应用和无线可穿戴应用。
- Grove Vision AI V2 模块概述
图 2 Grove Vision AI V2 模块
Grove Vision AI (V2) 是一款基于 MCU 的视觉 AI 模块,它采用 Himax WiseEye2 HX6538 处理器、双核 Arm Cortex-M55 和集成的 ARM Ethos-U55 神经网络单元。Arm Ethos-U55 是一种新的机器学习 (ML) 处理器类,称为 microNPU,专门设计用于加速面积受限的嵌入式和物联网设备中的 ML 推理。Ethos-U55 与支持 AI 的 Cortex-M55 处理器相结合,与现有的基于 Cortex-M 的系统相比,ML 性能提升了 480 倍。其时钟频率为 400Mhz,其内部系统存储器 (SRAM) 可配置,最高可达 2.4MB。
图 3 Grove Vision AI V2 模块引脚介绍
下面是 Grove Vision AI (V2) 系统的框图,包括摄像头和主控制器。
图 4 Grove Vision AI V2 系统框图
Grove Vision AI (V2) 具有 IIC、UART、SPI 和 Type-C 等接口,可以轻松连接到 XIAO、Raspberry Pi、BeagleBoard 和基于 ESP 的产品等设备,以进行进一步开发。例如,将 Grove Vision AI V2 与 XIAO 系列的设备之一集成,可以轻松访问通过 Arduino IDE 或 Micropython 在设备上推理产生的数据,并方便地连接到云或专用服务器(如家庭协助)。
摄像机安装
准备好 Grove Vision AI (V2) 和摄像头后,您可以通过 CSI 电缆连接它们。连接时,请注意排针的方向,不要向后插入。
2. SenseCraft 网络工具包
SenseCraft Web Toolkit 是包含在 SSCMA(Seeed SenseCraft Model Assistant)中的可视化模型部署工具。该工具允许您通过简单的操作轻松地将模型部署到各种平台。该工具提供了一个用户友好的界面,不需要任何编码。
按照以下步骤启动 SenseCraft-Web-Toolkit:
- 使用 Type-C 数据线将 Grove Vision AI (V2) 连接到您的计算机,在网站上选择设备并点击连接 :
图 5 SenseCraft 使用说明(1)
- 在弹窗上选择设备/端口并按:[Connect]
图 6 SenseCraft 使用说明(2)
- 选择宠物检测(Pet Detection)的模型,这个模型可以识别到猫狗,并将其区分。然后点击发送(Send)
图 7 SenseCraft 使用说明(3)
搭建开发环境
根据您的操作系统下载并安装最新版本的Arduino IDE
图 8 下载 Arudino IDE
第一步. 添加ESP32板包到Arduino IDE
第二步,启动Arduino应用程序。
第三步,将 Seeed Studio XIAO nRF52840 (Sense) 板包添加到您的 Arduino IDE。
点击到“文件>首选项”,然后 使用以下 URL 填写 “ 其他 Boards 管理器 URL ”:https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
图 9 添加其他 Boards 管理器 URL
点击到 工具 > 板 >板管理器...,在搜索框中输入关键字“ esp32 ”,选择所需的开发板的最新版本,然后安装它。
图 10 安装ESP32板包
第四步,选择开发板和串口
开发板
点击至工具 > 电路板 > ESP32 Arduino 并选择 "XIAO_ESP32C3"。电路板列表有点长,几乎需要滚动到底部才能找到。
图 11 选择开发板
端口
点击至 "工具">"端口",然后选择所连接的 XIAO ESP32C3 的串行端口名称。这可能是 COM3 或更高版本(COM1 和 COM2 通常保留给硬件串行端口)。
第五步. 安装Arduino SSMA库
从其 GitHub 将 Arduino SSMA 库下载为 zip 文件,并将其安装到 Arduino IDE ()。项目 > 包括库 > 添加 .Zip 库( sketch > Include Library > Add .Zip Library ) 将下载的Arduino SSMA库添加进去
图 12 安装Arduino SSMA库
第六步. 安装其他库
转到Sketch项目菜单,然后选择包括库>管理库...。这将打开库管理器。在库管理器顶部的搜索栏中,键入ArduinoJSON。搜索结果将列出ArduinoJSON库。在库旁边会有一个安装按钮。单击“安装”按钮。Arduino IDE将自动下载并安装该库到您的Arduino开发环境。
图 13 安装ArduinoJSON库
还需要一个驱动舵机的库,在库管理器顶部的搜索栏中,键入ESP32Sever。选择对应的库,单击“安装”按钮。Arduino IDE将自动下载并安装该库到您的Arduino开发环境。
图 14 安装ESP32Sever库
现在我们的环境搭载已经完成,接下来正式进入制作项目环节。
布置云台
先把摄像头和激光在舵机上定位标记好,打好孔之后将摄像头,激光器和舵机都安装在云台上面。
图 15 云台安装
建议先参考程序1把舵机的角度都调到90度,防止在后续操作中损害舵机或者云台。
程序1
#include <ESP32Servo.h>
#define SERVO_X_PIN D6
#define SERVO_Y_PIN D7
Servo myservo1;
Servo myservo2;
int pos = 0;
void setup() {
Serial.begin(115200);
myservo1.attach(servoPin1);
myservo2.attach(servoPin2);
}
void loop() {
myservo1.write(90);
myservo2.write(90);
delay(30);
}
接线
Grove Vision AI V2通过Grove接口简单地连接到XIAO扩展版的IIC接口。
图 16 Grove Vision AI V2 接线
其余接线如下
图 17 其余硬件接线
由于现使用的云台较轻,当舵机运作时云台也会一起动十分不稳定,可以将其固定在塑料板、木板或者3D打印件上增加配重,使整个设备稳定
外观
由于目前使用的舵机云台重量较轻,当舵机工作时,云台会十分不稳定。为了解决这个问题,我们可以将其固定在塑料板或木板上,或利用 3D 打印部件增加配重,从而增强装置的稳定性。这种改装将确保整个设备即使在舵机运行时也能保持稳定。
这个项目在开始时是打算在桌面上操作逗猫器,因此我制作了一个简单的外观壳
图 18 外壳- 上
图 19 外壳 - 底
代码程序
本项目是使用Arduino IDE进行开发,目前代码的逻辑:
1. 有猫经过摄像头就激活逗猫器,
2. 再移动激光到随机一个地方,然后检测猫是否跟上激光点(重新进入摄像头范围)并且小幅度摇摆吸引猫。
3. 如果检测到猫跟上激光点,才会移动激光到下一个点。
4. 玩过一定次数就进入睡眠模式,等一定时间再唤醒。
图 20 逻辑导图
程序2
#include <ESP32Servo.h>
#include <Seeed_Arduino_SSCMA.h>
// 定义舵机连接的引脚
#define SERVO_X_PIN D6 // 控制x轴角度的舵机连接引脚D6
#define SERVO_Y_PIN D7 // 控制x轴角度的舵机连接引脚D7
// 定义激光笔引脚
#define Laser_Pin 8 // 激光器连接引脚8
// 定义X和Y轴的范围
#define X_MIN 0
#define X_MAX 240
#define Y_MIN 0
#define Y_MAX 240
#define Central_coor_X 120;
#define Central_coor_Y 120;
// 定义PID比例参数
#define kp 0.1
#define ki 0.001
#define kd 0
// 睡眠时间为 1 小时
#define uS_TO_S_FACTOR 1000000ULL
#define TIME_TO_SLEEP 60*60
RTC_DATA_ATTR int bootCount = 0;
// 创建Servo对象
Servo servoX;
Servo servoY;
SSCMA AI;
// 创建初始角度,中心角度,根据实际调整
const float C_angle_x = 90;
const float C_angle_y = 60;
float Last_angle_x = C_angle_x;
float Last_angle_y = C_angle_y;
int errror = 2;
int speed = 50;
/*
打印唤醒原因
*/
void print_wakeup_reason(){
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason)
{
case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
}
}
void sleep_in_loop(){
delay(100);
Serial.println("wake");
Serial.println("Going to sleep now");
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");
Serial.flush();
esp_deep_sleep_start();
Serial.println("This will never be printed");
}
// 停止转动
void stop_rotate(void) {
digitalWrite(D6, HIGH);
delayMicroseconds(1500);
digitalWrite(D6, LOW);
}
/*
功能简介:
检测猫是否在摄像头内
收集三次图像信息,取置信度平均值
输入:
无输入值
返回:
返回Ture -- 检测到有猫
返回False -- 检测到没猫
*/
bool check_cat(){
Serial.println("checking ");
int total_score = 0;
float average_score;
int c_n;
int j = 0;
while( j < 3 ){
if(!AI.invoke()) {
if (AI.boxes().size() > 0){
for (int i = 0; i < AI.boxes().size(); i++){
if(AI.boxes()[i].target == 0 || AI.boxes()[i].target == 1){
c_n = i;
total_score += int(AI.boxes()[i].score);
}
}
}
j++;
}
}
// 可以调节total_score/3的 对比值来更好的识别猫/狗
if(total_score/3 > 60){
Serial.println(total_score/3);
return true;
}else{
return false;
}
}
/*
功能简介:
吸引猫
在检测猫是否在是摄像头的时候,小幅度抖动吸引猫
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
Attach : 是否需要吸引,0则需要吸引
返回值:
无返回值
*/
void attach_cat(int angleX, int angleY, int Attach){
int d = 1;
int n = random(1, 5); //随机选 1~5的值
//随着y轴角度变小而增大
float vibrate = map(angleY,100,35,5,10);
if(Attach == 0){
switch (n) {
case 1:
for(int i = 0; i < 6; i++){
servoX.write(angleX + vibrate);
vibrate = -vibrate;
delay(100);
}
break;
case 2:
for(int i = 0; i < 6; i++){
servoY.write(angleY + vibrate);
vibrate = -vibrate;
delay(100);
}
break;
case 3:
delay(100);
break;
case 4:
delay(100);
break;
}
}
delay(100);
}
/*
功能简介:
移动激光/摄像头到指定角度
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
target_x :目标x轴角度
target_y : 目标y轴角度
返回值:
无返回值
*/
void go_to_target(int angleX, int angleY, int target_x, int target_y){
int offet_set_X = target_x - angleX;
int offet_set_Y = target_y - angleY;
float deltaDergeeX = offet_set_X * kp;
float deltaDergeeY = offet_set_Y * kp;
for(int i = 0; i < 10; i++){
float angleX = Last_angle_x + deltaDergeeX;
float angleY = Last_angle_y + deltaDergeeY;
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(speed);
}
}
/*
功能简介:
移动激光/摄像头到指定角度
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
返回值:
无返回值
*/
void back_to_centrel(int angleX, int angleY){
int offet_set_X = C_angle_x - angleX;
int offet_set_Y = C_angle_y - angleY;
float deltaDergeeX = offet_set_X * kp;
float deltaDergeeY = offet_set_Y * kp;
for(int i = 0; i < 10; i++){
float angleX = Last_angle_x + deltaDergeeX;
float angleY = Last_angle_y + deltaDergeeY;
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(speed);
}
}
/*
功能简介:
移动激光折返运动,折返四个点
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
返回值:
无返回值
*/
void forward_back_X(int angleX, int angleY) {
int total_dergee = 4;
int n = 0;
int Attach = 0;
for (int i = 0; i < total_dergee; i++) {
while(!check_cat()){
attach_cat(Last_angle_x,Last_angle_y, Attach);
n++;
if(n > 20){
digitalWrite(Laser_Pin, LOW);
Attach = 1;
}
}
n = 0;
Attach = 0;
if (i < total_dergee / 4) {
angleX = 120;
} else if (i < total_dergee / 2) {
angleX = 60;
} else if (i < total_dergee / 4 * 3) {
angleX = 120;
} else {
angleX = 60;
}
angleY = 60;
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(speed);
}
}
/*
功能简介:
移动激光沿Y轴折线运动,可根据以下代码修改做曲线运动
void Laser_Curve_X(int angleX, int angleY) {
int total_dergee = 4*12;
const int remember_x = angleX;
const int remember_y = angleY;
for (int i = 0; i < total_dergee; i++) {
// Serial.println(i);
if (i < total_dergee / 4) {
angleX += 3;
} else if (i < total_dergee / 2) {
angleX -= 3 ;
} else if (i < total_dergee / 4 * 3) {
angleX += 3;
} else {
angleX -= 3;
}
angleY += 3;
if(angleX >= 150 || angleY >= 150 ){
i = total_dergee;
break;
}
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(50);
}
angleX = remember_x;
angleY = remember_y;
Last_angle_x = angleX;
Last_angle_y = angleY;
servoX.write(remember_x);
servoY.write(remember_y);
// Laser_Curve_Back_X(Last_angle_x, Last_angle_y);
Serial.println("Laser_Curve_X done");
delay(1000);
}
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
返回值:
无返回值
*/
void Laser_Curve_X(int angleX, int angleY) {
int total_dergee = 4;
int n = 0;
int Attach = 0;
for (int i = 0; i < total_dergee; i++) {
while(!check_cat()){
attach_cat(Last_angle_x,Last_angle_y,Attach);
n++;
if(n > 20){
digitalWrite(Laser_Pin, LOW);
Attach= 1;
}else{
}
}
n = 0;
Attach= 0;
digitalWrite(Laser_Pin, HIGH);
if (i < total_dergee / 4) {
angleX = 130;
} else if (i < total_dergee / 2) {
angleX = 70 ;
} else if (i < total_dergee / 4 * 3) {
angleX = 130;
} else {
angleX = 70;
}
angleY += 2 ;
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(speed);
}
}
/*
功能简介:
移动激光沿Y轴反向折线运动
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
返回值:
无返回值
*/
void Laser_Curve_Back_X(int angleX, int angleY) {
int total_dergee = 4;
int n = 0; //吸引次数
int Attach = 0;
for (int i = 0; i < total_dergee; i++) {
while(!check_cat()){
attach_cat(Last_angle_x,Last_angle_y,Attach);
n++;
if(n > 20){
digitalWrite(Laser_Pin, LOW);
Attach= 1;
// delay(5000);
}
}
n = 0;
Attach=0;
digitalWrite(Laser_Pin, HIGH);
if (i < total_dergee / 4) {
angleX = 80;
} else if (i < total_dergee / 2) {
angleX = 130;
} else if (i < total_dergee / 4 * 3) {
angleX = 70;
} else {
angleX = 130;
}
angleY -= 5;
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(speed);
}
}
/*
功能简介:
移动激光沿X轴向折线运动
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
返回值:
无返回值
*/
void Laser_Curve_Y(int angleX, int angleY) {
int total_dergee = 4;
const int remember_x = angleX;
const int remember_y = angleY;
int n = 0;
int Attach = 0;
for (int i = 0; i < total_dergee; i++) {
while(!check_cat()){
attach_cat(Last_angle_x,Last_angle_y,Attach);
n++;
if(n > 20){
digitalWrite(Laser_Pin, LOW);
Attach= 1;
}
}
n = 0;
Attach=0;
digitalWrite(Laser_Pin, HIGH);
if (i < total_dergee / 4) {
angleY = 70;
} else if (i < total_dergee / 2) {
angleY = 45;
} else if (i < total_dergee / 4 * 3) {
angleY = 70;
} else {
angleY = 45;
}
angleX += 5;
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(speed);
}
}
/*
功能简介:
移动激光沿X轴反向折线运动
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
返回值:
无返回值
*/
void Laser_Curve_Back_Y(int angleX, int angleY) {
int total_dergee = 4;
int n = 0;
int Attach = 0;
for (int i = 0; i < total_dergee; i++) {
while(!check_cat()){
attach_cat(Last_angle_x,Last_angle_y,Attach);
n++;
if(n > 20){
digitalWrite(Laser_Pin, LOW);
Attach= 1;
}
}
n = 0;
Attach=0;
digitalWrite(Laser_Pin, HIGH);
if (i < total_dergee / 4) {
angleY = 45;
} else if (i < total_dergee / 2) {
angleY = 70;
} else if (i < total_dergee / 4 * 3) {
angleY = 45;
} else {
angleY = 70;
}
angleX -= 5;
// if(angleX <= 30){
// Serial.println("too much");
// i = total_dergee;
// break;
// }
// Serial.println("angleY");
// Serial.println(angleY);
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(speed);
}
}
/*
功能简介:
移动激光做圆圈运动
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
返回值:
无返回值
*/
void Laser_Cricle(int angleX, int angleY) {
int total_dergee = 4;
int n = 0;
int Attach = 0;
for (int i = 0; i < total_dergee; i++) {
while(!check_cat()){
attach_cat(Last_angle_x,Last_angle_y,Attach);
n++;
if(n > 20){
digitalWrite(Laser_Pin, LOW);
Attach= 1;
}
}
n = 0;
Attach=0;
digitalWrite(Laser_Pin, HIGH);
if (i < total_dergee / 4) {
angleX = 140;
angleY = 45;
} else if (i < total_dergee / 2) {
angleX = 90;
angleY = 30;
} else if (i < total_dergee / 4 * 3) {
angleX = 60;
angleY = 45;
} else {
angleX = 90;
angleY = 60;
}
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(speed);
}
}
/*
功能简介:
移动激光做反向圆圈运动
输入:
angleX : 当前x轴角度
angleY : 当前y轴角度
返回值:
无返回值
*/
void Laser_Cricle_Back(int angleX, int angleY) {
int total_dergee = 4;
int n = 0;
int Attach = 0;
for (int i = 0; i < total_dergee; i++) {
while(!check_cat()){
attach_cat(Last_angle_x,Last_angle_y,Attach);
n++;
if(n > 20){
digitalWrite(Laser_Pin, LOW);
Attach= 1;
}
}
n = 0;
Attach=0;
digitalWrite(Laser_Pin, HIGH);
if (i < total_dergee / 4) {
angleX = 50 ;
angleY = 45;
} else if (i < total_dergee / 2) {
angleX = 90;
angleY = 30;
} else if (i < total_dergee / 4 * 3) {
angleX = 140;
angleY = 45;
} else {
angleX = 90;
angleY = 60 ;
}
if(angleX <= 30 || angleY <= 40 ){
Serial.println("too much");
i = total_dergee;
break;
}
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
delay(speed);
}
}
/*
功能简介:
PID控制可用于跟随,不过相对于猫会比较缓慢。
输入:
x : 检测物在摄像头x轴坐标
y : 检测物在摄像头y轴坐标
返回值:
无返回值
*/
void server_control(int x, int y)
{
// 将摄像头参数值映射到舵机角度范围
float targetAngleX = map(x, X_MAX, X_MIN, 0, 180);
float targetAngleY = map(y, Y_MIN, Y_MAX, 0, 180);
// 得出真实偏移量
int offet_set_X = targetAngleX - 90;
int offet_set_Y = targetAngleY - 90;
float deltaDergeeX = offet_set_X * kp;
float deltaDergeeY = offet_set_Y * kp;
float angleX = Last_angle_x + deltaDergeeX;
float angleY = Last_angle_y + deltaDergeeY + errror;
servoX.write(angleX);
servoY.write(angleY);
Last_angle_x = angleX;
Last_angle_y = angleY;
}
void setup() {
Serial.begin(9600);
AI.begin();
// 将激光器连接的引脚设为输出
pinMode(Laser_Pin, OUTPUT);
// 将舵机连接的引脚设为输出
digitalWrite(SERVO_X_PIN, HIGH);
digitalWrite(SERVO_Y_PIN, HIGH);
delayMicroseconds(1500);
digitalWrite(SERVO_X_PIN, LOW);
digitalWrite(SERVO_Y_PIN, LOW);
servoX.attach(SERVO_X_PIN);
servoY.attach(SERVO_Y_PIN);
servoX.write(Last_angle_x);
servoY.write(Last_angle_y);
delay(1000); //延迟1s
//在每次重启时增加和打印boot数
++bootCount;
Serial.println("Boot number: " + String(bootCount));
//打印唤醒原因
print_wakeup_reason();
/*
设置唤醒时间为一小时
*/
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) +
" Seconds");
}
void loop() {
Serial.println("one loop finish");
if (!AI.invoke()) {
//因为发现激光器也可以用于逗狗,如果需要专门逗猫可以把条件改为
//if (AI.boxes().size() > 0 && AI.boxes()[0].score >= 70 && target == 0 )
// 可以调节AI.boxes()[0].score的 对比值来更好的识别猫/狗
if (AI.boxes().size() > 0 && AI.boxes()[0].score >= 70 ) //&& target == 0 // 如果是猫
{
server_control(AI.boxes()[0].x, AI.boxes()[0].y);
delay(500);
// 打开激光笔
digitalWrite(Laser_Pin, HIGH);
Serial.println("invoke success");
// 激光活动 n =12345 种活动
for (int i = 0; i < 20; i ++){
delay(50);
int n = random(1, 6); //随机选 1~5的值
Serial.print(" movtion is ");
switch (n) {
case 1:
Serial.println(n);
Laser_Curve_X(Last_angle_x, Last_angle_y);
back_to_centrel(Last_angle_x, Last_angle_y);
break;
case 2:
Serial.println(n);
Laser_Cricle(Last_angle_x, Last_angle_y);
back_to_centrel(Last_angle_x, Last_angle_y);
break;
case 3:
Serial.println(n);
Laser_Curve_Y(Last_angle_x, Last_angle_y);
back_to_centrel(Last_angle_x, Last_angle_y);
break;
case 4:
Serial.println(n);
Laser_Curve_Back_Y(Last_angle_x, Last_angle_y);
back_to_centrel(Last_angle_x, Last_angle_y);
break;
case 5:
Serial.println(n);
Laser_Curve_Back_X(Last_angle_x, Last_angle_y);
back_to_centrel(Last_angle_x, Last_angle_y);
break;
// case 6:
// Serial.println(n);
// forward_back_X(Last_angle_x, Last_angle_y);
// back_to_centrel(Last_angle_x, Last_angle_y);
// break;
}
}
digitalWrite(Laser_Pin, LOW);
back_to_centrel(Last_angle_x, Last_angle_y);
Last_angle_x = 90;
Last_angle_y = 60;
servoX.write(Last_angle_x);
servoY.write(Last_angle_y);
// delay(10000);
sleep_in_loop();
Serial.println("rest");
// angleX = Last_angle_x ;
// angleY = Last_angle_y ;
}
delay(500);
}
delay(500);
}
本项目的所有程序如上图所示,程序2使用了Grove Vision AI V2图像识别模块、Seeed_Arduino_SSCMA库、ESP32Servo库以及一些自定义函数,实现了一个激光逗猫器。程序2通过调用SSCMA库的API,可以对摄像头采集的图像进行处理,实现对猫的识别。通过控制激光器引脚,程序能够控制激光笔的开关,以及激光笔在x轴和y轴上的移动。
程序设置了自动休眠功能,当没有检测到猫时,设备会进入休眠状态定时唤醒,以节省电能。
程序设计了多种激光移动模式,包括折线运动、圆圈运动等,可以吸引猫的注意力,增加与猫互动的乐趣。为了增加逗猫的趣味性,程序设置了随机选择逗猫活动的功能,随机选择一种移动模式来与宠物互动。
项目重点:
如果不了解如何使用Grove vision AI V2 时,可以参考库中提供的示例代码:
程序3
#include <Seeed_Arduino_SSCMA.h>
SSCMA AI;
void setup()
{
AI.begin();
Serial.begin(9600);
}
void loop()
{
if (!AI.invoke())
{
Serial.println("invoke success");
Serial.print("perf: prepocess=");
Serial.print(AI.perf().prepocess);
Serial.print(", inference=");
Serial.print(AI.perf().inference);
Serial.print(", postpocess=");
Serial.println(AI.perf().postprocess);
for (int i = 0; i < AI.boxes().size(); i++)
{
Serial.print("Box[");
Serial.print(i);
Serial.print("] target=");
Serial.print(AI.boxes()[i].target);
Serial.print(", score=");
Serial.print(AI.boxes()[i].score);
Serial.print(", x=");
Serial.print(AI.boxes()[i].x);
Serial.print(", y=");
Serial.print(AI.boxes()[i].y);
Serial.print(", w=");
Serial.print(AI.boxes()[i].w);
Serial.print(", h=");
Serial.println(AI.boxes()[i].h);
}
for (int i = 0; i < AI.classes().size(); i++)
{
Serial.print("Class[");
Serial.print(i);
Serial.print("] target=");
Serial.print(AI.classes()[i].target);
Serial.print(", score=");
Serial.println(AI.classes()[i].score);
}
for (int i = 0; i < AI.points().size(); i++)
{
Serial.print("Point[");
Serial.print(i);
Serial.print("] target=");
Serial.print(AI.points()[i].target);
Serial.print(", score=");
Serial.print(AI.points()[i].score);
Serial.print(", x=");
Serial.print(AI.points()[i].x);
Serial.print(", y=");
Serial.println(AI.points()[i].y);
}
}
}
程序3处理并打印出有关推理结果的详细信息,包括:
边界框(AI.boxes()),识别宽度和高度、x和y坐标形式中的检测对象的位置和尺寸。
分类(AI.classes()),识别被检测对象的类别及其置信度分数。
表示被检测对象的特定特征或关键点的点(AI.points()),以及它们的x和y坐标和置信度分数。
在逗猫棒项目里面,当target等于0的时候,识别的是猫;当target等于1的时候,识别的是狗
AI.boxes()[i].target == 0
且只有置信度高于70才会激活设备执行动作, 即:
AI.boxes()[i].score > 70
成品展示:
请大家扫码观看
图 21 视频二维码
未来改进:
增加更多的功能:
- 可以增加更多与猫狗互动的功能,如自动喂食,宠物有追逐激光一定次数,即运动一定次数可以自动喂小零食来奖励宠物。
- 可以增加MP3模块,播放声音吸引宠物来互动
优化用户体验:
1. 可以通过设计运用OLED屏,使用户能更方便了解宠物每日运动状态。
2. 由于XIAO ESP32C3也有连接WIFI的功能,甚至可以让用户远程操控设备来逗宠物也能实时查看宠物状态。
总结
猫狗识别能运用在多个方面,类似猫狗喂食器,宠物监护等。而Grove Vision AI V2 不仅仅有可以识别猫狗的模型还有人物识别,手势识别等等,为创客们提供强大也极易入门的图像识别设备,更轻松快速的实现自己的创意,这为开发各种智能设备提供了可能。这个项目不仅展示了人工智能技术在宠物监护领域的应用,也展示了Grove Vision AI V2作为一个强大且易于上手的图像识别设备的实用性。