2009-02-09 177 views
6

我有一个应用程序在图像/照片顶部定义了一个真实世界的矩形,当然在2D中它可能不是矩形,因为您从一个角度看它。如何在2D中绘制透视正确的网格

问题是,例如矩形需要在其上绘制网格线,例如如果它是3x5,所以我需要从边1到边3绘制2条线,从边2到边4绘制4条线

截至目前,我将每行分成等距的部分,以获得所有网格线的起点和终点。然而,矩形所处的角度越多,这些线条越“不正确”,因为离你更远的水平线应该靠得更近。

有谁知道我应该搜索的算法的名称?

是的我知道你可以在3D中做到这一点,但是我被限制在这个特定应用程序的2D中。

+0

所以一个例子可能试图绘制的图片的窗口上的一个矩形? – MSN 2009-02-09 23:48:59

+0

是的,这将是一个例子 – 2009-02-10 22:48:11

+0

你有没有运气与这个项目?我需要非常相似的东西!谢谢 – PyWebDesign 2014-11-02 11:49:49

回答

14

这里的解决方案: http://freespace.virgin.net/hugo.elias/graphics/x_persp.htm

的基本想法是,你可以通过连接角角落找到你的矩形的角度正确的“中心”。两条结果线的交点是您的透视正确中心。从那里你将你的矩形细分为四个更小的矩形,然后重复这个过程。次数取决于你想要的准确程度。您可以细分为略低于像素大小以获得有效完美的视角。

然后在您的子矩形中,您只需应用标准未修正的“纹理”三角形或矩形或其他。

您可以执行此算法,而不会遇到构建“真实”3d世界的复杂问题。如果有一个真实的3D世界建模,但你的textriangles没有硬件透视校正,或者你需要一个高性能的方式来获得透视正确的飞机,而不是每像素渲染欺骗。

-1

问题是,它是从3D到2D的转变,正在让你。

Here是关于如何完成的教程。

+0

这是严格的2D,没有3D数据投影。 – 2009-02-09 23:00:41

4

虽然我的google-fu未能产生任何坚如磐石的数学解决方案,但我发现这幅图可能会有所帮助。

http://studiochalkboard.evansville.edu/lp-diminish.html

我认为它实际上可能是相当困难的拿出你自己的正确的数学,它可能是某种对数或求和的表达。希望该链接中的图形和术语可能会为您提供更多的搜索内容。

+0

谢谢,其实非常有价值。您的Google技能在我之前就已经确定了。 – 2009-02-09 23:15:59

-1

你需要做的是在3D(世界)中表示它,然后将其投影到2D(屏幕)。

这将要求您使用4D变换矩阵,该矩阵将4D均匀投影到3D均匀矢量上,然后将其转换为2D屏幕空间矢量。

我也无法在Google中找到它,但是一本很好的计算机图形图书将会有详细信息。

关键字是投影矩阵,投影变换,仿射变换,同质矢量,世界空间,屏幕空间,透视变换,3D变换

顺便说一下,这通常需要几个讲座,解释这一切。祝你好运。

+2

使用3D数学来实现这一点是大规模的矫枉过正,计算复杂度超过所需。 – Sparr 2009-02-10 04:09:36

+1

它只是一堆数学,它不需要任何IO。所以它会超快。 – Pyrolistical 2009-02-10 05:43:17

3

使用布列塔尼的细分方法(与Mongo的扩展方法有关),可以得到精确的任意二次幂分割。要使用这些方法拆分为非幂次分割,您将不得不细分为子像素间距,这在计算上可能很昂贵。但是,我相信你可以应用Haga's Theorem(用于折纸将一边划分为(N-1)个边的一侧划分为N个)到透视平方划分的变体以产生从最接近的2次方中任意分割,而不必继续细分。

0

在特例当你垂直于侧面1和3时,你可以将这些侧面等分。然后绘制一条对角线,并通过前面绘制的对角线和分界线的每个交点平行于第1边。

1

最优雅和最快的解决方案将是找到单应矩阵,它将矩形坐标映射到照片坐标。

有了一个体面的矩阵库,它不应该是一个困难的任务,只要你知道你的数学。

关键词:共点共线,单应,直接线性变换

然而,递归算法上面应该工作,但可能是,如果你的资源是有限的,射影几何是唯一的出路。

6

image description 图片:变换双线性&视角的实施例(注:顶部&底部水平网格线的高度实际上是其余线高度的一半,在两个附图中)

======= =================================

我知道这是一个老问题,但我有一个通用的因此我决定发布它,这对未来的读者将会有所帮助。下面的代码可以绘制任意的透视网格,而不需要重复计算。

我开始实际上有一个类似的问题:绘制2D透视网格,然后转换下划线图像以恢复透视。

我开始在这里读到:这里 http://www.imagemagick.org/Usage/distorts/#bilinear_forward

,然后(在Leptonica库): http://www.leptonica.com/affine.html

是我发现这一点:

当你看到一个物体平面从 有限距离的某个任意方向,您会在 图像中获得额外的“梯形失真”失真。这是一个投影变换,它保持直线 笔直,但不保留线条之间的角度。这种变形 不能用线性仿射变换来描述,实际上 在分母中因x和y依赖项而不同。

转换不是线性的,因为很多人已经在这个线程中指出了。它涉及求解8个方程的线性系统(一次)来计算8个所需的系数,然后您可以使用它们来根据需要转换任意数量的点。为了避免在我的项目中包含所有的Leptonica库,我从中取出了一些代码段,删除了所有特殊的Leptonica数据类型宏,我修复了一些内存泄漏,并将其转换为C++类(主要用于封装原因),它只做一件事: 它将(Qt)QPointF float(x,y)坐标映射到相应的透视坐标。

如果要将代码调整到另一个C++库,则重新定义/替换的唯一方法是QPointF坐标类。

我希望有些未来的读者会觉得它有用。 代码波纹管被分成3个部分:

A.关于如何使用genImageProjective C++类绘制2D透视网格的例子

B. genImageProjective.h文件

C. genImageProjective。 cpp文件

//============================================================ 
// C++ Code Example on how to use the 
//  genImageProjective class to draw a perspective 2D Grid 
//============================================================ 

#include "genImageProjective.h" 

// Input: 4 Perspective-Tranformed points: 
//  perspPoints[0] = top-left 
//  perspPoints[1] = top-right 
//  perspPoints[2] = bottom-right 
//  perspPoints[3] = bottom-left 
void drawGrid(QPointF *perspPoints) 
{ 
(...) 
     // Setup a non-transformed area rectangle 
     // I use a simple square rectangle here because in this case we are not interested in the source-rectangle, 
     // (we want to just draw a grid on the perspPoints[] area) 
     // but you can use any arbitrary rectangle to perform a real mapping to the perspPoints[] area 
     QPointF topLeft = QPointF(0,0); 
     QPointF topRight = QPointF(1000,0); 
     QPointF bottomRight = QPointF(1000,1000); 
     QPointF bottomLeft = QPointF(0,1000); 
     float width = topRight.x() - topLeft.x(); 
     float height = bottomLeft.y() - topLeft.y(); 

     // Setup Projective trasform object 
     genImageProjective imageProjective; 
     imageProjective.sourceArea[0] = topLeft; 
     imageProjective.sourceArea[1] = topRight; 
     imageProjective.sourceArea[2] = bottomRight; 
     imageProjective.sourceArea[3] = bottomLeft; 
     imageProjective.destArea[0] = perspPoints[0]; 
     imageProjective.destArea[1] = perspPoints[1]; 
     imageProjective.destArea[2] = perspPoints[2]; 
     imageProjective.destArea[3] = perspPoints[3]; 
     // Compute projective transform coefficients 
     if (imageProjective.computeCoeefficients() != 0) 
      return; // This can actually fail if any 3 points of Source or Dest are colinear 

     // Initialize Grid parameters (without transform) 
     float gridFirstLine = 0.1f; // The normalized position of first Grid Line (0.0 to 1.0) 
     float gridStep = 0.1f;  // The normalized Grd size (=distance between grid lines: 0.0 to 1.0) 

     // Draw Horizonal Grid lines 
     QPointF lineStart, lineEnd, tempPnt; 
     for (float pos = gridFirstLine; pos <= 1.0f; pos += gridStep) 
     { 
      // Compute Grid Line Start 
      tempPnt = QPointF(topLeft.x(), topLeft.y() + pos*width); 
      imageProjective.mapSourceToDestPoint(tempPnt, lineStart); 
      // Compute Grid Line End 
      tempPnt = QPointF(topRight.x(), topLeft.y() + pos*width); 
      imageProjective.mapSourceToDestPoint(tempPnt, lineEnd); 

      // Draw Horizontal Line (use your prefered method to draw the line) 
      (...) 
     } 
     // Draw Vertical Grid lines 
     for (float pos = gridFirstLine; pos <= 1.0f; pos += gridStep) 
     { 
      // Compute Grid Line Start 
      tempPnt = QPointF(topLeft.x() + pos*height, topLeft.y()); 
      imageProjective.mapSourceToDestPoint(tempPnt, lineStart); 
      // Compute Grid Line End 
      tempPnt = QPointF(topLeft.x() + pos*height, bottomLeft.y()); 
      imageProjective.mapSourceToDestPoint(tempPnt, lineEnd); 

      // Draw Vertical Line (use your prefered method to draw the line) 
      (...) 
     } 
(...) 
} 

========================================== 



//======================================== 
//C++ Header File: genImageProjective.h 
//======================================== 

#ifndef GENIMAGE_H 
#define GENIMAGE_H 

#include <QPointF> 

// Class to transform an Image Point using Perspective transformation 
class genImageProjective 
{ 
public: 
    genImageProjective(); 

    int computeCoeefficients(void); 
    int mapSourceToDestPoint(QPointF& sourcePoint, QPointF& destPoint); 

public: 
    QPointF sourceArea[4]; // Source Image area limits (Rectangular) 
    QPointF destArea[4]; // Destination Image area limits (Perspectivelly Transformed) 

private: 
    static int gaussjordan(float **a, float *b, int n); 

    bool coefficientsComputed; 
    float vc[8];   // Vector of Transform Coefficients 
}; 

#endif // GENIMAGE_H 
//======================================== 


//======================================== 
//C++ CPP File: genImageProjective.cpp 
//======================================== 

#include <math.h> 
#include "genImageProjective.h" 

// ---------------------------------------------------- 
// class genImageProjective 
// ---------------------------------------------------- 
genImageProjective::genImageProjective() 
{ 
    sourceArea[0] = sourceArea[1] = sourceArea[2] = sourceArea[3] = QPointF(0,0); 
    destArea[0] = destArea[1] = destArea[2] = destArea[3] = QPointF(0,0); 
    coefficientsComputed = false; 
} 


// -------------------------------------------------------------- 
// Compute projective transform coeeeficients 
// RetValue: 0: Success, !=0: Error 
/*-------------------------------------------------------------* 
*    Projective coordinate transformation   * 
*-------------------------------------------------------------*/ 
/*! 
* computeCoeefficients() 
* 
*  Input: this->sourceArea[4]: (source 4 points; unprimed) 
*    this->destArea[4]: (transformed 4 points; primed) 
*    this->vc (computed vector of transform coefficients) 
*  Return: 0 if OK; <0 on error 
* 
* We have a set of 8 equations, describing the projective 
* transformation that takes 4 points (sourceArea) into 4 other 
* points (destArea). These equations are: 
* 
*   x1' = (c[0]*x1 + c[1]*y1 + c[2])/(c[6]*x1 + c[7]*y1 + 1) 
*   y1' = (c[3]*x1 + c[4]*y1 + c[5])/(c[6]*x1 + c[7]*y1 + 1) 
*   x2' = (c[0]*x2 + c[1]*y2 + c[2])/(c[6]*x2 + c[7]*y2 + 1) 
*   y2' = (c[3]*x2 + c[4]*y2 + c[5])/(c[6]*x2 + c[7]*y2 + 1) 
*   x3' = (c[0]*x3 + c[1]*y3 + c[2])/(c[6]*x3 + c[7]*y3 + 1) 
*   y3' = (c[3]*x3 + c[4]*y3 + c[5])/(c[6]*x3 + c[7]*y3 + 1) 
*   x4' = (c[0]*x4 + c[1]*y4 + c[2])/(c[6]*x4 + c[7]*y4 + 1) 
*   y4' = (c[3]*x4 + c[4]*y4 + c[5])/(c[6]*x4 + c[7]*y4 + 1) 
* 
* Multiplying both sides of each eqn by the denominator, we get 
* 
*   AC = B 
* 
* where B and C are column vectors 
* 
*   B = [ x1' y1' x2' y2' x3' y3' x4' y4' ] 
*   C = [ c[0] c[1] c[2] c[3] c[4] c[5] c[6] c[7] ] 
* 
* and A is the 8x8 matrix 
* 
*    x1 y1  1  0 0 0 -x1*x1' -y1*x1' 
*    0 0  0 x1 y1 1 -x1*y1' -y1*y1' 
*    x2 y2  1  0 0 0 -x2*x2' -y2*x2' 
*    0 0  0 x2 y2 1 -x2*y2' -y2*y2' 
*    x3 y3  1  0 0 0 -x3*x3' -y3*x3' 
*    0 0  0 x3 y3 1 -x3*y3' -y3*y3' 
*    x4 y4  1  0 0 0 -x4*x4' -y4*x4' 
*    0 0  0 x4 y4 1 -x4*y4' -y4*y4' 
* 
* These eight equations are solved here for the coefficients C. 
* 
* These eight coefficients can then be used to find the mapping 
* (x,y) --> (x',y'): 
* 
*   x' = (c[0]x + c[1]y + c[2])/(c[6]x + c[7]y + 1) 
*   y' = (c[3]x + c[4]y + c[5])/(c[6]x + c[7]y + 1) 
* 
*/ 
int genImageProjective::computeCoeefficients(void) 
{ 
    int retValue = 0; 
    int  i; 
    float *a[8]; /* 8x8 matrix A */ 
    float *b = this->vc; /* rhs vector of primed coords X'; coeffs returned in vc[] */ 

    b[0] = destArea[0].x(); 
    b[1] = destArea[0].y(); 
    b[2] = destArea[1].x(); 
    b[3] = destArea[1].y(); 
    b[4] = destArea[2].x(); 
    b[5] = destArea[2].y(); 
    b[6] = destArea[3].x(); 
    b[7] = destArea[3].y(); 

    for (i = 0; i < 8; i++) 
     a[i] = NULL; 
    for (i = 0; i < 8; i++) 
    { 
     if ((a[i] = (float *)calloc(8, sizeof(float))) == NULL) 
     { 
      retValue = -100; // ERROR_INT("a[i] not made", procName, 1); 
      goto Terminate; 
     } 
    } 

    a[0][0] = sourceArea[0].x(); 
    a[0][1] = sourceArea[0].y(); 
    a[0][2] = 1.; 
    a[0][6] = -sourceArea[0].x() * b[0]; 
    a[0][7] = -sourceArea[0].y() * b[0]; 
    a[1][3] = sourceArea[0].x(); 
    a[1][4] = sourceArea[0].y(); 
    a[1][5] = 1; 
    a[1][6] = -sourceArea[0].x() * b[1]; 
    a[1][7] = -sourceArea[0].y() * b[1]; 
    a[2][0] = sourceArea[1].x(); 
    a[2][1] = sourceArea[1].y(); 
    a[2][2] = 1.; 
    a[2][6] = -sourceArea[1].x() * b[2]; 
    a[2][7] = -sourceArea[1].y() * b[2]; 
    a[3][3] = sourceArea[1].x(); 
    a[3][4] = sourceArea[1].y(); 
    a[3][5] = 1; 
    a[3][6] = -sourceArea[1].x() * b[3]; 
    a[3][7] = -sourceArea[1].y() * b[3]; 
    a[4][0] = sourceArea[2].x(); 
    a[4][1] = sourceArea[2].y(); 
    a[4][2] = 1.; 
    a[4][6] = -sourceArea[2].x() * b[4]; 
    a[4][7] = -sourceArea[2].y() * b[4]; 
    a[5][3] = sourceArea[2].x(); 
    a[5][4] = sourceArea[2].y(); 
    a[5][5] = 1; 
    a[5][6] = -sourceArea[2].x() * b[5]; 
    a[5][7] = -sourceArea[2].y() * b[5]; 
    a[6][0] = sourceArea[3].x(); 
    a[6][1] = sourceArea[3].y(); 
    a[6][2] = 1.; 
    a[6][6] = -sourceArea[3].x() * b[6]; 
    a[6][7] = -sourceArea[3].y() * b[6]; 
    a[7][3] = sourceArea[3].x(); 
    a[7][4] = sourceArea[3].y(); 
    a[7][5] = 1; 
    a[7][6] = -sourceArea[3].x() * b[7]; 
    a[7][7] = -sourceArea[3].y() * b[7]; 

    retValue = gaussjordan(a, b, 8); 

Terminate: 
    // Clean up 
    for (i = 0; i < 8; i++) 
    { 
     if (a[i]) 
      free(a[i]); 
    } 

    this->coefficientsComputed = (retValue == 0); 
    return retValue; 
} 


/*-------------------------------------------------------------* 
*    Gauss-jordan linear equation solver   * 
*-------------------------------------------------------------*/ 
/* 
* gaussjordan() 
* 
*  Input: a (n x n matrix) 
*    b (rhs column vector) 
*    n (dimension) 
*  Return: 0 if ok, 1 on error 
* 
*  Note side effects: 
*   (1) the matrix a is transformed to its inverse 
*   (2) the vector b is transformed to the solution X to the 
*    linear equation AX = B 
* 
*  Adapted from "Numerical Recipes in C, Second Edition", 1992 
*  pp. 36-41 (gauss-jordan elimination) 
*/ 
#define SWAP(a,b) {temp = (a); (a) = (b); (b) = temp;} 
int genImageProjective::gaussjordan(float **a, float *b, int n) 
{ 
    int retValue = 0; 
    int i, icol=0, irow=0, j, k, l, ll; 
    int *indexc = NULL, *indexr = NULL, *ipiv = NULL; 
    float big, dum, pivinv, temp; 

    if (!a) 
    { 
     retValue = -1; // ERROR_INT("a not defined", procName, 1); 
     goto Terminate; 
    } 
    if (!b) 
    { 
     retValue = -2; // ERROR_INT("b not defined", procName, 1); 
     goto Terminate; 
    } 

    if ((indexc = (int *)calloc(n, sizeof(int))) == NULL) 
    { 
     retValue = -3; // ERROR_INT("indexc not made", procName, 1); 
     goto Terminate; 
    } 
    if ((indexr = (int *)calloc(n, sizeof(int))) == NULL) 
    { 
     retValue = -4; // ERROR_INT("indexr not made", procName, 1); 
     goto Terminate; 
    } 
    if ((ipiv = (int *)calloc(n, sizeof(int))) == NULL) 
    { 
     retValue = -5; // ERROR_INT("ipiv not made", procName, 1); 
     goto Terminate; 
    } 

    for (i = 0; i < n; i++) 
    { 
     big = 0.0; 
     for (j = 0; j < n; j++) 
     { 
      if (ipiv[j] != 1) 
      { 
       for (k = 0; k < n; k++) 
       { 
        if (ipiv[k] == 0) 
        { 
         if (fabs(a[j][k]) >= big) 
         { 
          big = fabs(a[j][k]); 
          irow = j; 
          icol = k; 
         } 
        } 
        else if (ipiv[k] > 1) 
        { 
         retValue = -6; // ERROR_INT("singular matrix", procName, 1); 
         goto Terminate; 
        } 
       } 
      } 
     } 
     ++(ipiv[icol]); 

     if (irow != icol) 
     { 
      for (l = 0; l < n; l++) 
       SWAP(a[irow][l], a[icol][l]); 
      SWAP(b[irow], b[icol]); 
     } 

     indexr[i] = irow; 
     indexc[i] = icol; 
     if (a[icol][icol] == 0.0) 
     { 
      retValue = -7; // ERROR_INT("singular matrix", procName, 1); 
      goto Terminate; 
     } 
     pivinv = 1.0/a[icol][icol]; 
     a[icol][icol] = 1.0; 
     for (l = 0; l < n; l++) 
      a[icol][l] *= pivinv; 
     b[icol] *= pivinv; 

     for (ll = 0; ll < n; ll++) 
     { 
      if (ll != icol) 
      { 
       dum = a[ll][icol]; 
       a[ll][icol] = 0.0; 
       for (l = 0; l < n; l++) 
        a[ll][l] -= a[icol][l] * dum; 
       b[ll] -= b[icol] * dum; 
      } 
     } 
    } 

    for (l = n - 1; l >= 0; l--) 
    { 
     if (indexr[l] != indexc[l]) 
     { 
      for (k = 0; k < n; k++) 
       SWAP(a[k][indexr[l]], a[k][indexc[l]]); 
     } 
    } 

Terminate: 
    if (indexr) 
     free(indexr); 
    if (indexc) 
     free(indexc); 
    if (ipiv) 
     free(ipiv); 
    return retValue; 
} 


// -------------------------------------------------------------- 
// Map a source point to destination using projective transform 
// -------------------------------------------------------------- 
// Params: 
// sourcePoint: initial point 
// destPoint: transformed point 
// RetValue: 0: Success, !=0: Error 
// -------------------------------------------------------------- 
// Notes: 
// 1. You must call once computeCoeefficients() to compute 
//  the this->vc[] vector of 8 coefficients, before you call 
//  mapSourceToDestPoint(). 
// 2. If there was an error or the 8 coefficients were not computed, 
//  a -1 is returned and destPoint is just set to sourcePoint value. 
// -------------------------------------------------------------- 
int genImageProjective::mapSourceToDestPoint(QPointF& sourcePoint, QPointF& destPoint) 
{ 
    if (coefficientsComputed) 
    { 
     float factor = 1.0f/(vc[6] * sourcePoint.x() + vc[7] * sourcePoint.y() + 1.); 
     destPoint.setX(factor * (vc[0] * sourcePoint.x() + vc[1] * sourcePoint.y() + vc[2])); 
     destPoint.setY(factor * (vc[3] * sourcePoint.x() + vc[4] * sourcePoint.y() + vc[5])); 
     return 0; 
    } 
    else // There was an error while computing coefficients 
    { 
     destPoint = sourcePoint; // just copy the source to destination... 
     return -1;    // ...and return an error 
    } 
} 
//======================================== 
0

这是我想到的几何解决方案。我不知道'算法'是否有名字。

假设您想要首先将“矩形”划分为n个竖线。

我们的目标是将顶点P1..Pn-1放置在顶部线上,我们可以用它来画线,直到左右线遇到或平行于这些点的点不存在时为止。

如果顶部和底部的线相互平行,只需放置多个点,以便等距离分割顶部线。

其他地方n点Q1..Qn在左边的线上,这样它们和左上角是等距的,并且我更接近于左上角而不是Qj。 为了将Q点映射到顶部线,通过顶部和底部线的交点找到从Qn到右上角的线和与左侧线平行的交点S.现在将S连接到Q1..Qn-1。新行与顶行的交集是所需的P点。

做这个水平线的模拟。

0

给定围绕y轴的旋转,特别是如果旋转曲面是平面的,则透视图由垂直梯度生成。这些观点逐渐接近。而不是使用对角线来定义四个矩形,可以使用给定的两个幂...定义左右两个矩形。如果继续将表面划分为更窄的垂直线段,它们将高于宽度。这可以适应不是方形的表面。如果一个旋转围绕x轴,则需要水平渐变。

0

我认为选择的答案不是最好的解决方案。更好的解决方案是将矩形的透视(投影)变换应用于简单网格,如下面的Matlab脚本和图像显示。你也可以用C++和OpenCV来实现这个算法。

function drawpersgrid 
sz  = [ 24, 16 ]; % [x y] 
srcpt = [ 0 0; sz(1) 0; 0 sz(2); sz(1) sz(2)]; 
destpt = [ 20 50; 100 60; 0 150; 200 200;]; 

% make rectangular grid 
[X,Y] = meshgrid(0:sz(1),0:sz(2)); 

% find projective transform matching corner points 
tform = maketform('projective',srcpt,destpt); 

% apply the projective transform to the grid 
[X1,Y1] = tformfwd(tform,X,Y); 

hold on; 

%% find grid 

for i=1:sz(2) 
    for j=1:sz(1) 
     x = [ X1(i,j);X1(i,j+1);X1(i+1,j+1);X1(i+1,j);X1(i,j)]; 
     y = [ Y1(i,j);Y1(i,j+1);Y1(i+1,j+1);Y1(i+1,j);Y1(i,j)]; 
     plot(x,y,'b'); 
    end 
end 
hold off; 

Projective grid