198|0

46

帖子

3

TA的资源

一粒金砂(中级)

楼主
 

【Follow me第二季第4期】EdgeImpluse在线训练机器学习模型并部署 [复制链接]

 

本帖最后由 iexplore123 于 2024-12-27 01:54 编辑

【Follow me第二季第4期】 通过IMU数据结合机器学习算法,识别运动状态,并通过串口打印

任务概述

在这个任务中,我们将使用Edge Impulse平台构建机器学习模型,通过IMU数据识别运动状态,并串口打印。
任务二中我们已经读取了nano RP2040 Connect板载的IMU传感器(LSM6DSOX)的六轴原始数据,它包含三轴加速度计和三轴陀螺仪的数据,可以用于运动检测和姿态跟踪。接下来我们将采集IMU数据构建数据集,并使用简单的分类模型

Edge Impulse

Edge Impulse是一个端到端的开发平台,可以帮助开发者快速构建、部署和管理嵌入式机器学习模型。Edge Impulse提供在线开发工具,包括数据采集、特征提取、模型训练和部署功能,支持多种硬件平台和开发环境,适合嵌入式开发者和机器学习爱好者低门槛地进行机器学习模型开发。

使用Edge Impulse构建机器学习模型

  1. 注册Edge Impulse账号,创建一个新项目。

    我创建的项目名为RP2040,项目是公开的,可以在https://studio.edgeimpulse.com/public/586314/live查看。

    项目界面如下:

左边是项目导航栏,仪表盘(Dashboard)显示项目的概览信息,数据(Data Acquisition)提供数据采集和标注功能,Impulse Design栏目是我们的重头戏,包括特征提取、模型训练和验证等功能,Deployment栏目提供模型部署功能。

  1. 数据采集 Edge Impulse Studio提供了数据采集功能,可以通过Web浏览器采集数据。Edge Impulse Studio提供了nano RP2040 Connect的官方固件,可以直接在板子上运行固件与web端连接,采集数据。

    Edge Impluse提供的RP2040固件(UF2)下载地址:https://cdn.edgeimpulse.com/firmware/raspberry-rp2040.zip 该固件源码仓库:https://github.com/edgeimpulse/firmware-pi-rp2040

确保刷上固件后,就可以通过WebUSB连接到网页端直接采集数据啦。我们这里在Sensor下拉菜单中选择Inertial,即IMU传感器,填写数据标签,点击Start sampling采集数据。我在这里录入了五种动作的数据:静止,画圈,上下摆动,左右摆动,挥舞,每种动作划分了12条数据(其中两条作为测试集),每条数据持续5秒,共计60条数据。

  1. 设计Impulse 数据采集完成后,我们就可以进行特征提取、模型训练和验证了。Edge Impulse Studio提供了多种特征提取器和模型,我们可以根据数据特点选择合适的特征提取器和模型。在这里我们选择Spectral features特征提取器和Classification分类模型,特征提取器提取频谱特征,分类模型进行动作分类。

接下来我们在Spectral features下设置参数,然后点击Save Impulse保存。

生成特征后我们发现录入的五种动作的数据在频谱特征下有明显的区分度,接下来我们就可以训练模型了。

我们直接使用默认的神经网络参数,点击Save & train开始训练模型。训练完成后我们可以查看模型的准确率和混淆矩阵。

训练完的模型我们也可以直接在网页上进行模型效果的实时验证,连接开发板进行在线采样后模型在线进行推理并给出结果。

  1. 部署模型

模型训练完成后,我们可以部署模型到nano RP2040 Connect板子上。Edge Impulse可以将模型导出为各种常用的库类型。点击Deployment栏目,选择Arduino Library,然后点击Build编译模型库。编译完成后,我们可以下载模型库,然后在Arduino IDE中导入库,库中包含简单的示例代码,编译烧录rp2040_fusion即可在板子上运行改模型。

代码实现(rp2040_fusion)

  1. /* Includes ---------------------------------------------------------------- */
  2. #include <RP2040_inferencing.h>
  3. #include <Arduino_LSM6DSOX.h> //Click here to get the library: http://librarymanager/All#Arduino_LSM6DSOX
  4. enum sensor_status {
  5. NOT_USED = -1,
  6. NOT_INIT,
  7. INIT,
  8. SAMPLED
  9. };
  10. /** Struct to link sensor axis name to sensor value function */
  11. typedef struct{
  12. const char *name;
  13. float *value;
  14. uint8_t (*poll_sensor)(void);
  15. bool (*init_sensor)(void);
  16. int8_t status;// -1 not used 0 used(unitialized) 1 used(initalized) 2 data sampled
  17. } eiSensors;
  18. /* Constant defines -------------------------------------------------------- */
  19. #define CONVERT_G_TO_MS2 9.80665f
  20. #define MAX_ACCEPTED_RANGE4.0f // starting 03/2022, models are generated setting range to +-2,
  21. // but this example use Arudino library which set range to +-4g.
  22. // If you are using an older model, ignore this value and use 4.0f instead
  23. /** Number sensor axes used */
  24. #define N_SENSORS 7
  25. /* Forward declarations ------------------------------------------------------- */
  26. float ei_get_sign(float number);
  27. static bool ei_connect_fusion_list(const char *input_list);
  28. bool init_IMU(void);
  29. uint8_t poll_acc(void);
  30. uint8_t poll_gyr(void);
  31. uint8_t poll_mag(void);
  32. uint8_t poll_temp(void);
  33. /* Private variables ------------------------------------------------------- */
  34. static const bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
  35. static float data[N_SENSORS];
  36. static int8_t fusion_sensors[N_SENSORS];
  37. static int fusion_ix = 0;
  38. /* Function declarations --------------------------------------------------- */
  39. void print_inference_result(ei_impulse_result_t result);
  40. /** Used sensors value function connected to label name */
  41. eiSensors sensors[] =
  42. {
  43. "accX", &data[0], &poll_acc, &init_IMU, NOT_USED,
  44. "accY", &data[1], &poll_acc, &init_IMU, NOT_USED,
  45. "accZ", &data[2], &poll_acc, &init_IMU, NOT_USED,
  46. "gyrX", &data[3], &poll_gyr, &init_IMU, NOT_USED,
  47. "gyrY", &data[4], &poll_gyr, &init_IMU, NOT_USED,
  48. "gyrZ", &data[5], &poll_gyr, &init_IMU, NOT_USED,
  49. "temperature", &data[6], &poll_temp, &init_IMU, NOT_USED,
  50. };
  51. /**
  52. * @brief Arduino setup function
  53. */
  54. void setup()
  55. {
  56. /* Init serial */
  57. Serial.begin(115200);
  58. // comment out the below line to cancel the wait for USB connection (needed for native USB)
  59. while (!Serial);
  60. Serial.println("Edge Impulse Sensor Fusion Inference\r\n");
  61. /* Connect used sensors */
  62. if(ei_connect_fusion_list(EI_CLASSIFIER_FUSION_AXES_STRING) == false) {
  63. ei_printf("ERR: Errors in sensor list detected\r\n");
  64. return;
  65. }
  66. /* Init & start sensors */
  67. for(int i = 0; i < fusion_ix; i++) {
  68. if (sensors[fusion_sensors<i>].status == NOT_INIT) {
  69. sensors[fusion_sensors<i>].status = (sensor_status)sensors[fusion_sensors<i>].init_sensor();
  70. if (!sensors[fusion_sensors<i>].status) {
  71. ei_printf("%s axis sensor initialization failed.\r\n", sensors[fusion_sensors<i>].name);
  72. }
  73. else {
  74. ei_printf("%s axis sensor initialization successful.\r\n", sensors[fusion_sensors<i>].name);
  75. }
  76. }
  77. }
  78. }
  79. /**
  80. * @brief Get data and run inferencing
  81. */
  82. void loop()
  83. {
  84. ei_printf("\nStarting inferencing in 2 seconds...\r\n");
  85. delay(2000);
  86. if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != fusion_ix) {
  87. ei_printf("ERR: Sensors don't match the sensors required in the model\r\n"
  88. "Following sensors are required: %s\r\n", EI_CLASSIFIER_FUSION_AXES_STRING);
  89. return;
  90. }
  91. ei_printf("Sampling...\r\n");
  92. // Allocate a buffer here for the values we'll read from the sensor
  93. float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };
  94. for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME) {
  95. // Determine the next tick (and then sleep later)
  96. int64_t next_tick = (int64_t)micros() + ((int64_t)EI_CLASSIFIER_INTERVAL_MS * 1000);
  97. for(int i = 0; i < fusion_ix; i++) {
  98. if (sensors[fusion_sensors<i>].status == INIT) {
  99. sensors[fusion_sensors<i>].poll_sensor();
  100. sensors[fusion_sensors<i>].status = SAMPLED;
  101. }
  102. if (sensors[fusion_sensors<i>].status == SAMPLED) {
  103. buffer[ix + i] = *sensors[fusion_sensors<i>].value;
  104. sensors[fusion_sensors<i>].status = INIT;
  105. }
  106. }
  107. int64_t wait_time = next_tick - (int64_t)micros();
  108. if(wait_time > 0) {
  109. delayMicroseconds(wait_time);
  110. }
  111. }
  112. // Turn the raw buffer in a signal which we can the classify
  113. signal_t signal;
  114. int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
  115. if (err != 0) {
  116. ei_printf("ERR:(%d)\r\n", err);
  117. return;
  118. }
  119. // Run the classifier
  120. ei_impulse_result_t result = { 0 };
  121. err = run_classifier(&signal, &result, debug_nn);
  122. if (err != EI_IMPULSE_OK) {
  123. ei_printf("ERR:(%d)\r\n", err);
  124. return;
  125. }
  126. print_inference_result(result);
  127. }
  128. /**
  129. * @brief Go through sensor list to find matching axis name
  130. *
  131. * @param axis_name
  132. * @return int8_t index in sensor list, -1 if axis name is not found
  133. */
  134. static int8_t ei_find_axis(char *axis_name)
  135. {
  136. int ix;
  137. for(ix = 0; ix < N_SENSORS; ix++) {
  138. if(strstr(axis_name, sensors[ix].name)) {
  139. return ix;
  140. }
  141. }
  142. return -1;
  143. }
  144. /**
  145. * @brief Check if requested input list is valid sensor fusion, create sensor buffer
  146. *
  147. * @param[in]input_list Axes list to sample (ie. "accX + gyrY + magZ")
  148. * @retvalfalse if invalid sensor_list
  149. */
  150. static bool ei_connect_fusion_list(const char *input_list)
  151. {
  152. char *buff;
  153. bool is_fusion = false;
  154. /* Copy const string in heap mem */
  155. char *input_string = (char *)ei_malloc(strlen(input_list) + 1);
  156. if (input_string == NULL) {
  157. return false;
  158. }
  159. memset(input_string, 0, strlen(input_list) + 1);
  160. strncpy(input_string, input_list, strlen(input_list));
  161. /* Clear fusion sensor list */
  162. memset(fusion_sensors, 0, N_SENSORS);
  163. fusion_ix = 0;
  164. buff = strtok(input_string, "+");
  165. while (buff != NULL) { /* Run through buffer */
  166. int8_t found_axis = 0;
  167. is_fusion = false;
  168. found_axis = ei_find_axis(buff);
  169. if(found_axis >= 0) {
  170. if(fusion_ix < N_SENSORS) {
  171. fusion_sensors[fusion_ix++] = found_axis;
  172. sensors[found_axis].status = NOT_INIT;
  173. }
  174. is_fusion = true;
  175. }
  176. buff = strtok(NULL, "+ ");
  177. }
  178. ei_free(input_string);
  179. return is_fusion;
  180. }
  181. /**
  182. * @brief Return the sign of the number
  183. *
  184. * @param number
  185. * @return int 1 if positive (or 0) -1 if negative
  186. */
  187. float ei_get_sign(float number) {
  188. return (number >= 0.0) ? 1.0 : -1.0;
  189. }
  190. bool init_IMU(void) {
  191. static bool init_status = false;
  192. if (!init_status) {
  193. init_status = IMU.begin();
  194. }
  195. return init_status;
  196. }
  197. uint8_t poll_acc(void) {
  198. if (IMU.accelerationAvailable()) {
  199. IMU.readAcceleration(data[0], data[1], data[2]);
  200. for (int i = 0; i < 3; i++) {
  201. if (fabs(data<i>) > MAX_ACCEPTED_RANGE) {
  202. data<i> = ei_get_sign(data<i>) * MAX_ACCEPTED_RANGE;
  203. }
  204. }
  205. data[0] *= CONVERT_G_TO_MS2;
  206. data[1] *= CONVERT_G_TO_MS2;
  207. data[2] *= CONVERT_G_TO_MS2;
  208. }
  209. return 0;
  210. }
  211. uint8_t poll_gyr(void) {
  212. if (IMU.gyroscopeAvailable()) {
  213. IMU.readGyroscope(data[3], data[4], data[5]);
  214. }
  215. return 0;
  216. }
  217. uint8_t poll_temp(void) {
  218. if (IMU.temperatureAvailable()) {
  219. int temp;
  220. IMU.readTemperature(temp);
  221. data[6] = temp;
  222. }
  223. return 0;
  224. }
  225. void print_inference_result(ei_impulse_result_t result) {
  226. // Print how long it took to perform inference
  227. ei_printf("Timing: DSP %d ms, inference %d ms, anomaly %d ms\r\n",
  228. result.timing.dsp,
  229. result.timing.classification,
  230. result.timing.anomaly);
  231. ei_printf("Predictions:\r\n");
  232. for (uint16_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
  233. ei_printf("%s: ", ei_classifier_inferencing_categories<i>);
  234. ei_printf("%.5f", result.classification<i>.value);
  235. }
  236. ei_printf("\r\n");
  237. // Print anomaly result (if it exists)
  238. #if EI_CLASSIFIER_HAS_ANOMALY == 1
  239. ei_printf("Anomaly prediction: %.3f\r\n", result.anomaly);
  240. #endif
  241. }
  242. #if !defined(EI_CLASSIFIER_SENSOR) || (EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_FUSION && EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_ACCELEROMETER)
  243. #error "Invalid model for current sensor"
  244. #endif

程序说明

该程序使用Edge Impulse SDK进行机器学习推理。代码主要负责进行初始化、传感器数据采集和推理。

在setup函数中,首先初始化串口通信,并等待USB连接。然后调用ei_connect_fusion_list函数连接所需的传感器。该函数解析传感器轴列表,并将匹配的传感器标记为未初始化状态。接下来,程序遍历所有连接的传感器并初始化它们。
loop主循环中首先打印推理开始的提示信息,并延迟两秒。然后检查所需的传感器数量是否与模型匹配,如果不匹配则打印错误信息并返回。接下来,程序开始采样传感器数据,将数据存储在缓冲区中。每次采样后,程序计算下一次采样的时间,并在需要时延迟以保持采样间隔一致。

采样完成后,程序将原始数据缓冲区转换为信号,并调用run_classifier函数进行推理。推理结果通过print_inference_result函数打印,包括推理时间、分类结果和异常检测结果(如果存在)。

传感器初始化和数据采集函数包括init_IMU、poll_acc、poll_gyr和poll_temp。init_IMU函数初始化IMU传感器,poll_acc函数采集加速度数据并进行范围限制和单位转换,poll_gyr函数采集陀螺仪数据,poll_temp函数采集温度数据。

效果展示

静止状态下模型预测idle的可能性接近100%,其他动作的可能性接近0%。

左右平移运动时模型对left-right的预测概率也相当高,能够很好的区分左右平移与左右挥舞。

最后附上作为arduino库导出的分类模型:

ei-rp2040-arduino-1.0.2.zip

(4.98 MB, 下载次数: 1)

任务进度

  • 搭建环境并开启第一步Blink三色LED / 串口打印Hello DigiKey & EEWorld!
  • 学习IMU基础知识,调试IMU传感器,通过串口打印六轴原始数据。
  • 学习PDM麦克风技术知识,调试PDM麦克风,通过串口打印收音数据和音频波形。
  • 通过RGB LED不同颜色、亮度显示PDM麦克风收到的声音大小。
  • 通过IMU数据结合机器学习算法,识别运动状态,并通过串口打印。
点赞(1) 关注
 
 

回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条
Microchip 直播|利用motorBench开发套件高效开发电机磁场定向控制方案 报名中!
直播主题:利用motorBench开发套件高效开发电机磁场定向控制方案
直播时间:2025年3月25日(星期二)上午10:30-11:30
快来报名!

查看 »

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表