【问题标题】:Projective transformation投影变换
【发布时间】:2010-09-15 06:08:28
【问题描述】:

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

我也在寻找一种通用的(不是特定于语言或库的)方法,以便它可以合理地应用于任何语言,而不依赖于“为我完成所有工作的魔法函数 X” .

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

【问题讨论】:

  • 澄清请求:缓冲区是矩形,但被复制的区域是正方形?
  • 目标区域是一个矩形,但源区域是一个潜在的非矩形四边形。
  • 你有什么收获吗?
  • @CarlWitthoft 该链接没有,它指向 this 问题。
  • @MatthiasUrlichs 哎呀——对不起;在这么晚的日子里,我忘记了应该联系什么问题。已删除。

标签: graphics image-processing


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

    如果您向下滚动到最后,您将看到一个与您所描述的内容完全相同的示例。我希望英特尔 OpenCV 库中有一个函数可以做到这一点。

    【讨论】:

      【解决方案3】:

      原则上是这样的:

      • 通过平移向量 t 将 A 的原点映射到 B 的原点。
      • 获取 A (1,0) 和 (0,1) 的单位向量,并计算它们如何映射到 B 的单位向量上。
      • 这为您提供了一个变换矩阵 M,因此 A 中的每个向量 a 都映射到 M a + t
      • 反转矩阵并对平移向量求反,因此对于 B 中的每个向量 b,您都有逆映射 b -> M-1 (@987654327 @ - t)
      • 一旦你有了这个转换,对于B中目标区域中的每个点,在A中找到对应的并复制。

      这种映射的优点是你只计算你需要的点,即你在 target 点上循环,而不是 source 点。这是几年前“演示编码”场景中广泛使用的技术。

      【讨论】:

      • 您正在描述一种线性变换,它允许平移、旋转、缩放、反射和剪切。不幸的是,射影变换通常不是线性的(从某种意义上说,它不仅仅是矩阵乘法)。
      • 显然我说得太早了;根据维基百科(见线性地图)的“线性变换”甚至不包括翻译。但我的观点是,射影变换比您描述的要复杂。
      • 嗯,线性变换没有 traslation 元素。此外,线性变换当然包括投影(例如((1,0),(0,0))是投影)
      • 无论如何我从来没有说过单个元素应该是常量。它们可能是点的函数,在这种情况下计算逆函数会更复杂,但原理仍然适用。
      • 顺便说一句,投影变换通常不是投影。
      【解决方案4】:

      编辑

      下面关于角度比不变性的假设是不正确的。相反,投影变换保留了交叉比率和发生率。一个解决方案是:

      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 的位置。

      嗯....我会试一试这个。该解决方案依赖于在转换中保留角度比率的假设。请参阅图像以获取指导(抱歉图像质量差……真的太晚了)。该算法仅提供四边形中的点到正方形中的点的映射。您仍然需要实现处理多个四边形点映射到同一个正方形点。

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

      计算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)
      

      设 thetaD 为顶点 D 处的角度,thetaC 为顶点 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 与 thetaD 的比值应该是 thetaQ1 与 90 的比值。因此,计算 thetaQ1:

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

        thetaQ2 = thetaP2 * 90 / thetaC
        
      • 寻找距离总部:

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

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

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

      【讨论】:

      • 我知道这是一个旧答案,但你能解释一下角度比的假设吗?我试过你的方法,我得到以下信息:i.imgur.com/Wr76L.png四边形的形状有限制吗?
      • @zaf - 回想起来,我认为角度比会被保留是一个错误的假设。相反,我应该假设交叉比率的不变性。我对此进行了编辑以添加一些注释。
      • 这将教你深夜答案。赞成票很有趣。如果您可以为您的新算法添加方程式,那就太好了。实际测试甚至是超级的。我花了将近一整天的时间来研究你的原始算法,以及在 stackoverflow 和互联网上的其他算法,并且对我在让某些东西工作时遇到的困难感到惊讶。最后,我花了一个小时在白板和键盘上自己动手。因此,这一天免于成为一场彻底的灾难:)
      • @zaf - 很高兴听到您制定了自己的解决方案。希望我没有让你误入歧途。我会尽快发布更新的解决方案。与此同时,请继续对此答案投反对票。
      • 您应该继续投票,您的回答令人深思。至少它让我更深入地思考了这个问题。
      【解决方案5】:

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

      因此,除非您想进行一些复杂的编码,否则请按照 smacl 和 Ian 的建议使用别人的魔法函数

      【讨论】:

        【解决方案6】:

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

        您想要的是从旧 (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  ]
        

        你需要知道 a 到 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 插入你的角落。 (角落的效果最好,因为如果你从用户点击中挑选点,它们相距很远可以减少错误。)取 4x4 矩阵的倒数并将其乘以等式的右侧。该矩阵的转置是 P。您应该能够找到计算矩阵逆和在线乘法的函数。

        您可能会遇到错误的地方:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-07-06
          • 2016-07-09
          相关资源
          最近更新 更多