6

如何 使用 PHP GD 库对图像应用透视变换?

我不想使用别人制作的功能 我想了解发生了什么

4

1 回答 1

9

老实说,我不知道如何在数学上描述透视失真。您可以尝试搜索相关文献(例如Google Scholar)。另请参阅 OpenGL 文档中的glFrustum.


编辑:有趣的是,从版本 8 开始,Mathematica 有一个ImagePerspectiveTransformation. 在相关部分,它说:

对于 3*3 矩阵mImagePerspectiveTransformation[image,m]适用LinearFractionalTransform[m]于图像。

这是一个转换,对于某些a(矩阵)、b(向量)、c(向量)和d(标量),将向量转换r(a.r+b)/(c.r+d)。在二维情况下,这给出了齐次矩阵

a_11 a_12 b_1
a_21 a_22 b_2
c_1  c_2  d

要应用转换,请将此矩阵乘以扩展的列向量z=1,然后将结果的前两个元素除以第三个元素:

{{a11, a12, b1}, {a21, a22, b2}, {c1, c2, d}}.{{x}, {y}, {1}} // #[[
     1 ;; 2, All]]/#[[3, 1]] & // First /@ # &

这使:

{(b1 + a11 x + a12 y)/(d + c1 x + c2 y),
  (b2 + a21 x + a22 y)/(d + c1 x + c2 y)}

举个例子:

a = {{0.9, 0.1}, {0.3, 0.9}}
b = {0, -0.1}
c = {0, 0.1}
d = 1

你得到这个转换:

im = Import["/home/cataphract/Downloads/so_q.png"];
orfun = BSplineFunction[ImageData[im], SplineDegree -> 1];

(*transf=TransformationFunction[{{0.9, 0.1, 0.}, {0.3, 
   0.9, -0.1}, {0., 0.1, 1.}}] -- let's expand this:*)

transf = {(0.9 x + 0.1 y)/(1.+ 0.1 y), (-0.1 + 0.3 x + 0.9 y)/(
     1. + 0.1 y)} /. {x -> #[[1]], y -> #[[2]]} &;

ParametricPlot[transf[{x, y}], {x, 0, 1}, {y, 0, 1},
 ColorFunction -> (orfun[1 - #4, #3] &),
 Mesh -> None,
 FrameTicks -> None,
 Axes -> False,
 ImageSize -> 200,
 PlotRange -> All,
 Frame -> False
 ]

转换结果


一旦你有了一张地图,它根据原始图像中的一个点来描述最终图像的一个点的位置,只需为新图像中的每个点找到它的值即可。

还有一个额外的困难。由于图像是离散的,即具有像素而不是连续值,因此您必须使其连续。

假设您有一个将图像大小加倍的转换。计算{x,y}最终图像中的点的函数将{x/2, y/2}在原始图像中查找点。这一点不存在,因为图像是离散的。所以你必须插入这一点。有几种可能的策略。

在这个 Mathematica 示例中,我做了一个简单的 2D 旋转并使用 1 度样条函数进行插值:

im = Import["d:\\users\\cataphract\\desktop\\img.png"]
orfun = BSplineFunction[ImageData[im], SplineDegree -> 1];
transf = Function[{coord}, RotationMatrix[20. Degree].coord];
ParametricPlot[transf[{x, y}], {x, 0, 1}, {y, 0, 1}, 
 ColorFunction -> (orfun[1 - #4, #3] &), Mesh -> None, 
 FrameTicks -> None, Axes -> None, ImageSize -> 200, 
 PlotRange -> {{-0.5, 1}, {0, 1.5}}]

这给出了:

替代文字

PHP:

对于插值,谷歌搜索“B 样条”。其余如下。

首先为原始图像选择一个参考,假设图像是 200x200,像素 (1,1) 映射 (0,0) 和像素 (200,200) 映射到 (1,1)。

然后,您必须猜测应用转换后最终图像的位置。这取决于转换,例如,您可以将其应用于图像的角落或只是猜测。

假设您像我一样考虑 (-.5,0) 和 (1, 1.5) 之间的映射,并且您的最终图像也应该是 200x200。然后:

$sizex = 200;
$sizey = 200;
$x = array("min"=>-.5, "max" => 1);
$y = array("min"=>0, "max" => 1.5);
// keep $sizex/$sizey == $rangex/$rangey
$rangex = $x["max"] - $x["min"];
$rangey = $y["max"] - $y["min"];
for ($xp = 1; $xp <= $sizex; $xp++) {
    for ($yp = 1; $yp <= $sizey; $yp++) {
        $value = transf(
             (($xp-1)/($sizex-1)) * $rangex + $x["min"],
             (($yp-1)/($sizey-1)) * $rangey + $y["min"]);
        /* $value should be in the form array(r, g, b), for instance */
    }
}
于 2010-08-22T04:19:27.603 回答