Создание приложения для фитнес-тренировок с использованием Python и машинного обучения

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

Бизнес-кейс

С появлением пандемии Covid-19 многие отрасли бизнеса сильно пострадали. Из-за того, что спортивные залы были закрыты в соответствии с нормами и протоколами covid-19, индустрия фитнеса потерпела серьезный удар. Однако, как говорится «Необходимость — мать изобретения», концепция «виртуализации» пришла и в фитнес-индустрию — как и во многие другие — обучение, социальные встречи и т. д. Однако , он приходит со своими проблемами. Это требует определенного уровня технических знаний со стороны пользователя. И инструкторы, и стажеры должны привыкнуть к подключению и обучению через Интернет. Также со стороны тренера не просто проверить и исправить всех по видео, так как сложно вручную отслеживать всех стажеров одновременно. Можно сказать, что компьютерное зрение и ИИ сейчас неизбежны в фитнес-индустрии. По мере того, как смартфоны становятся дешевле и мощнее, а прорывы в IoT прокладывают путь к дешевым датчикам и умным носимым устройствам, эта гипотеза становится все более весомой. Итак, мы пытаемся создать решение, которое может помочь любому инструктору тренажерного зала. Это не может заменить инструктора, но может сделать его работу более эффективной и организованной. Идея состоит в том, чтобы оцифровать рутинную часть работы, такую ​​как наблюдение за позами, ведение подсчета, внесение небольших исправлений и т. д., и таким образом позволить инструктору взаимодействовать с большим количеством людей. Пользователь может практиковаться перед системой и следить за собой в свое сладкое время.

В приложении мы рассматриваем следующие упражнения:
1. Прыжки с трамплина
2. Скручивания
3. Выпады
4. Планка
5. Приседания

Мы постараемся внедрить в наше приложение следующие функции:
● Обеспечьте в режиме реального времени понятный аудиовизуальный отклик для коррекции позы
● Подсчитайте повторения и сохраните время для пользователя
● Проводить базовую аналитику и предоставлять информацию пользователям

Системный дизайн

Модули приложений

Мы разбиваем всю систему на различные модули, каждый из которых имеет определенные функции.

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

Модуль Обработчик кадров берет каждый из извлеченных кадров и обрабатывает их последовательно. Первая и главная задача заключалась в том, чтобы обнаружить человеческое тело в заданном кадре изображения и отметить различные ключевые точки (точки тела) с помощью оценки позы. Для этого мы исследовали два современных открытых пакета для определения позы — например, MoveNet (17 ключевых точек) и MediaPipe (33 ключевых точки). Изучив обе библиотеки, мы решили использовать MediaPipe, который имеет хороший баланс скорости и точности. Этот пакет довольно прост в установке и использовании. У нас также есть контроль над установкой порогов достоверности отслеживания и обнаружения, а также мы можем дополнить наши наборы функций оценкой видимости, которую он дает. После оценки позы мы используем эти ключевые точки для выделения данного кадра изображения. Наконец, процессор кадров рекомендует пользователю любые исправления с учетом обнаруженной позы.

Модуль Классификатор поз использует предварительно обученные модели классификации и возвращает обнаруженный тип упражнения из заданного кадра изображения. Первый уровень классификации пытается определить правильный тип упражнений. Это обнаруженное упражнение вместе с функциями далее используется для перехода к конкретной конечной позе этого типа упражнения. Чтобы избежать слишком большого джиттера в классификации, мы также используем метод скользящего окна класса 3 кадра. Это гарантирует, что предсказанные метки классов будут сглажены.

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

Main Engine — это фактический управляющий модуль приложения. Он извлекает кадры в формате RGB из потока с интервалом в 200 мс (чтобы избежать слишком большой перегрузки) и отправляет кадры нижестоящим модулям. Он получает предсказанные метки для соответствующих кадров и рекомендуемые исправления для пользователя (если таковые имеются). Он отображает эталонный gif для пользователя в пользовательском интерфейсе для обнаруженного типа упражнений и поддерживает установленное количество для каждого из упражнений, выполненных пользователем.

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

Сбор данных

Сбор качественных обучающих данных является одним из основных условий решения любой задачи машинного обучения. Нам нужны были данные для обучения нашего классификатора типов упражнений и классификатора поз упражнений. Мы разделили все упражнения, которые мы рассматриваем, на 2 терминальные позы — начальную и конечную. Идея такова — любое упражнение начинается со стартовой позы и переходит в конечную позу. Мы пытались найти в открытом доступе изображения для типов упражнений, которые мы тренируем, но данных о тренировках с правильной маркировкой не было. Поэтому мы искали на YouTube и в интернет-статьях изображения экспертов, выполняющих эти упражнения, и собирали скриншоты. Некоторые из образцов, как показано ниже:

Особенности

Во-первых, мы отбрасываем некоторые точки тела (из 33 точек) из выходных данных MediaPipe, которые слишком малы по сравнению с размером тела и могут добавить больше шума, чем информации. Мы рассматриваем только эти конкретные точки тела:

Мы также вычисляем еще несколько точек тела, используя точки, которые мы уже получили выше. Они есть:

  • шея = (левое плечо + правое плечо) х 0,5
  • длина туловища = (расстояние от шеи до левого бедра + расстояние от шеи до правого бедра) x 0,5
  • середина бедер = (левое бедро + правое бедро) х 0,5
  • кор = (шея + середина бедер) х 0,5

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

Объекты расстояния между точками тела (евклидово расстояние в двухмерной плоскости)

Расстояние между 1 суставом
● Сердцевина — нос
● Сердцевина — левый локоть
● Сердцевина — правый локоть
● Сердцевина — левое запястье
● Сердцевина — правое запястье
● Ядро — левое колено
● Ядро — правое колено
● Ядро — левая лодыжка
● Ядро — правая лодыжка

Расстояние между двумя суставами
● Левое плечо — левое запястье
● Правое плечо — правое запястье
● Левое бедро — левый локоть
● Правое бедро — правый локоть
● Левое Плечо — левое колено
● Правое плечо — правое колено
● Левое бедро — левая лодыжка
● Правое бедро — правая лодыжка
● Левое колено — указатель левой стопы
● Правое колено — указатель правой стопы

Расстояние между суставами
● Левое запястье — правое запястье
● Левый локоть — правый локоть
● Левое плечо — правое плечо
● Левое бедро — правое бедро
● Левое Колено — правое колено

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

Угловые объекты между тремя точками тела (косинусные углы в двухмерной плоскости)

Вычисляем угол косинуса, образованный 2-мя точками тела с 3-й точкой. Углы рассчитываются в диапазоне 0–360°.
● Угол левого локтя — шея — правый локоть
● Угол левого колена — середина бедра — правое колено
● Угол носа — шея — середина бедер
● Угол носа — ядро ​​— земля
Наконец, мы также нормализуем углы на 360.

Функции видимости левого и правого профилей тела

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

Левый профиль
● (видимость левого плеча + видимость левого бедра) x 0,5
● (видимость левого бедра + видимость левого колена) x 0,5

Правый профиль
● (видимость правого плеча + видимость правого бедра) x 0,5
● (видимость правого бедра + видимость правого колена) x 0,5

Весь набор функций создается путем объединения всех этих составляющих функций по горизонтали.

Тренировочные данные

Первоначально мы начали с 10 фотографий каждого типа упражнений (по 5 для начальной и конечной позы), сделанных с одинаковых ракурсов с небольшими различиями. Итак, у нас есть 50 картинок для 5 видов упражнений. Мы обнаружили, что, поскольку количество точек данных чрезвычайно мало, почти все классификаторы имели проблему переобучения. Кроме того, поскольку в тренировочных данных были только снимки фитнес-видео экспертов YouTube, это означает, что все они были почти идеальными позами. Однако в нашем случае конечные пользователи не являются экспертами, поэтому мы должны сделать классификатор надежным, способным распознавать позы упражнений, даже если они выполняются неправильно.

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

multiply data by noise(data, target_n, to_append):
1. size_mu:= 50% of the total number of features in given data
2. create target_n-1 sample sizes by drawing samples from a normal distribution having:
   mean:= size_mu, standard deviation:= 1.5
3. for every size in sample_sizes from step 2:
   a. size = max(1, size)
   b. generate random noise for all the features from a normal distribution having:
   mean:= 0, standard deviation:= 0.025
   c. randomly chose the ‘size’ number of features and add noise to them
   d. if any of the angles features become negative we simply complement it, or if any of the distance features become negative we cap it to alpha=0.00001
4. if to_append is not null, we append its value to the end of the data list and return it

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

negative sampling by noise(data, target_n, to_append):
1. size_mu:= 5% of the total number of features in given data
2. create target_n sample sizes by drawing samples from a normal distribution having:
   mean:= size_mu, standard deviation:= 1.5
3. for every size in sample_sizes from step 2:
   a. size = max(1, size)
   b. generate random noise for all the features from a normal
distribution having:
   mean:= 0.5, standard deviation:= 0.05
   c. randomly chose the ‘size’ number of features and add noise to them
   d. if any of the angles features become negative we simply complement it, or if any of the distance features become negative we cap it to alpha=0.00001
4. if to_append is not null, we append its value to the end of the data list and return it

50 точек данных преобразованы в 3500 точек данных после применения обоих вышеперечисленных алгоритмов.

Обучение и настройка моделей

Приложению требуется классификатор с чрезвычайно низкой задержкой. Кроме того, у нас нет большого количества хорошо размеченных обучающих данных, поэтому возможность использования любого метода глубокого обучения (который может выполнять автоматическое извлечение признаков) не представляется осуществимой. Итак, мы продолжаем наши эксперименты с данными, собранными с использованием классических методов машинного обучения. Мы изучаем различные алгоритмы классификации, такие как логистическая регрессия, k-ближайший сосед, машина опорных векторов, классификатор SGD, полиномиальный наивный байесовский алгоритм и XGBoost.

Уровень классификатора 1
Наш первый классификатор — это тот, который предсказывает тип упражнения с учетом набора признаков на изображении. Мы запускаем все алгоритмы классификации и получаем следующие результаты:

Судя по метрикам результатов F1 и Test Score, мы видим, что XGBoost является лучшим классификатором для типа упражнений. Хотя это занимает наибольшее количество времени прогнозирования по сравнению со всеми из них, абсолютное время составляет около 100 мс, что является приемлемым, учитывая хорошую точность, которую он обеспечивает. Интуитивно понятно, что алгоритм на основе дерева решений, вероятно, будет хорошо работать с такими функциями, созданными вручную.

Уровень классификатора 2

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

Интересно отметить, что для 2-го уровня классификации почти все классификаторы могут работать хорошо, за исключением полиномиального наивного байесовского метода. Результат классификации 1-го уровня, добавленный в качестве функции, оказывается чрезвычайно полезным. Основываясь на баллах F1 и Test Score, kNN и SVM являются лучшими классификаторами, но мы выбираем kNN, так как он также имеет лучший показатель Train и CV.

При использовании классификаторов в приложении мы столкнулись с проблемой. Поскольку мы работали с камерой ноутбука с низким FPS, иногда извлеченные кадры были некачественными, и MediaPipe не мог извлечь функции из этих кадров. Это также происходит, если пользователь был слишком быстрым, и изображение становится размытым. Это вызывало дрожание, так как некоторые кадры не имели связанного с ними типа упражнения или класса позы. Чтобы избежать этой проблемы, мы используем подход с движущимся окном. Наше теоретическое предположение заключается в том, что любая поза вряд ли будет радикально отличаться от только что предыдущей позы за миллисекунды, поэтому мы можем связать этот кадр с позой, имеющей максимальное большинство в размере окна. Мы выбрали размер окна 3.

Коррекция позы

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

Для начальной позы прыжков на скакалке мы проверяем, соответствует ли расстояние между ступнями ширине плеч, а угол, создаваемый руками, должен быть между 120 и 180. Для конечной позы этого упражнения мы проверяем, чтобы руки были хорошо над головой, а стопы в 1,5–3 раза шире плеч.

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

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

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

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

Как только мы обнаруживаем какое-либо серьезное отклонение от установленных правил, мы сообщаем пользователю, что нужно исправить осанку.

Развертывание приложения

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

Мы также разрабатываем наши модули приложений, чтобы они функционировали отдельно как разные процессы (многопроцессорные), чтобы они работали согласованно при общении друг с другом с использованием очередей Python.

Заключение и будущая работа

Наконец-то мы создали приложение, которое может работать как виртуальный фитнес-тренер для любого пользователя. Мы работали над сбором и сопоставлением данных с нуля, а также применили наши методы увеличения, используя случайный гауссов шум по точкам данных. Мы также применили нашу модель k-Nearest Boosted Pose и добились довольно высокой точности на тестовых данных. Мы использовали подход, основанный на правилах, для решения проблемы коррекции позы, используя модуль классификации и выделения признаков. Наконец, мы развернули рабочее приложение с помощью пакета Streamlit для конечного пользователя.

Мы также смогли определить некоторые области улучшения в нашей работе. Во-первых, задержка MediaPipe является основным узким местом для нашего приложения. Поскольку наша система основана только на процессоре, MediaPipe требуется чуть меньше 200 мс для обработки одного кадра изображения. Поэтому нам приходится брать кадры через какие-то определенные промежутки времени и отбрасывать остальные — но это приводит к потере информации. Если мы будем обрабатывать кадры слишком быстро, то через некоторое время очередь MediaPipe будет перегружена, и эффект приложения в реальном времени будет потерян. Пользователь выполняет какое-то упражнение, которое отсчитывается движком с некоторой задержкой. Кроме того, наши классификаторы обучаются на тщательно подобранных данных с использованием дополнений. Это поможет, если мы получим более тщательно отобранные фактические данные. Другой подход, который мы могли бы использовать с увеличением количества данных, — это коррекция позы на основе некоторых эвристических методов или методов на основе машинного обучения.

Вы можете найти полный код этого проекта здесь на GitHub.