投射变换

时间:2008-10-04 07:14:15

标签: graphics image-processing

给定两个图像缓冲区(假设它是一个大小宽度为*的整数数组,每个元素都有一个颜色值),如何将一个四边形定义的区域从一个图像缓冲区映射到另一个(总是正方形)图像缓冲?我被理解这被称为“投射变换”。

我也在寻找一种通用的(不是特定于语言或库的)方式,这样它可以合理地应用于任何语言,而不依赖于“为我做所有工作的魔术功能X”

一个例子:我用Java编写了一个简短的程序,使用处理库(processing.org)从相机中捕获视频。在初始“校准”步骤期间,捕获的视频直接输出到窗口中。然后,用户点击四个点来定义将被变换的视频区域,然后在程序的后续操作期间映射到方形窗口。如果用户单击定义在相机输出中以一定角度可见的门角的四个点,则此变换将导致后续视频将门的变换图像映射到窗口的整个区域,尽管有点扭曲。

6 个答案:

答案 0 :(得分:8)

使用线性代数比所有几何都容易得多!另外,您不需要使用正弦,余弦等,因此您可以将每个数字存储为有理分数,并在需要时获得精确的数值结果。

您想要的是从旧的(x,y)坐标到新的(x',y')坐标的映射。你可以用矩阵来做。你需要找到2乘4的投影矩阵P,使P乘以旧坐标等于新的坐标。我们假设您将线条映射到线条(例如,不是直线到抛物线)。因为你有一个投影(平行线不保持平行)和平移(滑动),你也需要一个因子(xy)和(1)。绘制为矩阵:

          [x  ]
[a b c d]*[y  ] = [x']
[e f g h] [x*y]   [y']
          [1  ]

你需要知道一个通​​过h来解决这些方程:

a*x_0 + b*y_0 + c*x_0*y_0 + d = i_0
a*x_1 + b*y_1 + c*x_1*y_1 + d = i_1
a*x_2 + b*y_2 + c*x_2*y_2 + d = i_2
a*x_3 + b*y_3 + c*x_3*y_3 + d = i_3

e*x_0 + f*y_0 + g*x_0*y_0 + h = j_0
e*x_1 + f*y_1 + g*x_1*y_1 + h = j_1
e*x_2 + f*y_2 + g*x_2*y_2 + h = j_2
e*x_3 + f*y_3 + g*x_3*y_3 + h = j_3

同样,你可以使用线性代数:

[x_0 y_0 x_0*y_0 1]   [a e]   [i_0 j_0]
[x_1 y_1 x_1*y_1 1] * [b f] = [i_1 j_1]
[x_2 y_2 x_2*y_2 1]   [c g]   [i_2 j_2]
[x_3 y_3 x_3*y_3 1]   [d h]   [i_3 j_3]

插入x_n,y_n,i_n,j_n的角落。 (Corners工作得最好,因为如果你从用户点击中选择点数,它们相距很远就能减少误差。)取4x4矩阵的倒数并乘以等式的右边。该矩阵的转置为P.您应该能够找到计算矩阵逆函数和在线乘法的函数。

你可能会遇到错误:

  • 计算时,记得检查除零。这表明你的矩阵是不可逆的。如果您尝试将一个(x,y)坐标映射到两个不同的点,则可能会发生这种情况。
  • 如果您编写自己的矩阵数学,请记住矩阵通常指定为行,列(垂直,水平),屏幕图形为x,y(水平,垂直)。第一次你肯定会出错。

答案 1 :(得分:5)

修改

以下关于角度比不变性的假设是不正确的。投射变换反而保留了交叉比率和发生率。然后解决方案是:

  1. 在由段AD和CP定义的线的交叉处找到点C'。
  2. 在由AD和BP段定义的线的交点处找到点B'。
  3. 确定B'DAC'的交叉比率,即r =(BA'* DC')/(DA * B'C')。
  4. 构建投影线F'HEG'。这些点的交叉比等于r,即r =(F'E * HG')/(HE * F'G')。
  5. F'F和G'G将在投影点Q相交,因此等于交叉比率并知道正方形边长,你可以用一些算术体操来确定Q的位置。

  6. 嗯......我会捅这个。该解决方案依赖于在变换中保持角度比率的假设。请参阅图像以获得指导(抱歉图像质量不佳......真的很晚)。该算法仅提供四边形中的点到方形中的点的映射。您仍然需要实现处理多个四点映射到同一个方点。

    让ABCD为四边形,其中A是左上顶点,B是右上顶点,C是右下顶点,D是左下顶点。对(xA,yA)表示顶点A的x和y坐标。我们将这个四边形中的点映射到方形EFGH,其边长等于m。

    alt text

    计算长度AD,CD,AC,BD和BC:

    AD = sqrt((xA-xD)^2 + (yA-yD)^2)
    CD = sqrt((xC-xD)^2 + (yC-yD)^2)
    AC = sqrt((xA-xC)^2 + (yA-yC)^2)
    BD = sqrt((xB-xD)^2 + (yB-yD)^2)
    BC = sqrt((xB-xC)^2 + (yB-yC)^2)
    

    让θD为顶点D处的角度,θC为顶点处的角度C.使用余弦定律计算这些角度:

    thetaD = arccos((AD^2 + CD^2 - AC^2) / (2*AD*CD))
    thetaC = arccos((BC^2 + CD^2 - BD^2) / (2*BC*CD))
    

    我们将四边形中的每个点P映射到正方形中的点Q.对于四边形中的每个点P,请执行以下操作:

    • 找到距离DP:

      DP = sqrt((xP-xD)^2 + (yP-yD)^2)
      
    • 找到距离CP:

      CP = sqrt((xP-xC)^2 + (yP-yC)^2)
      
    • 找到CD和DP之间的角度thetaP1:

      thetaP1 = arccos((DP^2 + CD^2 - CP^2) / (2*DP*CD))
      
    • 找到CD和CP之间的角度thetaP2:

      thetaP2 = arccos((CP^2 + CD^2 - DP^2) / (2*CP*CD))
      
    • thetaP1与θD的比值应为thetaQ1与90的比值。因此,计算thetaQ1:

      thetaQ1 = thetaP1 * 90 / thetaD
      
    • 同样,计算thetaQ2:

      thetaQ2 = thetaP2 * 90 / thetaC
      
    • 找到距离HQ:

      HQ = m * sin(thetaQ2) / sin(180-thetaQ1-thetaQ2)
      
    • 最后,Q相对于EFGH左下角的x和y位置是:

      x = HQ * cos(thetaQ1)
      y = HQ * sin(thetaQ1)
      

    您必须跟踪有多少颜色值映射到方块中的每个点,以便您可以计算每个点的平均颜色。

答案 2 :(得分:4)

我认为你所追求的是平面单应性,看看这些讲义:

http://www.cs.utoronto.ca/~strider/vis-notes/tutHomography04.pdf

如果你向下滚动到最后,你会看到一个你正在描述的例子。我希望英特尔OpenCV库中有一个功能可以做到这一点。

答案 3 :(得分:2)

C++ project on CodeProject包含位图投影转换的来源。数学在维基百科here上。请注意,据我所知,投影变换不会将任意四边形映射到另一个,但是对于三角形也是如此,您可能还想查找偏斜变换。

答案 4 :(得分:2)

如果此转换必须看起来很好(与在Paint中调整大小的位图的方式相反),您不能只创建将目标像素映射到源像素的公式。目标缓冲区中的值必须基于附近源像素的复杂平均值,否则结果将高度像素化。

因此,除非你想进入一些复杂的编码,使用别人的魔法功能,正如smacl和Ian所建议的那样。

答案 5 :(得分:0)

以下是原则上的做法:

  • 通过翻译向量t将A的原点映射到B的原点。
  • 取单位向量A(1,0)和(0,1)并计算它们如何映射到B的单位向量。
  • 这会为您提供转换矩阵 M ,以便A中的每个向量a映射到 M a + t < / LI>
  • 反转矩阵并否定相关矢量,因此对于B中的每个矢量b,您都有逆映射b - &gt; M -1 b - t
  • 进行此转换后,对于B中目标区域中的每个点,找到A中的对应并复制。

此映射的优点是您只计算所需的点数,即您在目标点上循环,而不是点。几年前,它是“演示编码”场景中广泛使用的技术。