2015-07-15 60 views
7

Tło: Próbuję wypaczyć twarz do innej o innym kształcie.Zwiększenie wydajności obliczania współrzędnych barycentrycznych w pythonie

Aby wypaczyć jedno zdjęcie na drugie, używam delangadnej triangulacji charakterystycznych rysów twarzy i wypaczając trójkąty jednego portretu do odpowiednich trójkątów drugiego portretu. Używam barycentrycznego układu współrzędnych do mapowania punktu w trójkącie do odpowiadającej mu wypaczonej lokalizacji w drugim trójkącie.

Moje pierwsze podejście polegało na rozwiązaniu układu Ax = b metodą odwrotnego mnożenia, gdzie A składa się z trzech rogów trójkąta, b reprezentuje bieżący punkt, a x reprezentuje barycentryczne współrzędne tego punktu (alfa, beta i gamma). Znalazłem odwrotność macierzy A raz na każdy trójkąt, a następnie dla każdego punktu w tym trójkącie obliczono współrzędne barycentryczne, znajdując iloczyn punktowy A^-1 i punkt b. Zauważyłem, że jest to bardzo powolne (wykonanie tej funkcji zajmuje 36 sekund).

Zgodnie z zaleceniami innych stanowisk, próbowałem użyć rozwiązania najmniejszych kwadratów, aby poprawić efektywność tego procesu. Jednak czas ten wzrósł do 154 sekund, gdy użyłem metody numsu w lsq. Sądzę, że wynika to z faktu, że matryca A jest uwzględniana za każdym razem, gdy pętla wewnętrzna działa, podczas gdy zanim udało mi się znaleźć odwrotność tylko jeden raz, zanim zaczną się obie pętle.

Moje pytanie brzmi: jak mogę poprawić efektywność tej funkcji? Czy istnieje sposób na przechowywanie faktoryzacji A, aby za każdym razem, gdy obliczono najmniejsze kwadratowe rozwiązanie dla nowego punktu, nie powtórzyło się to samo?

Pseudokod dla tej funkcji:

# 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

Kluczem do zwiększenia wydajności jest wyeliminowanie pętli i wykorzystanie możliwości wektoryzacji. Na dodatek mam wrażenie, że możesz zrobić dla ciebie scipy.spatial wiele ciężkiego podnoszenia. Czy mógłbyś dodać opis swojego problemu o wyższym poziomie spodziewanego typu input-ouput? –

+2

Prostą optymalizacją byłoby najpierw wektoryzować pętle ponad xiy. Po prostu utwórz listę punktów używając np.mgrid, przekształć wszystkie te punkty w przestrzeń barycentryczną i odfiltruj wszystkie punkty, które nie mają wyłącznie dodatnich współrzędnych. To powinno przynieść wydajność rzędu. Jednak nadal uważam, że to rozwiązanie nie jest optymalne, jeśli spojrzymy na problem z wyższego poziomu. –

+0

btw; lstsq nie oblicza współrzędnych barycentrycznych jako takich. Rozważmy trójkąt z jego COM na początku. "Współrzędne barycentryczne" dla [0,0] obliczone dla lstsq wynoszą [0,0,0]; które nie sumują się do jednego. Znalezienie przekształcenia w kable barycentryczne powinno zasadniczo obejmować ograniczenie sumy do jednego. –

Odpowiedz

3

Oto moje sugestie, wyrażone w Pseudokod. Zwróć uwagę, że wektoryzacja pętli nad trójkątami nie powinna być o wiele trudniejsza.

# 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

Po obliczeniu wszystkich współrzędnych barycentrycznych dla trójkąta, jaka byłaby najskuteczniejsza metoda przeprowadzenia rzeczywistej transformacji bez jakiekolwiek użycie pętli? –

+0

Nie rozumiem problemu wyższego poziomu, który próbujesz rozwiązać na tyle dobrze, aby odpowiedzieć na to pytanie z jakąkolwiek specyfiką. Osobiście, zgaduję, że twój problem najlepiej rozwiązać za pomocą istniejących funkcji wyższego poziomu, takich jak scipy.spatial.Delaunay.find_simplex –

Powiązane problemy