pil from python python-imaging-library perspective

from - pil image python



¿Cómo funciona la transformación de perspectiva en PIL? (4)

La función de transformación de PIL tiene un modo de perspectiva que requiere 8 topes de datos, pero no puedo descifrar cómo convertir, digamos una inclinación hacia la derecha de 30 grados con respecto a ese tupel.

¿Alguien puede explicarlo?

Aquí está la documentación: http://effbot.org/imagingbook/image.htm


Aquí hay una versión pura de Python de generar los coeficientes de transformación (como he visto esto solicitado por varios). Lo hice y lo usé para hacer el paquete de dibujo de imágenes PyDraw pure-Python.

Si lo usa para su propio proyecto, tenga en cuenta que los cálculos requieren varias operaciones de matriz avanzadas, lo que significa que esta función requiere otra, afortunadamente pura, biblioteca de matriz de Python llamada matfunc originalmente escrita por Raymond Hettinger y que puede descargar aquí o here .

import matfunc as mt def perspective_coefficients(self, oldplane, newplane): """ Calculates and returns the transform coefficients needed for a perspective transform, ie tilting an image in 3D. Note: it is not very obvious how to set the oldplane and newplane arguments in order to tilt an image the way one wants. Need to make the arguments more user-friendly and handle the oldplane/newplane behind the scenes. Some hints on how to do that at http://www.cs.utexas.edu/~fussell/courses/cs384g/lectures/lecture20-Z_buffer_pipeline.pdf | **option** | **description** | --- | --- | oldplane | a list of four old xy coordinate pairs | newplane | four points in the new plane corresponding to the old points """ # first find the transform coefficients, thanks to http://.com/questions/14177744/how-does-perspective-transformation-work-in-pil pb,pa = oldplane,newplane grid = [] for p1,p2 in zip(pa, pb): grid.append([p1[0], p1[1], 1, 0, 0, 0, -p2[0]*p1[0], -p2[0]*p1[1]]) grid.append([0, 0, 0, p1[0], p1[1], 1, -p2[1]*p1[0], -p2[1]*p1[1]]) # then do some matrix magic A = mt.Matrix(grid) B = mt.Vec([xory for xy in pb for xory in xy]) AT = A.tr() ATA = AT.mmul(A) gridinv = ATA.inverse() invAT = gridinv.mmul(AT) res = invAT.mmul(B) a,b,c,d,e,f,g,h = res.flatten() # finito return a,b,c,d,e,f,g,h


Los 8 coeficientes de transformación (a, b, c, d, e, f, g, h) corresponden a la siguiente transformación:

x ''= (a x + b y + c) / (g x + h y + 1)
y ''= (d x + e y + f) / (g x + h y + 1)

Estos 8 coeficientes se pueden encontrar en general a partir de la resolución de 8 ecuaciones (lineales) que definen cómo se transforman 4 puntos en el plano (4 puntos en 2D -> 8 ecuaciones), consulte la respuesta de mmgp para obtener un código que resuelva esto, aunque podría encontrar un poco más preciso para cambiar la línea

res = numpy.dot(numpy.linalg.inv(A.T * A) * A.T, B)

a

res = numpy.linalg.solve(A, B)

es decir, no hay una razón real para invertir realmente la matriz A allí, o para multiplicarla por su transposición y perder un poco de precisión, para poder resolver las ecuaciones.

En cuanto a su pregunta, para una simple inclinación de theta grados alrededor (x0, y0), los coeficientes que está buscando son:

def find_rotation_coeffs(theta, x0, y0): ct = cos(theta) st = sin(theta) return np.array([ct, -st, x0*(1-ct) + y0*st, st, ct, y0*(1-ct)-x0*st,0,0])

Y, en general, cualquier transformación Affine debe tener (g, h) igual a cero. ¡Espero que ayude!


Para aplicar una transformación de perspectiva, primero debes saber cuatro puntos en un plano A que se mapeará a cuatro puntos en un plano B. Con esos puntos, puedes derivar la transformación homográfica. Al hacer esto, obtienes tus 8 coeficientes y la transformación puede tener lugar.

El sitio xenia.media.mit.edu/~cwren/interpolator , así como muchos otros textos, describe cómo se pueden determinar esos coeficientes. Para facilitar las cosas, aquí hay una implementación directa según el enlace mencionado:

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)

donde pb son los cuatro vértices en el plano actual, y pa contiene cuatro vértices en el plano resultante.

Entonces, supongamos que transformamos una imagen como en:

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

Aquí hay una entrada y salida de muestra con el código anterior:

Podemos continuar con el último código y realizar una transformación de perspectiva para revertir el corte:

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

Resultando en:

También puedes divertirte con los puntos de destino:


Voy a secuestrar esta pregunta solo un poquito porque es lo único en Google relacionado con las transformaciones de perspectiva en Python. Aquí hay un código ligeramente más general basado en lo anterior que crea una matriz de transformación de perspectiva y genera una función que ejecutará esa transformación en puntos arbitrarios:

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://.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