Обратная кинематика с OpenGL/Eigen3: нестабильная псевдоинверсия Якоби

Я пытаюсь реализовать простой тест обратной кинематики, используя OpenGL, Eigen3 и "Jacobian псевдоинверсный" метод.

Система отлично работает с использованием алгоритма «Jacobian transpose», однако, как только я пытаюсь использовать «псевдоинверсию», суставы становятся нестабильными и начинают дергаться (в конце концов они полностью зависают - если я не использую резервное вычисление «Jacobian transpose»). Я исследовал проблему и обнаружил, что в некоторых случаях Jacobian.inverse()*Jacobian имеет нулевой определитель и не может быть инвертирован. Тем не менее, я видел другие демонстрации в Интернете (Youtube), которые утверждают, что используют тот же метод, и у них, похоже, нет этой проблемы. Поэтому я не уверен, где причина проблемы. Код прикреплен ниже:

*.h:

struct Ik{
    float targetAngle;
    float ikLength;
    VectorXf angles;
    Vector3f root, target;
    Vector3f jointPos(int ikIndex);
    size_t size() const;
    Vector3f getEndPos(int index, const VectorXf& vec);
    void resize(size_t size);
    void update(float t);
    void render();
    Ik(): targetAngle(0), ikLength(10){
    }
};

*.cpp:

size_t Ik::size() const{
    return angles.rows();
}

Vector3f Ik::getEndPos(int index, const VectorXf& vec){
    Vector3f pos(0, 0, 0);
    while(true){
        Eigen::Affine3f t;
        float radAngle = pi*vec[index]/180.0f;
        t = Eigen::AngleAxisf(radAngle, Vector3f(-1, 0, 0))
            * Eigen::Translation3f(Vector3f(0, 0, ikLength));
        pos = t * pos;

        if (index == 0)
            break;
        index--;
    }
    return pos;
}

void Ik::resize(size_t size){
    angles.resize(size);
    angles.setZero();
}

void drawMarker(Vector3f p){
    glBegin(GL_LINES);
    glVertex3f(p[0]-1, p[1], p[2]);
    glVertex3f(p[0]+1, p[1], p[2]);
    glVertex3f(p[0], p[1]-1, p[2]);
    glVertex3f(p[0], p[1]+1, p[2]);
    glVertex3f(p[0], p[1], p[2]-1);
    glVertex3f(p[0], p[1], p[2]+1);
    glEnd();
}

void drawIkArm(float length){
    glBegin(GL_LINES);
    float f = 0.25f;
    glVertex3f(0, 0, length);
    glVertex3f(-f, -f, 0);
    glVertex3f(0, 0, length);
    glVertex3f(f, -f, 0);
    glVertex3f(0, 0, length);
    glVertex3f(f, f, 0);
    glVertex3f(0, 0, length);
    glVertex3f(-f, f, 0);
    glEnd();
    glBegin(GL_LINE_LOOP);
    glVertex3f(f, f, 0);
    glVertex3f(-f, f, 0);
    glVertex3f(-f, -f, 0);
    glVertex3f(f, -f, 0);
    glEnd();
}

void Ik::update(float t){
    targetAngle += t * pi*2.0f/10.0f;
    while (t > pi*2.0f)
        t -= pi*2.0f;
    target << 0, 8 + 3*sinf(targetAngle), cosf(targetAngle)*4.0f+5.0f;

    Vector3f tmpTarget = target;
    Vector3f targetDiff = tmpTarget - root;
    float l = targetDiff.norm();
    float maxLen = ikLength*(float)angles.size() - 0.01f;
    if (l > maxLen){
        targetDiff *= maxLen/l;
        l = targetDiff.norm();
        tmpTarget = root + targetDiff;
    }

    Vector3f endPos = getEndPos(size()-1, angles);
    Vector3f diff = tmpTarget - endPos;


    float maxAngle = 360.0f/(float)angles.size();

    for(int loop = 0; loop < 1; loop++){
        MatrixXf jacobian(diff.rows(), angles.rows());
        jacobian.setZero();
        float step = 1.0f;
        for (int i = 0; i < angles.size(); i++){
            Vector3f curRoot = root;
            if (i)
                curRoot = getEndPos(i-1, angles);
            Vector3f axis(1, 0, 0);
            Vector3f n = endPos - curRoot;
            float l = n.norm();
            if (l)
                n /= l;
            n = n.cross(axis);
            if (l)
                n *= l*step*pi/180.0f;
            //std::cout << n << "\n";

            for (int j = 0; j < 3; j++)
                jacobian(j, i) = n[j];
        }

        std::cout << jacobian << std::endl;
        MatrixXf jjt = jacobian.transpose()*jacobian;
        //std::cout << jjt << std::endl;
        float d = jjt.determinant();
        MatrixXf invJ; 
        float scale = 0.1f;
        if (!d /*|| true*/){
            invJ = jacobian.transpose();
            scale = 5.0f;
            std::cout << "fallback to jacobian transpose!\n";
        }
        else{
            invJ = jjt.inverse()*jacobian.transpose();
            std::cout << "jacobian pseudo-inverse!\n";
        }
        //std::cout << invJ << std::endl;

        VectorXf add = invJ*diff*step*scale;
        //std::cout << add << std::endl;
        float maxSpeed = 15.0f;
        for (int i = 0; i < add.size(); i++){
            float& cur = add[i];
            cur = std::max(-maxSpeed, std::min(maxSpeed, cur));
        }
        angles += add;
        for (int i = 0; i < angles.size(); i++){
            float& cur = angles[i];
            if (i)
                cur = std::max(-maxAngle, std::min(maxAngle, cur));
        }
    }
}

void Ik::render(){
    glPushMatrix();
    glTranslatef(root[0], root[1], root[2]);
    for (int i = 0; i < angles.size(); i++){
        glRotatef(angles[i], -1, 0, 0);
        drawIkArm(ikLength);
        glTranslatef(0, 0, ikLength);
    }
    glPopMatrix();
    drawMarker(target);
    for (int i = 0; i < angles.size(); i++)
        drawMarker(getEndPos(i, angles));
}

скриншот


person SigTerm    schedule 11.04.2012    source источник
comment
Я подозреваю, что вы неправильно вычисляете псевдоинверсию. Я думаю, вы должны использовать Eigen::SVD для его вычисления. См. также здесь.   -  person sbabbi    schedule 12.04.2012
comment
Я подозреваю, что вы неправильно вычисляете псевдоинверсию. Я предоставил полный исходный код для текущего класса Ik, поэтому, если вы знакомы с псевдоинверсией, вы сможете проверить, правильно ли выполняются вычисления.   -  person SigTerm    schedule 12.04.2012
comment
Использование вами OpenGL — это старый устаревший подход, основанный на предварительном конвейерном ЦП, который не использует графический процессор и шейдеры по назначению. Как только вы освоите свой алгоритм, я рекомендую вам вернуться к написанию шейдеров, а не кода на основе процессора (IE. glBegin — красный флаг).   -  person Scott Stensland    schedule 18.01.2016


Ответы (2)


Похоже, ваша система слишком жесткая.

  1. Попробуйте использовать методы Eigen SVD: см. http://eigen.tuxfamily.org/dox-2.0/TutorialAdvancedLinearAlgebra.html. Это требует больших вычислительных ресурсов, но, возможно, более безопасно. Если вы решаете задачу aX=b, лучше всего использовать методы, предназначенные для обращения матрицы. (a — ваш якобиан, а X — то, что вы хотите).
  2. Наконец, попробуйте обмануть условность вашей матрицы, умножив диагональ на коэффициент, например 1,0001. Это не сильно изменит решение (особенно для игры), но позволит улучшить решение.
  3. Мне любопытно, почему вы решили не использовать итеративную схему времени, такую ​​как RK4.

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

person Mikhail    schedule 11.04.2012
comment
ваша система слишком жесткая. В системе нет никакой жесткости, нет физической симуляции, всего 4 сустава. Попробуйте использовать методы Eigen SVD. Я мог бы попробовать, но предпочел бы выяснить, что не так с текущим псевдообратным вычислением. элементы как качающиеся пружины. Они не пружины и не должны вести себя как пружины. почему вы решили не использовать итеративную схему времени Потому что нет времени. Мне нужно/хочу узнать окончательную конфигурацию сустава мгновенно, в текущем кадре. - person SigTerm; 12.04.2012
comment
По поводу SVD: насколько я знаю, проблема с Ik в том, что система, скорее всего, имеет бесконечное количество решений или не имеет решения. Я не уверен, что упомянутый вами модуль подходит для такого рода проблем. - person SigTerm; 12.04.2012
comment
# 2 решил проблему в некоторой степени. - person SigTerm; 12.04.2012
comment
SVD дает вам лучшее из бесконечного множества. Я знаю много людей, которые занимаются робототехникой, и они будут использовать SVD именно для той проблемы, которую вы описали, и я знаю по крайней мере одного промышленного робота, который использует SVD. - person Mikhail; 12.04.2012
comment
СВД проверю позже. Однако я был бы признателен за пример кода. - person SigTerm; 12.04.2012
comment
Я попробовал ваше предложение SVD (JacobiSVD в Eigen 3) (jtjangles = jt*diff, решить для углов), похоже, что оно работает немного быстрее и более стабильно, чем якобианская псевдоинверсия. Тем не менее, мне все еще нужно умножить диагональ матрицы на 1,0001 (иначе она будет колебаться), но как только это будет сделано, ИК-решатель может без проблем обрабатывать цепочку из 64..100 звеньев. Для сравнения, якобианская псевдоинверсия требует большего диагонального множителя, чтобы работать даже в цепочке 8..16. Спасибо за предложение, я думаю... - person SigTerm; 12.04.2012

Что-то вроде этого должно работать.

VectorXf solveViaSVD(const MatrixXf & jacobian,const VectorXf & diff) {
     Eigen::JacobiSVD<MatrixXf> svd (jacobian,ComputeThinU | ComputeThinV);
     return svd.solve(diff);
}

Проблема в том, как вы сказали, что ваш метод не может вычислить псевдоинверсию, когда (J*J') единственное число.

Википедия говорит: "[псевдоинверсия] формируется путем замены каждого отличного от нуля вход по диагонали в обратном направлении». Вычисление pinv(A) как inv(A'*A)*A' не дает "ненулевой" точки.

person sbabbi    schedule 12.04.2012
comment
Разве JacobiSVD не нужны квадратные матрицы? - person SigTerm; 12.04.2012
comment
@SigTerm Я протестировал этот код на матрице 2x4, и он отлично работает (за исключением некоторых предупреждений gcc, которые я не совсем понимаю). - person sbabbi; 12.04.2012
comment
Ну, ваш пример работает, но, насколько я могу судить, вам не нужна переменная. Я проголосовал за ваш ответ, однако я уже принял ответ Миши, и вы рекомендуете то же самое, что и он в своей рекомендации № 1. - person SigTerm; 12.04.2012
comment
@SigTerm Сначала я использовал eigen2, а когда перешел на eigen3, просто забыл удалить этот «ответ». Я исправил свой ответ сейчас. - person sbabbi; 12.04.2012