是否有一个用于图像变形/图像变形的库,用于具有受控点的python?

sml*_*mln 8 python image-manipulation image-processing

您将拍摄图像并标记特定点(例如,标记人眼睛,鼻子,嘴巴周围的区域),然后将它们转换为标记为另一图像的点.就像是:

transform(original_image, marked_points_in_the_original, marked_points_in_the_reference)
Run Code Online (Sandbox Code Playgroud)

我似乎找不到描述它的算法,也找不到任何库.我也愿意自己做,只要我能找到好的/易于遵循的材料.我知道这是可能的,因为我已经看到一些不完整的(不是真的解释怎么做).pdfs谷歌与它.

以下是标记点和转换的示例,因为您要求澄清.虽然我之前说的这个并没有使用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)是屏幕最左上角.如果我做的一切都正确,那么这种方法并没有真正做到我需要的.

War*_*ean 8

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()
Run Code Online (Sandbox Code Playgroud)

之前: 在此输入图像描述 后: 在此输入图像描述

我建议在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]]]
Run Code Online (Sandbox Code Playgroud)

  • 如何指定图像的哪些部分要变形? (3认同)

Geo*_*nza 7

在类似的说明中,您可以使用ImageMagick的Python API来执行Shepards的失真.

考拉耳朵 考拉耳朵拉


pas*_*cha 6

我通过对变换点进行三角测量,得到了使用 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()
Run Code Online (Sandbox Code Playgroud)

src_points主函数中的 和是dst_points硬编码的,对应于上图中标记为绿色的地标。该代码部分受到这篇在线文章的启发,但代码被清理了一些。回答这个问题后,我还使用交互式 python 应用程序创建了自己的FaceChanger github repo,使用与此答案中描述的相同功能。

要求

怎么运行的

三角测量

首先,我们需要对图像进行三角测量,这会将顶部两个图像中的点转换为底部的三角形。我们需要三角形而不是点,因为这使我们能够单独变换各个三角形,这将使我们的生活变得更轻松。三角测量是使用Delaunay Triangluation和 OpenCV 完成的。第一个和第二个图像的点不一定会产生相同的三角剖分,因此该get_triangulation_indices函数返回每个三角形的所有角的索引。使用这些索引,我们可以将每个源三角形映射到一个目标三角形

扭曲三角形

使用 OpenCV 的方法使三角形变形warpAffine。这种方法的问题在于它会扭曲整个图像而不仅仅是一个三角形,因此我们必须在那里做更多的工作才能仅扭曲三角形。

剪出三角形

首先,我们仅剪切源图像和目标图像中包含源三角形或目标三角形的部分。理论上这不是必需的,但这样会快得多,因为这样我们就不必每次都对整个图像进行扭曲。这是使用crop_to_triangle- 方法完成的。

变换图像

然后我们看到如何扭曲图像才能从源三角形到目标三角形,其中cv2.getAffineTransform。这将为我们提供一个变换矩阵,我们可以使用它来cv2.warpAffine将图像扭曲到目标比例。

蒙版到三角形

现在我们遇到的问题是,扭曲变换不仅变换了我们的三角形,还变换了整个src_img_cropped. 所以现在我们必须只将属于三角形的像素传递到目标图像。我们可以使用它cv2.fillConvexPoly来创建目标三角形的蒙版,并使用它从目标图像中删除我们要粘贴的三角形内的所有像素,以便将扭曲的三角形添加到我们刚刚清空的位置。这是使用 Numpy 数组操作完成的。

结论

这是实现该任务的相当简单的方法。然而,有时它确实会导致一些看起来不自然的直边,因此可能并不适合所有用途。然而,如果您向源图像和目标图像添加更多点,结果的质量就会提高。如果您希望复制整个图像,您还需要将源图像和目标图像的角添加到您的点,否则目标图像将被源图像的部分内容覆盖,我认为这是一个功能。这也可以与面部检测结合起来创建面部交换效果,我个人使用dlib,它给出了很好的结果。


Ble*_*der 5

是的,有.它有点低级,但PIL(Python Imaging Library)具有执行此类转换的功能.我从来没有真正为它工作(因为我的问题有点简单),但你可以玩它.

这是PIL转换的一个很好的资源(你想看看MESH):http://effbot.org/tag/PIL.Image.Image.transform.


从文档:

与QUAD类似,但数据是目标矩形和相应的源四边形的列表.

im.transform(size, MESH, data)
Run Code Online (Sandbox Code Playgroud)

数据是矩形的元组:

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

它将第一个矩形转换为第二个矩形.