创客项目秀|基于Grove vision AI模块制作的AI逗猫设备

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

电烙铁

硬件介绍

  1. 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 XIAOSeeeduino XIAO Expansion board完全兼容。除Seeeduino XIAO Expansion board外,该板上的SWD弹簧触点不兼容。

基于以上特点,XIAO ESP32C3定位为高性能、低功耗、高性价比的物联网微型开发板,适用于低功耗物联网应用和无线可穿戴应用。

  1. 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:

  1. 打开 SenseCraft-Web-Toolkit 网站。

SenseCraft AI

  1. 使用 Type-C 数据线将 Grove Vision AI (V2) 连接到您的计算机,在网站上选择设备并点击连接 :

图 5 SenseCraft 使用说明(1)

  1. 在弹窗上选择设备/端口并按:[Connect]

图 6 SenseCraft 使用说明(2)

  1. 选择宠物检测(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 视频二维码

未来改进:

增加更多的功能:

  1. 可以增加更多与猫狗互动的功能,如自动喂食,宠物有追逐激光一定次数,即运动一定次数可以自动喂小零食来奖励宠物。
  2. 可以增加MP3模块,播放声音吸引宠物来互动

优化用户体验:

1. 可以通过设计运用OLED屏,使用户能更方便了解宠物每日运动状态。

2. 由于XIAO ESP32C3也有连接WIFI的功能,甚至可以让用户远程操控设备来逗宠物也能实时查看宠物状态。

总结

猫狗识别能运用在多个方面,类似猫狗喂食器,宠物监护等。而Grove Vision AI V2 不仅仅有可以识别猫狗的模型还有人物识别,手势识别等等,为创客们提供强大也极易入门的图像识别设备,更轻松快速的实现自己的创意,这为开发各种智能设备提供了可能。这个项目不仅展示了人工智能技术在宠物监护领域的应用,也展示了Grove Vision AI V2作为一个强大且易于上手的图像识别设备的实用性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值