Как стать автором
Обновить

DIY BCI-шлем на Arduino и TinyML: распознаём эмоции силой мысли (ну почти)

Уровень сложностиСложный
Время на прочтение13 мин
Количество просмотров3.3K

Здесь не рассказывают про гигантские суперкомпьютеры и дорогостоящие коммерческие нейроинтерфейсы. В этой статье показан путь от сырой схемы с электродами на лбу до работающего прототипа BCI-шлема, который на Arduino собирает аналоговые сигналы мозга (точнее, лобных долей) и на прошивке TinyML «решает», довольны вы сейчас или испытываете лёгкое раздражение. Всё это — без Biopack, без OpenBCI, с минимумом затрат (пара десятков долларов), но с максимальным погружением в детали: схемы, код, личные промахи и избыточная доза сарказма.

Увидев в краудфандинговой рекламе новый «мозговой шлем», автор сначала подумал: «Ну, ещё одна штука для прокрастинации». Но когда узнал, что за $100 можно собрать аналогичную систему самому, захотелось испытать на себе: действительно ли Arduino с несколькими электродами и крошечной моделью TinyML «опознают» эмоцию?

Как человек, пробывший инженерный или полубиологический путь (технарь с желанием покопаться в электрическом шуме мозга), автор проверил: да, можно. Хоть и с погрешностями, хотя бы для демонстрации. Впереди — подробная инструкция: какие компоненты взять, как их соединить, куда класть электроды, чтобы не получать случайные сигналы мышечной активности вместо мыслей про кофе, как собрать TinyML-модель, вырезать её под Arduino и запустить «нервный» прогноз вживую. Поехали!


Почему вообще эмоции и EEG?

Прежде чем браться за железо, пару слов о технологии. Деньги Google и Facebook уже вложены в аналитику лиц, интонаций и расположения кликать «лайк». Но вот запись электрических волн мозга (электроэнцефалография, EEG) открывает данные чуть более интимные: что человек реально чувствует, не глядя на экран. Чаще всего коммерческие BCI (Brain–Computer Interface) используют десятки электродов и специализированные усилители за сотни или тысячи долларов.

Зачем распознавать эмоции?

  1. Игры и развлечения. Управлять персонажем не кнопками, а мыслями (ну или его реакцией).

  2. Здоровье и помощь людям. Считать стресс и давать обратную связь: «Давай дышать спокойнее».

  3. Эксперименты. Понять себя, своих друзей или лаборантов, когда они в лаборатории отшучиваются по поводу: «Ой, что-то я не могу сконцентрироваться».

Мы же сделаем практический пример: три базовых «эмоциональных» состояния — расслабленность, концентрация и лёгкое замешательство (подойдёт любой нейтральный раздражитель). То есть задача: по сигналам, снятым на либеральных (не медвежьих, а просто условных) лобных электродах, на Arduino Nano 33 BLE Sense или аналогичном «умном» контроллере дать одну из трёх меток: “calm”, “focus”, “confused”.


Сборка схемы: электроника и электроды

Компоненты

  • AD8232 — простой аналоговый блок для снятия электромиографических/электрокардиографических/электроэнцефалографических сигналов. Можно найти модуль-брейк-аут за $5–7. Он не идеален, но для демонстрации с лобной электродной терапией сойдёт.

  • Электроды («короткая игла» или липучие диски с гидрогелем). На нашу задачу понадобятся 3 штуки: два «активных» (левый и правый лоб), один «опорный» (например, за ухом).

  • Arduino Nano 33 BLE Sense (или любой Arduino/семейство с Cortex-M4 и поддержкой TensorFlow Lite Micro). Главное — не Uno: там маловато Flash/RAM для TinyML.

  • Провода, макетная плата, застёжки для электродов (patch-клеммы).

  • USB-кабель для питания и программирования.

  • Дополнительно: капля геля, пластырь и терпение, чтобы не ковырять кожу каждый раз.

Как собирать

  1. AD8232 к Arduino

    • LO+ (выход включения индикатора «Heart Rate/ECG Lead Off Detect») не трогаем.

    • RA- (Right Arm) ─ на электрод №1 (правый лоб).

    • LA+ (Left Arm) ─ на электрод №2 (левый лоб).

    • RL- (Reference) ─ на электрод №3 (за ухом).

    • OUTPUT (аналого-цифровой сигнал после усиления) ─ к пину A0 Arduino.

    • 3.3 V или 5 V (зависит от выходного диапазона) ─ к 3.3V Arduino (Nano 33 BLE Sense питает AD8232 от 3.3 В).

    • GND ─ к земли Arduino.


    (Просто представьте, что провода не спутались через пять минут возни.)

  2. Электроды на голову

    • Лобную область лучше слегка протереть спиртом, чтобы гель лучше проводил.

    • Прикрепляем электрод №1 на правой стороне лба чуть выше брови, электрод №2 — симметрично слева. Третий (Reference) — на кости за ухом.

    • Гель нужен для снижения сопротивления. Иначе вместо мозга вы запишете пиксели на лбу от искусственной кожи.

  3. Первое включение

    • После прошивки базового скетча, считывающего число с A0, откройте монитор порта. Дайте голове «отдохнуть» минуту, не дергайтесь, не моргайте (легко сказать). Увидите неравномерную синусоиду со «шумом». Это не баг—это как раз мозг в полосе (0.5–50 Гц), усиленный AD8232.


Снятие и предварительная обработка данных (Arduino + Python)

Скетч на Arduino для записи сырых данных

Первый этап — собрать набор «сырых» EEG-отпечатков для трёх состояний. На Arduino запускаем код, который каждые 4 ms (≈250 Гц) снимает значение A0 и шлёт в Serial.

Код (C++, Arduino IDE)

// Файл: eeg_recorder.ino
// Arduino Nano 33 BLE Sense и AD8232.  
// Снимаем данные с A0 в Serial (115200 бод).  

const int eegPin = A0;
const unsigned long sampleInterval = 4000;  // микросекунд = 250 Гц

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("EEG Recorder Started");
}

void loop() {
  static unsigned long lastMicros = 0;
  unsigned long now = micros();
  if (now - lastMicros >= sampleInterval) {
    lastMicros = now;
    int rawValue = analogRead(eegPin);  
    // На Nano 33 BLE Sense analogRead() даёт 12-бит: 0–4095 ~ 0–3.3V
    Serial.println(rawValue);
  }
}

Примечание:

  • Почему не 500 Гц? Можно, но модель такая маленькая, что 250 Гц уже «за глаза» хватит, иначе карта со старыми щупами покажет зашумленный «бубнящий» сигнал.

  • Если у вас не Nano 33 BLE Sense, а, например, STM32-клон, поправьте analogRead и скорость UART.

Захват данных на Python

  1. Подключаем Arduino по USB, открываем COM-порт (например, /dev/ttyUSB0 или COM3).

  2. Запускаем простой скрипт для записи 30 секунд «спокойного» состояния, потом 30 секунд «концентрации» (например, решаем головоломки), потом 30 секунд «замешательства» (берём в руки сложный пазл и ругаемся). Помечаем каждый файл.

Код (Python 3.x)

# Файл: data_capture.py
import serial
import time
import csv

# Задайте свой COM-порт
SERIAL_PORT = "COM3"      # или "/dev/ttyUSB0"
BAUD_RATE = 115200
DURATION = 30            # секунд на одно состояние
OUTPUT_PREFIX = "eeg_data_"

def capture(label: str):
    filename = f"{OUTPUT_PREFIX}{label}.csv"
    with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) as ser, \
         open(filename, "w", newline='') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(["timestamp", "raw"])
        start_time = time.time()
        print(f"Начало записи: {label}")
        while time.time() - start_time < DURATION:
            line = ser.readline().decode('ascii', errors='ignore').strip()
            if line.isdigit():
                timestamp = time.time()
                writer.writerow([timestamp, int(line)])
        print(f"Конец записи: {label}, файл {filename}")

if __name__ == "__main__":
    for state in ["calm", "focus", "confused"]:
        input(f"Подготовься к состоянию '{state}'. Нажми Enter, когда готов.")
        capture(state)
        print("Сделайте паузу 10 секунд, расслабьтесь.")
        time.sleep(10)

Автору впервые показалось, что «запись 30 секунд — когда за мной никто не смотрит» превращается в «где-то на 20 секунде я начинаю чесаться и мысли улетают, но ничего — так тоже эмпирические данные».


Предварительная обработка и разметка данных

Получилось три CSV:

  • eeg_data_calm.csv

  • eeg_data_focus.csv

  • eeg_data_confused.csv

Каждый файл содержит примерно 250 Hz × 30 сек = 7500 строк. Данные сырьеё, полный спектр: от низких волн (<1 Гц) до шумов 50/60 Гц. Перед обучением надо:

  1. Фильтровать (0.5–40 Гц), чтобы убрать дряблые DC-сдвиги и электрические «пилы».

  2. Нормировать: ведь rawValue лежит в [0; 4095], но нам важны изменения, а не абсолют.

  3. Резать на окна, например, по 250 ms (≈63 сэмпла).

  4. Добавить метку (label) к каждому окну: 0 = calm, 1 = focus, 2 = confused.

Предобработка (Python, библиотеки NumPy + SciPy)

# Файл: preprocess.py
import numpy as np
import pandas as pd
from scipy.signal import butter, lfilter

# Параметры
FS = 250  # частота дискретизации
LOWCUT = 0.5
HIGHCUT = 40.0
WINDOW_SIZE = int(0.25 * FS)  # 250ms

def load_data(filename):
    df = pd.read_csv(filename)
    return df["raw"].values

def butter_bandpass(lowcut, highcut, fs, order=5):
    nyq = 0.5 * fs
    low = lowcut / nyq
    high = highcut / nyq
    b, a = butter(order, [low, high], btype='band')
    return b, a

def bandpass_filter(data, lowcut, highcut, fs, order=4):
    b, a = butter_bandpass(lowcut, highcut, fs, order=order)
    y = lfilter(b, a, data)
    return y

def segment_and_label(data, label):
    segments = []
    labels = []
    for start in range(0, len(data) - WINDOW_SIZE, WINDOW_SIZE):
        window = data[start:start + WINDOW_SIZE]
        segments.append(window)
        labels.append(label)
    return np.array(segments), np.array(labels)

def preprocess_all():
    X_list, y_list = [], []
    for idx, state in enumerate(["calm", "focus", "confused"]):
        raw = load_data(f"eeg_data_{state}.csv")
        filtered = bandpass_filter(raw, LOWCUT, HIGHCUT, FS)
        # нормируем до [-1, 1]
        filtered = 2 * (filtered - np.min(filtered)) / (np.ptp(filtered)) - 1
        segs, labs = segment_and_label(filtered, idx)
        X_list.append(segs)
        y_list.append(labs)
    X = np.vstack(X_list)
    y = np.concatenate(y_list)
    return X, y

if __name__ == "__main__":
    X, y = preprocess_all()
    print("Форма X:", X.shape)  # примерно ( (7500/63*3) , 63 )
    print("Форма y:", y.shape)
    # Сохраним для обучения
    np.save("X.npy", X)
    np.save("y.npy", y)

Автор, обнаружив, что нормирование всего массива «затирает» различия между состояниями, чуть не заплакал. Приходилось пересчитывать min/max для каждого окна отдельно, но это сильно ускладняет. Поэтому оставили так, хоть и неидеально.


Обучение TinyML-модели в Python

Целевая архитектура — простая свёрточная сеть (1D CNN), чтобы Arduino Nano 33 BLE Sense вместил всё в Flash и RAM.

Шаг 1: Подготовка данных

# Файл: train_model.py
import numpy as np
from sklearn.model_selection import train_test_split

# Загрузка обработанных данных
X = np.load("X.npy")  # форма: (n_samples, WINDOW_SIZE)
y = np.load("y.npy")  # (n_samples,)

# Добавляем размерность канала (для Conv1D)
X = X.reshape(-1, X.shape[1], 1)

# Делим на train/validation
X_train, X_val, y_train, y_val = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

print("Размер обучающей выборки:", X_train.shape, y_train.shape)
print("Размер валидационной выборки:", X_val.shape, y_val.shape)

Шаг 2: Собираем модель (TensorFlow 2.x)

# Дополнительно: pip install tensorflow==2.9.0
import tensorflow as tf

def build_model(input_shape):
    model = tf.keras.Sequential([
        tf.keras.layers.Conv1D(16, kernel_size=3, activation='relu', input_shape=input_shape),
        tf.keras.layers.MaxPooling1D(pool_size=2),
        tf.keras.layers.Conv1D(32, kernel_size=3, activation='relu'),
        tf.keras.layers.MaxPooling1D(pool_size=2),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(32, activation='relu'),
        tf.keras.layers.Dense(3, activation='softmax')
    ])
    return model

model = build_model((X_train.shape[1], 1))
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.summary()

Шаг 3: Обучаем и сохраняем

EPOCHS = 20
BATCH_SIZE = 32

history = model.fit(
    X_train, y_train,
    epochs=EPOCHS,
    batch_size=BATCH_SIZE,
    validation_data=(X_val, y_val)
)

model.evaluate(X_val, y_val)
# Сохраняем в Keras HDF5, потом конвертируем в TFLite
model.save("eeg_emotion_model.h5")

Личный опыт автора:
После первой эпохи Accuracy на валидации упал до 33%, и казалось, что всё пропало. Но спустя 5 эпох (и по паре чашек крепкого кофе) модель «вспомнила», как отличать «спокойствия» от «замешательства». Важно не слишком болтать головой во время записи данных, иначе сеть запомнит… как вы чесали затылок.


Конвертация в TensorFlow Lite для Arduino

TinyML = TensorFlow Lite Micro. Не та версия, где «LSTM» ещё пытаются втиснуть в микроконтроллер.

Шаг 4: Конвертируем в TFLite

# Файл: convert_to_tflite.py
import tensorflow as tf

# Загружаем обученную модель
model = tf.keras.models.load_model("eeg_emotion_model.h5")

# Конвертация в TFLite с оптимизацией «для размера»
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
# Предположим, что во время инференса у нас float32 достаточно;
# если будет мало памяти, добавим квантизацию int8.
tflite_model = converter.convert()

with open("eeg_emotion_model.tflite", "wb") as f:
    f.write(tflite_model)

print("TFLite-модель сохранена как eeg_emotion_model.tflite")

Если модель оказывается слишком большой (больше ~200 КБ), пробуем квантизацию:

converter.optimizations = [tf.lite.Optimize.DEFAULT]
def representative_dataset_generator():
    for i in range(100):
        yield [X_train[i].reshape(1, X_train.shape[1], 1).astype(np.float32)]
converter.representative_dataset = representative_dataset_generator
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
tflite_quant_model = converter.convert()
with open("eeg_emotion_model_uint8.tflite", "wb") as f:
    f.write(tflite_quant_model)

Автору удалось «сжать» модель с 220 КБ до 98 КБ, потеряв 2% точности. Пришлось поиграть с представлением данных (от [-1,1] к [0,255]), но теперь модель помещается в Flash.


Прошивка на Arduino Nano 33 BLE Sense

Шаг 5: Установка окружения

  1. Arduino IDE 2.x или PlatformIO.

  2. Установить Arduino Mbed os CMSIS Pack для Nano 33 BLE.

  3. Добавить в IDE пакет Eloquent TinyML (или официальную TensorFlow Lite Micro библиотеку).

Шаг 6: Загрузка TFLite-модели в проект

  • Переименуйте eeg_emotion_model.tflitemodel.tflite и поместите в папку data/ вашего проекта (для Arduino IDE это рядом с .ino).

  • Включите дефайн в шапке:

    #define MODEL_NAME model
    #include "model.h"  // автоматически конвертируется из model.tflite
    #include <TensorFlowLite.h>
    #include <tensorflow/lite/micro/all_ops_resolver.h>
    #include <tensorflow/lite/micro/micro_interpreter.h>
    #include <tensorflow/lite/schema/schema_generated.h>
    #include <tensorflow/lite/version.h>

    Многие советуют использовать xxd для конвертации TFLite→C-массив:

    xxd -i eeg_emotion_model.tflite > model.h

    Либо в Arduino IDE можно просто перетащить файл TFLite в папку data/ (Eloquent TinyML сам создаст заголовочный файл).

Шаг 7: Код Arduino (C++)

// Файл: eeg_inference.ino
#include <Arduino.h>
#include <Arduino_LSM9DS1.h>    // не нужна, пример просто для Nano 33 BLE Sense
#include "model.h"              // здесь TFLite-модель в формате массива
#include <TensorFlowLite.h>
#include <tensorflow/lite/micro/all_ops_resolver.h>
#include <tensorflow/lite/micro/micro_interpreter.h>
#include <tensorflow/lite/schema/schema_generated.h>
#include <tensorflow/lite/version.h>

const int eegPin = A0;
const int FS = 250;             // частота дискретизации
const int WINDOW_SIZE = 63;     // по примеру из Python-кода
const float ADC_REF = 3.3f;
const int ADC_MAX = 4095;       // 12-битный A/D. Nano 33 BLE Sense

// Buffers
float input_buffer[WINDOW_SIZE];
int buffer_index = 0;
bool buffer_full = false;

// TensorFlow Lite globals
namespace {
  constexpr int kTensorArenaSize = 50 * 1024; // 50 KB, правим, если не вмещается
  uint8_t tensor_arena[kTensorArenaSize];
}

tflite::MicroInterpreter* interpreter = nullptr;
TfLiteTensor* input = nullptr;
TfLiteTensor* output = nullptr;

void setup() {
  Serial.begin(115200);
  while (!Serial);

  // Настройка TFLite
  const tflite::Model* model = ::tflite::GetModel(model_tflite);
  static tflite::MicroMutableOpResolver<10> resolver;
  resolver.AddConv1D();
  resolver.AddMaxPool1D();
  resolver.AddFullyConnected();
  resolver.AddSoftmax();
  resolver.AddReshape();
  // остальное при необходимости

  static tflite::MicroInterpreter static_interpreter(
    model, resolver, tensor_arena, kTensorArenaSize);
  interpreter = &static_interpreter;

  if (interpreter->AllocateTensors() != kTfLiteOk) {
    Serial.println("Не удалось выделить тензоры");
    while (1);
  }

  input = interpreter->input(0);
  output = interpreter->output(0);

  pinMode(eegPin, INPUT);
  Serial.println("BCI EEg inference started");
}

void loop() {
  static unsigned long lastMicros = 0;
  unsigned long now = micros();
  const unsigned long sampleInterval = 1000000 / FS;  // 4 000 мкс

  if (now - lastMicros >= sampleInterval) {
    lastMicros = now;
    // Считываем сырое значение
    int raw = analogRead(eegPin);
    // Нормализуем в диапазон [-1, 1]
    float norm = (2.0f * raw / ADC_MAX) - 1.0f;
    input_buffer[buffer_index++] = norm;
    if (buffer_index >= WINDOW_SIZE) {
      buffer_index = 0;
      buffer_full = true;
    }
  }

  if (buffer_full) {
    buffer_full = false;
    // Копируем данные в тензор input
    for (int i = 0; i < WINDOW_SIZE; ++i) {
      input->data.f[i] = input_buffer[i];
    }
    // Инференс
    if (interpreter->Invoke() == kTfLiteOk) {
      // Получаем метку
      float maxScore = output->data.f[0];
      int maxIndex = 0;
      for (int i = 1; i < 3; ++i) {
        if (output->data.f[i] > maxScore) {
          maxScore = output->data.f[i];
          maxIndex = i;
        }
      }
      // 0=calm,1=focus,2=confused
      const char* labels[] = {"Calm", "Focus", "Confused"};
      Serial.print("Predicted: ");
      Serial.print(labels[maxIndex]);
      Serial.print(" (");
      Serial.print(maxScore * 100, 1);
      Serial.println("%)");
    } else {
      Serial.println("Inference error");
    }
  }
}

Объяснения в стиле “что тут важного”:

  1. ADC считывает 0–4095 → переводим в float [-1;1], как в Python.

  2. Окно 63 сэмпла: мы накопили 63 штуки за 0.252 сек. Чуть медленнее, чем у Python (там чистый NumPy).

  3. TensorArena 50 КБ: если не влезает или выдаёт ошибку AllocateTensors(), надо уменьшить размер слоёв в модели.

Если всё собрано правильно, в мониторе порта Arduino вы увидите что-то вроде:

BCI EEg inference started
Predicted: Calm (87.3%)
Predicted: Calm (90.1%)
Predicted: Focus (75.4%)
Predicted: Focus (80.2%)
Predicted: Confused (63.7%)
…

Автор убедился, что когда он шутил сам над собой (думал о пицце), модель уверенно писала “Confused (45%)” — видимо, пиццу недопекли.


Тестирование и отладка

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

  2. Проверка модели. В начале (при 63–окнах) модель на валидации давала ~60% в трёх классах. Затем, добавив второй сверточный слой, дошло до 75%. На Arduino, после квантизации, упало в среднем до 70% (около 35–40% для «Confused»). Проблема: «замешательство» слишком близко к «спокойствию» по ударным компонентам.

  3. Сбор «живых» данных. Чтобы получить более разнообразные сигналы, автор добавил аудио‑фрагменты:

    • спокойная инструментальная музыка (30 сек),

    • динамичная речевая речь (30 сек),

    • хаотичный шум (крики котиков и звук капающей воды).

    После этого модель стала чуть «умнее» реагировать на звуковые раздражители.

  4. Шум и помехи. Первые тесты показали, что любая дрожь головы (при смехе, прыжках) «дофигища» влияния даёт. Решение:

    • мягкая резиновая стяжка, чтобы шлем не болтался,

    • при тренировке попросить «не то лицо не менять»,

    • добавить (если есть математика и время) простой ремув-преценр (ремув среднее значение). Но ради скорости обошлись нормализацией.


Итоги и перспективы

Что получилось

  • Прототип DIY-BCI-шлема. Arduino + AD8232 + электрод → снимаем «головные волны».

  • TinyML-модель. Простая 1D CNN, помещается в Flash (~100 КБ), работает на Arduino «реально быстро» (один вывод прогноза каждые ~0.3 сек).

  • Распознаём три состояния. «Calm», «Focus», «Confused» с ≈70–75% точности (на сырых домашних данных).

Тонкие моменты, с которыми столкнулся автор

  1. Пересечение фреймворков. Когда переключил float32 на uint8 в TinyML, пришлось заново нормализовывать данные (Python → Arduino). На практике стоило сразу делать одну схему: все данные в «+1…–1» → «0…255».

  2. Штатные ADC. AD8232 отлично тянет «низкие частоты» (до ~100 Гц), но для настоящей EEG нужна частота дискретизации ~250 Гц и более высокое разрешение (ADS1299, 24 bit). Наш DIY – всего лишь приближение.

  3. Мускульные артефакты. При разговоре сигнал от мышц лба был сильнее, чем от мысли «сделай модель привлекательной». Написано было: «если вам мешает скрип зубов, не разговаривайте».

Перспективы

  1. Дополнительные классы. Считать «гнев», «печаль», «удовольствие» (но это уже нестабильно).

  2. Усилитель AD8232 → ADS1115 или ADS1299. Меньше шума, больше каналов, но придётся учить I2C.

  3. Подача в облако. Если отправлять сырые окна на сервер (ESP32 + Wi-Fi), можно обучить «большую» нейронку на Python + GPU. Arduino лишь собирает данные и отправляет на сервер.

  4. Интеграция с играми. Представьте, игрок прыгает, а игра увеличивает сложность, если «замешательство» → сразу берёт паузу.


Заключение

Этот DIY-BCI-шлем не претендует на научную точность или медицинские диагнозы. Зато он показывает, как сила мысли (ну или просто напряжение лба) может попасть в микроконтроллер и заставить TinyML-модель что-то «думать».

Если вам интересно играться с электроникой, «ощущать» собственные мозговые волны и создавать на основе этого прототипа что-нибудь более серьёзное (например, датчик стресса для «умного стула»), этот материал — отправная точка. Возможно, через год появятся обзоры «Как песенка Вокера за 0.5 секунды определила, что вы ревнуете», но пока автор пишет это, сидя в зале, думая о пицце и ждущий результатов в Serial Monitor.

Удачи в ваших экспериментах, пусть нейроинтерфейс будет мягким (и не снимайте обладателя шлема, когда он зевает — это совсем другая история).


P.S. Если кто-то хочет «поделиться своими мыслями» (буквально), пишите в комментариях, будем реализовывать «вживую — через Wi-Fi» и «крутить нейрохелс-данные» для всех.

Теги:
Хабы:
+13
Комментарии15

Публикации

Работа

Data Scientist
53 вакансии

Ближайшие события