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


68

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

我没有使用任何特定的API,并且没有现有的投影矩阵。我只是在寻找基本的数学来做到这一点。当然,没有足够的数据将单个2D点转换为没有其他参考的3D,但是我想像一下,如果您有4个点,您会知道它们在同一平面上彼此成直角,而且您知道它们之间的距离,那么您应该能够从那里弄清楚。不幸的是我无法完全解决。

这可能属于摄影测量法的范围,但是google搜索并没有带我任何有用的信息。


如果我没记错的话,您可以为3D坐标提供两种选择。
Omer van Kloeten

而且这个问题(目前尚不清楚)暗示了视角(因此z距离越大,投影越小)
tzot

您好约书亚,我也一直在努力。您找到满意的解决方案了吗?谢谢,T
Theo.T

还没有西奥。我正在从事的项目已被搁置一旁,但最终我需要弄清楚这一点。我正在考虑开始赏金。
约书亚·卡莫迪

嗨,约书亚,还好吗?如果您愿意的话,我非常想看看解决该问题的代码!:D
CodeAndCats

Answers:


80

好吧,我来这里是为了寻找答案,却没有找到简单明了的东西,所以我继续做愚蠢但有效(相对简单)的事情:蒙特卡洛优化。

简而言之,算法如下:随机扰动投影矩阵,直到将已知的3D坐标投影到已知的2D坐标为止。

这是Thomas坦克引擎的静态照片:

托马斯坦克引擎

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

带有正方形的轮廓

我2D图像中得到4分:(318, 247)(326, 312)(418, 241),和(452, 303)

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

通过将4D向量[x, y, z, 1]与4x4投影矩阵相乘,然后将x和y分量除以z来实际获得透视校正,即可将这些3D坐标中的每一个投影到2D中。这差不多是gluProject()所做的,除了gluProject()还考虑了当前视口并考虑了单独的modelview矩阵(我们可以假设modelview矩阵是恒等矩阵)。查看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,这是因为我们在这里确实不需要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。(请注意,我们所说的未在计算中使用的元素实际上并未从初始矩阵中更改;这是因为更改这些条目不会更改评估结果,因此更改永远不会进行。)

我们可以使用将该矩阵传递到OpenGL中glLoadMatrix()(但请记住先对其进行转置,并记住使用恒等矩阵加载您的modelview矩阵):

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()

带3D翻译

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

cho!


6
令人印象深刻的视觉效果!
Morgan Wilde

10
只是为会发现此问题/答案的人提供的便条。在计算机视觉中,此问题称为透视n点(PnP)。计算机视觉库(OpenCVMatlab等)可能具有此功能。使用随机方法(蒙特卡洛)可能有效,但是在文献中已经存在一些解决此类问题的方法。
Catree

9

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

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

我尚不清楚该领域的最新贡献,但至少在一定程度上(2009年)RPP的表现要优于上面提到的POSIT(这确实是一种经典方法),请参见链接,它们也提供资源。

(附言:我知道这是一个有点老的话题,但是无论如何,该帖子可能会对某人有所帮助)


感谢您的回答!这可能是一个古老的问题,但从未得到令人满意的回答。我将看看这些链接。
约书亚·卡莫迪

5

D. DeMenthon设计了一种算法,可以在知道对象模型时根据2D图像中的特征点计算对象的姿态(其在空间中的位置和方向)-这是您的确切问题

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

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

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

这个想法是用缩放的正投影法反复逼近透视投影,直到收敛到准确的姿势为止。


5

对于我的OpenGL引擎,以下片段将鼠标/屏幕坐标转换为3D世界坐标。阅读评论,以了解发生了什么。

/ *功能:YCamera :: CalculateWorldCoordinates
     参数:x鼠标x坐标
                      y鼠标y坐标
                      VEC在哪里存储坐标
     返回:不适用
     描述:将鼠标坐标转换为世界坐标
* /
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;
}

4

在二维空间中,将可以构建2个有效的矩形。不知道原始矩阵投影,就不会知道哪一个是正确的。与“盒子”问题相同:您看到两个正方形,一个正方形在另一个正方形内,其中四个内部顶点分别连接到四个外部顶点。您是从上到下还是自下而上查看一个框?

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

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

(4 x 3)x T =(4 x 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逆。

@Rob:是的,有无限数量的投影,但是没有无限数量的项目,这些点必须满足矩形的要求。

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

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

更新:

  1. 除非您固定其中一个点,否则投影的数量是无限的。如果固定原始矩形的点,则有两个可能的原始矩形。

@“嗯...我应该回家写这个小宝石。听起来很有趣。” 如果您愿意,我将不胜感激!:-)
Joshua Carmody

我希望有人解释一下给定的矩形(例如票证)如何使眼睛稳定并在票证与票证之间放置一块透明玻璃,该矩形可以有无限的位置,而其角投影到玻璃板上的相同4个点。请。
tzot

您听起来像杰瑞特(Jarrett)如此亲密!我不认为您真的相信会有无限的预测。我的意思是,如果您在前面举起一张纸,则3d空间中只有一个位置和方向,您可以握住它,使其看起来完全一样(忽略翻转/旋转纸张)。此外,如果您闭上眼睛,而我在您面前举起了一张纸,当您睁开眼睛时,只需将世界视为2D图像并知道纸张的大小。因此,数学当然可以做到吗?
CodeAndCats

为了澄清,通过“忽略翻转/旋转纸张”,我的意思是忽略将矩形纸张旋转180度的重复情况。
CodeAndCats

2

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

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


2

遵循Rons方法:如果知道如何旋转矩形,则可以找到z值。

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

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

这样,您可以恢复投影过程中丢失的每个顶点的同质部分。

现在您仍然剩下四行而不是点(如罗恩所解释)。由于您知道原始矩形的大小,因此不会丢失任何内容。现在,您可以将Ron方法和2D方法中的数据插入线性方程式求解器,并求解z。您可以通过这种方式获得每个顶点的确切z值。

注意:这样做是因为:

  1. 原始形状是矩形
  2. 您知道3D空间中矩形的确切大小。

真的是特例。

希望能有所帮助,尼尔斯


2

感谢@Vegard的出色回答。我整理了一下代码:

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

用.1进行的近似通话对我不起作用,因此我将其取出。我也跑了一段时间,最后我检查了一下

[[0.7576315397559887, 0, 0.11439449272592839, -0.314856490473439], 
[0.06440497208710227, 1, -0.5607502645413118, 0.38338196981556827], 
[0, 0, 1, 0], 
[0.05421620936883742, 0, -0.5673977598434641, 2.693116299312736]]

误差约为0.02。


1

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

这样考虑:您有四个3D点组成3D矩形。称它们为(x0,y0,z0),(x1,y1,z1),(x2,y2,z2)和(x3,y3,z3)。将这些点投影到xy平面上时,将放下z坐标:(x0,y0),(x1,y1),(x2,y2),(x3,y3)。

现在,要投影回3D空间,需要对z0,..,z3进行反向工程。但是,任何a组z坐标(a)在点之间保持相同的xy距离,b)保持矩形的形状都可以。因此,该(无限)集合的任何成员都将做:{(z0 + i,z1 + i,z2 + i,z3 + i)| 我<-R}。

编辑@Jarrett:假设您解决了这个问题,最后在3D空间中得到一个矩形。现在,想象一下将矩形沿z轴上下滑动。那些无限数量的平移矩形都具有相同的xy投影。您怎么知道您找到了“正确”的?

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

想象一下,在办公桌上方拿着一张纸。假装纸张的每个角上都装有一个失重的激光指示器,该指示器向下指向桌子。纸是3D对象,桌子上的激光指示器点是2D投影。

现在,你怎么能告诉纸有多高从桌子上是看刚刚激光指示器点?

你不能 垂直向上和向下移动纸张。无论纸张的高度如何,激光笔仍会照在桌子上的相同位置上。

在反向投影中找到z坐标就像试图仅在桌子上基于激光指示器点找到纸张的高度一样。


1
是的,但是我在问题中说我也知道矩形的大小。应该只有一组Z坐标,其中所有4个点都是适当的距离。沿z轴上下滑动将导致更大或更小的多边形。
Joshua Carmody

不,多边形的大小相同。在书桌上方拿一张纸。想象一下,纸张的角一直向下延伸到办公桌表面。现在,上下移动纸张。这些点在所有这些位置都碰到桌子上的相同位置,但是纸张尺寸相同。
Rob Dickerson

Rob,您完全忽略了透视的可能性。由于某些原因,您只考虑正交投影。
tzot

确实。在无限的投影集中,正交情况只有1个情况。最重要的是,它是最可能的一种(视点必须在投影到的平面上无限远)。-1。
xtofl


1

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

在单点的简单情况下,逆投影会给您无限的光线穿过3d空间。

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

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

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

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

在矩形平面平行于z轴的情况下,您还有更多解决方案。

由于您没有原始的投影矩阵,因此任何投影中都存在一个不确定的比例因子,这进一步引入了歧义。您无法区分投影的缩放比例和z轴方向的3d平移。如果您只对3d空间中4个点的相互之间的相对位置感兴趣,而对2d投影的平面不感兴趣,则这不是问题。

从透视图的角度看,事情变得越来越难...


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的向量,因此其中之一是多余的),并且具有focus_length / image_width w的因数 这些数字变成3x3投影矩阵。

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

您总共有8个已知值:4个x + y对。

这样就可以解决。

下一步是使用Matlab或Mathmatica。


1

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

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


0

是的,蒙特卡洛工作,但我找到了解决此问题的更好解决方案。这段代码可以完美地工作(并使用OpenCV):

Cv2.CalibrateCamera(new List<List<Point3f>>() { points3d }, new List<List<Point2f>>() { points2d }, new Size(height, width), cameraMatrix, distCoefs, out rvecs, out tvecs, CalibrationFlags.ZeroTangentDist | CalibrationFlags.FixK1 | CalibrationFlags.FixK2 | CalibrationFlags.FixK3);

此函数获取已知的3d和2d点,屏幕大小并返回旋转(rvecs [0]),平移(tvecs [0])和相机的固有值矩阵。这就是您需要的一切。


OpenCV还具有FindHomography()方法,该方法也可以完美地工作。
机上
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.