2015-07-15 13 views
7

Sfondo: sto tentando di deformare un volto con un altro di forma diversa.Aumentare l'efficienza del calcolo delle coordinate baricentriche in python

Al fine di deformare un'immagine a un'altra, sto usando una triangolazione delaunay di punti di riferimento facciali e deformando i triangoli di un ritratto ai triangoli corrispondenti del secondo ritratto. Sto usando un sistema di coordinate baricentriche per mappare un punto all'interno di un triangolo nella sua posizione deformata corrispondente sull'altro triangolo.

Il mio primo approccio è stato risolvere il sistema Ax = b con il metodo di moltiplicazione inversa, dove A è costituito dai tre angoli del triangolo, b rappresenta il punto corrente e x rappresenta le coordinate baricentriche di questo punto (alfa, beta e gamma). Ho trovato l'inverso della matrice A una volta per triangolo, e quindi per ogni punto all'interno di quel triangolo calcolato le coordinate baricentriche trovando il prodotto punto di A^-1 e il punto b. Ho trovato questo molto lento (la funzione richiede 36 secondi per essere completata).

In seguito alla raccomandazione di altri post, ho tentato di utilizzare una soluzione dei minimi quadrati per migliorare l'efficienza di questo processo. Tuttavia, il tempo è aumentato a 154 secondi quando ho usato il metodo lsq di numpy. Credo che ciò sia dovuto al fatto che la matrice A viene calcolata ogni volta che il ciclo interno viene eseguito, mentre prima ero in grado di trovare l'inverso solo una volta, prima che inizino i due cicli.

La mia domanda è, come posso migliorare l'efficienza di questa funzione? C'è un modo per memorizzare la fattorizzazione di A in modo che ogni volta che viene calcolata la soluzione dei minimi quadrati per un nuovo punto, non si ripeta lo stesso lavoro?

Pseudocodice per questa funzione:

# Iterate through each triangle (and get corresponding warp triangle) 
for triangle in triangulation: 

    # Extract corners of the unwarped triangle 
    a = firstCornerUW 
    b = secondCornerUW 
    c = thirdCornerUW 

    # Extract corners of the warp triangle 
    a_prime = firstCornerW 
    b_prime = secondCornerW 
    c_prime = thirdCornerW 

    # This matrix will be the same for all points within the triangle 
    triMatrix = matrix of a, b, and c 

    # Bounding box of the triangle 
    xleft = min(ax, bx, cx) 
    xright = max(ax, bx, cx) 
    ytop = min(ay, by, cy) 
    ybottom = max(ay, by, cy) 

    for x in range(xleft, xright): 

     for y in range(ytop, ybottom): 

      # Store the current point as a matrix 
      p = np.array([[x], [y], [1]]) 

      # Solve for least squares solution to get barycentric coordinates 
      barycoor = np.linalg.lstsq(triMatrix, p) 

      # Pull individual coordinates from the array 
      alpha = barycoor[0] 
      beta = barycoor[1] 
      gamma = barycoor[2] 

      # If any of these conditions are not met, the point is not inside the triangle 
      if alpha, beta, gamma > 0 and alpha + beta + gamma <= 1: 

       # Now calculate the warped point by multiplying by alpha, beta, and gamma 
       # Warp the point from image to warped image 
+2

L'essenza di aumentare l'efficienza qui è eliminare i loop e sfruttare le possibilità di vettorizzazione. Inoltre, ho l'impressione che tu possa fare scipy.spatial fare un sacco di lavori pesanti per te. Potresti aggiungere una descrizione più dettagliata del tipo di input previsto di alto livello del tuo problema? –

+2

Una semplice ottimizzazione sarebbe quella di vettorizzare i loop su xey. Basta creare un elenco di punti usando np.mgrid, trasformare tutti questi punti nello spazio baricentrico e filtrare tutti i punti che non hanno coordinate esclusivamente positive. Questo dovrebbe facilmente dare un ordine di grandezza in termini di prestazioni. Ma ancora, non penso che questa soluzione sia ottimale se prendiamo una prospettiva di più alto livello del problema. –

+0

btw; lstsq non calcola le coordinate baricentriche di per sé. Considera un triangolo con la sua COM all'origine. Le 'coordinate baricentriche' per [0,0] come calcolate con lstsq sono [0,0,0]; che non sommano a uno. Trovare la trasformazione in corde baricentriche dovrebbe includere fondamentalmente il vincolo somma-a-uno. –

risposta

3

qui sono i miei suggerimenti, espresso nel vostro pseudocodice. Si noti che la vettorizzazione del loop sui triangoli non dovrebbe essere molto più difficile.

# Iterate through each triangle (and get corresponding warp triangle) 
for triangle in triangulation: 

    # Extract corners of the unwarped triangle 
    a = firstCornerUW 
    b = secondCornerUW 
    c = thirdCornerUW 

    # Bounding box of the triangle 
    xleft = min(ax, bx, cx) 
    xright = max(ax, bx, cx) 
    ytop = min(ay, by, cy) 
    ybottom = max(ay, by, cy) 

    barytransform = np.linalg.inv([[ax,bx,cx], [ay,by,cy], [1,1,1]])  

    grid = np.mgrid[xleft:xright, ytop:ybottom].reshape(2,-1) 
    grid = np.vstack((grid, np.ones((1, grid.shape[1])))) 

    barycoords = np.dot(barytransform, grid) 
    barycoords = barycoords[:,np.all(barycoords>=0, axis=0)] 
+0

Dopo aver calcolato tutte le coordinate baricentriche per il triangolo, quale sarebbe il metodo più efficiente per eseguire la trasformazione effettiva senza qualsiasi utilizzo di loop? –

+0

Non capisco il problema di livello superiore che si sta cercando di affrontare abbastanza bene da rispondere a questa domanda con qualsiasi specificità. Personalmente, immagino che il tuo problema venga affrontato al meglio dalle funzionalità di livello superiore esistenti, come scipy.spatial.Delaunay.find_simplex –