• 主页
所有文章 关于我

  • 主页

利用dlib进行图片换脸

2016-12-17

本文基于http://python.jobbole.com/82546/ 。

在使用文中的python代码时,需要准备好一个face_landmarks的特征库,这个特征库属于dlib的一部分,可以在这里下载

开始前需要先将依赖库将好,我使用的是fedora+python2,可以直接使用dnf来安装。

1
2
3
dnf install python2-numpy
dnf install python2-dlib
dnf install opencv-python

示例中的图片谷歌了一个,也使用了同样的人物头像,如下


然后直接输入face_landmark_detection的路径,以及两位主角的图片路径作为参数,启动python脚本即可。

过程中可能会遇到以下的几个问题:

Traceback (most recent call last):
File “./face_change.py”, line 156, in
im1, landmarks1 = read_im_and_landmarks(sys.argv[1])
IndexError: list index out of range

这是没有正确地传入参数。

Traceback (most recent call last):
File “./face_change.py”, line 168, in
warped_corrected_im2 = correct_colours(im1, warped_im2, landmarks1)
File “./face_change.py”, line 151, in correct_colours
im2_blur += 128 * (im2_blur <= 1.0)
TypeError: Cannot cast ufunc add output from dtype(‘int64’) to dtype(‘uint8’) with casting rule ‘same_kind’

由于我使用的是64位系统,所以显示”dtype(‘int64’) to dtype(‘uint8’)”,如果是32位的可能会显示“dtype(‘int32’) to dtype(‘uint8’)”,这是类型转换的问题,需要将

1
2
3
4
5
6
7
8
 .....
im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0)

# Avoid divide-by-zero errors.
** im2_blur += 128 * (im2_blur <= 1.0)**

return (im2.astype(numpy.float64) * im1_blur.astype(numpy.float64) /
.....

修改为

1
2
3
4
5
6
7
8
.....
im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0)

# Avoid divide-by-zero errors.
**im2_blur += (128 * (im2_blur <= 1.0)).astype(im2_blur.dtype)**

return (im2.astype(numpy.float64) * im1_blur.astype(numpy.float64)
.....

修改后的完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#!/usr/bin/python

import cv2
import dlib
import numpy

import sys

PREDICTOR_PATH = sys.argv[1]
SCALE_FACTOR = 1
FEATHER_AMOUNT = 11

FACE_POINTS = list(range(17, 68))
MOUTH_POINTS = list(range(48, 61))
RIGHT_BROW_POINTS = list(range(17, 22))
LEFT_BROW_POINTS = list(range(22, 27))
RIGHT_EYE_POINTS = list(range(36, 42))
LEFT_EYE_POINTS = list(range(42, 48))
NOSE_POINTS = list(range(27, 35))
JAW_POINTS = list(range(0, 17))

# Points used to line up the images.
ALIGN_POINTS = (LEFT_BROW_POINTS + RIGHT_EYE_POINTS + LEFT_EYE_POINTS +
RIGHT_BROW_POINTS + NOSE_POINTS + MOUTH_POINTS)

# Points from the second image to overlay on the first. The convex hull of each
# element will be overlaid.
OVERLAY_POINTS = [
LEFT_EYE_POINTS + RIGHT_EYE_POINTS + LEFT_BROW_POINTS + RIGHT_BROW_POINTS,
NOSE_POINTS + MOUTH_POINTS,
]

# Amount of blur to use during colour correction, as a fraction of the
# pupillary distance.
COLOUR_CORRECT_BLUR_FRAC = 0.6

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(PREDICTOR_PATH)

class TooManyFaces(Exception):
pass

class NoFaces(Exception):
pass

def get_landmarks(im):
rects = detector(im, 1)

if len(rects) > 1:
raise TooManyFaces
if len(rects) == 0:
raise NoFaces

return numpy.matrix([[p.x, p.y] for p in predictor(im, rects[0]).parts()])

def annotate_landmarks(im, landmarks):
im = im.copy()
for idx, point in enumerate(landmarks):
pos = (point[0, 0], point[0, 1])
cv2.putText(im, str(idx), pos,
fontFace=cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,
fontScale=0.4,
color=(0, 0, 255))
cv2.circle(im, pos, 3, color=(0, 255, 255))
return im

def draw_convex_hull(im, points, color):
points = cv2.convexHull(points)
cv2.fillConvexPoly(im, points, color=color)

def get_face_mask(im, landmarks):
im = numpy.zeros(im.shape[:2], dtype=numpy.float64)

for group in OVERLAY_POINTS:
draw_convex_hull(im,
landmarks[group],
color=1)

im = numpy.array([im, im, im]).transpose((1, 2, 0))

im = (cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0) > 0) * 1.0
im = cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0)

return im

def transformation_from_points(points1, points2):
"""
Return an affine transformation [s * R | T] such that:
sum ||s*R*p1,i + T - p2,i||^2
is minimized.
"""
# Solve the procrustes problem by subtracting centroids, scaling by the
# standard deviation, and then using the SVD to calculate the rotation. See
# the following for more details:
# https://en.wikipedia.org/wiki/Orthogonal_Procrustes_problem

points1 = points1.astype(numpy.float64)
points2 = points2.astype(numpy.float64)

c1 = numpy.mean(points1, axis=0)
c2 = numpy.mean(points2, axis=0)
points1 -= c1
points2 -= c2

s1 = numpy.std(points1)
s2 = numpy.std(points2)
points1 /= s1
points2 /= s2

U, S, Vt = numpy.linalg.svd(points1.T * points2)

# The R we seek is in fact the transpose of the one given by U * Vt. This
# is because the above formulation assumes the matrix goes on the right
# (with row vectors) where as our solution requires the matrix to be on the
# left (with column vectors).
R = (U * Vt).T

return numpy.vstack([numpy.hstack(((s2 / s1) * R,
c2.T - (s2 / s1) * R * c1.T)),
numpy.matrix([0., 0., 1.])])

def read_im_and_landmarks(fname):
im = cv2.imread(fname, cv2.IMREAD_COLOR)
im = cv2.resize(im, (im.shape[1] * SCALE_FACTOR,
im.shape[0] * SCALE_FACTOR))
s = get_landmarks(im)

return im, s

def warp_im(im, M, dshape):
output_im = numpy.zeros(dshape, dtype=im.dtype)
cv2.warpAffine(im,
M[:2],
(dshape[1], dshape[0]),
dst=output_im,
borderMode=cv2.BORDER_TRANSPARENT,
flags=cv2.WARP_INVERSE_MAP)
return output_im

def correct_colours(im1, im2, landmarks1):
blur_amount = COLOUR_CORRECT_BLUR_FRAC * numpy.linalg.norm(
numpy.mean(landmarks1[LEFT_EYE_POINTS], axis=0) -
numpy.mean(landmarks1[RIGHT_EYE_POINTS], axis=0))
blur_amount = int(blur_amount)
if blur_amount % 2 == 0:
blur_amount += 1
im1_blur = cv2.GaussianBlur(im1, (blur_amount, blur_amount), 0)
im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0)

# Avoid divide-by-zero errors.
im2_blur += (128 * (im2_blur <= 1.0)).astype(im2_blur.dtype)

return (im2.astype(numpy.float64) * im1_blur.astype(numpy.float64) /
im2_blur.astype(numpy.float64))

im1, landmarks1 = read_im_and_landmarks(sys.argv[2])
im2, landmarks2 = read_im_and_landmarks(sys.argv[3])

M = transformation_from_points(landmarks1[ALIGN_POINTS],
landmarks2[ALIGN_POINTS])

mask = get_face_mask(im2, landmarks2)
warped_mask = warp_im(mask, M, im1.shape)
combined_mask = numpy.max([get_face_mask(im1, landmarks1), warped_mask],
axis=0)

warped_im2 = warp_im(im2, M, im1.shape)
warped_corrected_im2 = correct_colours(im1, warped_im2, landmarks1)

output_im = im1 * (1.0 - combined_mask) + warped_corrected_im2 * combined_mask

cv2.imwrite('output.jpg', output_im)

输入命令:

./xxx.py {landmarks} {pic1} {pic2}

最终会在同目录生成一个output.jpg,上述pic1与pic2的位置可换,使用pic2的脸,使用pic1的其他,生成的两张图片如下:
](/uploads/2016/12/output.jpg)

  • 生活常识

扫一扫,分享到微信

微信分享二维码
Build LineageOS under Fedora
泛型参数转换问题
© 2022 SamsenLee
Hexo Theme Yilia fork from Litten
  • 所有文章
  • 关于我

tag:

  • Security
  • Linux
  • Android
  • AES
  • UnitTest
  • Java
  • BUCK
  • django
  • Javassist
  • 生活常识
  • lint
  • 想多了
  • 还在的青春
  • 未分类
  • 生活技能
  • Eclipse
  • Vim
  • Questions
  • Embedded
  • Mysql
  • Functional Progamming
  • Scala
  • Algorithm
  • java
  • Python
  • plugin
  • resource
  • asset
  • webview
  • Zip
  • iOS
  • Travel

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

mail:kofmax28@gmail.com