如何将2D点反转投影到3D?

时间:2008-09-16 19:42:33

标签: 3d geometry 2d photogrammetry reverseprojection

我在屏幕空间中有4个2D点,​​我需要将它们反向投影回3D空间。我知道4个点中的每一个都是3D旋转刚性矩形的一个角,我知道矩形的大小。如何从中获取3D坐标?

我没有使用任何特定的API,也没有现有的投影矩阵。我只是在寻找基本的数学来做到这一点。当然没有足够的数据将单个2D点转换为3D而没有其他参考,但我想如果你有4个点,你知道它们在同一个平面上都是直角相交的,而且你知道它们之间的距离,你应该能够从中找出它。不幸的是,我无法理解如何。

这可能属于摄影测量的范畴,但谷歌搜索这些并没有让我获得任何有用的信息。

14 个答案:

答案 0 :(得分:62)

好吧,我来到这里寻找答案并没有找到简单明了的事情,所以我继续前进并做了一件愚蠢但有效(而且相对简单)的事情:Monte Carlo优化。

非常简单地说,算法如下:随机扰动投影矩阵,直到它将已知的3D坐标投影到已知的2D坐标。

这是Thomas the Tank Engine的静态照片:

Thomas the Tank Engine

假设我们使用GIMP找到我们认为地平面上的正方形的二维坐标(它是否真的是一个正方形取决于您对深度的判断):

With an outline of the square

我在2D图像中获得了四个点:(318, 247)(326, 312)(418, 241)(452, 303)

按照惯例,我们说这些点应对应于3D点:(0, 0, 0)(0, 0, 1)(1, 0, 0)(1, 0, 1)。换句话说,y = 0平面中的单位平方。

将这些3D坐标中的每一个投影到2D中是通过将4D矢量[x, y, z, 1]与4x4投影矩阵相乘,然后将x和y分量除以z来实际获得透视校正来完成的。这或多或少是gluProject()所做的,除了gluProject()还考虑当前视口并考虑单独的模型视图矩阵(我们可以假设模型视图矩阵是单位矩阵)。查看gluProject()文档是非常方便的,因为我实际上想要一个适用于OpenGL的解决方案,但要注意文档中缺少z中的除法。

请记住,算法是从一些投影矩阵开始并随机扰动它直到它给出我们想要的投影。所以我们要做的是投影四个3D点中的每一个,看看我们到达我们想要的2D点的距离。如果我们的随机扰动导致投影的2D点更接近我们上面标记的那些,那么我们将该矩阵保持为对我们的初始(或之前)猜测的改进。

让我们来定义我们的观点:

# Known 2D coordinates of our rectangle
i0 = Point2(318, 247)
i1 = Point2(326, 312)
i2 = Point2(418, 241)
i3 = Point2(452, 303)

# 3D coordinates corresponding to i0, i1, i2, i3
r0 = Point3(0, 0, 0)
r1 = Point3(0, 0, 1)
r2 = Point3(1, 0, 0)
r3 = Point3(1, 0, 1)

我们需要从一些矩阵开始,单位矩阵似乎是一个自然的选择:

mat = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
]

我们需要实际实现投影(基本上是矩阵乘法):

def project(p, mat):
    x = mat[0][0] * p.x + mat[0][1] * p.y + mat[0][2] * p.z + mat[0][3] * 1
    y = mat[1][0] * p.x + mat[1][1] * p.y + mat[1][2] * p.z + mat[1][3] * 1
    w = mat[3][0] * p.x + mat[3][1] * p.y + mat[3][2] * p.z + mat[3][3] * 1
    return Point(720 * (x / w + 1) / 2., 576 - 576 * (y / w + 1) / 2.)

这基本上是gluProject()所做的,720和576分别是图像的宽度和高度(即视口),我们从576中减去以计算我们从中计算y坐标的事实虽然OpenGL通常从底部开始计算。您会注意到我们没有计算z,因为我们在这里并不需要它(尽管它可以很方便地确保它落在OpenGL用于深度缓冲区的范围内)。

现在我们需要一个函数来评估我们与正确解决方案的接近程度。此函数返回的值是我们将用于检查一个矩阵是否优于另一个矩阵的值。我选择了平方距离的总和,即:

# The squared distance between two points a and b
def norm2(a, b):
    dx = b.x - a.x
    dy = b.y - a.y
    return dx * dx + dy * dy

def evaluate(mat): 
    c0 = project(r0, mat)
    c1 = project(r1, mat)
    c2 = project(r2, mat)
    c3 = project(r3, mat)
    return norm2(i0, c0) + norm2(i1, c1) + norm2(i2, c2) + norm2(i3, c3)

为了扰乱矩阵,我们只需在一定范围内选择一个随机量扰动的元素:

def perturb(amount):
    from copy import deepcopy
    from random import randrange, uniform
    mat2 = deepcopy(mat)
    mat2[randrange(4)][randrange(4)] += uniform(-amount, amount)

(值得注意的是,我们的project()函数实际上根本不使用mat[2],因为我们不计算z,因为我们所有的y坐标都是0 mat[*][1]价值也是无关紧要的。我们可以利用这个事实,从不试图扰乱这些价值,这会带来一个小的加速,但这只是一个练习......)

为方便起见,让我们一遍又一遍地调用perturb()来添加一个能够完成大部分近似的函数:到目前为止我们发现的最佳矩阵是什么:

def approximate(mat, amount, n=100000):
    est = evaluate(mat)

    for i in xrange(n):
        mat2 = perturb(mat, amount)
        est2 = evaluate(mat2)
        if est2 < est:
            mat = mat2
            est = est2

    return mat, est

现在剩下要做的就是运行它......:

for i in xrange(100):
    mat = approximate(mat, 1)
    mat = approximate(mat, .1)

我发现这已经给出了一个非常准确的答案。运行一段时间后,我找到的矩阵是:

[
    [1.0836000765696232,  0,  0.16272110011060575, -0.44811064935115597],
    [0.09339193527789781, 1, -0.7990570384334473,   0.539087345090207  ],
    [0,                   0,  1,                    0                  ],
    [0.06700844759602216, 0, -0.8333379578853196,   3.875290562060915  ],
]

错误大约为2.6e-5。 (请注意我们所说的元素在计算中没有被使用的实际上并没有从我们的初始矩阵中改变;这是因为更改这些条目不会改变评估的结果,因此改变永远不会被带进。)

我们可以使用glLoadMatrix()将矩阵传递给OpenGL(但请记住首先转置它,并记住使用单位矩阵加载模型视图矩阵):

def transpose(m):
    return [
        [m[0][0], m[1][0], m[2][0], m[3][0]],
        [m[0][1], m[1][1], m[2][1], m[3][1]],
        [m[0][2], m[1][2], m[2][2], m[3][2]],
        [m[0][3], m[1][3], m[2][3], m[3][3]],
    ]

glLoadMatrixf(transpose(mat))

现在我们可以例如沿着z轴平移以沿着轨道获得不同的位置:

glTranslate(0, 0, frame)
frame = frame + 1

glBegin(GL_QUADS)
glVertex3f(0, 0, 0)
glVertex3f(0, 0, 1)
glVertex3f(1, 0, 1)
glVertex3f(1, 0, 0)
glEnd()

With 3D translation

从数学的角度来看,这不是很优雅;你没有得到一个封闭形式的等式,你只需将你的数字插入并得到一个直接(和准确)的答案。但是,它确实允许您添加其他约束,而不必担心使方程复杂化;例如,如果我们想要合并高度,我们可以使用房子的那个角,并说(在我们的评估函数中)从地面到屋顶的距离应该是某某,并再次运行算法。所以,是的,这是一种蛮力,但有效,并且运作良好。

Choo choo!

答案 1 :(得分:7)

这是基于标记的增强现实的经典问题。

您有一个方形标记(2D条形码),并且您想要在找到标记的四个边缘后找到其姿势(相对于相机的平移和旋转)。 Overview-Picture

我不知道对该领域的最新贡献,但至少在某一点上(2009年)RPP应该胜过上面提到的POSIT(并且确实是一种经典的方法) 请看链接,他们也提供来源。

(PS - 我知道这有点老话题,但无论如何,帖子可能对某人有帮助)

答案 2 :(得分:5)

d。 DeMenthon 设计了一种算法,当知道对象的模型时,从2D图像中的特征点计算对象的姿势(它在空间中的位置和方向) - this是你的确切问题

  

我们描述了一种从单个图像中查找对象姿势的方法。我们假设我们可以在图像中检测并匹配对象的四个或更多非共面特征点,并且我们知道它们在对象上的相对几何形状。

该算法被称为 Posit ,并在其经典文章“25行代码中基于模型的对象姿势”(可在its website上获得,第4节)中进行了描述。

直接链接到文章:http://www.cfar.umd.edu/~daniel/daniel_papersfordownload/Pose25Lines.pdf OpenCV实施:http://opencv.willowgarage.com/wiki/Posit

这个想法是通过缩放的正投影重复近似透视投影,直到收敛到准确的姿势。

答案 3 :(得分:4)

从2-D空间可以建立2个有效的矩形。在不知道原始矩阵投影的情况下,您将不知道哪一个是正确的。它与“盒子”问题相同:你看到两个正方形,一个在另一个里面,4个内部顶点连接到4个相应的外部顶点。你是从上到下还是自下而上看盒子?

话虽如此,你正在寻找一个矩阵变换T,其中......

{{x1,y1,z1},{x2,y2,z2},{x3,y3,z3},{x4,y4,z4}} x T = {{x1,y1},{x2,y2 },{x3,y3},{x4,y4}}

(4×3)×T =(4×2)

因此T必须是(3 x 2)矩阵。所以我们有6个未知数。

现在在T上建立一个约束系统并用Simplex解决。要构建约束,您知道通过前两个点的直线必须与传递到后两个点的直线平行。你知道通过点1和3的直线必须与通过点2和4的直线平行。你知道通过1和2的直线必须与通过点2和3的直线正交。你知道长度1和2的行必须等于3和4行的长度。你知道从1和3开始的行的长度必须等于2和4行的长度。

为了使这更容易,你知道矩形,所以你知道所有边的长度。

这应该会给你很多限制来解决这个问题。

当然,要回来,你可以找到T-inverse。

@Rob:是的,有无数个投影,但没有无数个项目,其中点必须满足矩形的要求。

@nlucaroni:是的,如果投影中有四个点,这只能解决。如果矩形投影到仅2个点(即矩形的平面与投影表面正交),则无法解决。

嗯......我应该回家写这个小宝石。这听起来很有趣。

更新

  1. 除非您修复其中一个点,否则会有无数个投影。如果你修改原始矩形的点,那么有两个可能的原始矩形。

答案 4 :(得分:4)

对于我的OpenGL引擎,以下片段会将鼠标/屏幕坐标转换为3D世界坐标。阅读提交内容,了解实际情况。

/*   FUNCTION:        YCamera :: CalculateWorldCoordinates
     ARGUMENTS:       x         mouse x coordinate
                      y         mouse y coordinate
                      vec       where to store coordinates
     RETURN:          n/a
     DESCRIPTION:     Convert mouse coordinates into world coordinates
*/

void YCamera :: CalculateWorldCoordinates(float x, float y, YVector3 *vec) { // START GLint viewport[4]; GLdouble mvmatrix[16], projmatrix[16];

GLint real_y;
GLdouble mx, my, mz;

glGetIntegerv(GL_VIEWPORT, viewport);
glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);
glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);

real_y = viewport[3] - (GLint) y - 1;   // viewport[3] is height of window in pixels
gluUnProject((GLdouble) x, (GLdouble) real_y, 1.0, mvmatrix, projmatrix, viewport, &mx, &my, &mz);

/*  'mouse' is the point where mouse projection reaches FAR_PLANE.
    World coordinates is intersection of line(camera->mouse) with plane(z=0) (see LaMothe 306)

    Equation of line in 3D:
        (x-x0)/a = (y-y0)/b = (z-z0)/c      

    Intersection of line with plane:
        z = 0
        x-x0 = a(z-z0)/c  <=> x = x0+a(0-z0)/c  <=> x = x0 -a*z0/c
        y = y0 - b*z0/c

*/
double lx = fPosition.x - mx;
double ly = fPosition.y - my;
double lz = fPosition.z - mz;
double sum = lx*lx + ly*ly + lz*lz;
double normal = sqrt(sum);
double z0_c = fPosition.z / (lz/normal);

vec->x = (float) (fPosition.x - (lx/normal)*z0_c);
vec->y = (float) (fPosition.y - (ly/normal)*z0_c);
vec->z = 0.0f;

GLint real_y; GLdouble mx, my, mz; glGetIntegerv(GL_VIEWPORT, viewport); glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix); glGetDoublev(GL_PROJECTION_MATRIX, projmatrix); real_y = viewport[3] - (GLint) y - 1; // viewport[3] is height of window in pixels gluUnProject((GLdouble) x, (GLdouble) real_y, 1.0, mvmatrix, projmatrix, viewport, &mx, &my, &mz); /* 'mouse' is the point where mouse projection reaches FAR_PLANE. World coordinates is intersection of line(camera->mouse) with plane(z=0) (see LaMothe 306) Equation of line in 3D: (x-x0)/a = (y-y0)/b = (z-z0)/c Intersection of line with plane: z = 0 x-x0 = a(z-z0)/c <=> x = x0+a(0-z0)/c <=> x = x0 -a*z0/c y = y0 - b*z0/c */ double lx = fPosition.x - mx; double ly = fPosition.y - my; double lz = fPosition.z - mz; double sum = lx*lx + ly*ly + lz*lz; double normal = sqrt(sum); double z0_c = fPosition.z / (lz/normal); vec->x = (float) (fPosition.x - (lx/normal)*z0_c); vec->y = (float) (fPosition.y - (ly/normal)*z0_c); vec->z = 0.0f;

答案 5 :(得分:2)

假设这些点确实是矩形的一部分,我给出了一个通用的想法:

找到具有最大距离的两个点:这些最可能定义一个对角线(例外:矩形几乎与YZ平面平行的特殊情况,留给学生)。称它们为A,C。计算BAD,BCD角度。与直角相比,这些可以让您在3d空间中进行定位。要了解z距离,您需要将投影边与已知边相关联,然后根据三维投影方法(是1 / z?),您可以在正确的轨道上了解距离。

答案 6 :(得分:2)

跟进Rons方法:如果你知道如何旋转矩形,你可以找到你的z值。

诀窍是找到进行投影的投影矩阵。幸运的是,这是可能的,甚至便宜。相关的数学可以在Paul Heckbert撰写的“图像变形的投影映射”一文中找到。

http://pages.cs.wisc.edu/~dyer/cs766/readings/heckbert-proj.pdf

通过这种方式,您可以恢复投影期间丢失的每个顶点的同质部分。

现在你还剩下四条线而不是点(正如罗恩解释的那样)。既然您知道原始矩形的大小,那么什么都不会丢失。您现在可以将Ron的方法和2D方法中的数据插入线性方程求解器并求解z。您可以通过这种方式获得每个顶点的精确z值。

注意:这只是因为:

  1. 原始形状为矩形
  2. 您知道3D空间中矩形的确切大小。
  3. 这是一个特例。

    希望它有所帮助,   尼尔斯

答案 7 :(得分:1)

如果没有人回答,我回家时会拿出线性代数书。但是@ D G,并非所有矩阵都是可逆的。 Singular matrices aren't invertible(当行列式= 0时)。这实际上会一直发生,因为投影矩阵必须具有0和1的特征值,并且是正方形(因为它是幂等的,所以p ^ 2 = p)。

一个简单的例子是,[[0 1] [0 1]]因为行列式= 0,那就是x = y上的投影!

答案 8 :(得分:1)

您在2D表面上的投影具有无限多的3D矩形,这些矩形将投射到相同的2D形状。

以这种方式思考:您有四个构成3D矩形的3D点。称它们为(x0,y0,z0),(x1,y1,z1),(x2,y2,z2)和(x3,y3,z3)。将这些点投影到x-y平面上时,可以删除z坐标:(x0,y0),(x1,y1),(x2,y2),(x3,y3)。

现在,您想要投射回3D空间,需要对z0,..,z3进行逆向工程。但是任何一组z坐标a)在点之间保持相同的x-y距离,并且b)保持形状为矩形将起作用。所以,这个(无限)集的任何成员都会这样做:{(z0 + i,z1 + i,z2 + i,z3 + i)|我&lt; - R}。

编辑@Jarrett:想象一下,你解决了这个问题,结果在3D空间中出现了一个矩形。现在,想象一下在z轴上上下滑动该矩形。那些无限量的平移矩形都具有相同的x-y投影。你怎么知道你找到了“正确的”?

编辑#2:好的,这是我对这个问题的评论 - 一个更直观的推理方法。

想象一下,在你桌子上方拿一张纸。假装纸张的每个角落都有一个无重力的激光指示器,它指向桌面。纸张是3D物体,桌面上的激光指示点是2D投影。

现在,你怎么能通过看只是激光指针点来判断纸张的高度?

你做不到。将纸张垂直向上和向下移动。无论纸张的高度如何,激光指示器仍会照在桌面上的相同位置。

在反投影中找到z坐标就像试图根据桌面上的激光指针点找到纸张的高度。

答案 9 :(得分:1)

当您从3D投影到2D时,您将丢失信息。

在单个点的简单情况下,反投影将为您提供通过3d空间的无限光线。

立体重建通常从两个2d图像开始,然后投射回3D。然后寻找产生的两条3D射线的交叉点。

投影可以采取不同的形式。正交或透视。我猜你正在假设正交投影?

在您的情况下假设您拥有原始矩阵,您将在3D空间中拥有4条光线。然后,您可以通过3d矩形尺寸约束问题并尝试解决。

解决方案不是唯一的,因为围绕任一轴的平行于2d投影平面的旋转在方向上将是模糊的。换句话说,如果2d图像垂直于z轴,则围绕x轴顺时针或逆时针旋转3d矩形将产生相同的图像。同样适用于y轴。

在矩形平面与z轴平行的情况下,您可以获得更多解决方案。

由于您没有原始投影矩阵,因此任何投影中存在的任意比例因子都会引入模糊性。您无法区分投影中的缩放和z轴方向上的平移。如果您只关心3d空间中4个点的相对位置(彼此相关而不是2d投影的平面),则这不是问题。

在透视投影中事情变得更难......

答案 10 :(得分:1)

如果您知道形状是平面中的矩形,则可以进一步限制问题。您当然无法弄清楚“哪个”平面,因此您可以选择它位于z = 0且其中一个角位于x = y = 0的平面上,并且边缘平行于x / y轴。

因此,3d中的点为{0,0,0},{w,0,0},{w,h,0}和{0,h,0}。我很确定无法找到绝对大小,所以只有w / h的比例是重要的,所以这是一个未知的。

相对于这个平面,摄像机必须位于空间中的某个点cx,cy,cz,必须指向nx,ny,nz方向(长度为1的向量,因此其中一个是多余的),并且有一个wr的focal_length / image_width因子。这些数字变成3x3投影矩阵。

总共有7个未知数:w / h,cx,cy,cz,nx,ny和w。

你总共有8个知识:4 x + y对。

所以这可以解决。

下一步是使用Matlab或Mathmatica。

答案 11 :(得分:1)

答案 12 :(得分:1)

感谢@Vegard提供了出色的答案。我稍微清理了一下代码:

$products = 10;

.1的近似调用对我不起作用,所以我把它拿出来了。我跑了一会儿,最后我检查了它是在

import pandas as pd
import numpy as np

class Point2:
    def __init__(self,x,y):
        self.x = x
        self.y = y

class Point3:
    def __init__(self,x,y,z):
        self.x = x
        self.y = y
        self.z = z

# Known 2D coordinates of our rectangle
i0 = Point2(318, 247)
i1 = Point2(326, 312)
i2 = Point2(418, 241)
i3 = Point2(452, 303)

# 3D coordinates corresponding to i0, i1, i2, i3
r0 = Point3(0, 0, 0)
r1 = Point3(0, 0, 1)
r2 = Point3(1, 0, 0)
r3 = Point3(1, 0, 1)

mat = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
]

def project(p, mat):
    #print mat
    x = mat[0][0] * p.x + mat[0][1] * p.y + mat[0][2] * p.z + mat[0][3] * 1
    y = mat[1][0] * p.x + mat[1][1] * p.y + mat[1][2] * p.z + mat[1][3] * 1
    w = mat[3][0] * p.x + mat[3][1] * p.y + mat[3][2] * p.z + mat[3][3] * 1
    return Point2(720 * (x / w + 1) / 2., 576 - 576 * (y / w + 1) / 2.)

# The squared distance between two points a and b
def norm2(a, b):
    dx = b.x - a.x
    dy = b.y - a.y
    return dx * dx + dy * dy

def evaluate(mat): 
    c0 = project(r0, mat)
    c1 = project(r1, mat)
    c2 = project(r2, mat)
    c3 = project(r3, mat)
    return norm2(i0, c0) + norm2(i1, c1) + norm2(i2, c2) + norm2(i3, c3)    

def perturb(mat, amount):
    from copy import deepcopy
    from random import randrange, uniform
    mat2 = deepcopy(mat)
    mat2[randrange(4)][randrange(4)] += uniform(-amount, amount)
    return mat2

def approximate(mat, amount, n=1000):
    est = evaluate(mat)
    for i in xrange(n):
        mat2 = perturb(mat, amount)
        est2 = evaluate(mat2)
        if est2 < est:
            mat = mat2
            est = est2

    return mat, est

for i in xrange(1000):
    mat,est = approximate(mat, 1)
    print mat
    print est

错误大约0.02。

答案 13 :(得分:1)

是的,蒙特卡洛有效,但我找到了更好的解决方案。此代码完美运行(并使用OpenCV):

select

此功能采用已知的3d和2d点,屏幕大小并返回旋转(rvecs [0]),平移(tvecs [0])和相机的内在值矩阵。这就是你需要的一切。