Как правильно перенести позу SMPL модели в Godot: решение проблемы скручивания суставов
Перенос анимации с SMPL-модели на произвольный скелет в Godot - частая задача при разработке приложений с захватом движения через веб-камеру. Однако разработчики нередко сталкиваются с эффектом «сломанной куклы», когда локти выворачиваются, а суставы перекручиваются. В этой статье мы разберём причины проблемы и предложим работающее решение.
Почему возникает скручивание суставов при переносе позы?
Основная причина - различие в системах координат и ориентации костей между SMPL и Godot. SMPL использует глобальные оси, совпадающие с мировыми, тогда как в Godot локальные оси каждой кости ориентированы вдоль её длины. Прямое применение матриц вращения из SMPL приводит к накоплению ошибок и неестественным позам.
Особенности SMPL-модели
SMPL (Skinned Multi-Person Linear Model) - параметрическая модель человеческого тела, которая регрессирует 24 сустава с матрицами вращения 3x3. Эти матрицы заданы в глобальной системе координат, что удобно для нейросетей, но требует преобразования при переносе на другой скелет.
Система координат в Godot
В Godot каждая кость скелета имеет свою локальную систему координат, определённую в состоянии покоя (rest pose). Локальные оси кости направлены вдоль её геометрии: ось Y - вдоль кости, X и Z - перпендикулярно. Применение глобальных вращений без учёта rest-позы приводит к скручиванию.
Пошаговое решение: коррекция осей и применение позы
Чтобы корректно перенести позу, необходимо преобразовать глобальные матрицы SMPL в локальные вращения Godot с учётом rest-ориентации кости. Ниже приведён исправленный код на C#.
1. Загрузка и инференс ONNX-модели
Код загрузки модели и выполнения инференса остаётся без изменений. Важно правильно подготовить входной тензор: преобразовать BGR в RGB, нормализовать по mean/std ImageNet.
2. Преобразование матриц вращения
После получения тензора theta (24 матрицы 3x3) выполняем следующие шаги:
- Извлекаем матрицу для каждого сустава
- Применяем глобальную коррекцию (поворот на 180° вокруг оси Y, если оси SMPL и Godot не совпадают)
- Вычисляем локальную позу:
localPose = globalRestBasis.Inverse() * correctedGlobal
Basis modelCorrection = new Basis(Vector3.Up, Mathf.Pi); // Поворот на 180° вокруг Y
for (int j = 0; j < numJoints; j++)
{
Basis smplGlobalBasis = new Basis(
new Vector3(thetaTensor[0, j, 0, 0], thetaTensor[0, j, 0, 1], thetaTensor[0, j, 0, 2]),
new Vector3(thetaTensor[0, j, 1, 0], thetaTensor[0, j, 1, 1], thetaTensor[0, j, 1, 2]),
new Vector3(thetaTensor[0, j, 2, 0], thetaTensor[0, j, 2, 1], thetaTensor[0, j, 2, 2])
);
Basis correctedGlobal = modelCorrection * smplGlobalBasis;
ApplyToBone(j, correctedGlobal);
}3. Функция ApplyToBone с учётом rest-позы
Ключевой момент - использовать GetBoneGlobalRest для получения rest-ориентации кости, затем вычислить локальное вращение и применить его через SetBonePoseRotation:
private void ApplyToBone(int smplJointId, Basis smplGlobalBasis)
{
string boneName = GetBoneNameForSMPLJoint(smplJointId);
if (string.IsNullOrEmpty(boneName)) return;
int boneIdx = skeleton.FindBone(boneName);
if (boneIdx == -1) return;
Basis globalRestBasis = skeleton.GetBoneGlobalRest(boneIdx).Basis;
Basis localPose = globalRestBasis.Inverse() * smplGlobalBasis;
skeleton.SetBonePoseRotation(boneIdx, localPose.GetRotationQuaternion());
}Альтернативный подход: обратная кинематика (IK)
Если прямое применение вращений всё равно даёт артефакты (например, из-за разной длины костей), стоит рассмотреть обратную кинематику. IK позволяет задавать положение концевых эффекторов (кисти, стопы), а скелет вычисляет углы в суставах автоматически. В Godot есть встроенный SkeletonIK3D, который можно настроить для каждой конечности.
Часто задаваемые вопросы
Почему после переноса позы локти выворачиваются?
Это происходит из-за несовпадения локальных осей костей в Godot и глобальных осей SMPL. Без коррекции через rest-позу вращение применяется относительно неверного центра.
Нужно ли менять систему координат в ONNX-модели?
Нет, проще сделать преобразование на стороне Godot: повернуть глобальную матрицу на 180° вокруг оси Y (если модель стояла лицом к камере).
Как узнать соответствие суставов SMPL и костей моей модели?
Создайте таблицу маппинга, как в примере с GetBoneNameForSMPLJoint. Для каждой модели она своя, но обычно совпадают основные группы: плечи, локти, кисти, бёдра, колени, стопы.
Что делать, если модель всё ещё дёргается?
Проверьте частоту кадров инференса - если она ниже 15 FPS, анимация будет прерывистой. Используйте GPU-ускорение (CUDA) и оптимизируйте предобработку изображения.