Матрица обратной модели Vuforia дает нежелательные эффекты. Как исправить?

Я использую Vuforia на Android для разработки дополненной реальности. Мы можем получить modelViewMatrix, используя

Matrix44F modelViewMatrix_Vuforia = Tool.convertPose2GLMatrix(trackableResult.getPose());

Это прекрасно работает. Любая геометрия, умноженная на эту матрицу, а затем на матрицу проекции, отображается на экране, как и ожидалось, с (0,0,0) в центре отслеживаемой цели.

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

        Matrix44F inverseMV = SampleMath.Matrix44FInverse(invTranspMV);
        Matrix44F invTranspMV = SampleMath.Matrix44FTranspose(modelViewMatrix_Vuforia);
        modelViewMatrixInverse = invTranspMV.getData();

Это работает очень хорошо, например если я нарисую куб, используя эту матрицу, то, когда я наклоняю свой телефон вверх и вниз, куб также наклоняется вверх и вниз правильно, но когда я поворачиваюсь влево и вправо, возникает проблема. Левый поворот заставляет куб поворачиваться не в ту сторону, как будто я смотрю в правую сторону от него. Аналогично с поворотом направо. Что должно произойти, так это то, что куб должен казаться «прилипшим» к экрану, то есть, как бы я ни повернулся, я всегда должен видеть одно и то же лицо, «прилипшее» к экрану.

Я думаю, что проблема может быть связана с матрицей проекции Vuforia, и я собираюсь создать свою собственную матрицу проекции (используя руководство здесь), чтобы поэкспериментировать с различными настройками. Как это сообщение говорит, что это могло быть связано с внутренней калибровкой камеры конкретного устройства.

Я на правильном пути? Есть идеи, что может быть не так и как я могу это решить?

ОБНОВЛЕНИЕ

Я больше не думаю, что это матрица проекции (из-за экспериментов и комментария к ответу peedee ниже)

Посмотрев этот пост, я думаю, что добился некоторого прогресса. Сейчас я использую следующий код:

        Matrix44F modelViewMatrix_Vuforia = Tool.convertPose2GLMatrix(trackableResult.getPose());
        Matrix44F inverseMV = SampleMath.Matrix44FInverse(modelViewMatrix_Vuforia);
        Matrix44F invTranspMV = SampleMath.Matrix44FTranspose(inverseMV);
        modelViewMatrixInverse = invTranspMV.getData();

        float [] position = {0, 0, 0, 1};
        float [] lookAt = {0, 0, 1, 0};
        float [] cam_position = new float[16];
        float [] cam_lookat = new float[16];

        Matrix.multiplyMV(cam_position, 0, modelViewMatrixInverse, 0, position, 0);
        Matrix.multiplyMV(cam_lookat, 0, modelViewMatrixInverse, 0, lookAt, 0);

        Log.v("QCV", "posx = " + cam_position[0] + ", posy = " + cam_position[1] + ", posz = " + cam_position[2]);
        Log.v("QCV", "latx = " + cam_lookat[0] + ", laty = " + cam_lookat[1] + ", latz = " + cam_lookat[2]);

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

ОБНОВЛЕНИЕ2

Хорошо, некоторый прогресс достигнут. Сейчас я использую следующий код. Он делает то же самое, что и предыдущий блок кода, но использует класс Matrix вместо класса SampleMath.

        float [] temp = new float[16];
        temp = modelViewMatrix_Vuforia.getData();
        Matrix.invertM(modelViewMatrixInverse, 0, temp, 0);

        float [] position = {0, 0, 0, 1};
        float [] lookAt = {0, 0, 1, 0};
        float [] cam_position = new float[16];
        float [] cam_lookat = new float[16];

        Matrix.multiplyMV(cam_position, 0, modelViewMatrixInverse, 0, position, 0);
        Matrix.multiplyMV(cam_lookat, 0, modelViewMatrixInverse, 0, lookAt, 0);

        Log.v("QCV", "posx = " + cam_position[0] / kObjectScale + ", posy = " + cam_position[1] / kObjectScale + ", posz = " + cam_position[2] / kObjectScale);
        Log.v("QCV", "latx = " + cam_lookat[0] + ", laty = " + cam_lookat[1] + ", latz = " + cam_lookat[2]);

Следующий фрагмент кода дает (почти) желаемый результат:

        modelViewMatrix = modelViewMatrix_Vuforia.getData();
        Matrix.translateM(modelViewMatrix, 0, 0, 0, kObjectScale);
        Matrix.scaleM(modelViewMatrix, 0, kObjectScale, kObjectScale, kObjectScale);

        line.setVerts(cam_position[0] / kObjectScale,
               cam_position[1] / kObjectScale,
               cam_position[2] / kObjectScale,
               cam_position[0] / kObjectScale + 0.5f,
               cam_position[1] / kObjectScale + 0.5f,
               cam_position[2] / kObjectScale - 30);

Это определяет линию вдоль отрицательной оси z от вектора положения, равного положению камеры (которое рассчитывается исходя из положения фактического физического устройства). Поскольку вектор нормален, я сместил X / Y, чтобы нормаль можно было визуализировать.

По мере того, как вы перемещаете свое физическое устройство, нормальное состояние перемещается вместе с вами. Большой!

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

Примечание - вы можете спросить, почему я не использую что-то вроде:

        line.setVerts(cam_position[0] / kObjectScale,
               cam_position[1] / kObjectScale,
               cam_position[2] / kObjectScale,
               cam_position[0] / kObjectScale + cam_lookat[0] * 30,
               cam_position[1] / kObjectScale + cam_lookat[1] * 30,
               cam_position[2] / kObjectScale + cam_lookat[2] * 30);

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

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

Я думаю, это правильный путь. Я обновлю еще раз, если это сработает!


person Pixel    schedule 08.05.2015    source источник
comment
также, почему вы транспонируете после инвертирования? Я этого не делаю. а что это за класс SampleMath? Вы сами это писали? Я просто использую Matrix.invertM (...)   -  person peedee    schedule 12.05.2015
comment
Нет SampleMath - это библиотека, которая, похоже, доступна из Vuforia SDK. В сообщениях на форуме Vuforia он довольно часто используется, поэтому я думаю, что он широко используется. Он является строковым, а не основным столбцом. Это обратная функция по какой-то причине дает транспонирование, поэтому мы должны транспонировать ее, чтобы получить правильную матрицу. Просто чтобы увидеть, я ранее также писал свой код с классом Matrix, поэтому мне не нужно использовать SampleMath, но я все равно получаю тот же результат.   -  person Pixel    schedule 12.05.2015
comment
NB Vuforia также предоставляет класс Tool, который также содержит множество полезных функций.   -  person Pixel    schedule 12.05.2015
comment
Я вижу, что вы сделали больше обновлений. Но я не совсем понимаю последнюю часть о настройке вершин линии, почему вы нигде не используете cam_lookat? это линия, по которой луч должен двигаться, верно?   -  person peedee    schedule 12.05.2015
comment
Я обновил свое последнее обновление, чтобы, надеюсь, ответить на ваш комментарий. Имеет ли это смысл? Суть в том, что я пробовал использовать cam_lookat, но это не сработало, что наводит меня на мысль, что мне нужно повернуть линию ...   -  person Pixel    schedule 12.05.2015
comment
вы в конечном итоге решили это? если да, можете ли вы опубликовать ответ?   -  person peedee    schedule 15.05.2015
comment
Я решил это - см. Ниже. Какое облегчение!   -  person Pixel    schedule 15.05.2015


Ответы (2)


Хорошо, это был крепкий орешек, но успех ооочень сладкий!

Важной частью является то, что он использует функцию от SampleMath для вычисления начала линии пересечения от центра физического устройства к цели. Мы комбинируем это с вектором нормали камеры, чтобы получить нужную линию!

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

Это код, который работает. Это не оптимально, так что вы, вероятно, можете немного прибрать его!

        modelViewMatrix44F = Tool.convertPose2GLMatrix(trackableResult.getPose());
        modelViewMatrixInverse44F = SampleMath.Matrix44FInverse(modelViewMatrix44F);
        modelViewMatrixInverseTranspose44F = SampleMath.Matrix44FTranspose(modelViewMatrix44F);

        modelViewMatrix = modelViewMatrix44F.getData();
        Matrix.translateM(modelViewMatrix, 0, 0, 0, kObjectScale);
        Matrix.scaleM(modelViewMatrix, 0, kObjectScale, kObjectScale, kObjectScale);
        modelViewMatrix44F.setData(modelViewMatrix);

        projectionMatrix44F = vuforiaAppSession.getProjectionMatrix();
        projectionMatrixInverse44F = SampleMath.Matrix44FInverse(projectionMatrix44F);
        projectionMatrixInverseTranspose44F = SampleMath.Matrix44FTranspose(projectionMatrixInverse44F);

        // work out camera position and direction
        modelViewMatrixInverse = modelViewMatrixInverseTranspose44F.getData();

        position = new float [] {0, 0, 0, 1};               // camera position
        lookAt = new float [] {0, 0, 1, 0};                 // camera direction
        float [] rotate = new float [] {(float) Math.cos(angle_degrees * 0.017453292f), (float) Math.sin(angle_degrees * 0.017453292f), 0, 0};

        angle_degrees += 10;

        if(angle_degrees > 359)
            angle_degrees = 0;

        float [] cam_position = new float[16];
        float [] cam_lookat = new float[16];
        float [] cam_rotate = new float[16];

        Matrix.multiplyMV(cam_position, 0, modelViewMatrixInverse, 0, position, 0);
        Matrix.multiplyMV(cam_lookat, 0, modelViewMatrixInverse, 0, lookAt, 0);
        Matrix.multiplyMV(cam_rotate, 0, modelViewMatrixInverse, 0, rotate, 0);

        Vec3F line_start = SampleMath.getPointToPlaneLineStart(projectionMatrixInverse44F, modelViewMatrix44F, 2*kObjectScale, 2*kObjectScale, new Vec2F(0, 0), new Vec3F(0, 0, 0), new Vec3F(0, 0, 1));

        float x1 = line_start.getData()[0];
        float y1 = line_start.getData()[1];
        float z1 = line_start.getData()[2];
        float x2 = x1 + cam_lookat[0] * 3 + cam_rotate[0] * 0.1f;
        float y2 = y1 + cam_lookat[1] * 3 + cam_rotate[1] * 0.1f;
        float z2 = z1 + cam_lookat[2] * 3 + cam_rotate[2] * 0.1f;

        line.setVerts(x1, y1, z1, x2, y2, z2);

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

А сегодня пятница, так что я могу пойти в паб позже отпраздновать :-)

ОБНОВЛЕНИЕ

Фактически, метод getPointToPlaneLineStart Java SampleMath вызывает следующий код (C ++), поэтому вы, вероятно, сможете расшифровать математические вычисления из него, если не хотите использовать класс SampleMath (cf этот пост)

SampleMath::projectScreenPointToPlane(QCAR::Matrix44F inverseProjMatrix, QCAR::Matrix44F modelViewMatrix,
                      float contentScalingFactor, float screenWidth, float screenHeight,
                      QCAR::Vec2F point, QCAR::Vec3F planeCenter, QCAR::Vec3F planeNormal,
                      QCAR::Vec3F &intersection, QCAR::Vec3F &lineStart, QCAR::Vec3F &lineEnd)
{
    // Window Coordinates to Normalized Device Coordinates
QCAR::VideoBackgroundConfig config = QCAR::Renderer::getInstance().getVideoBackgroundConfig();

    float halfScreenWidth = screenHeight / 2.0f;
    float halfScreenHeight = screenWidth / 2.0f;

    float halfViewportWidth = config.mSize.data[0] / 2.0f;
    float halfViewportHeight = config.mSize.data[1] / 2.0f;

    float x = (contentScalingFactor * point.data[0] - halfScreenWidth) / halfViewportWidth;
    float y = (contentScalingFactor * point.data[1] - halfScreenHeight) / halfViewportHeight * -1;

    QCAR::Vec4F ndcNear(x, y, -1, 1);
    QCAR::Vec4F ndcFar(x, y, 1, 1);

    // Normalized Device Coordinates to Eye Coordinates
    QCAR::Vec4F pointOnNearPlane = Vec4FTransform(ndcNear, inverseProjMatrix);
    QCAR::Vec4F pointOnFarPlane = Vec4FTransform(ndcFar, inverseProjMatrix);
    pointOnNearPlane = Vec4FDiv(pointOnNearPlane, pointOnNearPlane.data[3]);
    pointOnFarPlane = Vec4FDiv(pointOnFarPlane, pointOnFarPlane.data[3]);

    // Eye Coordinates to Object Coordinates
    QCAR::Matrix44F inverseModelViewMatrix = Matrix44FInverse(modelViewMatrix);

    QCAR::Vec4F nearWorld = Vec4FTransform(pointOnNearPlane, inverseModelViewMatrix);
    QCAR::Vec4F farWorld = Vec4FTransform(pointOnFarPlane, inverseModelViewMatrix);

    lineStart = QCAR::Vec3F(nearWorld.data[0], nearWorld.data[1], nearWorld.data[2]);
    lineEnd = QCAR::Vec3F(farWorld.data[0], farWorld.data[1], farWorld.data[2]);
    linePlaneIntersection(lineStart, lineEnd, planeCenter, planeNormal, intersection);
}
person Pixel    schedule 15.05.2015

Я ни в коем случае не эксперт, но мне кажется, что этой инверсии влево / вправо следовало ожидать. На мой взгляд, объект в мировом пространстве смотрит в направлении положительной оси z к камере, а пространство камеры смотрит в направлении отрицательной оси z, обращенной к камере. Такое преобразование системы координат должно инвертировать одну из осей x / y, чтобы система координат оставалась согласованной.

ELI5: Когда вы стоите перед кем-то и говорите им: «на счет 3 мы оба шагаем влево», вы больше не будете стоять друг перед другом после этого.

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

К сожалению, я также не знаю, как решить эту проблему прямо сейчас, но то, что я сказал, было слишком длинным для комментария. Извиняюсь :-(

person peedee    schedule 11.05.2015
comment
Я думаю, что вы правы насчет матрицы проекции. Первоначально у меня было подозрение, что Vuforia комбинирует матрицы View и Projection, но проверив этот пост (developer.vuforia.com/forum/faq/), теперь я в этом не уверен. - person Pixel; 11.05.2015
comment
Если задуматься, modelViewMatrix на самом деле представляет собой объединенные матрицы Model и View. Все дело в названии! - Дох. То есть modelViewMatrix будет принимать координаты объекта непосредственно из пространства объекта в пространство камеры, полностью избегая мирового пространства. Это осознание должно помочь мне немного лучше думать о вещах. - person Pixel; 11.05.2015
comment
Недавно я написал здесь кое-что о матрицах Vuforia, что также может помочь вам понять: stackoverflow.com/a/30095620/1446479 - person peedee; 12.05.2015
comment
Спасибо. Я добавил ответ на другой ваш пост, который тоже может помочь. - person Pixel; 12.05.2015