Реализация AI-тренера с коррекцией техники упражнений в мобильном приложении
Разница между подсчётом повторений и коррекцией техники — принципиальная. Повторения — это «сколько». Техника — это «как»: угол сгибания колена в приседании (должен быть ≥ 90°, не уходить за носок), нейтральное положение поясницы, контроль лопаток. Всё это требует точного геометрического анализа позы на каждом кадре — и понятной обратной связи в реальном времени.
Pose Estimation: точность важна
Для коррекции техники MediaPipe BlazePose Full (33 точки включая кисти и стопы) точнее, чем Vision VNDetectHumanBodyPoseRequest (19 точек). Критичное различие: MediaPipe даёт 3D координаты (x, y, z) в нормализованном пространстве — это позволяет рассчитывать углы в реальном 3D, а не только в плоскости камеры.
// MediaPipe Tasks iOS SDK
import MediaPipeTasksVision
class FormAnalyzer: PoseLandmarkerLiveStreamDelegate {
private var poseLandmarker: PoseLandmarker?
func setup() throws {
let options = PoseLandmarkerOptions()
options.baseOptions.modelAssetPath = Bundle.main.path(
forResource: "pose_landmarker_full",
ofType: "task"
)!
options.runningMode = .liveStream
options.numPoses = 1
options.minPoseDetectionConfidence = 0.7
options.minPosePresenceConfidence = 0.7
options.minTrackingConfidence = 0.7
options.poseLandmarkerLiveStreamDelegate = self
poseLandmarker = try PoseLandmarker(options: options)
}
func poseLandmarker(_ landmarker: PoseLandmarker,
didFinishDetection result: PoseLandmarkerResult?,
timestampInMilliseconds: Int,
error: Error?) {
guard let landmarks = result?.landmarks.first else { return }
analyzeSquatForm(landmarks: landmarks)
}
}
Геометрический анализ техники: приседание как пример
Приседание — наиболее аналитически разобранное упражнение. Ключевые метрики:
Угол сгибания колена
func kneeFlexionAngle(landmarks: [NormalizedLandmark]) -> Double {
// Точки: бедро (23/24), колено (25/26), голеностоп (27/28)
let hip = landmarks[23] // leftHip
let knee = landmarks[25] // leftKnee
let ankle = landmarks[27] // leftAnkle
// Векторы от колена к бедру и от колена к голеностопу
let vecToHip = SIMD2<Double>(
Double(hip.x - knee.x),
Double(hip.y - knee.y)
)
let vecToAnkle = SIMD2<Double>(
Double(ankle.x - knee.x),
Double(ankle.y - knee.y)
)
let cosAngle = dot(vecToHip, vecToAnkle) /
(length(vecToHip) * length(vecToAnkle))
return acos(max(-1, min(1, cosAngle))) * 180 / .pi
}
Норма в нижней точке приседания: 80–100°. Меньше 80° — слишком глубоко для начинающих, больше 100° — неполная амплитуда. Вывод: «Чуть глубже — согните колени ещё на 10–15°».
Колено выходит за носок
func kneeOverToe(landmarks: [NormalizedLandmark]) -> Bool {
let knee = landmarks[25]
let toe = landmarks[31] // leftFootIndex
// Если X колена (горизонталь) значительно дальше X носка при виде сбоку
return Double(knee.z - toe.z) > 0.05 // z в MediaPipe — глубина
}
MediaPipe 3D координата z — глубина от камеры. При виде сбоку это даёт проекцию вперёд-назад. Для фронтального вида нужен боковой ракурс или 3D-реконструкция из фронтальных данных (сложнее).
Спина: нейтральный позвоночник
func spineAngle(landmarks: [NormalizedLandmark]) -> Double {
let shoulder = landmarks[11] // leftShoulder
let hip = landmarks[23] // leftHip
// Угол линии плечо-бедро к вертикали
let dx = Double(shoulder.x - hip.x)
let dy = Double(shoulder.y - hip.y)
return atan2(dx, -dy) * 180 / .pi // 0° = вертикаль
}
30° от вертикали при нижней точке = сутулость / «вываливание» корпуса вперёд. Коррекция: «Поднимите грудь, сведите лопатки».
Голосовая обратная связь в реальном времени
Текстовые подсказки на экране — неудобно: пользователь смотрит на упражнение, не на телефон. Голосовые подсказки работают лучше.
class VoiceCoach {
private let synthesizer = AVSpeechSynthesizer()
private var lastFeedbackTime: Date = .distantPast
private let feedbackCooldown: TimeInterval = 3.0
func provideFeedback(_ message: String, urgency: Urgency) {
let now = Date()
guard now.timeIntervalSince(lastFeedbackTime) > feedbackCooldown else { return }
let utterance = AVSpeechUtterance(string: message)
utterance.voice = AVSpeechSynthesisVoice(language: "ru-RU")
utterance.rate = urgency == .critical ? 0.55 : 0.48
utterance.pitchMultiplier = urgency == .critical ? 1.1 : 1.0
utterance.volume = 0.9
synthesizer.speak(utterance)
lastFeedbackTime = now
}
}
feedbackCooldown = 3.0 — критично. Без cooldown система долбит одно и то же сообщение 30 раз в секунду. Пользователь вырубает звук.
Приоритизация: несколько ошибок одновременно → выбираем самую критичную. Иерархия: безопасность (колено вовнутрь → риск травмы) > техника (неполная амплитуда) > рекомендация (дышите равномернее).
Анализ фазы упражнения
Коррекция техники релевантна только в нужной фазе:
- Фаза эксцентрики (опускание в присед): проверяем спину и положение коленей
- Нижняя точка: проверяем угол сгибания, положение коленей над носком
- Фаза концентрики (вставание): проверяем, не «складывается» ли пользователь
Детекция фазы — через направление движения tracking-точки (бедра). Производная Y-координаты: отрицательная (опускание) = эксцентрика, у нижней точки = минимум, положительная = концентрика.
Поддерживаемые упражнения и масштабирование
Каждое упражнение требует своего набора метрик и правил. Реализуем через протокол:
protocol ExerciseFormAnalyzer {
var exerciseType: ExerciseType { get }
func analyze(landmarks: [NormalizedLandmark], phase: ExercisePhase) -> [FormFeedback]
func detectPhase(landmarks: [NormalizedLandmark], history: [[NormalizedLandmark]]) -> ExercisePhase
}
Каждое упражнение — отдельный conforming type. Легко добавлять новые без изменения ядра системы.
Набор для старта: присед, выпад, отжимание, становая тяга, планка, бёрпи. Это покрывает большинство домашних тренировок без оборудования.
Процесс работы
Выбор и интеграция MediaPipe / Vision. Разработка геометрических метрик для каждого упражнения (совместно с методистом/тренером). Система приоритизации и cooldown обратной связи. Голосовые подсказки через AVSpeechSynthesizer. UI: skeleton overlay, метрики в реальном времени, постсессионный анализ. Тестирование на людях разной комплекции и роста.
Ориентиры по срокам
AI-тренер для 3–5 упражнений с голосовыми подсказками — 2–4 недели. Расширенная система с автоопределением упражнения, постсессионным отчётом и прогрессом по времени — 5–8 недель.







