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

31
demandé sur mmgp 2013-01-06 03:54:29

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)

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:

enter image description here enter image description here

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:

enter image description here

vous pouvez également vous amuser avec les points de destination:

enter image description here enter image description here

57
répondu mmgp 2018-08-05 12:50:59

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
7
répondu David Wolever 2014-06-06 23:38:27

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
4
répondu Karim Bahgat 2014-09-23 21:11:40

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!

3
répondu Amir 2017-06-01 11:39:40