PIL Image.transform
a un mode perspective qui nécessite un 8-Tuple de données mais je ne peux pas comprendre comment convertir disons une inclinaison à droite de 30 degrés à ce Tuple.
Quelqu'un peut-il l'expliquer?
Pour appliquer une transformation de perspective, vous devez d'abord connaître quatre points dans un plan A qui seront mappés à quatre points dans un plan B. Avec ces points, vous pouvez dériver la transformation homographique. Ce faisant, vous obtenez vos 8 coefficients et la transformation peut avoir lieu.
Le site http://xenia.media.mit.edu/~cwren/interpolator/ (miroir: WebArchive ), ainsi que de nombreux autres textes, décrit comment ces coefficients peut être déterminé. Pour vous faciliter la tâche, voici une implémentation directe selon le lien mentionné:
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)
où pb
est les quatre sommets du plan actuel et pa
contient quatre sommets du plan résultant.
Supposons donc que nous transformions une image comme:
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])
Voici un exemple d'entrée et de sortie avec le code ci-dessus:
Nous pouvons continuer sur le dernier code et effectuer une transformation de perspective pour inverser le cisaillement:
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])
Résultant en:
Vous pouvez également vous amuser avec les points de destination:
Je vais détourner cette question juste un tout petit pe parce que c'est la seule chose sur Google concernant les transformations de perspective en Python. Voici un code légèrement plus général basé sur ce qui précède qui crée une matrice de transformation en perspective et génère une fonction qui exécutera cette transformation sur des points arbitraires:
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
Voici une version pure-Python de génération des coefficients de transformation (comme je l'ai vu cela demandé par plusieurs). Je l'ai créé et utilisé pour créer le package de dessin d'images PyDraw pure-Python.
Si vous l'utilisez pour votre propre projet, notez que les calculs nécessitent plusieurs opérations de matrice avancées, ce qui signifie que cette fonction nécessite une autre bibliothèque de matrice, en pur Python, appelée matfunc
écrite à l'origine par Raymond Hettinger et que vous pouvez - télécharger ici ou ici .
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://stackoverflow.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
Les 8 coefficients de transformation (a, b, c, d, e, f, g, h) correspondent à la transformation suivante:
x '= (a x + b y + c)/(g x + h y + 1)
y '= (d x + e y + f)/(g x + h y + 1)
Ces 8 coefficients peuvent en général être trouvés en résolvant 8 équations (linéaires) qui définissent comment 4 points sur la transformation plane (4 points en 2D -> 8 équations), voir la réponse par mmgp pour un code qui résout cela, bien que vous puissiez trouver un peu plus précis pour changer la ligne
res = numpy.dot(numpy.linalg.inv(A.T * A) * A.T, B)
à
res = numpy.linalg.solve(A, B)
c'est-à-dire qu'il n'y a aucune raison réelle d'inverser réellement la matrice A ou de la multiplier par sa transposition et de perdre un peu de précision, afin de résoudre les équations.
Quant à votre question, pour une simple inclinaison des degrés thêta autour de (x0, y0), les coefficients que vous recherchez sont:
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])
Et en général, toute transformation Affine doit avoir (g, h) égal à zéro. J'espère que cela pourra aider!