Как правильно перенести позу 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) и оптимизируйте предобработку изображения.

    Часто задаваемые вопросы