Vuforia逆模型矩阵产生不希望的效果。如何解决?

时间:2015-05-08 12:05:57

标签: augmented-reality vuforia

我在Android上使用Vuforia进行AR开发。我们可以使用

获取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投影矩阵有关,我将创建自己的投影矩阵(使用指南here)来试验不同的设置。正如this帖子所说,它可能与特定设备的内在相机校准有关。

我是否在正确的轨道上?任何可能出错的想法以及我如何解决这个问题?

更新

我认为它不再是投影矩阵(由于实验和皮尔迪的答案评论如下)

看了this帖后,我觉得我已经取得了一些进展。我现在使用以下代码:

        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]);

当您将相机移动到目标位置时,这会成功返回相机位置和相机的法线。我想我应该可以用它来按照我想要的方式投影几何体。如果有效,将在以后更新。

UPDATE2

好的,取得了一些进展。我现在使用以下代码。它与前一个代码块完全相同,但使用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单位矢量计算的角度调整世界空间中线的位置。这些可以用来更新线的顶点,这样无论你定向手机的方向,法线总是出现在相机的中心。

我认为这是正确的方法。如果有效,我会再次更新!

2 个答案:

答案 0 :(得分: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 this post

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);
}

答案 1 :(得分:1)

我绝不是专家,但对我来说这应该是预期的左/右反转。在我看来,世界空间中的物体朝向相机的正z轴方向看,而相机空间朝向面向相机的负z轴方向。坐标系的这种变换必然会反转其中一个x / y轴,以保持坐标系的一致性。

ELI5:当你站在某人面前告诉他们“在3点钟我们都向左走”时,你不会再站在对方面前了。

我认为如你所说,投影矩阵不太可能出现问题。投影矩阵仅将3d对象转换为2D屏幕。相机内在函数听起来也不适合我。该矩阵将纠正由相机镜头形状和位置引起的小扭曲,没有像左/右反转那样剧烈。

不幸的是,我现在也不知道如何解决它,但我不得不说的是评论太久了。对不起: - (