2013-01-05 17 views
29

La funzione di trasformazione del PIL ha una modalità prospettica che richiede un 8-tupel di dati, ma non riesco a capire come convertire diciamo un'inclinazione giusta di 30 gradi a quella tupel.Come funziona la trasformazione prospettica in PIL?

Qualcuno può spiegarlo?

Ecco la documentazione ad esso: http://effbot.org/imagingbook/image.htm

+2

Sei a conoscenza delle equazioni coinvolte nella trasformazione prospettica? Vedi http://xenia.media.mit.edu/~cwren/interpolator/ – mmgp

risposta

52

Per applicare una trasformazione prospettiva bisogna prima conoscere quattro punti in un piano A che verrà mappata a quattro punti in un piano B. Con questi punti, è possibile derivare la trasformazione omografica. In questo modo, ottieni i tuoi 8 coefficienti e la trasformazione può aver luogo.

Il sito http://xenia.media.mit.edu/~cwren/interpolator/, così come molti altri testi, descrive come tali coefficienti possono essere determinati. Per facilitare le cose, ecco un'implementazione diretta secondo dal link riportato:

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) 

dove pb è quattro vertici sul piano attuale, e pa contiene quattro vertici nel piano risultante.

Così, supponiamo di trasformare un'immagine come in:

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

Ecco un input di esempio e di uscita con il codice di cui sopra:

enter image description hereenter image description here

Possiamo proseguire sulla ultimo codice ed eseguire una trasformazione prospettica per annullare la cesoiatura:

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

Con conseguente:

enter image description here

Si può anche divertirsi un po 'con i punti di destinazione:

enter image description hereenter image description here

+1

La tua risposta è molto utile e chiara. Grazie. Sei a conoscenza di qualsiasi implementazione in puro pitone di 'def find_coeffs (pa, pb)'? Spero di evitare di aggiungere una dipendenza numpy per una parte non centrale del mio sistema. Credo di poterlo risolvere da solo, ma spero che sia già lì da qualche parte. – KobeJohn

+1

Potrebbe essere troppo tardi per il tuo particolare progetto @kobejohn, ma ho appena pubblicato una nuova risposta che ha una soluzione in puro Python per generare i coefficienti. –

+2

@mmgp Il link che hai fornito nella tua risposta è ora rotto, dando 403. – bcdan

7

ho intenzione di dirottare questa domanda un pochino perché è l'unica cosa su Google relativa alle trasformazioni prospettiche in Python. Ecco del codice leggermente più generali legate al di sopra del quale crea una prospettiva trasformata matrice e genera una funzione che eseguirà che trasformano in punti arbitrari:

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 
4

qui è una versione pura pitone di generare trasformata coefficienti (come ho visto questo richiesto da diversi). L'ho creato e utilizzato per creare il pacchetto di disegno immagine Python puro PyDraw.

Se si utilizza per il proprio progetto, si noti che i calcoli richiede numerose operazioni di matrice avanzata che significa che questa funzione richiede un altro, per fortuna puro Python, biblioteca matrice chiamato matfunc originariamente scritto da Raymond Hettinger e che si può download here 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://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 
+0

È bellissimo! Grazie per aver lasciato un messaggio per me. Ho terminato la mia app calcolando come calcolare solo coefficienti affini di base da solo, ma potrei tornare in futuro e usarlo per trasformazioni più complesse. – KobeJohn

2

L'8 trasformare coefficienti (a, b, c, d, e, f, g, h) corrispondono alla seguente trasformazione:

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

Questi 8 coefficienti possono in generale si può trovare dalla soluzione 8 (lineare) equazioni che definiscono come 4 punti sulla trasformazione del piano (4 punti in 2D -> 8 equazioni), vedere la risposta per mmgp per un codice che risolve questo, anche se potresti trovare un po 'più preciso per cambiare la linea

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

a

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

per esempio, non v'è alcun motivo reale per invertire effettivamente la matrice a lì, o moltiplicare per sua trasposta e perdendo un po 'di precisione, per risolvere le equazioni.

Per quanto riguarda la tua domanda, per una semplice inclinazione del theta gradi intorno (x0, y0), i coefficienti che stai cercando sono:

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

E in generale ogni trasformazione affine deve avere (g, h) uguale a zero. Spero possa aiutare!

Problemi correlati