Испытание платформы машинного обучения Edge Impulse на плате XIAO BLE Sense

Мы видели платформу разработки Edge Impulse для машинного обучения на периферийных устройствах, используемую несколькими платами, но пока у нас не было возможности опробовать ее. Поэтому, когда представители компании Seeed Studio спросили нас, интересно ли нам протестировать плату XIAO BLE Sense на базе nRF52840 , мы подумали, что было бы неплохо протестировать ее с помощью Edge Impulse, поскольку мы видели демонстрацию распознавания движения/жестов на плате.

Это было не просто, так как нам потребовалось четыре месяца, чтобы завершить обзор с того момента, как Seeed Studio впервые связалась с нами, в основном из-за плохой работы DHL, из-за которой первые платы застряли на таможни, а затем тратить время на не самые лучшие инструкции, которые нам приходилось видеть (теперь исправлены), и другие моменты. Но нам удалось заставить его работать (вроде), так что давайте посмотрим.

Распаковка XIAO BLE (Sense) и OLED-дисплея

Поскольку в демонстрации распознавания жестов использовался OLED-дисплей, мы также попросили его и получили плату XIAO BLE (без датчика), плату XIAO BLE Sense и OLED-дисплей Grove 0,66″ .

Обе платы очень маленькие и абсолютно одинаковые, за исключением того, что в XIAO BLE отсутствует встроенный 6-осевой IMU LSM6DS3TR (внизу слева).

Xiao BLE Sense (вверху) против Xiao BLE (внизу)

Необходимость пайки…

Перед загрузкой прошивки на плату пришлось припаять дисплей к плате. Мы просто перерезали кабель Grove и припаяли черный и красный провода к питанию, а белый и желтый к I2C.

У нас нет 3D-принтера (так же застрял на таможне), поэтому вместо этого мы использовали несколько слоев двустороннего скотча, чтобы соединить две платы вместе, но это необязательно.

Эскизы Arduino для OLED-дисплея и акселерометра XIAO BLE Sense

Нам потребовалось некоторое время, чтобы найти инструкции для демонстрации распознавания жестов, так как они не были указаны в описании видео, и нам не удалось ничего найти об этом в вики Seeed Studio. В конце концов нам дали ссылку на инструкции, и компания изменила веб-сайт, чтобы его было легче найти.

Прежде чем испробовать Edge Impulse, мы запустим два скетча Arduino, чтобы проверить, как работает OLED-дисплей и акселерометр.

Первым шагом является добавление URL-адреса диспетчера плат Seeed Studio: https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json.

Теперь мы можем установить пакет для поддержки плат Seeed nRF52…

… и как только это будет сделано, давайте подключим плату кабелем USB-C к нашему компьютеру и выберем плату Seeed XIAO BLE Sense — nRF52840 с настройками по умолчанию.

Давайте попробуем программу «Hello World», чтобы убедиться, что наша плата работает и соединение с OLED-дисплеем в порядке.

#include <Arduino.h>
#include <U8g2lib.h>
 
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
 
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/  PIN_WIRE_SCL, /* data=*/ PIN_WIRE_SDA,  /* reset=*/ U8X8_PIN_NONE);
 
void setup(void) {
  u8g2.begin();
}
 
void loop(void) {
  u8g2.clearBuffer();                   // clear the internal memory
  u8g2.setFont(u8g2_font_ncenB08_tr);   // choose a suitable font
  u8g2.drawStr(32,30,"Hello");    // write something to the internal memory
  u8g2.drawStr(32,45,"Seeed!"); 
  u8g2.sendBuffer();                    // transfer internal memory to the display
  delay(1000);  
}

Но устройство работало не так как ожидалось, и во время компиляции мы получили ошибку:

exec: "adafruit-nrfutil": executable file not found in $PATH
Error compiling for board Seeed XIAO BLE Sense - nRF52840.

Ответ на сайте Adafruit, где мы узнаем две важные детали:

  • Для nRF52 требуется Arduino 1.8.15 или выше, поэтому вам, возможно, придется обновиться до последней версии.
  • Linux требует установки adafruit-nrfutil

Поскольку мы используем Ubuntu 20.04, нам пришлось запустить:

pip3 install --user adafruit-nrfutil
export PATH=:$PATH:$HOME/.local/bin

Обратите внимание, что это НЕ требуется, если вы используете Arduino IDE в Windows или MacOS. Утилита была установлена ​​в $HOME/.local/bin, поэтому вам нужно добавить ее в свой путь и перезапустить Arduino IDE. Это можно временно сделать в командной строке:

export PATH=:$PATH:$HOME/.local/bin

Или вы можете изменить файл /etc/environment или ~/.bashrc, чтобы навсегда добавить папку в свой PATH. На удалось создать скетч и двоичный файл был записан на плату без проблем.

Но на дисплее ничего не отображалось. Мы добавили отладочное сообщение serial.println в основной цикл, чтобы убедиться, что он действительно работает, дважды проверили соединения с помощью мультиметра и не смогли найти очевидного решения. Представители Seeed Studio посоветовали нам перейти на версию 1.0.0 пакета плат Seeed nRF52.

И это действительно помогло!

Обратите внимание, что вам больше не нужно переходить на версию 1.0.0 в остальной части обзора, и рекомендуется использовать 2.6.1 и выше. Новый образец «Hello World» выглядит так:

#include <Arduino.h>
#include <U8x8lib.h>
 
U8X8_SSD1306_64X48_ER_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE); 
 
void setup(void) {
  u8x8.begin();
}
 
void loop(void) {
      u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
      u8x8.drawString(0,0,"idle");
      u8x8.drawString(0,1,"left");
      u8x8.drawString(0,2,"right");
      u8x8.drawString(0,3,"up&down");   
}

Пора переходить к тестированию демо акселерометра. Во-первых, нам нужно установить библиотеку LSM6DS3 Arduino от Seeed Studio.

Обратите внимание, что существует также официальный Arduino_LSM6DS3, который вам может потребоваться удалить, чтобы избежать конфликтов. Вот код:

#include "LSM6DS3.h"
#include "Wire.h"
 
//Create a instance of class LSM6DS3
LSM6DS3 myIMU(I2C_MODE, 0x6A);    //I2C device address 0x6A
 
#define CONVERT_G_TO_MS2    9.80665f
#define FREQUENCY_HZ        50
#define INTERVAL_MS         (1000 / (FREQUENCY_HZ + 1))
 
static unsigned long last_interval_ms = 0;
 
void setup() {
    // put your setup code here, to run once:
    Serial.begin(115200);
    while (!Serial);
    //Call .begin() to configure the IMUs
    if (myIMU.begin() != 0) {
        Serial.println("Device error");
    } else {
        Serial.println("Device OK!");
    }
}
 
void loop() {
 
    if (millis() > last_interval_ms + INTERVAL_MS) {
        last_interval_ms = millis();
 
        Serial.print(myIMU.readFloatGyroX() * CONVERT_G_TO_MS2,4);
        Serial.print('\t');
        Serial.print(myIMU.readFloatGyroY() * CONVERT_G_TO_MS2,4);
        Serial.print('\t');
        Serial.println(myIMU.readFloatGyroZ() * CONVERT_G_TO_MS2,4);
    }
}

Обратите внимание, что когда мы использовали платы Seeed nRF52 v1.0.0 с этим образцом, мы получали сообщения «ошибка декодирования», которые исчезли с версией 2.6.1. Нам нужно открыть последовательный монитор, чтобы проверить, отображаются ли значения X, Y, Z.

Важно: для следующего шага нам все равно потребуется запустить демо-версию ускорителя. Поэтому, если вы сначала играете с другими примерами, убедитесь, что демо-версия accelerator запущена, прежде чем переключаться на Edge Impulse.

Edge Impulse на XIAO BLE Sense

Теперь, когда мы знаем, что наше оборудование работает должным образом, давайте перейдем к Edge Impulse Studio, чтобы начать работу. Давайте зарегистрируемся и создадим наш первый проект.

Мы выберем данные акселерометра.

Нам также потребуется установить Edge Impulse CLI в Linux (Ubuntu 20.04 для этого обзора). Для этого сначала требуется установить NodeJS 14.x:

curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt install -y nodejs

Каталог по умолчанию будет находиться в /usr, доступном для root, давайте изменим его на каталог пользователя, который мы также добавим в наш путь:

mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc

Теперь мы можем установить Edge Impulse CLI:

npm install -g edge-impulse-cli

Возможно, вам придется выйти из терминала и перезапустить его, чтобы применить новый PATH. Теперь мы можем запустить edge-impulse-data-forwarder, используемый для плат, официально не поддерживаемых Edge Impulse (например, XIAO BLE Sense):

$ edge-impulse-data-forwarder
Edge Impulse data forwarder v1.14.12
Endpoints:
    Websocket: wss://remote-mgmt.edgeimpulse.com
    API:       https://studio.edgeimpulse.com/v1
    Ingestion: https://ingestion.edgeimpulse.com
 
[SER] Connecting to /dev/ttyACM0
[SER] Serial is connected (5B:5B:83:F3:C6:0A:0C:DD)
[WS ] Connecting to wss://remote-mgmt.edgeimpulse.com
[WS ] Connected to wss://remote-mgmt.edgeimpulse.com
 
[SER] Detecting data frequency...
[SER] Detected data frequency: 50Hz
? 3 sensor axes detected (example values: [10.9834,-15.7887,-4.8053]). What do y
ou want to call them? Separate the names with ',': Ax, Ay, Az
? What name do you want to give this device? XIAO BLE SENSE
[WS ] Device "XIAO BLE SENSE" is now connected to project "XIAO BLE Sense motion detection"
[WS ] Go to https://studio.edgeimpulse.com/studio/91558/acquisition/training to build your machine learning model!

При первом запуске команды вам потребуется ввести имя пользователя и пароль (не показано выше). Утилита сканирует последовательные устройства, подключается к граничному импульсу, пытается обнаружить данные из последовательного порта и после этого просит нас назвать поля данных (Ax, Ay, Az), устройство (XIAO BLE SENSE) и автоматически подключит его в проект, который мы только что создали. Если в Edge Impulse несколько проектов, вам будет предложено сначала выбрать проект. Это означает, что он в основном не зависит от аппаратного обеспечения, и пока ваша плата выводит данные акселерометра на последовательный интерфейс, он должен работать.

Вернёмся к Edge Impulse, нажмём на Сбор данных, и мы увидим наше устройство вместе с параметрами датчика и настройками частоты данных.

Давайте установим размер выборки на 20 000 мс, определим метку, нажмем «Начать выборку» и переместим плату вверх и вниз примерно на 1 секунду в течение 20 секунд, чтобы получить данные.

Затем нам нужно разделить данные, щелкнув три точки в необработанном разделе и выбрав «Разделить выборку». Нажмите «+Добавить сегмент», чтобы добавить дополнительный раздел. Мы должны повторять это, пока не получим около 20 сегментов, представляющих движение вверх и вниз. Если вы двигались медленнее или быстрее, чем 1 с, отрегулируйте время в «Установить длину сегмента (мс)».

Мы используем Firefox, и у нас была ошибка, из-за которой мы могли добавить сегмент, но когда мы выбирали его для перемещения, он смещался вправо, иногда за пределы экрана. Но если бы мы продолжали нажимать кнопку мыши и двигать ее влево, это могло бы вернуть ее в поле зрения. Это было не совсем удобно, и мы не можем слишком сильно увеличивать масштаб, иначе коробка слишком сильно выйдет за пределы дисплея. Использование Chrome или Microsoft Edge с Edge Impulse может быть лучше.

Как только мы нажмем «Разделить», мы увидим выбранную нами 1-секундную выборку данных.

Мы можем повторить сбор и разделение данных для других жестов, таких как левый и правый, круг по часовой стрелке и круг против часовой стрелки. Но мы бы порекомендовали сначала сделать это простым, как мы увидим ниже.

На этом этапе вы можете увидеть проблему:

Одна или несколько меток в вашем наборе данных имеют плохое разделение обучения/тестирования.

Чтобы исправить это, вы можете либо собирать тестовые данные в течение более короткого периода (например, 2 секунды), либо повторно сбалансировать набор данных, щелкнув «Панель инструментов» в левом меню и прокрутив вниз, чтобы найти кнопку «Выполнить обучение/тестовое разделение».

Теперь мы готовы создать импульс. Нажмите «Создать импульс» -> «Добавить блок обработки» -> «Спектральный анализ» -> «Добавить блок обучения» -> «Выбрать классификацию (Keras)» -> «Сохранить импульс».

Щелкните Спектральные объекты в Спектральном анализе , затем Сохранить параметры и Создать объекты .

Вы ожидаете, что данные будут четко разделены, но явно есть некоторое совпадение, поэтому обученные данные не идеальны. Мы все же постараемся продолжить.

Нажмите «Классификатор NN», затем «Начать обучение», что займет около одной минуты. Затем мы можем выбрать Неоптимизированный (float32)

Точность действительно низкая, и модель практически непригодна для использования, поскольку правильно определяется только образец «против часовой стрелки».

Давайте попробуем еще раз, но только вверх и вниз, влево и вправо и по кругу (по часовой стрелке), стараясь уложить каждое движение в одну секунду.

Диаграмма обозревателя функций выглядит намного лучше с синими, оранжевыми и зелеными точками в своих областях. Также можно удалить некоторые образцы, которые могут вызвать проблемы. Результаты не идеальны, но мы все же можем попробовать.

Пройдемся по «Тестированию модели» в левом меню.

Это разочаровывает, так как хорошо работает только круг по часовой стрелке, в то время как «лево и право» можно обнаружить примерно в половине случаев, а «вверх и вниз» ошибочно определяют как лево и право. Таким образом, помимо обучения данных, оператор также должен быть обучен.

Важной частью является метод создания импульса, и со временем мы сможем создавать более качественные данные. Давайте создадим библиотеку Arduino, щелкнув Deployment в левом меню, затем Arduino Library,  Build и, наконец, загрузим файл .ZIP.

Вернемся к среде разработки Arduino. Загрузите образец Arduino, предоставленный Seeed Studio, который претерпел множество изменений за последние несколько месяцев. Измените заголовок файла Edge Impulse (строка 24 в примере ниже), чтобы он соответствовал вашему собственному, и нам также пришлось закомментировать строку с библиотекой U8X8lib.h. Мы также немного модифицировали код, так как не тренировался на «холостом ходу», как в их демо:

/* Edge Impulse Arduino examples
 * Copyright (c) 2021 EdgeImpulse Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
 
/* Includes ---------------------------------------------------------------- */
#include <XIAO_BLE_Sense_motion_detection_inferencing.h>
#include <LSM6DS3.h>
#include <U8g2lib.h>
// #include <U8X8lib.h>
#include <Wire.h>
/* Constant defines -------------------------------------------------------- */
#define CONVERT_G_TO_MS2    9.80665f
#define MAX_ACCEPTED_RANGE  2.0f        // starting 03/2022, models are generated setting range to +-2, but this example use Arudino library which set range to +-4g. If you are using an older model, ignore this value and use 4.0f instead
 
/*
 ** NOTE: If you run into TFLite arena allocation issue.
 **
 ** This may be due to may dynamic memory fragmentation.
 ** Try defining "-DEI_CLASSIFIER_ALLOCATION_STATIC" in boards.local.txt (create
 ** if it doesn't exist) and copy this file to
 ** `<ARDUINO_CORE_INSTALL_PATH>/arduino/hardware/<mbed_core>/<core_version>/`.
 **
 ** See
 ** (https://support.arduino.cc/hc/en-us/articles/360012076960-Where-are-the-installed-cores-located-)
 ** to find where Arduino installs cores on your machine.
 **
 ** If the problem persists then there's not enough memory for this model and application.
 */
 
U8X8_SSD1306_64X48_ER_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE); 
 
/* Private variables ------------------------------------------------------- */
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
LSM6DS3 myIMU(I2C_MODE, 0x6A);
/**
* @brief      Arduino setup function
*/
 
const int RED_ledPin =  11;
const int BLUE_ledPin =  12;
const int GREEN_ledPin =  13; 
 
void setup()
{
    // put your setup code here, to run once:
    Serial.begin(115200);
    //u8g2.begin();
    u8x8.begin();
    Serial.println("Edge Impulse Inferencing Demo");
 
    //if (!IMU.begin()) {
      if (!myIMU.begin()) {
        ei_printf("Failed to initialize IMU!\r\n");
    }
    else {
        ei_printf("IMU initialized\r\n");
    }
 
    if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != 3) {
        ei_printf("ERR: EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME should be equal to 3 (the 3 sensor axes)\n");
        return;
    }
}
 
/**
 * @brief Return the sign of the number
 * 
 * @param number 
 * @return int 1 if positive (or 0) -1 if negative
 */
float ei_get_sign(float number) {
    return (number >= 0.0) ? 1.0 : -1.0;
}
 
/**
* @brief      Get data and run inferencing
*
* @param[in]  debug  Get debug info if true
*/
void loop()
{   
    u8x8.clear();
    u8x8.setFont(u8g2_font_ncenB08_tr);
  
    ei_printf("\nStarting inferencing in 2 seconds...\n");
 
    delay(2000);
 
    ei_printf("Sampling...\n");
 
    // Allocate a buffer here for the values we'll read from the IMU
    float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };
 
    for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += 3) {
        // Determine the next tick (and then sleep later)
        uint64_t next_tick = micros() + (EI_CLASSIFIER_INTERVAL_MS * 1000);
 
        buffer[ix] = myIMU.readFloatAccelX();
        buffer[ix+1] = myIMU.readFloatAccelY();
        buffer[ix+2] = myIMU.readFloatAccelZ();
 
        for (int i = 0; i < 3; i++) {
            if (fabs(buffer[ix + i]) > MAX_ACCEPTED_RANGE) {
                buffer[ix + i] = ei_get_sign(buffer[ix + i]) * MAX_ACCEPTED_RANGE;
            }
        }
 
        buffer[ix + 0] *= CONVERT_G_TO_MS2;
        buffer[ix + 1] *= CONVERT_G_TO_MS2;
        buffer[ix + 2] *= CONVERT_G_TO_MS2;
 
        delayMicroseconds(next_tick - micros());
    }
 
    // Turn the raw buffer in a signal which we can the classify
    signal_t signal;
    int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
    if (err != 0) {
        ei_printf("Failed to create signal from buffer (%d)\n", err);
        return;
    }
 
    // Run the classifier
    ei_impulse_result_t result = { 0 };
 
    err = run_classifier(&signal, &result, debug_nn);
    if (err != EI_IMPULSE_OK) {
        ei_printf("ERR: Failed to run classifier (%d)\n", err);
        return;
    }
 
    // print the predictions
    ei_printf("Predictions ");
    ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
        result.timing.dsp, result.timing.classification, result.timing.anomaly);
    ei_printf(": \n");
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        ei_printf("    %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
    }
#if EI_CLASSIFIER_HAS_ANOMALY == 1
    ei_printf("    anomaly score: %.3f\n", result.anomaly);
#endif
 
  if (result.classification[0].value > 0.5) {
      digitalWrite(RED_ledPin, LOW);
      digitalWrite(BLUE_ledPin, HIGH);                          // circle       red
      digitalWrite(GREEN_ledPin, HIGH);
      u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
      u8x8.drawString(1,2,"Circle");
      u8x8.refreshDisplay();
      delay(2000);
   } else if (result.classification[1].value > 0.5) {
       digitalWrite(RED_ledPin, HIGH);                              //left&right           blue
       digitalWrite(BLUE_ledPin, LOW);
       digitalWrite(GREEN_ledPin, HIGH);
      u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
      u8x8.drawString(2,3,"left");
      u8x8.drawString(2,4,"right");
      u8x8.refreshDisplay();
      delay(2000);
   } else if (result.classification[2].value > 0.5) {
        digitalWrite(RED_ledPin, HIGH);
        digitalWrite(BLUE_ledPin, HIGH);
        digitalWrite(GREEN_ledPin, LOW);                           //up&down             green
          u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
          u8x8.drawString(2,3,"up");
          u8x8.drawString(2,4,"down");
          u8x8.refreshDisplay();
          delay(2000);
   } else {
        digitalWrite(RED_ledPin, LOW);
        digitalWrite(BLUE_ledPin, LOW);
        digitalWrite(GREEN_ledPin, LOW);                           //idle             off LEDs off
          u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
          u8x8.drawString(2,3,"idle");
          u8x8.refreshDisplay();
          delay(2000);
   }
}

Нам нужно добавить библиотеку ZIP, которую мы загрузили из Edge Impulse, в библиотеку Arduino, и теперь мы можем собрать и записать код на плату. Это займет около 5 минут в первый раз и около 2 минут для последующих сборок.

Вот вывод из последовательной консоли.

Starting inferencing in 2 seconds...
Sampling...
Predictions (DSP: 10 ms., Classification: 0 ms., Anomaly: 0 ms.): 
    circle clockwise: 0.59337
    left and right: 0.14814
    up and down: 0.25850
 
Starting inferencing in 2 seconds...
Sampling...
Predictions (DSP: 10 ms., Classification: 0 ms., Anomaly: 0 ms.): 
    circle clockwise: 0.35315
    left and right: 0.30938
    up and down: 0.33747
 
Starting inferencing in 2 seconds...
Sampling...
Predictions (DSP: 10 ms., Classification: 0 ms., Anomaly: 0 ms.): 
    circle clockwise: 0.67348
    left and right: 0.09551
    up and down: 0.23102

Таким образом, каждый раз, когда значение превышает 50, будет отображаться соответствующий текст (например, «Круг»), и если ни один из результатов не превышает 50, программа просто отображает «Idle».

Вот как это выглядит на видео.

Круг распознается (даже если мы идем не в ту сторону, как на видео ниже), но мы только несколько раз попадали «влево и вправо» и никогда не «вверх и вниз». Таким образом, должно пройти некоторое время, чтобы получить надлежащую демонстрацию, важной частью которой является сбор данных и точное разделение, чтобы убедиться, что все образцы для определенного жеста выглядят примерно одинаково.

Нам хотелось бы поблагодарить Seeed Studio за отправку плат XIAO BLE (Sense) и OLED-дисплея Grove для тестирования Edge Impulse. Нам просто хотелось бы, чтобы документация была правильной с первого раза. Если вы заинтересованы в воспроизведении приведенной выше демонстрации, плата XIAO BLE Sense продается за 15,99 долларов США, а OLED-дисплей — за 5,50 долларов США, и это необязательно, мы можем увидеть результаты в последовательном терминале.

Выражаем свою благодарность источнику из которого взята и переведена статья, сайту cnx-software.com.

Оригинал статьи вы можете прочитать здесь.

0 0 vote
Article Rating
Подписаться
Уведомление о
guest

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

0 Комментарий
Inline Feedbacks
View all comments