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)是屏幕最左上角.如果我做的一切都正确,那么这种方法并没有真正做到我需要的.
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)
我通过对变换点进行三角测量,得到了使用 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,它给出了很好的结果。
是的,有.它有点低级,但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)
它将第一个矩形转换为第二个矩形.
| 归档时间: |
|
| 查看次数: |
14806 次 |
| 最近记录: |