С помощью API ML Kit Pose Detection вы можете получить осмысленную интерпретацию позы, проверяя относительное положение различных частей тела. На этой странице показано несколько примеров.
Классификация поз и подсчет повторений с помощью алгоритма k-NN
Одним из наиболее распространенных применений определения позы является отслеживание фитнеса. Создание классификатора поз, который распознает конкретные фитнес-позы и подсчитывает повторения, может оказаться непростой задачей для разработчиков.
В этом разделе мы описываем, как мы создали собственный классификатор поз с помощью MediaPipe Colab , и демонстрируем рабочий классификатор в нашем примере приложения ML Kit.
Если вы не знакомы с Google Colaboratory, ознакомьтесь с вводным руководством .
Для распознавания поз мы используем алгоритм k-ближайших соседей (k-NN), потому что он прост и с него легко начать. Алгоритм определяет класс объекта на основе ближайших образцов в обучающем наборе.
Выполните следующие шаги, чтобы создать и обучить распознаватель:
1. Соберите образцы изображений
Мы собрали образцы изображений целевых учений из разных источников. Мы выбрали несколько сотен изображений для каждого упражнения, например, позиции «вверх» и «вниз» для отжиманий. Важно собирать образцы, охватывающие различные ракурсы камеры, условия окружающей среды, формы тела и варианты упражнений.

2. Запустите обнаружение позы на образцах изображений.
В результате создается набор ориентиров позы, которые будут использоваться для обучения. Нас не интересует само определение позы, поскольку на следующем этапе мы будем обучать собственную модель.
Алгоритм k-NN, который мы выбрали для пользовательской классификации поз, требует представления вектора признаков для каждого образца и метрики для вычисления расстояния между двумя векторами, чтобы найти цель, ближайшую к образцу позы. Это означает, что мы должны преобразовать только что полученные ориентиры позы.
Чтобы преобразовать ориентиры позы в вектор признаков, мы используем попарные расстояния между предопределенными списками суставов позы, например, расстояние между запястьем и плечом, лодыжкой и бедром, а также левым и правым запястьями. Поскольку масштаб изображений может различаться, перед преобразованием ориентиров мы нормализовали позы, чтобы они имели одинаковый размер туловища и вертикальную ориентацию туловища.
3. Тренируйте модель и считайте повторения.
Мы использовали MediaPipe Colab для доступа к коду классификатора и обучения модели.
Для подсчета повторений мы использовали другой алгоритм Colab для мониторинга порога вероятности целевой позиции позы. Например:
- Когда вероятность класса позы «вниз» впервые превышает заданный порог, алгоритм отмечает, что введен класс позы «вниз».
- Когда вероятность падает ниже порога, алгоритм отмечает, что из класса позы «вниз» произошел выход, и увеличивает счетчик.

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 .
Распознавание простых жестов путем расчета расстояния до ориентира
Когда два или более ориентира расположены близко друг к другу, их можно использовать для распознавания жестов. Например, если ориентир одного или нескольких пальцев на руке находится близко к ориентиру носа, можно сделать вывод, что пользователь, скорее всего, касается своего лица.

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

Эту позу можно описать как следующую комбинацию примерных углов частей тела:
- Угол 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
}
(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))
CGFloat rightHipAngle =
[self angleFromFirstLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightShoulder]
midLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightHip]
lastLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightKnee]];