Варианты классификации поз

С помощью API ML Kit Pose Detection вы можете получить осмысленную интерпретацию позы, проверяя относительное положение различных частей тела. На этой странице показано несколько примеров.

Классификация поз и подсчет повторений с помощью алгоритма k-NN

Одним из наиболее распространенных применений определения позы является отслеживание фитнеса. Создание классификатора поз, который распознает конкретные фитнес-позы и подсчитывает повторения, может оказаться непростой задачей для разработчиков.

В этом разделе мы описываем, как мы создали собственный классификатор поз с помощью MediaPipe Colab , и демонстрируем рабочий классификатор в нашем примере приложения ML Kit.

Если вы не знакомы с Google Colaboratory, ознакомьтесь с вводным руководством .

Для распознавания поз мы используем алгоритм k-ближайших соседей (k-NN), потому что он прост и с него легко начать. Алгоритм определяет класс объекта на основе ближайших образцов в обучающем наборе.

Выполните следующие шаги, чтобы создать и обучить распознаватель:

1. Соберите образцы изображений

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

Рисунок 1. Позы отжиманий вверх и вниз.

2. Запустите обнаружение позы на образцах изображений.

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

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

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

3. Тренируйте модель и считайте повторения.

Мы использовали MediaPipe Colab для доступа к коду классификатора и обучения модели.

Для подсчета повторений мы использовали другой алгоритм Colab для мониторинга порога вероятности целевой позиции позы. Например:

  • Когда вероятность класса позы «вниз» впервые превышает заданный порог, алгоритм отмечает, что введен класс позы «вниз».
  • Когда вероятность падает ниже порога, алгоритм отмечает, что из класса позы «вниз» произошел выход, и увеличивает счетчик.
Рисунок 2. Пример подсчета повторений

4. Интегрируйтесь с приложением быстрого запуска ML Kit.

Вышеупомянутый Colab создает файл CSV, который вы можете заполнить всеми образцами поз. В этом разделе вы узнаете, как интегрировать CSV-файл с приложением быстрого запуска ML Kit для Android, чтобы просматривать пользовательскую классификацию поз в режиме реального времени.

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

  • Получите проект приложения быстрого запуска ML Kit для Android на Github и убедитесь, что он собирается и работает хорошо.
  • Перейдите в LivePreviewActivity и включите Run classification с обнаружением позы на странице настроек. Теперь вы сможете классифицировать отжимания и приседания.

Добавьте свой CSV-файл

  • Добавьте файл CSV в папку ресурсов приложения.
  • В PoseClassifierProcessor обновите переменные POSE_SAMPLES_FILE и POSE_CLASSES , чтобы они соответствовали вашему CSV-файлу и образцам поз.
  • Создайте и запустите приложение.

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

Чтобы узнать больше и попробовать это самостоятельно, ознакомьтесь с руководством по классификации MediaPipe Colab и MediaPipe .

Распознавание простых жестов путем расчета расстояния до ориентира

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

Рисунок 3. Интерпретация позы

Распознавание позы йоги с помощью угловой эвристики

Вы можете определить позу йоги, рассчитав углы различных суставов. Например, на рисунке 2 ниже показана поза йоги «Воин II». Примерные углы, определяющие эту позу, записаны в:

Рисунок 4. Разбиение позы на ракурсы

Эту позу можно описать как следующую комбинацию примерных углов частей тела:

  • Угол 90 градусов на обоих плечах
  • 180 градусов в обоих локтях
  • Угол 90 градусов на передней ноге и талии
  • Угол 180 градусов в заднем колене
  • Угол талии 135 градусов

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

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

В приведенном ниже фрагменте кода показано, как использовать координаты X и Y для расчета угла между двумя частями тела. Этот подход к классификации имеет некоторые ограничения. Если проверять только X и Y, вычисленные углы будут различаться в зависимости от угла между объектом и камерой. Вы получите наилучшие результаты при ровном, прямолинейном изображении в лоб. Вы также можете попробовать расширить этот алгоритм, используя координату Z , и посмотреть, будет ли он работать лучше для вашего варианта использования.

Вычисление углов ориентиров на Android

Следующий метод вычисляет угол между любыми тремя ориентирами. Это гарантирует, что возвращаемый угол находится в диапазоне от 0 до 180 градусов.

Котлин

fun getAngle(firstPoint: PoseLandmark, midPoint: PoseLandmark, lastPoint: PoseLandmark): Double {
        var result = Math.toDegrees(atan2(lastPoint.getPosition().y - midPoint.getPosition().y,
                lastPoint.getPosition().x - midPoint.getPosition().x)
                - atan2(firstPoint.getPosition().y - midPoint.getPosition().y,
                firstPoint.getPosition().x - midPoint.getPosition().x))
        result = Math.abs(result) // Angle should never be negative
        if (result > 180) {
            result = 360.0 - result // Always get the acute representation of the angle
        }
        return result
    }

Джава

static double getAngle(PoseLandmark firstPoint, PoseLandmark midPoint, PoseLandmark lastPoint) {
  double result =
        Math.toDegrees(
            atan2(lastPoint.getPosition().y - midPoint.getPosition().y,
                      lastPoint.getPosition().x - midPoint.getPosition().x)
                - atan2(firstPoint.getPosition().y - midPoint.getPosition().y,
                      firstPoint.getPosition().x - midPoint.getPosition().x));
  result = Math.abs(result); // Angle should never be negative
  if (result > 180) {
      result = (360.0 - result); // Always get the acute representation of the angle
  }
  return result;
}

Вот как вычислить угол в правом бедре:

Котлин

val rightHipAngle = getAngle(
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_SHOULDER),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_HIP),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_KNEE))

Джава

double rightHipAngle = getAngle(
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_SHOULDER),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_HIP),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_KNEE));

Вычисление углов ориентиров на iOS

Следующий метод вычисляет угол между любыми тремя ориентирами. Это гарантирует, что возвращаемый угол находится в диапазоне от 0 до 180 градусов.

Быстрый

func angle(
      firstLandmark: PoseLandmark,
      midLandmark: PoseLandmark,
      lastLandmark: PoseLandmark
  ) -> CGFloat {
      let radians: CGFloat =
          atan2(lastLandmark.position.y - midLandmark.position.y,
                    lastLandmark.position.x - midLandmark.position.x) -
            atan2(firstLandmark.position.y - midLandmark.position.y,
                    firstLandmark.position.x - midLandmark.position.x)
      var degrees = radians * 180.0 / .pi
      degrees = abs(degrees) // Angle should never be negative
      if degrees > 180.0 {
          degrees = 360.0 - degrees // Always get the acute representation of the angle
      }
      return degrees
  }

Цель-C

(CGFloat)angleFromFirstLandmark:(MLKPoseLandmark *)firstLandmark
                      midLandmark:(MLKPoseLandmark *)midLandmark
                     lastLandmark:(MLKPoseLandmark *)lastLandmark {
    CGFloat radians = atan2(lastLandmark.position.y - midLandmark.position.y,
                            lastLandmark.position.x - midLandmark.position.x) -
                      atan2(firstLandmark.position.y - midLandmark.position.y,
                            firstLandmark.position.x - midLandmark.position.x);
    CGFloat degrees = radians * 180.0 / M_PI;
    degrees = fabs(degrees); // Angle should never be negative
    if (degrees > 180.0) {
        degrees = 360.0 - degrees; // Always get the acute representation of the angle
    }
    return degrees;
}

Вот как вычислить угол в правом бедре:

Быстрый

let rightHipAngle = angle(
      firstLandmark: pose.landmark(ofType: .rightShoulder),
      midLandmark: pose.landmark(ofType: .rightHip),
      lastLandmark: pose.landmark(ofType: .rightKnee))

Цель-C

CGFloat rightHipAngle =
    [self angleFromFirstLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightShoulder]
                     midLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightHip]
                    lastLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightKnee]];