Comment fonctionne la transformation de la perspective dans la LIP?
la fonction de transformation de PIL a un mode perspective qui nécessite un 8-tupel de données, mais je ne peux pas comprendre comment convertir disons une inclinaison droite de 30 degrés à ce tupel.
quelqu'un peut-il l'expliquer?
Voici la documentation: http://effbot.org/imagingbook/image.htm
4 réponses
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. En faisant cela, 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 peuvent être déterminés. Pour faciliter les choses, 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 dans le plan actuel, et pa
contient quatre sommets dans le résultat de l'avion.
ainsi, supposons que nous transformions une image comme dans:
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 en 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 peu parce que C'est la seule chose sur Google concernant les transformations de perspective en Python. Voici un peu plus de code 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 s'exécute qui se transforme 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 pur-Python de générer les coefficients de transformation (comme j'ai vu cela demandé par plusieurs). Je l'ai fait et utilisé pour faire le PyDraw purs-Python Image drawing package.
si vous l'utilisez pour votre propre projet, notez que les calculs nécessitent plusieurs opérations matricielles avancées, ce qui signifie que cette fonction nécessite une autre bibliothèque matricielle, heureusement pure-Python, appelée matfunc
écrit à 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 à partir de la résolution 8 équations (linéaires) qui définissent comment 4 points sur le plan transformer (4 points in 2D -> 8 equations), voir la réponse de mmgp pour un code qui résout cela, bien que vous pourriez trouver un peu plus précis de 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 de degrés thêta autour (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) égale à zéro. Espérons que ça aide!