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