Всем привет! Во втором семестре все первокурсники программы «Прикладная математика и информатика» в Питерской Вышке делают командные проекты по С++. Мы занимались разработкой умной футболки.

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


Мы — Денис Тарасов и Денис Филиппов — студенты уже второго курса бакалавриата «Прикладная математика и информатика» в Питерской Вышке. Мы работали над проектом вместе с Яном Фрейдкиным и Иваном Чунаревым. С ними мы учились в одной школе: Санкт-Петербургском ГФМЛ № 30.

В 11-м классе у Яна и Ивана был проект по робототехнике ? «Умная футболка» (она на первой фотографии). Идея такова: берётся футболка, на неё крепятся различные датчики (акселерометр, гироскоп и другие). По считываемым данным можно определить некоторые показатели, например, корректность осанки. Ребята успели принять участие в нескольких конференциях и собирались после окончания школы продолжить работу над проектом, но поняли, что им нужны программисты. Собственно вот мы и здесь.

Футболка Яна и Ивана могла считывать ЭКГ и отслеживать корректность осанки. Для этого использовалась Arduino UNO — платформа разработки с устаревшими микроконтроллерами AVR. Достаточно скоро им стало не хватать памяти для кода. На новой стадии работы над проектом мы поняли, что нужно предъявлять более строгие требования к микроконтроллеру: 

  • чтобы можно было подключить больше датчиков, необходим был процессор с большей частотой, большее количество периферии и более скоростная периферия;
  • больший объем flash памяти, оперативной памяти;
  • меньшая стоимость;
  • стабильность работы микроконтроллера.

Мы решили, что нужно менять микроконтроллер. У нас было два варианта: использовать более мощный микроконтроллер серии AVR (в нашем случае Arduino) или перейти на другую серию микроконтроллеров, а именно ARM (в нашем случае STM32). Несмотря на большое сообщество Arduino и простоту в использовании этого микроконтроллера, мы решили перейти на STM32, потому что его производительность лучше, а объем памяти — больше. Сейчас мы используем микроконтроллер серии STM32f4*.


В прошедшем семестре мы поставили перед собой цель реализовать получение информации об осанке человека и её обработку. Схема проекта следующая: у нас есть футболка, на ней закреплены акселерометры и гироскопы, с помощью которых мы получаем углы закручивания. Полученные с датчиков данные поступают на микроконтроллер. Там они обрабатываются и, если нужно, подается напряжение на вибромотор, стимулирующий человека выпрямиться. С помощью приложения на Android включается и выключается обработка осанки, а также осуществляется связь футболки с пользователем. Но в целом футболка может работать и без доступа к смартфону: можно включить обработку осанки в приложении, оставить телефон дома и пойти в футболке гулять. При этом данные будут обрабатываться на микроконтроллере и  вибромотор будет включаться, если положение неправильное.


Первые шаги. Зажгли светодиод


У нас не было опыта программирования микроконтроллеров, и мы прочитали, что инициализация и объявление датчиков и пинов на STM — это долго и сложно. Поэтому мы использовали высокий уровень абстракции с помощью библиотеки HAL и STM32CubeMX. STM32CubeMX ? инструмент, который с помощью графического интерфейса настраивает порты микроконтроллера и генерирует соответствующий код, использующий библиотеку HAL. Сначала мы решили сделать самую базовую в программировании микроконтроллеров вещь (на уровне «Hello world») ? зажечь светодиод.


Интерфейс STM32CubeMX

После долгого поиска IDE, которая была бы кроссплатформенной, бесплатной и удобной для разработки, мы остановились на STM32CubeIDE



Задача включения светодиода оказалась не самой простой, поскольку в интернете довольно мало информации по программированию STM32 (особенно по сравнению с Arduino). В дальнейшем этот фактор также усложнял работу над проектом.

Прошивка микроконтроллера


После того как мы научились зажигать светодиод и поняли, как вообще программировать микроконтроллер, мы занялись написанием основы прошивки микроконтроллера, на базе которой в дальнейшем можно было бы добавлять различный функционал. Для начала нужно было научиться инициализировать датчики и получать информацию с них. Из датчиков мы успели добраться лишь до IMU-сенсоров: гироскопа и акселерометра. У нас были сенсоры MPU-6050. 


Общение  с микроконтроллером осуществляется по шине I2C. Благодаря библиотеке HAL передача данных производится легко: необходимо вызвать функцию для чтения/записи. Пример чтения данных с акселерометра из кода:

HAL_I2C_Mem_Read(i2c_handle, addres, ACCEL_XOUT_H_REG, 1, buffer, 6, 1000)

Функция получает хэндлер нужной шины, адрес датчика на ней, регистр, из которого необходимо прочитать (регистры прописаны в документации к датчику), размер регистра, буфер для записи, размер буфера и таймаут (время, которое отводится на ожидание ответа) в миллисекундах. Функция записи обладает аналогичной сигнатурой. Пример установки диапазона измерений гироскопа:

HAL_I2C_Mem_Write(i2c_handle, addres, GYRO_CONFIG_RE	G, 1, &Data, 1, 1000)

Также у нас был Bluetooth модуль (HC-05 и HC-06), но он не требовал каких-либо особых манипуляций для работы. После его подключения можно просто слать данные через универсальный асинхронный приемопередатчик (UART) — специальное устройство внутри микроконтроллера, которое позволяет общаться с другими устройствами. Для него в HAL предусмотрены функции, схожие с функциями для I2C. Мы лишь написали небольшую обертку над ними, чтобы было проще выводить сообщения во время отладки.


HC-06

Далее мы создали класс Controller, который осуществляет инициализацию датчиков, запускает основной цикл программы и обрабатывает запросы, приходящие из приложения. Запросы хранятся в очереди, поступают через Bluetooth и принимаются с помощью механизма прерываний. Прерывание — сигнал от программного или аппаратного обеспечения, сообщающий процессору о наступлении какого-либо события, которое требует немедленного внимания. Механизм прерывания нужен в том числе для быстрого реагирования системы на важные события. 

Также Controller хранит список функций футболки с помощью базового класса BaseFunc. На данный момент у нас есть только обработка осанки, но в будущем при добавлении нового функционала достаточно будет просто создать класс-наследник и добавить его в список функций. Контроллер выглядит примерно так:

class Controller {
std::vector<std::unique_ptr<IMU>> IMUSensors;  // список сенсоров
std::queue<Request> reqQueue; // очередь запросов
std::vector<std::pair<std::unique_ptr<BaseFunc>, bool>> mithrilFuncs; // список пар 
//<функция футболки, включена ли она>
Controller();
void Run(); // основной цикл программы
};

Также для получения углов закручивания с IMU-сенсоров пришлось добавить специальный фильтр, который по данным с акселерометра и гироскопа выдает корректные углы отклонения. Мы решили использовать комплементарный фильтр, потому что он достаточно эффективен, и при этом его было легко реализовать. При реализации этого фильтра мы написали математику для кватернионов. Можно было обойтись и векторами, но кватернионы понадобились в другом фильтре, который мы тестировали, так что сейчас мы используем кватернионы.

Что там по прототипу


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

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

Приложение для Android


Мы поставили перед собой цель создать Android-приложение с возможностью общаться через Bluetooth с микроконтроллером. Как и в случае с программированием микроконтроллеров, опытом написания приложений под Android мы не обладали, однако найти информацию про Android оказалось намного легче, чем про STM32. Основы мы изучали при помощи  курса на Stepik.org (курс действительно неплохой), где сначала разбирают основы языка Kotlin, а затем рассказывают о программировании под Android. 

Первая версия приложения позволяла подключаться к микроконтроллеру через Bluetooth и передавать ему сообщения. Последняя версия не сильно отличается от первой (написание приложение для нас не было в приоритете): в ней появились виджеты для запуска калибровки и включения/выключения функции слежения за корректностью осанки. 

На то, чтобы написать первую рабочую версию приложения, ушло около 6 часов — мы потратили больше времени на изучение теории. Код такого простенького приложения занял примерно 400 строк, что приятно удивило. При этом наверняка его можно написать компактнее. 


Меню навигации


 Вкладка для подключения через Bluetooth


Вкладка для обмена данными

Обработка осанки


Мы пришли к двум разным способам обработки осанки: с помощью методов анализа данных и без них.

Обработка осанки с помощью методов анализа данных


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

Сначала мы сняли с себя данные: углы закручивания и показания акселерометра с каждого из датчиков. Получили примерно 2000 замеров: около 1000 положительных и 1000 отрицательных. Конфигурация датчиков была следующая (два датчика расположены на лопатках и два на позвоночнике):


 
К сожалению, на обоих прототипах возникли проблемы с одним из четырех датчиков, поэтому датчик под номером 3 не использовался. Мы расположили их интуитивно: хотели отслеживать положение лопаток и отдела позвоночника вблизи шеи.



Проекции данных в двумерное пространство.

Здесь зеленым цветом выделены области правильной осанки, красным — неправильной. В проекции на трехмерное пространство видно, что правильные положения и неправильные легко отделить друг от друга. 


Далее необходимо было выбрать модели для предсказания. Мы решили попробовать линейную регрессию, а точнее ее модификацию Ridge, метод опорных векторов (SVM) с линейным ядром и дерево принятия решений. Выбор обусловлен простотой переноса моделей на микроконтроллер: первые две описываются некоторым количеством коэффициентов, а последняя представляет из себя набор условий if-else. Модели были взяты из библиотеки scikit-learn. Пример переноса линейной регрессии:

bool isPostureCorrect =
           (a11 * deviceAngles1[0] + a12 * deviceAngles1[1] + a13 * deviceAngles1[2] +
           g11 * deviceGravity1[0] + g12 * deviceGravity1[1] + g13 * deviceGravity1[2] +
           a21 * deviceAngles2[0] + a22 * deviceAngles2[1] + a23 * deviceAngles2[2] +
           g21 * deviceGravity2[0] + g22 * deviceGravity2[1] + g23 * deviceGravity2[2] +
           a41 * deviceAngles3[0] + a42 * deviceAngles3[1] + a43 * deviceAngles3[2] +
           g41 * deviceGravity3[0] + g42 * deviceGravity3[1] + g43 * deviceGravity3[2]) > bias;

значения a??, g??, bias берутся из обученной модели.

Точность моделей с разной конфигурацией на тестовой выборке можно увидеть в таблице:


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

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


Пример дерева принятия решений.

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

Обратка осанки без методов анализа данных


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

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


Показатели осанки у людей с большим депрессивным расстройством до (A) и после (B) лечения из статьи J.Canales et al (2010)

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



Конфигурация датчиков

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


В результате из двух углов, которые мы пытались измерить (углы 3 и 4 на изображении из статьи), один измерялся корректно, а другой — нет. Значения угла 3 совпадали с нормальными, указанными в статье. Угол 4 измерялся плохо из-за особенностей конструкции. Скорее всего, проблема заключается в том, что футболка в некоторых местах не плотно прилегает к телу, из-за чего угол наклона касательных вычисляется неверно.

Демо-видео


Вот демо-видео работы футболки и приложения:


Сначала включается обработка осанки, затем запускается калибровка, а после этого начинают приходить сообщения о корректности осанки.

Заключение


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

Конечно, нельзя считать, что наша футболка закончена — но пока мы работали над ней в течение одного семестра первого курса. Еще многое остается доделать, и в следующем году мы планируем продолжить!

Теперь немного о наших планах на будущее. Мы хотим:

  • Усовершенствовать приближения позвоночника с помощью функции, чтобы точнее измерять углы, связанные с позвоночником.
  • Собрать побольше данных для обработки осанки. На текущий момент данные сняты только с нас двоих и наши прототипы немного отличаются. 
  • Расширить функционал. Например, добавить снятие электрокардиограммы, чтобы по ней можно было выявлять различные болезни, отклонения и т. д.
  • Усовершенствовать приложение: расширить его функционал. Например, подсчитывать статистику, анализировать её, рисовать графики.
  • Создать прототип футболки, который будет иметь более товарный вид. На текущем этапе прототипы предназначены исключительно для тестирования функционала. Хочется собрать футболку, готовую для полноценного использования.

Ссылки на гитхаб проекта:

github.com/DT6A/Mithril ? прошивка микроконтроллера,
github.com/DT6A/MithrilApp ? приложение для android,
github.com/DT6A/MithrilData ? работа с данными.