Отображение движения с использованием результатов инверсной динамики

Вопрос или проблема

Я совершенно новичок в OpenSim, так что это может быть тривиальная проблема, но все равно я не смог найти ответ на форуме или в интернете 🙁 . Я использую OpenSim 4.5 и хочу визуализировать движение на основе вывода обратной динамики (это означает использование сил и моментов).

Я вижу, что окно всплывает, но отображаемое движение даже не похоже на оригинальное. Мое начальное движение длится 15 секунд, но симуляция часто зависает после ~5 секунд. Более того, я получаю разные движения, когда запускаю один и тот же код несколько раз, даже не меняя файл движения.

Я опубликую свой код и надеюсь, что кто-то более опытный, чем я, сможет на него взглянуть, потому что мне кажется, что я упускаю что-то простое 🙏.

Вот краткое резюме кода: функция принимает модель, оригинальный файл движения .sto (для начальной позиции) и силы, произведенные симуляцией обратной динамики (тоже .sto).

  1. Сначала недостающие актуаторы (6 сил и моментов, действующих на таз) добавляются в модель.
  2. Затем я устанавливаю верхние и нижние пределы для координат.
  3. Я перебираю координаты, указанные в списке “coordinateNames”, и использую сплайн GCV для каждой координаты, чтобы установить prescribeControlForActuator.
  4. В конце я использую менеджер для интеграции до последнего момента движения.
void forwardForInverseResult(OpenSim::Model model, const std::string& inputSTOfn, const std::string& inputForceSTOfn) {
    std::cout << "Запуск симуляции..." << std::endl;

    // Содержит вывод обратной динамики (силы и моменты)
    auto inputSTO = std::unique_ptr<OpenSim::Storage>(new OpenSim::Storage(inputForceSTOfn));
    // Содержит начальную позицию модели, взятую из записанного оригинального движения.
    auto inputSTOposition = std::unique_ptr<OpenSim::Storage>(new OpenSim::Storage(inputSTOfn));

    if (inputSTO->isInDegrees())
        model.getSimbodyEngine().convertDegreesToRadians(*inputSTO);

    const double firstTime = inputSTO->getFirstTime();
    const double lastTime = inputSTO->getLastTime();

    /*  Добавление актуаторов контроллера */
    /* Этот раздел добавляет недостающие актуаторы, действующие на таз: наклон/крутение/подъем/длинный/вертикальный/боковой */
    auto controller = new OpenSim::PrescribedController();
    model.addController(controller);
    for (int i = 0; i < model.getNumCoordinates(); ++i) {
        const auto& c = model.getCoordinateSet().get(i);

        OpenSim::CoordinateActuator* ca;
        if (model.getActuators().contains(c.getName() + "_ca")) {
            std::cout << "Использование существующего " << c.getName() << "_ca." << std::endl;
            ca = OpenSim::CoordinateActuator::safeDownCast(
                &(model.updActuators().get(c.getName() + "_ca")));
            controller->addActuator(*ca);
        }
        else {
            std::cout << "Добавление нового " << c.getName() << "_ca." << std::endl;
            ca = new OpenSim::CoordinateActuator(c.getName());
            ca->setName(c.getName() + "_ca");
            ca->setOptimalForce(1.);
            ca->setMinControl(-1e3);
            ca->setMaxControl(1e3);
            model.addForce(ca);
            controller->addActuator(*ca);
        }
        controller->prescribeControlForActuator(ca->getName(), new OpenSim::Constant(0.));
    }
    /*  КОНЕЦ добавления актуаторов контроллера */

    model.setUseVisualizer(true);
    auto state = model.initSystem();

    /* Установка начальной позиции модели из файла координат. */
    for (int i = 0; i < model.getNumCoordinates(); ++i) {
        const auto& coord = model.getCoordinateSet().get(i);
        double initialValue = inputSTOposition->getStateVector(0)->getData()[i];
        coord.setValue(state, initialValue);
    }
    model.realizePosition(state);
    /* Конец установки начальной позиции модели. */

    /* Установка столбца времени */
    OpenSim::Array<double> times;
    inputSTO->getTimeColumn(times);
    std::vector<double> timesVec(times.getSize());
    // Установить вектор времени только один раз в начале
        for (int p = 0; p < times.getSize(); p++) {
            timesVec[p] = times[p];
        }
    /* Конец установки столбца времени */

    // Определение имен координат
    static const std::unordered_set<std::string> coordinateNames = {
        "Hip_RR_r", "Hip_DB_r", "Knee_FE_r", "Hip_EF_r",
        "Hip_EF_l", "Hip_RR_l", "Hip_BD_l", "Knee_FE_l",
        "Shoulder_BD_r", "Shoulder_EF_r", "Elbow_EF_r",
        "Shoulder_DB_l", "Shoulder_EF_l", "Shoulder_RR_l", "Elbow_EF_l",
        "Neck_LL", "Neck_RR", "Neck_FE", "Shoulder_RR_r", "SpineL_LL", "SpineL_RR", "SpineL_FE", "SpineU_FE",
    };

    state.setTime(firstTime);

    for (int i = 0; i < model.getNumCoordinates(); ++i) {
        auto& c = model.updCoordinateSet().get(i);

        // Если координата исключена, пропустите оставшуюся часть цикла
        // (Здесь, если функция "find" возвращает итератор конца, это означает, что координата не найдена)
        if (coordinateNames.find(c.getName()) != coordinateNames.end()) {
            continue;
        }

        // Перебор компонентов CoordinateLimitForce, чтобы применить ограничения
        for (const auto& x : model.getComponentList<OpenSim::CoordinateLimitForce>()) {
            if (x.get_coordinate() == c.getName()) {
                double upperLimitRad = SimTK::convertDegreesToRadians(x.getUpperLimit());
                double lowerLimitRad = SimTK::convertDegreesToRadians(x.getLowerLimit());

                // Настройка значения координаты в пределах ограничений
                double currentValue = c.getValue(state);
                if (currentValue > upperLimitRad) {
                    c.setValue(state, upperLimitRad);
                }
                else if (currentValue < lowerLimitRad) {
                    c.setValue(state, lowerLimitRad);
                }

                // Выйти из цикла после нахождения совпадения
                break;
            }
        }

    }

    // Начало симуляции
    auto manager = std::make_unique<OpenSim::Manager>(model, state);

    // Цикл по координатам
    for (int k = 0; k < model.getNumCoordinates(); ++k) {
        const auto& c = model.getCoordinateSet().get(k);
        cout << "Получение координаты на k:" << k << " которая есть: " << c.getName() << endl;

        // Пропустить исключенные координаты
        if (coordinateNames.find(c.getName()) == coordinateNames.end()) {
            cout << "Пропуск исключенной координаты: " << c.getName() << endl;
            continue;
        }

        // Безопасное преобразование к CoordinateActuator
        auto* ca = OpenSim::CoordinateActuator::safeDownCast(
            &(model.updActuators().get(c.getName() + "_ca")));
        if (!ca) continue;

        // Получение значения силы из файла входных сил
        OpenSim::Array<double> forceValues;

        auto columnName = c.getAbsolutePathString() + "/value";

        inputSTO->getDataColumn(columnName, forceValues);

        // Преобразование OpenSim::Array в std::vector для времени и значений силы
        std::vector<double> forceValuesVec(forceValues.getSize());
        for (int p = 0; p < times.getSize(); p++) {
            forceValuesVec[p]= forceValues[p];
        }

        auto newSpline = OpenSim::GCVSpline(5, forceValuesVec.size(), timesVec.data(), forceValuesVec.data(), "");

        controller->prescribeControlForActuator(
            controller->getActuatorSet().get(c.getName() + "_ca").getName(),
            new OpenSim::GCVSpline(newSpline));

    }

    // Интегрировать состояние модели во времени
    state = manager->integrate(lastTime);
}

Большое спасибо заранее!

Я даже пытался зафиксировать все координаты, кроме одной, и также установить все моменты/силы в входном файле в 0, кроме одной, но все равно все координаты двигались, и симуляция быстро зависала.

Ответ или решение

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

1. Проверьте масштаб данных

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

2. Проверьте начальные условия

Когда вы используете начальные позиции, взятые из вашего .sto файла, убедитесь, что значения координат действительно соответствуют желаемым и не вызывают конфликты внутри модели. Например, убедитесь, что устанавливаемые значения находятся в допустимых диапазонах для каждой координаты. Используйте model.realizePosition(state) после установки значений для проверки, что модель корректно инициализирована.

3. Актюаторы и управление

Способ, которым вы добавляете контроллеры и актюаторы в вашу модель, может быть причиной нестабильной симуляции. Убедитесь, что добавление всех необходимых контроллеров происходит без конфликтов. Возможно, вы добавляете больше актюаторов, чем требуется, или устанавливаете слишком высокие пределы для сил и моментов, что может привести к неустойчивости модели.

  • Попробуйте уменьшить значение силы на setOptimalForce(1.), чтобы избежать чрезмерных значений в начале симуляции.
  • Убедитесь, что для каждого актюатора в контроллере установлены корректные правила управления, особенно для координат, которые должны оставаться фиксированными.

4. Проблемы со временем интеграции

Ваш код запускает интеграцию с использованием manager->integrate(lastTime), однако, если ваш временной шаг задан неправильно, это также может приводить к сбоям. Рассмотрите возможность использования меньшего временного шага при интеграции, чтобы уменьшить вероятность столкновения модели внутри СимБоди, что может привести к "зависаниям".

5. Логи и отладка

Используйте отладочную информацию для отслеживания значений сил и координат во время симуляции. Вставьте выводы (например через std::cout) для отслеживания изменений координат и значений сил на различных шагах в процессе симуляции. Это может дать ценные подсказки о том, в какой момент ваши данные начинают расходиться с ожидаемыми.

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...