Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perspective transformation with GD

How can I apply a perspective transformation on an image using only the PHP GD library?

I don't want to use a function someone else made I want to UNDERSTAND what's going on

like image 446
Mark Lalor Avatar asked Aug 22 '10 03:08

Mark Lalor


1 Answers

I honestly don't know how to describe mathematically a perspective distortion. You could try searching the literature for that (e.g. Google Scholar). See also in the OpenGL documentation, glFrustum.


EDIT: Interestingly, starting with version 8, Mathematica has a ImagePerspectiveTransformation. In the relevant part, it says:

For a 3*3 matrix m, ImagePerspectiveTransformation[image,m] applies LinearFractionalTransform[m] to image.

This is a transformation that, for some a (matrix), b (vector), c (vector) and d (scalar), transforms the vector r to (a.r+b)/(c.r+d). In a 2D situation, this gives the homogeneous matrix:

a_11 a_12 b_1
a_21 a_22 b_2
c_1  c_2  d

To apply the transformation, you multiply this matrix by the column vector extended with z=1 and then take the first two elements of the result and divide them by the third:

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

which gives:

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

With the example:

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

You get this transformation:

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
 ]

Transformation result


Once you have a map that describes the position of a point of the final image in terms of a point in the original image, it's just a matter of finding its value for each of the points in the new image.

There's one additional difficulty. Since an image is discrete, i.e., has pixels instead of continuous values, you have to make it continuous.

Say you have a transformation that doubles the size of an image. The function to calculate a point {x,y} in the final image will look for point {x/2, y/2} in the original. This point doesn't exist, because images are discrete. So you have to interpolate this point. There are several possible strategies for this.

In this Mathematica example, I do a simple 2D rotation and use a degree-1 spline function to interpolate:

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}}]

This gives:

alt text

PHP:

For the interpolation, google for "B-spline". The rest is as follows.

First choose a referential for the original image, say if the image is 200x200, pixel (1,1) maps (0,0) and pixel (200,200) maps to (1,1).

Then you have to guess where your final image will land when the transformation is applied. This depends on the transformation, you can e.g. apply it to the corners of the image or just guess.

Say you consider the mapped between (-.5,0) and (1, 1.5) like I did and that your final image should be 200x200 also. Then:

$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 */
    }
}
like image 142
Artefacto Avatar answered Oct 26 '22 11:10

Artefacto