Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does perspective transformation work in PIL?

PIL's Image.transform has a perspective-mode which requires an 8-tuple of data but I can't figure out how to convert let's say a right tilt of 30 degrees to that tuple.

Can anyone explain it?

like image 582
Hedge Avatar asked Jan 05 '13 23:01

Hedge


People also ask

What is perspective transformation opencv?

In Perspective Transformation, we can change the perspective of a given image or video for getting better insights into the required information. In Perspective Transformation, we need to provide the points on the image from which want to gather information by changing the perspective.

What is perspective transformation in computer vision?

Whereas transformation is the transfer of an object e.t.c from one state to another. So overall, the perspective transformation deals with the conversion of 3d world into 2d image. The same principle on which human vision works and the same principle on which the camera works.

How do you calculate perspective transformation?

The perspective transformation is calculated in homogeneous coordinates and defined by a 3x3 matrix M . If the matrix is not known, how can I calculate it from the given points? So the equation is M*A=B and this can be solved for M in MATLAB by M = B/A or M = (A'\B')' .

Is perspective transformation a linear transformation?

The most general linear transformation is the perspective transformation. Lines that were parallel before perspective transformation can intersect after transformation.


2 Answers

To apply a perspective transformation you first have to know four points in a plane A that will be mapped to four points in a plane B. With those points, you can derive the homographic transform. By doing this, you obtain your 8 coefficients and the transformation can take place.

The site http://xenia.media.mit.edu/~cwren/interpolator/ (mirror: WebArchive), as well as many other texts, describes how those coefficients can be determined. To make things easy, here is a direct implementation according from the mentioned link:

import numpy  def find_coeffs(pa, pb):     matrix = []     for p1, p2 in zip(pa, pb):         matrix.append([p1[0], p1[1], 1, 0, 0, 0, -p2[0]*p1[0], -p2[0]*p1[1]])         matrix.append([0, 0, 0, p1[0], p1[1], 1, -p2[1]*p1[0], -p2[1]*p1[1]])      A = numpy.matrix(matrix, dtype=numpy.float)     B = numpy.array(pb).reshape(8)      res = numpy.dot(numpy.linalg.inv(A.T * A) * A.T, B)     return numpy.array(res).reshape(8) 

where pb is the four vertices in the current plane, and pa contains four vertices in the resulting plane.

So, suppose we transform an image as in:

import sys from PIL import Image  img = Image.open(sys.argv[1]) width, height = img.size m = -0.5 xshift = abs(m) * width new_width = width + int(round(xshift)) img = img.transform((new_width, height), Image.AFFINE,         (1, m, -xshift if m > 0 else 0, 0, 1, 0), Image.BICUBIC) img.save(sys.argv[2]) 

Here is a sample input and output with the code above:

enter image description hereenter image description here

We can continue on the last code and perform a perspective transformation to revert the shear:

coeffs = find_coeffs(         [(0, 0), (256, 0), (256, 256), (0, 256)],         [(0, 0), (256, 0), (new_width, height), (xshift, height)])  img.transform((width, height), Image.PERSPECTIVE, coeffs,         Image.BICUBIC).save(sys.argv[3]) 

Resulting in:

enter image description here

You can also have some fun with the destination points:

enter image description hereenter image description here

like image 179
mmgp Avatar answered Sep 19 '22 03:09

mmgp


I'm going to hijack this question just a tiny bit because it's the only thing on Google pertaining to perspective transformations in Python. Here is some slightly more general code based on the above which creates a perspective transform matrix and generates a function which will run that transform on arbitrary points:

import numpy as np  def create_perspective_transform_matrix(src, dst):     """ Creates a perspective transformation matrix which transforms points         in quadrilateral ``src`` to the corresponding points on quadrilateral         ``dst``.          Will raise a ``np.linalg.LinAlgError`` on invalid input.         """     # See:     # * http://xenia.media.mit.edu/~cwren/interpolator/     # * http://stackoverflow.com/a/14178717/71522     in_matrix = []     for (x, y), (X, Y) in zip(src, dst):         in_matrix.extend([             [x, y, 1, 0, 0, 0, -X * x, -X * y],             [0, 0, 0, x, y, 1, -Y * x, -Y * y],         ])      A = np.matrix(in_matrix, dtype=np.float)     B = np.array(dst).reshape(8)     af = np.dot(np.linalg.inv(A.T * A) * A.T, B)     return np.append(np.array(af).reshape(8), 1).reshape((3, 3))   def create_perspective_transform(src, dst, round=False, splat_args=False):     """ Returns a function which will transform points in quadrilateral         ``src`` to the corresponding points on quadrilateral ``dst``::              >>> transform = create_perspective_transform(             ...     [(0, 0), (10, 0), (10, 10), (0, 10)],             ...     [(50, 50), (100, 50), (100, 100), (50, 100)],             ... )             >>> transform((5, 5))             (74.99999999999639, 74.999999999999957)          If ``round`` is ``True`` then points will be rounded to the nearest         integer and integer values will be returned.              >>> transform = create_perspective_transform(             ...     [(0, 0), (10, 0), (10, 10), (0, 10)],             ...     [(50, 50), (100, 50), (100, 100), (50, 100)],             ...     round=True,             ... )             >>> transform((5, 5))             (75, 75)          If ``splat_args`` is ``True`` the function will accept two arguments         instead of a tuple.              >>> transform = create_perspective_transform(             ...     [(0, 0), (10, 0), (10, 10), (0, 10)],             ...     [(50, 50), (100, 50), (100, 100), (50, 100)],             ...     splat_args=True,             ... )             >>> transform(5, 5)             (74.99999999999639, 74.999999999999957)          If the input values yield an invalid transformation matrix an identity         function will be returned and the ``error`` attribute will be set to a         description of the error::              >>> tranform = create_perspective_transform(             ...     np.zeros((4, 2)),             ...     np.zeros((4, 2)),             ... )             >>> transform((5, 5))             (5.0, 5.0)             >>> transform.error             'invalid input quads (...): Singular matrix         """     try:         transform_matrix = create_perspective_transform_matrix(src, dst)         error = None     except np.linalg.LinAlgError as e:         transform_matrix = np.identity(3, dtype=np.float)         error = "invalid input quads (%s and %s): %s" %(src, dst, e)         error = error.replace("\n", "")      to_eval = "def perspective_transform(%s):\n" %(         splat_args and "*pt" or "pt",     )     to_eval += "  res = np.dot(transform_matrix, ((pt[0], ), (pt[1], ), (1, )))\n"     to_eval += "  res = res / res[2]\n"     if round:         to_eval += "  return (int(round(res[0][0])), int(round(res[1][0])))\n"     else:         to_eval += "  return (res[0][0], res[1][0])\n"     locals = {         "transform_matrix": transform_matrix,     }     locals.update(globals())     exec to_eval in locals, locals     res = locals["perspective_transform"]     res.matrix = transform_matrix     res.error = error     return res 
like image 39
David Wolever Avatar answered Sep 23 '22 03:09

David Wolever