Здесь не рассказывают про гигантские суперкомпьютеры и дорогостоящие коммерческие нейроинтерфейсы. В этой статье показан путь от сырой схемы с электродами на лбу до работающего прототипа 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» и «крутить нейрохелс-данные» для всех.

Комментарии (15)


  1. kashyapov
    12.06.2025 17:43

    Спасибо за подробный гайд! Давно хотел попробовать что-то подобное, но казалось слишком сложным. А тут всё разложено по полочкам - от схемы подключения до кода на Python и Arduino.

    Обязательно попробую повторить.

    Плюсик завтра поставлю (сегодняшние потратил)


  1. JBFW
    12.06.2025 17:43

    Полиграф же


  1. xSVPx
    12.06.2025 17:43

    Очень неточно :(. Ну т.е. на уровне гадания на кофейной гуще. И может оказаться, что вы в большей степени мимику снимаете, чем мозг...

    Попробуйте к марио это всё привернуть, уверен играть вы не сможете с такой точностью определения.

    PS. Что вы собрались учить для ads1115 понятно не очень, для него готовых библиотек попой жуй...


  1. acc0unt
    12.06.2025 17:43

    Потыкать палочкой можно, но практического будущего у неинвазивных BCI нет.


    1. jshapen
      12.06.2025 17:43

      Почему?


      1. acc0unt
        12.06.2025 17:43

        Информация потеряна.

        Пытаться понять активность мозга по тому, что протекает наружу через крышку черепа - это как пытаться смотреть телевизор стоя к экрану спиной. Чисто по засветке на противоположной от экрана стене. Видно что что-то происходит конечно - но никаких деталей восстановить нельзя.

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


        1. jshapen
          12.06.2025 17:43

          А что насчет БОС?


          1. acc0unt
            12.06.2025 17:43

            БОС?


            1. jshapen
              12.06.2025 17:43

              Биологическая Обратная Связь


              1. acc0unt
                12.06.2025 17:43

                В чём смысл? Пытаться извлечь данные из мозга через side channel?

                Проблема тут в том, что у нас уже есть канал связи банально через движения рук, и экзотика не даёт над ним практических преимуществ. Она во всём хуже.

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


    1. rPman
      12.06.2025 17:43

      но практического будущего у неинвазивных BCI нет.

      сильное заявление, технологии типа БАК смотрят на вас с удивлением, или вот неизвазивными методами исследуют печать на клавиатуре

      Проблема в потребительском оборудовании - очень маленькое количество датчиков, я отлично помню в 2011 году emotiv делал шлемы, и тут на хабре были статьи где несколько степеней свободы можно было обучить ее софт угадывать... мне кажется прогресс в этом деле приостановлен искусственно (особенно если посмотреть на первую мою ссылку, для работы БАК нужен дешевый шлем, компьютер и алгоритм, в неумелых руках можно сломать в мозгу много чего, а ломать будут потому что к примеру с ее помощью можно сделать буквально электронные наркотики), но доказать это сложно


      1. Vytian
        12.06.2025 17:43

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

        Нет никакого заговора -- просто хорошие неинвазивные сенсоры (те же SQUID- магнитометры из вашей ссылки или фМРТ ) -- это очень, неимоверно дорого, и ужасно непрактично (стационарные системы), без всяких перспектив разумного удешевления, потому что это уже зрелые и предельно отлаженные тенологии. А носимые сенсоры, будь то оптические магнитометры, fNIRS, HD-массивы контакных (не подкожных) электродов или разные виды акустики -- страдают нехваткой чувтвительности, фундаментальным недостатком пространственного разрешения, подвержены внешним возмущениям и техническим детским болезням (типа неприемлемой температуры датчика или тупого взаимопроникновения каналов). Базовые вещи продемонстрированы, и давно, а каждый новый шаг серьезно ограничен имеющимися инструментами.

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

        Ну или аналогичнвюый по масштабом прорыв для бесконтактных.


        1. pbw
          12.06.2025 17:43

          То есть оптические магнитометры "на кристалле", которые сейчас активно продвигают для "навигации без GPS", по магнитному полю земли - они достаточны только для этих целей, но их чувствительность никогда не приблизится к необходимой для регистрации сигналов мозга, так?


          1. Vytian
            12.06.2025 17:43

            Ну те, которые "на кристалле" -- они пока вообще ни для чего не нужны, разве что датчики Холла заменят, когда цена в 10000 упадет. Их уже между делом ГМС/ТМС переплюнули, которые TDK и Asahi, что ли, продают за денежку малую.

            Ну ладно, это я немного заострил, но там и вправду пока до любой биомедицины далековато. Единственный типа коммерческий сенсор от QAnt, который хоть к чему-то там приближается по чувствительности -- совсем беззубый, дай бог сердце зарегистрирует после усреднения 1000 ударов, а дмже до альфа волн мозга еще минимум полтора порядка надо по чувствительности, особенно на низких частотах.

            А вот классика (почти) из шестидесятых на парах щелочных металлов или метастабильном гелии-4 - что-то там может. И они в разы "шумнее" криогенки на эффекте Джозефсона. Впрочем, это все равно баловство , пока для регистрации нужен магнитный экран на полтонны пермаллоя. Подвижки для неэкранированных систем есть (типа померять что-то в чистом поле можно, а вот уже на городской улице нет). Но проблема в том, что все остальное еще хуже.


  1. michael108
    12.06.2025 17:43

    Было бы интересно посмотреть на фотографии вашего BCI-шлема. И как вы боролись с контактным шумом -- проводящим гелем электроды мазали, или как?