Има ли библиотека за изкривяване на изображение / преобразуване на изображение за python с контролирани точки? [затворено]

Ще направите изображения и ще маркирате конкретни точки (например маркирайте областта около очите, носа, устата и т.н. на хората) и след това ще ги трансформирате в маркираните точки в друго изображение. Нещо като:

transform(original_image, marked_points_in_the_original, marked_points_in_the_reference)

Изглежда не мога да намеря алгоритъм, който да го описва, нито мога да намеря библиотеки с него. Готов съм да го направя и сам, стига да намеря добър/лесен за следване материал за него. Знам обаче, че е възможно, тъй като видях някои непълни (наистина не обяснявайте как да го направя) .pdfs в google с него.

Ето пример за отбелязаните точки и трансформацията, тъй като поискахте пояснение. Въпреки че този не използва 2 души, както казах по-рано.


Редактиране: успях да накарам метода im.transform да работи, но аргументът е списък от ((box_x, box_y, box_width, box_height), (x0, y0, x1, y1, x2, y2, x3, y3)), като първата точка е NW, втората SW, третата NE и четвъртата SE. (0, 0) е най-лявата горна част на екрана, доколкото мога да разбера. Ако съм направил всичко правилно, тогава този метод всъщност не прави това, от което се нуждая.


person smln    schedule 21.02.2011    source източник
comment
трансформирам е неясна дума. Можете ли да обясните какво имате предвид? Искате ли да копирате части от едно изображение и да ги поставите директно в друго? Какъв регион?   -  person Devin Jeanpierre    schedule 21.02.2011


Отговори (4)


Примерният код, даден от Blender, не работи за мен. Освен това документацията на PIL за im.transform е двусмислена. Така че се зарових в изходния код на PIL и най-накрая разбрах как да използвам интерфейса. Ето моето пълно използване:

import numpy as np
from PIL import Image

def quad_as_rect(quad):
    if quad[0] != quad[2]: return False
    if quad[1] != quad[7]: return False
    if quad[4] != quad[6]: return False
    if quad[3] != quad[5]: return False
    return True

def quad_to_rect(quad):
    assert(len(quad) == 8)
    assert(quad_as_rect(quad))
    return (quad[0], quad[1], quad[4], quad[3])

def rect_to_quad(rect):
    assert(len(rect) == 4)
    return (rect[0], rect[1], rect[0], rect[3], rect[2], rect[3], rect[2], rect[1])

def shape_to_rect(shape):
    assert(len(shape) == 2)
    return (0, 0, shape[0], shape[1])

def griddify(rect, w_div, h_div):
    w = rect[2] - rect[0]
    h = rect[3] - rect[1]
    x_step = w / float(w_div)
    y_step = h / float(h_div)
    y = rect[1]
    grid_vertex_matrix = []
    for _ in range(h_div + 1):
        grid_vertex_matrix.append([])
        x = rect[0]
        for _ in range(w_div + 1):
            grid_vertex_matrix[-1].append([int(x), int(y)])
            x += x_step
        y += y_step
    grid = np.array(grid_vertex_matrix)
    return grid

def distort_grid(org_grid, max_shift):
    new_grid = np.copy(org_grid)
    x_min = np.min(new_grid[:, :, 0])
    y_min = np.min(new_grid[:, :, 1])
    x_max = np.max(new_grid[:, :, 0])
    y_max = np.max(new_grid[:, :, 1])
    new_grid += np.random.randint(- max_shift, max_shift + 1, new_grid.shape)
    new_grid[:, :, 0] = np.maximum(x_min, new_grid[:, :, 0])
    new_grid[:, :, 1] = np.maximum(y_min, new_grid[:, :, 1])
    new_grid[:, :, 0] = np.minimum(x_max, new_grid[:, :, 0])
    new_grid[:, :, 1] = np.minimum(y_max, new_grid[:, :, 1])
    return new_grid

def grid_to_mesh(src_grid, dst_grid):
    assert(src_grid.shape == dst_grid.shape)
    mesh = []
    for i in range(src_grid.shape[0] - 1):
        for j in range(src_grid.shape[1] - 1):
            src_quad = [src_grid[i    , j    , 0], src_grid[i    , j    , 1],
                        src_grid[i + 1, j    , 0], src_grid[i + 1, j    , 1],
                        src_grid[i + 1, j + 1, 0], src_grid[i + 1, j + 1, 1],
                        src_grid[i    , j + 1, 0], src_grid[i    , j + 1, 1]]
            dst_quad = [dst_grid[i    , j    , 0], dst_grid[i    , j    , 1],
                        dst_grid[i + 1, j    , 0], dst_grid[i + 1, j    , 1],
                        dst_grid[i + 1, j + 1, 0], dst_grid[i + 1, j + 1, 1],
                        dst_grid[i    , j + 1, 0], dst_grid[i    , j + 1, 1]]
            dst_rect = quad_to_rect(dst_quad)
            mesh.append([dst_rect, src_quad])
    return mesh

im = Image.open('./old_driver/data/train/c0/img_292.jpg')
dst_grid = griddify(shape_to_rect(im.size), 4, 4)
src_grid = distort_grid(dst_grid, 50)
mesh = grid_to_mesh(src_grid, dst_grid)
im = im.transform(im.size, Image.MESH, mesh)
im.show()

Преди: въведете описание на изображението тук След: въведете описание на изображението тук

Предлагам да изпълните горния код в iPython, след което да отпечатате mesh, за да разберете какъв вид въвеждане е необходимо за im.transform. За мен резултатът е:

In [1]: mesh
Out[1]:
[[(0, 0, 160, 120), [0, 29, 29, 102, 186, 120, 146, 0]],
 [(160, 0, 320, 120), [146, 0, 186, 120, 327, 127, 298, 48]],
 [(320, 0, 480, 120), [298, 48, 327, 127, 463, 77, 492, 26]],
 [(480, 0, 640, 120), [492, 26, 463, 77, 640, 80, 605, 0]],
 [(0, 120, 160, 240), [29, 102, 9, 241, 162, 245, 186, 120]],
 [(160, 120, 320, 240), [186, 120, 162, 245, 339, 214, 327, 127]],
 [(320, 120, 480, 240), [327, 127, 339, 214, 513, 284, 463, 77]],
 [(480, 120, 640, 240), [463, 77, 513, 284, 607, 194, 640, 80]],
 [(0, 240, 160, 360), [9, 241, 27, 364, 202, 365, 162, 245]],
 [(160, 240, 320, 360), [162, 245, 202, 365, 363, 315, 339, 214]],
 [(320, 240, 480, 360), [339, 214, 363, 315, 453, 373, 513, 284]],
 [(480, 240, 640, 360), [513, 284, 453, 373, 640, 319, 607, 194]],
 [(0, 360, 160, 480), [27, 364, 33, 478, 133, 480, 202, 365]],
 [(160, 360, 320, 480), [202, 365, 133, 480, 275, 480, 363, 315]],
 [(320, 360, 480, 480), [363, 315, 275, 480, 434, 469, 453, 373]],
 [(480, 360, 640, 480), [453, 373, 434, 469, 640, 462, 640, 319]]]
person Warbean    schedule 20.05.2016
comment
как обаче определяте кои части от изображението да бъдат деформирани? - person azal; 16.10.2018

По подобен начин можете да използвате Python API на ImageMagick, за да направите Изкривяването на Shepards.

уши на коалакоала дърпа ушите

person George Profenza    schedule 21.02.2011
comment
Това изглежда подобно на това, което се опитвам да направя. Ще го пробвам и ще докладвам, ако успея да го накарам да работи. - person smln; 22.02.2011
comment
ImageMagick работи, но резултатите, които получих, не бяха толкова добри, колкото се надявах. - person smln; 22.02.2011
comment
това работи доста добре за създаване на анимирани GIF файлове. приложих постепенните изкривявания на Шепърд с промяна на алфа за две изображения и резултатът изглежда така: i.imgur.com/ 1Lh4i.gif благодаря за съвета! - person Murat Ayfer; 12.12.2011
comment
@GeorgeProfenza всъщност ето крайния продукт :) muratayfer.com/morphin всичко това благодарение на тази тема. - person Murat Ayfer; 02.01.2012

Да, има. Това е малко на ниско ниво, но PIL (библиотеката за изображения на Python) има функция, която трябва да направи този вид трансформация. Никога не ми се е случвало да работи (тъй като моят проблем беше малко по-прост), но можете да си поиграете с него.

Ето един добър ресурс за трансформациите на PIL (бихте искали да погледнете MESH): http://effbot.org/tag/PIL.Image.Image.transform.


От документацията:

Подобно на QUAD, но данните са списък от целеви правоъгълници и съответните изходни четириъгълници.

im.transform(size, MESH, data)

Данните са набор от правоъгълници:

data = [((a, b, c, d), (e, f, g, h)), 
        ((i, j, k, l), (m, n, o, p))]

Той трансформира първия правоъгълник във втория.

person Blender    schedule 21.02.2011
comment
Чел съм тази документация и преди, но не разбирам какво влиза в тази функция. Бихте ли обяснили параметрите му? Ако го чета правилно, всичко, което трябва да направя, е да създам карта с оригиналните координати -› нови координати и да я предам като MESH и тя работи? - person smln; 22.02.2011
comment
Повече или по-малко. mesh трябва да се състои от двойки правоъгълници, но можете да направите изродени 1x1 правоъгълници. Колкото по-голям е размерът, толкова по-бързо върви. - person Blender; 22.02.2011

Имам решение, използващо OpenCV чрез триангулиране на точките на трансформация:
Трансформация

Не изглежда перфектно, но с повече точки върху изходното/целевото изображение резултатите стават по-добри.

Код

Ето кода, който използвах за трансформацията, в долната част можете да видите как да извикате вашата transform функция.

#!/bin/env python3

import cv2
import numpy as np

def get_triangulation_indices(points):
    """Get indices triples for every triangle
    """
    # Bounding rectangle
    bounding_rect = (*points.min(axis=0), *points.max(axis=0))

    # Triangulate all points
    subdiv = cv2.Subdiv2D(bounding_rect)
    subdiv.insert(list(points))

    # Iterate over all triangles
    for x1, y1, x2, y2, x3, y3 in subdiv.getTriangleList():
        # Get index of all points
        yield [(points==point).all(axis=1).nonzero()[0][0] for point in [(x1,y1), (x2,y2), (x3,y3)]]

def crop_to_triangle(img, triangle):
    """Crop image to triangle
    """
    # Get bounding rectangle
    bounding_rect = cv2.boundingRect(triangle)

    # Crop image to bounding box
    img_cropped = img[bounding_rect[1]:bounding_rect[1] + bounding_rect[3],
                      bounding_rect[0]:bounding_rect[0] + bounding_rect[2]]
    # Move triangle to coordinates in cropped image
    triangle_cropped = [(point[0]-bounding_rect[0], point[1]-bounding_rect[1]) for point in triangle]
    return triangle_cropped, img_cropped

def transform(src_img, src_points, dst_img, dst_points): 
    """Transforms source image to target image, overwriting the target image.
    """
    for indices in get_triangulation_indices(src_points):
        # Get triangles from indices
        src_triangle = src_points[indices]
        dst_triangle = dst_points[indices]

        # Crop to triangle, to make calculations more efficient
        src_triangle_cropped, src_img_cropped = crop_to_triangle(src_img, src_triangle)
        dst_triangle_cropped, dst_img_cropped = crop_to_triangle(dst_img, dst_triangle)

        # Calculate transfrom to warp from old image to new
        transform = cv2.getAffineTransform(np.float32(src_triangle_cropped), np.float32(dst_triangle_cropped))

        # Warp image
        dst_img_warped = cv2.warpAffine(src_img_cropped, transform, (dst_img_cropped.shape[1], dst_img_cropped.shape[0]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 )

        # Create mask for the triangle we want to transform
        mask = np.zeros(dst_img_cropped.shape, dtype = np.uint8)
        cv2.fillConvexPoly(mask, np.int32(dst_triangle_cropped), (1.0, 1.0, 1.0), 16, 0);

        # Delete all existing pixels at given mask
        dst_img_cropped*=1-mask
        # Add new pixels to masked area
        dst_img_cropped+=dst_img_warped*mask

if __name__ == "__main__":
    # Inputs
    src_img = cv2.imread("woman.jpg")
    dst_img = cv2.imread("cheetah.jpg")
    src_points = np.array([(40, 27), (38, 65), (47, 115), (66, 147), (107, 166), (147, 150), (172, 118), (177, 75), (173, 26), (63, 19), (89, 30), (128, 34), (152, 27), (75, 46), (142, 46), (109, 48), (95, 96), (107, 91), (120, 97), (84, 123), (106, 117), (132, 121), (97, 137), (107, 139), (120, 135)])
    dst_points = np.array([(2, 16), (0, 60), (2, 143), (47, 181), (121, 178), (208, 181), (244, 133), (241, 87), (241, 18), (41, 15), (73, 20), (174, 16), (218, 16), (56, 23), (191, 23), (120, 48), (94, 128), (120, 122), (150, 124), (83, 174), (122, 164), (159, 173), (110, 174), (121, 174), (137, 175)])

    # Apply transformation
    transform(src_img, src_points, dst_img, dst_points)

    # Show result
    cv2.imshow("Transformed", dst_img)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

src_points и dst_points в основната функция са твърдо кодирани и съответстват на ориентирите, които са маркирани в зелено на изображенията по-горе. Кодът е частично вдъхновен от тази онлайн статия, но кодът беше малко почистен. След като отговорих на този въпрос, създадох и мое собствено FaceChanger github repo с интерактивно приложение на Python, използвайки същата функционалност, както е описано в този отговор.

Изисквания

  • Numpy: pip3 install numpy
  • OpenCV: pip3 install opencv-python

Как работи

Триангулация

Първо трябва да триангулираме изображението, което ще трансформира точките от двете горни изображения в триъгълниците в долната част. Трябват ни триъгълници вместо точки, защото това ни позволява да трансформираме отделните триъгълници поотделно, което ще улесни живота ни по-нататък. Триангулацията се извършва с помощта на Delaunay Triangluation с OpenCV. Точките на първото и второто изображение не е задължително да водят до една и съща триангулация, следователно функцията get_triangulation_indices връща индексите на всички ъгли за всеки триъгълник. Използвайки тези индекси, можем да съпоставим всеки изходен триъгълник към един целеви триъгълник

Деформирани триъгълници

Триъгълниците са изкривени с помощта на warpAffine-метода на OpenCV. Проблемът с този метод е, че изкривява цялото изображение, а не само един триъгълник, така че трябва да свършим още малко работа, за да изкривим само триъгълници.

Изрежете триъгълник

Първо изрязваме само частта от изходното и целевото изображение, която съдържа изходния или целевия триъгълник. Теоретично това не би било необходимо, но така е много по-бързо, защото тогава не е нужно да правим изкривяването на цялото изображение всеки път. Това се прави с помощта на crop_to_triangle-метода.

Трансформиране на изображението

След това виждаме как трябва да изкривим изображението, за да стигнем от изходния триъгълник до целевия триъгълник, с cv2.getAffineTransform. Това ще ни даде матрица за трансформация, която можем да използваме с cv2.warpAffine, за да деформираме изображението до целевите пропорции.

Маска към триъгълник

Сега имаме проблем, че деформационната трансформация не трансформира само нашите триъгълници, но и цялото ни src_img_cropped. Така че сега трябва да преминем само пикселите, принадлежащи на нашия триъгълник към целевото изображение. Можем да използваме cv2.fillConvexPoly, за да създадем маска на нашия целеви триъгълник и да използваме това, за да изтрием всички пиксели от целевото изображение, които са в рамките на триъгълника, който искаме да поставим, за да добавим изкривения триъгълник към това място, което току-що изпразнихме. Това се прави с помощта на манипулации с масив Numpy.

Заключение

Това е доста прост метод за постигане на задачата. Това обаче понякога води до неестествено изглеждащи прави ръбове и следователно може да не е идеално за всички приложения. Качеството на резултата обаче се повишава, ако добавите повече точки към вашите изходни и целеви изображения. Трябва също така да добавите ъглите на изходното и целевото изображение към вашите точки, ако искате цялото изображение да бъде копирано, в противен случай целевото изображение просто ще бъде презаписано с части от изходното изображение, което аз виждам като функция. Това също може да се комбинира с разпознаване на лица, за да се създаде ефект на размяна на лица, за който аз лично използвам dlib, което дава страхотни резултати.

person pascscha    schedule 26.12.2020
comment
Това трябва да остане в историята като един от най-добрите отговори на StackOverflow! В момента работя върху изключително важен личен проект и търся нещо подобно от векове. Ако искам да науча повече за тази методология, имате ли препоръки за курсове/ресурси онлайн, където мога да потърся повече информация? Запознат съм с хомографията и други глобални операции за изкривяване на изображението, но съм съвсем нов в локалното преобразуване. - person kairocks2002; 30.05.2021