Smartwatch Arduino rozpoznający gesty

Smartwatch Arduino rozpoznający gesty

Rozpoznawanie gestów umożliwia opracowywanie intuicyjnych i łatwych w obsłudze interfejsów użytkownika, które rewolucjonizują to, jak obsługujemy urządzenia elektroniczne. Technologia ta staje się coraz lepsza i sprawia, że interakcja z gadżetami i rzeczami wokół nas staje się prawdziwie futurystyczna i wygodna. Zaprezentowany projekt to inteligentny zegarek (smartwatch), który dzięki algorytmom uczenia maszynowego zastosowanym do wykrywania gestów, potrafi je interpretować i uruchamiać odpowiednie funkcje.

Autor już od długiego czasu rozwija projekt smartwatcha. Teraz postanowił dodać do projektu funkcjonalność uczenia maszynowego. Początkowo wydawało się to proste, jednak podczas prac okazało się to dużo bardziej skomplikowane. Wdrożenie algorytmu wykrywania gestów, bazującego na uczeniu maszynowym, okazało się bardziej złożone i wymagało frustrującego zbierania danych i trenowania modelu.

System powstał z użyciem frameworku TinyML, dedykowanego do implementacji algorytmów uczenia maszynowego na systemach wbudowanych, w szczególności na małych układach, takich jak mikrokontrolery stosowane w modułach Arduino Nano i Uno.

Potrzebne elementy

Do zestawienia urządzenia potrzebne będą następujące moduły i elementy elektroniczne:

  • moduł ESP-12E,
  • wyświetlacz OLED,
  • moduł z układem MPU6050,
  • moduł z konwerterem USB – interfejs szeregowy,
  • złącze USB,
  • silnik wibracyjny (do sprzęgu haptycznego),
  • stabilizator napięcia 3,3 V,
  • akumulator 105 mAh,
  • złącza (proste i kątowe),
  • kable połączeniowe,
  • rzep.

Montaż zegarka

Montaż smartwatcha jest bardzo prosty i nie zajmuje dużo czasu. Wszystko dzięki zastosowaniu odpowiedniej płytki drukowanej, na której znajdują się wszystkie elementy zegarka. Montaż płytki należy rozpocząć od montażu elementów SMD. Najpierw należy sobie przygotować wszystkie elementy, a potem przylutować je po kolei do płytki drukowanej, postępując według schematu, pokazanego na rysunku 1. Aby ułatwić lutowanie, należy przejść od lutowania mniejszych elementów SMD (rezystory, kondensatory, drobne układy scalone, takie jak stabilizatory) do większych elementów SMD, a następnie do elementów przewlekanych (moduły z MPU6050 czy wyświetlacz OLED). Podczas lutowania należy szczególnie uważać na baterię litowo-polimerową, która musi być umieszczona pomiędzy płytką a wyświetlaczem OLED. Aby bateria się nie przesuwała, dobrze jest umocować ją z pomocą dwustronnie klejącej taśmy.

Rysunek 1. Schemat smartwatcha

Dostępne są dwie wersje urządzenia, różniące się zastosowanym stabilizatorem. Jak pisze autor, typowo w swoich projektach stosuje scalony stabilizator liniowy AMS1117, ponieważ jest on znacznie tańszy i łatwiejszy do znalezienia niż inne układy. Jednak, aby ten projekt był znacznie sprawniejszy energetycznie, niż poprzednie układy, konieczne było użycie przetwornicy impulsowej. Na płytce drukowanej znajduje się miejsce na MCP1700 lub LSD3985. Należy zamontować tylko jeden z tych układów.

Uruchomienie

Autor umieścił na płytce drukowanej miejsce do podłączenia modułu FTDI, aby uprościć programowanie modułu. Pozwala to na nawiązanie komunikacji z komputerem PC i przesłanie do procesora kodu. Aby zaprogramować płytkę, należy uruchomić ESP8266 w trybie flashowania (trzymając naciśnięty przycisk, który podłączony jest do GPIO0 modułu ESP12E podczas podłączania zegarka do zasilania np. z USB).

Aby sprawdzić, czy moduł działa można wgrać prosty przykład testowy, który zawarty jest w repozytorium autora na Githubie (https://bit.ly/3fJBulc). Program pozwala na przetestowanie podstawowych funkcjonalności zegarka, takich jak pobieranie czasu NTP. Wybudzanie systemu i obsługa ekranu. Jeśli wszystko działa, to dobrze – zakończyliśmy część sprzętową i możemy przyjrzeć się dokładniej oprogramowaniu do uczenia maszynowego i rozpoznawania gestów.

Uczenie maszynowe

Hasło „uczenie maszynowe” brzmi fantazyjnie i skomplikowanie. W rzeczywistości jest dosyć prostym konceptem, szczególnie w porównaniu do niektórych złożonych konwencjonalnych algorytmów. W przypadku większości algorytmów, gdy trzeba znaleźć odpowiedź na jakieś pytanie, musimy zaprogramować pewną sekwencję procesów, które system musi wykonać, aby uzyskać wynik końcowy. Ujęto to schematycznie na rysunku 2, prostym przykładem może być mnożenie. Jeśli chcemy znaleźć odpowiedź na problem w postaci mnożenia, znaleźć iloczyn liczb 2 i 5, możemy po prostu powiedzieć komputerowi, aby wykonał odpowiednio wiele działań dodawania i uzyskał poszukiwaną odpowiedź. Dokładnie tak, jak pokazano na rysunku 2b.

Rysunek 2. Schemat (a) i przykład (b) działania algorytmu

Uczenie maszynowe działa trochę inaczej. W tego rodzaju algorytmie wskazujemy komputerowi kilka pytań i kilka odpowiedzi na nie, a następnie nakazujemy mu samodzielnie odnaleźć sposób lub proces, który pozwala doprowadzić do tych odpowiedzi (rysunek 3), bez konieczności ręcznego programowania.

Rysunek 3. Schemat działania algorytmu uczenia maszynowego

Weźmy, jako przykład odnajdywanie jabłka na zdjęciu. Jest to bardzo łatwe dla człowieka, ale dość trudno jest zapisać w kodzie programu to, czym jest jabłko i sprawić, by komputer zrozumiał wszystkie funkcje jabłka. Nie jest to niemożliwe, ale jest bardzo żmudne i trudne.

Rysunek 4. Przykład działania algorytmu uczenia maszynowego

Zamiast tego możemy napisać algorytm, który sam się uczy, po prostu patrząc na tysiące zdjęć jabłek. Jest jeszcze jedna zaleta korzystania z algorytmów uczenia maszynowego – mogą one nawet wymyślić nowe sposoby znajdowania jabłek na zdjęciach, o których człowiek nawet by nie pomyślał. Proces ten zilustrowano na rysunku 4.

Algorytm klasyfikacyjny

Istnieje wiele sposobów i technik w uczeniu maszynowym, stosowanych do rozwiązywania problemów technicznych. W zaprezentowanym przypadku do wykrywania gestów autor postanowił użyć techniki zwanej klasyfikacją. Dlaczego klasyfikacja? Bo idealnie wychwytuje wzory i powtarzające się układy w analizowanych danych. Wystarczy spojrzeć na wykresy pokazane na rysunku 5.

Rysunek 5. Wykresy danych z akcelerometrów i żyroskopu w czasie dla różnych gestów

Dane z akcelerometrów wydają się być zupełnie przewidywalne, przynajmniej dla ludzkich oczu, po tym, jak ruch jest cyklicznie powtarzany. Po wykonaniu danego ruchu można w miarę łatwo odgadnąć, jaki to ruch, na podstawie samych wykresów. Człowiek może to robić nawet dla innych gestów i ruchów. Dzieje się tak, ponieważ nasz mózg łatwo przypisuje różne nazwy różnym wzorom, które obserwuje.

Podobnie, jeśli możemy wielokrotnie pokazać ten sam wzór danych algorytmowi uczenia maszynowego, jest on w stanie „zrozumieć” te dane i umieścić je w różnych grupach. Można powiedzieć, że algorytm klasyfikuje próbki danych do różnych klas. Więc następnym razem, gdy zobaczy podobny wzorzec w danych, będzie w stanie zidentyfikować, jaki to rodzaj ruchu lub gestu. Dlatego, do rozwiązania problemu rozpoznawania gestów autor konstrukcji wybrał algorytm klasyfikacyjny. Dokładniej mówiąc, wybrał algorytm losowego lasu decyzyjnego (random forest), który polega na tworzeniu wielu drzew decyzyjnych na etapie uczenia i wyboru najlepszego z nich.

Zbieranie danych

Uczenie algorytmu należy rozpocząć od zebrania danych, które będą potrzebne do trenowania algorytmu uczenia maszynowego. Aby rozwiązać ten problem, autor przygotował prosty program (listing 1), który gromadzi dane z modułu inercyjnego i przesyła je później do komputera. Aplikacja ta pracuje na układzie ESP8266. Dla większej wygody stan zbierania i zapisywania danych wyświetlany jest na ekranie OLED.

Listing 1. Szkic programu do akwizycji danych do nauki algorytmu

#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include <FS.h> // system plików do magazynowania pomiarów
#include <LittleFS.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h> // wyświetlacz OLED
#define SCREEN_WIDTH 128 // szerokość wyświetlacza OLED
#define SCREEN_HEIGHT 32 // wysokość wyświetlacza OLED
#define OLED_RESET -1 // pin reset wyświetlacza OLED
#define NUM_SAMPLES 100
#define NUM_AXES 6
#define TRUNCATE 20
#define ACCEL_THRESHOLD 5
int INT_PIN = 0;

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_MPU6050 mpu;

int sample_read = 0; //
double baseline[NUM_AXES];
float features[NUM_AXES * NUM_SAMPLES];
volatile boolean addFeature = false;
volatile boolean isCaptureStarted = false;
void ICACHE_RAM_ATTR IntCallback()
{
addFeature = true;
isCaptureStarted = true;
}

void setup(void) {
Serial.begin(115200);
pinMode(INT_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(INT_PIN), IntCallback, FALLING);
if (!LittleFS.begin()) {
Serial.println("LittleFS mount failed");
return;
}
while(!isCaptureStarted)
{
delay(10);
}
readFile();
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // adres 0x3C dla 128x32
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
testdrawchar("booting...");
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip");
while (1) {
delay(10);
}
}
mpu.setAccelerometerRange(MPU6050_RANGE_4_G);
mpu.setGyroRange(MPU6050_RANGE_500_DEG);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
Serial.println("");
delay(100);
calibrate();
}

void loop(){
float ax, ay, az;
float gx, gy, gz;
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
if(addFeature)
{
File file = LittleFS.open("./Gesture.txt", "a");
if (!file) {
Serial.println("Failed to open file for appending");
return;
}
if (file.print("\n\t\tNew Feature\n\n\n")) {
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
file.close();
addFeature = false;
testdrawchar("Added new feature!");
delay(1000);
testdrawchar("start recording");
}

ax = constrain(a.acceleration.x - baseline[0], -TRUNCATE, TRUNCATE);
ay = constrain(a.acceleration.y - baseline[1], -TRUNCATE, TRUNCATE);
az = constrain(a.acceleration.z - baseline[2], -TRUNCATE, TRUNCATE);
gx = constrain(g.acceleration.x - baseline[0], -TRUNCATE, TRUNCATE);
gy = constrain(g.acceleration.y - baseline[1], -TRUNCATE, TRUNCATE);
gz = constrain(g.acceleration.z - baseline[2], -TRUNCATE, TRUNCATE);

if (!motionDetected(ax, ay, az)) {
delay(10);
return;
}
recordIMU();
delay(100);
}

void calibrate(){
sensors_event_t a, g, temp;
Serial.println("Calibrating...");
testdrawchar("Calibrating...");
for (int i = 0; i < 10; i++) {
mpu.getEvent(&a, &g, &temp);
delay(100);
}
Serial.println("done Calibrating!");
testdrawchar("done Calibrating...");
baseline[0] = double(a.acceleration.x);
baseline[1] = double(a.acceleration.y);
baseline[2] = double(a.acceleration.z);
baseline[3] = double(g.acceleration.z);
baseline[4] = double(g.acceleration.z);
baseline[5] = double(g.acceleration.z);
testdrawchar("Start recording");
}

void recordIMU() {
float ax, ay, az;
float gx, gy, gz;
sensors_event_t a, g, temp;
for (int i = 0; i < NUM_SAMPLES; i++) {
mpu.getEvent(&a, &g, &temp);

ax = constrain(a.acceleration.x - baseline[0], -TRUNCATE, TRUNCATE);
ay = constrain(a.acceleration.y - baseline[1], -TRUNCATE, TRUNCATE);
az = constrain(a.acceleration.z - baseline[2], -TRUNCATE, TRUNCATE);
gx = constrain(g.acceleration.x - baseline[0], -TRUNCATE, TRUNCATE);
gy = constrain(g.acceleration.y - baseline[1], -TRUNCATE, TRUNCATE);
gz = constrain(g.acceleration.z - baseline[2], -TRUNCATE, TRUNCATE);
int index = i*NUM_AXES;
features[index] = ax;
features[index+1] = ay;
features[index+2] = az;
features[index+3] = gx;
features[index+4] = gy;
features[index+5] = gz;
}
for(int i = 0; i < 10; i++)
{
Serial.println();
}
for(int i = 0; i < NUM_SAMPLES;i++)
{
int index = i*NUM_AXES;
String data = String(features[index])+","+String(features[index+1])+","+String(features[index+2])+","+(features[index+3])+","+(features[index+4])+","+(features[index+5])+"\n";
Serial.print(data);
}
appendFile();
}

bool motionDetected(float ax, float ay, float az) {
return (abs(ax) + abs(ay) + abs(az)) > ACCEL_THRESHOLD;
}

void testdrawchar(String text) {
display.clearDisplay();
display.setTextSize(1); // normalna skala - 1:1
display.setTextColor(SSD1306_WHITE); // rysowanie białym kolorem
display.setCursor(0, 0); // początek w górnym lewym narożniku
display.print(text);
display.display();
delay(1000);
}

void readFile() {
File file = LittleFS.open("./Gesture.txt", "r");
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
while (file.available()) {
Serial.write(file.read());
}
file.close();
}

void appendFile() {
String data;
File file = LittleFS.open("./Gesture.txt", "a");
if (!file) {
Serial.println("Failed to open file for appending");
return;
}
for(int i = 0; i < NUM_SAMPLES;i++)
{
int index = i*NUM_AXES;
data = String(features[index])+","+String(features[index+1])+","+String(features[index+2])+","+(features[index+3])+","+(features[index+4])+","+(features[index+5])+"\n";
if (file.print(""+data)) {
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
}
if (file.print("\n\n")) {
Serial.println("added space");
} else {
Serial.println("Append failed");
}
file.close();
testdrawchar("done recording!");
delay(1000);
testdrawchar("start recording!");
}

Po przesłaniu kodu do zegarka, aby przetestować, należy trzymać rękę nieruchomo, gdy tylko urządzenie się uruchomi. Początkowo układ skalibruje akcelerometr i żyroskop. Można już wtedy zacząć zbierać dane. Wystarczy nacisnąć przycisk, do którego jest podłączone GPIO-0, a urządzenie utworzy nowy zbiór. Trzeba zacząć poruszać ręką, aby zarejestrować ruch. Im więcej zbierzemy próbek, tym lepiej nauczymy algorytm rozpoznawania gestów. Autor rekomenduje, żeby każdy gest powtórzyć, co najmniej 25...30 razy, aby system nie miał problemu z klasyfikacją.

Przetwarzanie danych

Po zebraniu odpowiedniej liczby zapisów dla wszystkich gestów, jakie chcemy rozpoznawać, można zgrać je do komputera. Wystarczy podłączyć zegarek do PC za pomocą konwertera USB-UART, a następnie zgrać wszystkie dane poprzez port szeregowy i zapisać je w pliku tekstowym. Plik ten należy podzielić na trzy pliki CSV, używając arkusza Excel, dla każdego z gestów, jakie będą wykrywane. Tych danych nie można używać bezpośrednio po zebraniu, muszą zostać przetworzone w celu usunięcia szumu, aby algorytm mógł dokładniej klasyfikować gesty. Autor pominął opis tego etapu dla uproszczenia opisu procedury.

Trenowanie algorytmu

Po zebraniu i przygotowaniu danych można rozpocząć naukę algorytmu. Autor do treningu stosuje program w języku Python, który trenuje model i konwertuje go do pliku w języku C, którego możemy używać z Arduino IDE.

Listing 2. Program do nauki algorytmu

import numpy as np
from glob import glob
from os.path import basename
from sklearn.ensemble import RandomForestClassifier
from micromlgen import port

fileNames = ["Gesture\LeftSwipeGesture.csv","Gesture\RightSwipeGesture.csv","Gesture\SlamGesture.csv"] # lista plików

def load_features(files):
dataset = None
classmap = {}
for class_idx, filename in enumerate(files):
class_name = basename(filename)[:-4]
classmap[class_idx] = class_name
samples = np.genfromtxt(filename, delimiter=’,’,filling_values=0.0)
labels = np.ones((len(samples), 1)) * class_idx
samples = np.hstack((samples, labels))
dataset = samples if dataset is None else np.vstack((dataset, samples))

return dataset, classmap

def get_classifier(features):
X, y = features[:, :-1], features[:, -1]
return RandomForestClassifier(20, max_depth=10).fit(X, y)

if __name__ == ‘__main__’:
features, classmap = load_features(fileNames)
classifier = get_classifier(features)
c_code = port(classifier, classmap=classmap)
print("Writing to a file")
modelFile = open("model.h", "w")
modelFile.write(c_code)
modelFile.close()
print("Model file created")

Kod ten (listing 2) odczytuje plik CSV i trenuje model, aby nauczył się gestu, który został wcześniej zarejestrowany. Jeśli chcemy użyć kilku różnych plików dla różnych gestów, wystarczy w zmiennej fileNames wpisać ich ścieżki. Po uruchomieniu tego kodu otrzymamy plik model.h. Plik ten zawiera wytrenowany model identyfikujący przechwycone gesty. Aby po prostu przetestować model, należy wkleić plik model.h do folderu z testowym szkicem, pokazanym na listingu 3 i skompilować go i wgrać do zegarka.

Listing 3. Testowy szkic Arduino do rozpoznawania gestów

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include "model.h" // tutaj podajemy wygenerowany wcześniej model
#define TRUNCATE 20 // ograniczenie odczytów do +/- 20
#define ACCEL_THRESHOLD 5 // próg uruchamiania detekcji
#define NUM_SAMPLES 100 // liczba odczytywanych próbek
#define NUM_AXES 6 // trzy osie akcelerometru + trzy żyroskopu

#define SCREEN_WIDTH 128 // szerokość wyświetlacza OLED
#define SCREEN_HEIGHT 32 // wysokość wyświetlacza OLED
#define OLED_RESET -1 // pin resetu wyświetlacza OLED

float features[NUM_AXES * NUM_SAMPLES]; // przechowuje gest przed detekcją
int INT_PIN = 0; // przerwanie od przycisku
volatile boolean StartDetection = false; // moment detekcji
double baseline[NUM_AXES]; // wykorzystywane w kalibracji

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_MPU6050 mpu;

Eloquent::ML::Port::RandomForest classifier; // klasa we wnętrzu model.h

void ICACHE_RAM_ATTR IntCallback() { // Upewnij się, że to przerwanie jest powyżej funkcji setup
StartDetection = true;
}

void calibrate(){
sensors_event_t a, g, temp;
Serial.println("Calibrating...");
testdrawchar("Calibrating...");
for (int i = 0; i < 10; i++) {
mpu.getEvent(&a, &g, &temp);
delay(100);
}
Serial.println("done Calibrating!");
testdrawchar("done Calibrating...");
baseline[0] = double(a.acceleration.x);
baseline[1] = double(a.acceleration.y);
baseline[2] = double(a.acceleration.z);
baseline[3] = double(g.acceleration.z);
baseline[4] = double(g.acceleration.z);
baseline[5] = double(g.acceleration.z);
testdrawchar("Do some gesture!");
}

void recordIMU() { // Funkcja nagrywające dane z sensora po wykryciu ruchu
float ax, ay, az;
float gx, gy, gz;
sensors_event_t a, g, temp;
for (int i = 0; i < NUM_SAMPLES; i++) {
mpu.getEvent(&a, &g, &temp);

ax = constrain(a.acceleration.x - baseline[0], -TRUNCATE, TRUNCATE);
ay = constrain(a.acceleration.y - baseline[1], -TRUNCATE, TRUNCATE);
az = constrain(a.acceleration.z - baseline[2], -TRUNCATE, TRUNCATE);
gx = constrain(g.acceleration.x - baseline[0], -TRUNCATE, TRUNCATE);
gy = constrain(g.acceleration.y - baseline[1], -TRUNCATE, TRUNCATE);
gz = constrain(g.acceleration.z - baseline[2], -TRUNCATE, TRUNCATE);

int index = i*NUM_AXES;
features[index] = ax;
features[index+1] = ay;
features[index+2] = az;
features[index+3] = gx;
features[index+4] = gy;
features[index+5] = gz;
}
}

bool motionDetected(float ax, float ay, float az) { // Funkcja wykrywająca ruch
return (abs(ax) + abs(ay) + abs(az)) > ACCEL_THRESHOLD;
}

void testdrawchar(String text) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print(text);
display.display();
delay(1000);
}

void classify() { // Funkcja realizująca klasyfikację
Serial.print("Detected gesture: ");
String gesture = classifier.predictLabel(features);
Serial.println(gesture);
testdrawchar(gesture);
delay(2000);
testdrawchar("Do some gesture!");
}

void setup() {
Serial.begin(115200);
pinMode(INT_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(INT_PIN), IntCallback, FALLING);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
while(!StartDetection)
{
delay(10);
}
testdrawchar("booting...");
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip");
while (1) {
delay(10);
}
}
mpu.setAccelerometerRange(MPU6050_RANGE_4_G);
mpu.setGyroRange(MPU6050_RANGE_500_DEG);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
Serial.println("");
delay(100);
calibrate();
}

void loop() { // Główna pętla programu
float ax, ay, az;
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);

ax = constrain(a.acceleration.x - baseline[0], -TRUNCATE, TRUNCATE);
ay = constrain(a.acceleration.y - baseline[1], -TRUNCATE, TRUNCATE);
az = constrain(a.acceleration.z - baseline[2], -TRUNCATE, TRUNCATE);

if (!motionDetected(ax, ay, az)) {
delay(10);
return;
}
Serial.println("Motion detected!");
recordIMU();
classify();
}

Implementacja algorytmu i inferencja

Algorytm uczenia maszynowego, po przesłaniu kodu, nie uczy się już bardziej. Wykorzystuje tylko kod, wgrany do niego i opracowany wcześniej na podstawie zbioru uczącego. Po pomyślnym przesłaniu programu, można wykonać dowolny gest, a wyświetlacz OLED powinien poinformować o wykrytym geście. Algorytm wygenerowany przez autora wykrywał gesty poprawnie w 95% przypadków. Prawdopodobnie wynika to z faktu, że zebrane dane zawierały szum, gest nie był wykonany poprawnie lub zbiór uczący był zbyt mały.

Podsumowanie

Jest jeszcze wiele rzeczy, które można zrobić, aby ulepszyć opisana konstrukcję lub dodać do niej dodatkowe funkcje. Można również wykorzystać zawartą w tym artykule wiedzę, aby zaimplementować podobne możliwości w innym produkcie lub projekcie. Można także wiele rzeczy zrobić lepiej, niż w opisanym systemie – zwiększyć częstotliwość próbkowania podczas zbierania danych, zwiększyć liczbę zebranych próbek, wyczyścić z szumu dane po ich zebraniu, dodać przetwarzanie sygnału w układzie, w celu usunięcia szumów, itp. Jednakże, z punktu widzenia nauki tego, jak wdrożyć się w hobbystyczne zastosowanie uczenia maszynowego, opisany przykład jest w stu procentach wystarczający.

Nikodem Czechowski, EP

Źródło: https://bit.ly/3rOuvtQ

Artykuł ukazał się w
Elektronika Praktyczna
kwiecień 2021
DO POBRANIA
Materiały dodatkowe

Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik kwiecień 2024

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio marzec - kwiecień 2024

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka, Podzespoły, Aplikacje marzec 2024

Automatyka, Podzespoły, Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna kwiecień 2024

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich kwiecień 2024

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów