赞
踩
在这个教程中,我们将使用人工智能(AI)和飞行时间(Time of Flight)来创建一个石头剪刀布游戏。
我们的目标是展示如何使用NanoEdge AI Studio和Arduino IDE来创建任何你能想到的、使用AI技术的项目。
NanoEdge AI Studio是由STMicroelectronics开发的一款工具,专为嵌入式系统用户设计,帮助他们获取一个AI库,以便将其嵌入到他们的项目中,并且只使用他们自己的数据。
通过一个简单且逐步的过程,我们将收集与你用例相关的数据,并使用这个工具以最小的努力获取最佳模型。
NanoEdge AI Studio库与任何Cortex M都兼容,从4.4版本开始,该工具能够编译出可以直接导入到Arduino IDE中的库。
硬件:
软件:
注意:对于Windows用户,我们建议使用Arduino IDE v1.8.19版本。请不要使用Microsoft应用商店的版本。
或更高版本。
注意:对于Windows用户,我们建议使用Arduino IDE v1.8.19版本。请不要使用Microsoft应用商店的版本。
语法说明
标题文本样式列表图片链接目录代码片表格注脚注释自定义列表LaTeX 数学公式插入甘特图插入UML图插入Mermaid流程图插入Flowchart流程图插入类图快捷键
目录复制
关于设置和组装,有一点需要注意。你需要将显示屏插接在GIGA R1开发板的顶部,并将TOF扩展板放在开发板的下面,像这样:
重要提示:我们需要修改TOF和Arduino板之间的I2C通信引脚,以避免与显示屏发生冲突:
对于与NanoEdge AI Studio相关的任何部分,你可以查阅相关文档以获取更多信息:NanoEdge AI Studio Docs
首先,我们需要安装NanoEdge AI Studio。
下载链接
然后:
在NanoEdge AI Studio中,有四种项目可供选择,每种项目服务于不同的目的:
在我们的案例中,我们想要创建一个能够识别三种手势(剪刀、石头和布)的人工智能,所以我们点击N类分类 > 创建新项目。
在项目设置中:
然后点击保存并下一步
注意:我们正在处理64个值(8x8)的TOF矩阵,但每次测量都是独立进行的。这就是为什么我们使用一个轴的原因。
在NanoEdge AI Studio的信号部分,我们需要导入四个数据集:
TOF可以收集8x8大小的矩阵数据,因此我们的数据集中的每个信号大小都是64。然后,我们需要收集数据以创建四个数据集,每个数据集包含每种手势的各种示例。
数据收集的Arduino代码:为了收集数据,请在Arduino IDE中创建一个新项目,并复制以下代码:
#include <Wire.h> #include <SparkFun_VL53L5CX_Library.h> //http://librarymanager/All#SparkFun_VL53L5CX SparkFun_VL53L5CX myImager; VL53L5CX_ResultsData measurementData; // Result data class structure, 1356 bytes of RAM int imageResolution = 0; //Used to pretty print output float neai_buffer[64]; void setup() { Serial.begin(115200); delay(100); Wire.begin(); //This resets to 100kHz I2C Wire.setClock(400000); //Sensor has max I2C freq of 400kHz if (myImager.begin() == false) { Serial.println(F("Sensor not found - check your wiring. Freezing")); while (1); } myImager.setResolution(8*8); //Enable all 64 pads myImager.setRangingFrequency(15); //Ranging frequency = 15Hz imageResolution = myImager.getResolution(); //Query sensor for current resolution - either 4x4 or 8x8 myImager.startRanging(); } void loop() { if (myImager.isDataReady() == true) { if (myImager.getRangingData(&measurementData)) //Read distance data into array { for(int i = 0 ; i < imageResolution ; i++) { neai_buffer[i] = (float)measurementData.distance_mm[i]; } for(int i = 0 ; i < imageResolution ; i++) { Serial.print(measurementData.distance_mm[i]); Serial.print(" "); } Serial.println(); } } }
我们需要向项目中添加两个库:
准备好后,点击验证符号来编译代码,然后点击向右的箭头将代码烧录到板上。请确保板已事先连接到电脑。
请确保选择了正确的COM端口。点击Tools > Port进行选择。如果板已插入,您应该能看到其名称显示。
回到NanoEdge:在步骤SIGNALS中:
非常重要:
不要像玩石头剪刀布那样多次记录数据,而是在捕捉器下方不同位置连续记录一个手势(上、下、左、右等)。例如,在做剪刀手势的同时,在收集500个信号时,不要离开捕捉器的视线。
我们确实只需要与该类对应的数据。如果您模拟玩游戏来记录数据,您将得到没有手势的数据(因为您不在捕捉器的视线内),然后是对应类别的数据。
请确保不要太靠近TOF,因为它将只能看到一个覆盖整个矩阵的大物体。
进入BENCHMARK步骤。
在这一步中,NanoEdge AI Studio将为您的数据、模型和参数寻找最佳预处理方式,以便找到最适合您用例的最佳组合。
完成后,您将在项目结束时能够将找到的组合编译为AI库,导入到Arduino IDE中。
studio会显示几个指标:
基准测试所需的时间与使用的缓冲区大小及其数量密切相关。基准测试开始时改进速度很快,然后趋于缓慢,以便在最后找到最优化的库。因此,当您对结果满意时(超过95%是一个很好的参考),您可以停止基准测试。
验证和仿真步骤旨在确保找到的库确实是最佳的。*要实现这一点,建议测试几个最佳库与新数据,并确保所选的库是最佳的。
注意:为了收集用于验证的新数据集,您可以返回到信号步骤,通过串行导入新数据集,并下载它们以在验证中使用:
要获取包含模型和将其添加到您的Arduino代码中的功能的AI库:
从NanoEdge AI Studio获取包含AI库的zip文件后,点击File > New(文件 > 新建)创建一个新项目。
要使这个项目能够运行,我们需要几个库。
要添加NanoEdge AI Studio库:
我们还需要添加incbin.h:
最后,我们需要选择GIGA R1 WIFI作为我们的开发板:
以下代码负责三个主要部分:
以下是代码:
#include "Arduino_H7_Video.h" #include "ArduinoGraphics.h" #include "incbin.h" #include <Wire.h> #include <SparkFun_VL53L5CX_Library.h> //http://librarymanager/All#SparkFun_VL53L5CX #include "NanoEdgeAI.h" #include "knowledge.h" // Online image converter: https://lvgl.io/tools/imageconverter (Output format: Binary RGB565) //#define DATALOG #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 480 #define SIGN_WIDTH 150 #define SIGN_HEIGHT 200 #define LEFT_SIGN_X 115 #define RIGHT_SIGN_X 530 #define SIGN_Y 200 #define INCBIN_PREFIX INCBIN(backgnd, "YOUR_PATH/backgnd.bin"); INCBIN(rock, "YOUR_PATH/rock.bin"); INCBIN(paper, "YOUR_PATH/paper.bin"); INCBIN(scissors, "YOUR_PATH/scissors.bin"); void signs_wheel(void); void signs_result(uint16_t neaiclass); uint16_t mostFrequent(uint16_t arr[], int n); Arduino_H7_Video Display(SCREEN_WIDTH, SCREEN_HEIGHT, GigaDisplayShield); Image img_backgnd(ENCODING_RGB16, (uint8_t *) backgndData, SCREEN_WIDTH, SCREEN_HEIGHT); Image img_rock(ENCODING_RGB16, (uint8_t *) rockData, SIGN_WIDTH, SIGN_HEIGHT); Image img_paper(ENCODING_RGB16, (uint8_t *) paperData, SIGN_WIDTH, SIGN_HEIGHT); Image img_scissors(ENCODING_RGB16, (uint8_t *) scissorsData, SIGN_WIDTH, SIGN_HEIGHT); Image img_classes[CLASS_NUMBER - 1] = {img_paper, img_rock, img_scissors}; SparkFun_VL53L5CX myImager; VL53L5CX_ResultsData measurementData; // Result data class structure, 1356 bytes of RAM int imageResolution = 0; //Used to pretty print output int imageWidth = 0; //Used to pretty print output float neai_buffer[DATA_INPUT_USER]; float output_buffer[CLASS_NUMBER]; // Buffer of class probabilities uint16_t neai_class = 0; uint16_t previous_neai_class = 0; int class_index = 0; uint16_t neai_class_array[10] = {0}; void setup() { randomSeed(analogRead(0)); Display.begin(); neai_classification_init(knowledge); Serial.begin(115200); delay(100); Wire.begin(); //This resets to 100kHz I2C Wire.setClock(400000); //Sensor has max I2C freq of 400kHz if (myImager.begin() == false) { Serial.println(F("Sensor not found - check your wiring. Freezing")); while (1); } myImager.setResolution(8*8); //Enable all 64 pads myImager.setRangingFrequency(15); //Ranging frequency = 15Hz imageResolution = myImager.getResolution(); //Query sensor for current resolution - either 4x4 or 8x8 imageWidth = sqrt(imageResolution); //Calculate printing width myImager.startRanging(); Display.beginDraw(); Display.image(img_backgnd, (Display.width() - img_backgnd.width())/2, (Display.height() - img_backgnd.height())/2); Display.endDraw(); delay(500); } void loop() { if (myImager.isDataReady() == true) { if (myImager.getRangingData(&measurementData)) //Read distance data into array { for(int i = 0 ; i < DATA_INPUT_USER ; i++) { neai_buffer[i] = (float)measurementData.distance_mm[i]; } #ifdef DATALOG for(int i = 0 ; i < DATA_INPUT_USER ; i++) { Serial.print(measurementData.distance_mm[i]); Serial.print(" "); } Serial.println(); #else neai_classification(neai_buffer, output_buffer, &neai_class); if(class_index < 10) { neai_class_array[class_index] = neai_class; Serial.print(F("class_index ")); Serial.print(class_index); Serial.print(F(" = class")); Serial.println(neai_class); class_index++; } else { neai_class = mostFrequent(neai_class_array, 10); Serial.print(F("Most frequent class = ")); Serial.println(neai_class); class_index = 0; if (neai_class == 4 && previous_neai_class != 4) // EMPTY { previous_neai_class = neai_class; Display.beginDraw(); Display.image(img_backgnd, (Display.width() - img_backgnd.width())/2, (Display.height() - img_backgnd.height())/2); Display.endDraw(); Serial.println(F("Empty class detected!")); } else if (neai_class == 1 && previous_neai_class != 1) // PAPER { previous_neai_class = neai_class; Serial.println(F("Paper class detected!")); signs_wheel(); signs_result(neai_class); } else if (neai_class == 3 && previous_neai_class != 3) // SCISSORS { previous_neai_class = neai_class; Serial.println(F("SCISSORS class detected!")); signs_wheel(); signs_result(neai_class); } else if (neai_class == 2 && previous_neai_class != 2) // ROCK { previous_neai_class = neai_class; Serial.println(F("Rock class detected!")); signs_wheel(); signs_result(neai_class); } } #endif } } } void signs_wheel(void) { for(int i = 0 ; i < 10 ; i++) { Display.beginDraw(); Display.image(img_backgnd, (Display.width() - img_backgnd.width())/2, (Display.height() - img_backgnd.height())/2); Display.image(img_SCISSORS, LEFT_SIGN_X, SIGN_Y); if(i % (CLASS_NUMBER - 1) == 0) { Display.image(img_rock, RIGHT_SIGN_X, SIGN_Y); } else if(i % (CLASS_NUMBER - 1) == 1) { Display.image(img_paper, RIGHT_SIGN_X, SIGN_Y); } else { Display.image(img_SCISSORS, RIGHT_SIGN_X, SIGN_Y); } Display.endDraw(); } } void signs_result(uint16_t neaiclass) { Display.beginDraw(); Display.image(img_backgnd, (Display.width() - img_backgnd.width())/2, (Display.height() - img_backgnd.height())/2); int random_img = random(0, CLASS_NUMBER - 1); if(random_img == neaiclass - 1) { Display.fill(255, 127, 127); Display.rect(LEFT_SIGN_X - 10, SIGN_Y - 10, SIGN_WIDTH + 20, SIGN_HEIGHT + 20); Display.rect(RIGHT_SIGN_X - 10, SIGN_Y - 10, SIGN_WIDTH + 20, SIGN_HEIGHT + 20); } else if(random_img == (neaiclass == 1) ? 2 : (neaiclass == 2) ? 0 : 1) { Display.fill(255, 0, 0); Display.rect(LEFT_SIGN_X - 10, SIGN_Y - 10, SIGN_WIDTH + 20, SIGN_HEIGHT + 20); Display.fill(0, 255, 0); Display.rect(RIGHT_SIGN_X - 10, SIGN_Y - 10, SIGN_WIDTH + 20, SIGN_HEIGHT + 20); } else { Display.fill(0, 255, 0); Display.rect(LEFT_SIGN_X - 10, SIGN_Y - 10, SIGN_WIDTH + 20, SIGN_HEIGHT + 20); Display.fill(255, 0, 0); Display.rect(RIGHT_SIGN_X - 10, SIGN_Y - 10, SIGN_WIDTH + 20, SIGN_HEIGHT + 20); } Display.image(img_classes[neaiclass - 1], LEFT_SIGN_X, SIGN_Y); Display.image(img_classes[random_img], RIGHT_SIGN_X, SIGN_Y); Display.endDraw(); delay(1000); } uint16_t mostFrequent(uint16_t arr[], int n) { int count = 1, tempCount; uint16_t temp = 0,i = 0,j = 0; //Get first element uint16_t popular = arr[0]; for (i = 0; i < (n- 1); i++) { temp = arr[i]; tempCount = 0; for (j = 1; j < n; j++) { if (temp == arr[j]) tempCount++; } if (tempCount > count) { popular = temp; count = tempCount; } } return popular; }
在能够烧录代码之前,我们需要做一些准备工作。
我们将在屏幕上显示背景以及SHIFUMI(剪刀、石头、布)游戏的手势。您可以下载以下图像:
背景图像:
获取所有二进制图像,并将它们复制到项目文件夹中。您可以创建一个名为images的文件夹(例如),然后将它们放入其中。
在代码中,更新图像路径。您可能需要添加图像的完整路径。
4.4 NanoEdge库的使用↑
关于NanoEdge AI库的使用,它非常简单:
如果您想要重现这个演示设置,以下是所使用的资源:
4x Brackets 20.stl
M3尼龙12mm螺丝和M3尼龙螺母
支撑物:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。