-
Notifications
You must be signed in to change notification settings - Fork 44
/
faceBlendCommon.py
283 lines (226 loc) · 9.32 KB
/
faceBlendCommon.py
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#!/usr/bin/python
# Copyright 2017 BIG VISION LLC ALL RIGHTS RESERVED
#
# This code is made available to the students of
# the online course titled "Computer Vision for Faces"
# by Satya Mallick for personal non-commercial use.
#
# Sharing this code is strictly prohibited without written
# permission from Big Vision LLC.
#
# For licensing and other inquiries, please email
#
import cv2
import dlib
import numpy as np
import math
# Returns 8 points on the boundary of a rectangle
def getEightBoundaryPoints(h, w):
boundaryPts = []
boundaryPts.append((0,0))
boundaryPts.append((w/2, 0))
boundaryPts.append((w-1,0))
boundaryPts.append((w-1, h/2))
boundaryPts.append((w-1, h-1))
boundaryPts.append((w/2, h-1))
boundaryPts.append((0, h-1))
boundaryPts.append((0, h/2))
return np.array(boundaryPts, dtype=np.float)
# Constrains points to be inside boundary
def constrainPoint(p, w, h):
p = (min(max(p[0], 0), w - 1), min(max(p[1], 0), h - 1))
return p
# convert Dlib shape detector object to list of tuples
def dlibLandmarksToPoints(shape):
points = []
for p in shape.parts():
pt = (p.x, p.y)
points.append(pt)
return points
# Compute similarity transform given two sets of two points.
# OpenCV requires 3 pairs of corresponding points.
# We are faking the third one.
def similarityTransform(inPoints, outPoints):
s60 = math.sin(60*math.pi/180)
c60 = math.cos(60*math.pi/180)
inPts = np.copy(inPoints).tolist()
outPts = np.copy(outPoints).tolist()
# The third point is calculated so that the three points make an equilateral triangle
xin = c60*(inPts[0][0] - inPts[1][0]) - s60*(inPts[0][1] - inPts[1][1]) + inPts[1][0]
yin = s60*(inPts[0][0] - inPts[1][0]) + c60*(inPts[0][1] - inPts[1][1]) + inPts[1][1]
inPts.append([np.int(xin), np.int(yin)])
xout = c60*(outPts[0][0] - outPts[1][0]) - s60*(outPts[0][1] - outPts[1][1]) + outPts[1][0]
yout = s60*(outPts[0][0] - outPts[1][0]) + c60*(outPts[0][1] - outPts[1][1]) + outPts[1][1]
outPts.append([np.int(xout), np.int(yout)])
# Now we can use estimateRigidTransform for calculating the similarity transform.
tform = cv2.estimateRigidTransform(np.array([inPts]), np.array([outPts]), False)
return tform
# Normalizes a facial image to a standard size given by outSize.
# Normalization is done based on Dlib's landmark points passed as pointsIn
# After normalization, left corner of the left eye is at (0.3 * w, h/3 )
# and right corner of the right eye is at ( 0.7 * w, h / 3) where w and h
# are the width and height of outSize.
def normalizeImagesAndLandmarks(outSize, imIn, pointsIn):
h, w = outSize
# Corners of the eye in input image
eyecornerSrc = [pointsIn[36], pointsIn[45]]
# Corners of the eye in normalized image
eyecornerDst = [(np.int(0.3 * w), np.int(h/3)),
(np.int(0.7 * w), np.int(h/3))]
# Calculate similarity transform
tform = similarityTransform(eyecornerSrc, eyecornerDst)
imOut = np.zeros(imIn.shape, dtype=imIn.dtype)
# Apply similarity transform to input image
imOut = cv2.warpAffine(imIn, tform, (w, h))
# reshape pointsIn from numLandmarks x 2 to numLandmarks x 1 x 2
points2 = np.reshape(pointsIn, (pointsIn.shape[0], 1, pointsIn.shape[1]))
# Apply similarity transform to landmarks
pointsOut = cv2.transform(points2, tform)
# reshape pointsOut to numLandmarks x 2
pointsOut = np.reshape(pointsOut, (pointsIn.shape[0], pointsIn.shape[1]))
return imOut, pointsOut
# find the point closest to an array of points
# pointsArray is a Nx2 and point is 1x2 ndarray
def findIndex(pointsArray, point):
dist = np.linalg.norm(pointsArray-point, axis=1)
minIndex = np.argmin(dist)
return minIndex
# Check if a point is inside a rectangle
def rectContains(rect, point):
if point[0] < rect[0]:
return False
elif point[1] < rect[1]:
return False
elif point[0] > rect[2]:
return False
elif point[1] > rect[3]:
return False
return True
# Calculate Delaunay triangles for set of points
# Returns the vector of indices of 3 points for each triangle
def calculateDelaunayTriangles(rect, points):
# Create an instance of Subdiv2D
subdiv = cv2.Subdiv2D(rect)
# Insert points into subdiv
for p in points:
subdiv.insert((p[0], p[1]))
# Get Delaunay triangulation
triangleList = subdiv.getTriangleList()
# Find the indices of triangles in the points array
delaunayTri = []
for t in triangleList:
# The triangle returned by getTriangleList is
# a list of 6 coordinates of the 3 points in
# x1, y1, x2, y2, x3, y3 format.
# Store triangle as a list of three points
pt = []
pt.append((t[0], t[1]))
pt.append((t[2], t[3]))
pt.append((t[4], t[5]))
pt1 = (t[0], t[1])
pt2 = (t[2], t[3])
pt3 = (t[4], t[5])
if rectContains(rect, pt1) and rectContains(rect, pt2) and rectContains(rect, pt3):
# Variable to store a triangle as indices from list of points
ind = []
# Find the index of each vertex in the points list
for j in range(0, 3):
for k in range(0, len(points)):
if(abs(pt[j][0] - points[k][0]) < 1.0 and abs(pt[j][1] - points[k][1]) < 1.0):
ind.append(k)
# Store triangulation as a list of indices
if len(ind) == 3:
delaunayTri.append((ind[0], ind[1], ind[2]))
return delaunayTri
# Apply affine transform calculated using srcTri and dstTri to src and
# output an image of size.
def applyAffineTransform(src, srcTri, dstTri, size):
# Given a pair of triangles, find the affine transform.
warpMat = cv2.getAffineTransform(np.float32(srcTri), np.float32(dstTri))
# Apply the Affine Transform just found to the src image
dst = cv2.warpAffine(src, warpMat, (size[0], size[1]), None,
flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101)
return dst
# Warps and alpha blends triangular regions from img1 and img2 to img
def warpTriangle(img1, img2, t1, t2):
# Find bounding rectangle for each triangle
r1 = cv2.boundingRect(np.float32([t1]))
r2 = cv2.boundingRect(np.float32([t2]))
# Offset points by left top corner of the respective rectangles
t1Rect = []
t2Rect = []
t2RectInt = []
for i in range(0, 3):
t1Rect.append(((t1[i][0] - r1[0]), (t1[i][1] - r1[1])))
t2Rect.append(((t2[i][0] - r2[0]), (t2[i][1] - r2[1])))
t2RectInt.append(((t2[i][0] - r2[0]), (t2[i][1] - r2[1])))
# Get mask by filling triangle
mask = np.zeros((r2[3], r2[2], 3), dtype=np.float32)
cv2.fillConvexPoly(mask, np.int32(t2RectInt), (1.0, 1.0, 1.0), 16, 0)
# Apply warpImage to small rectangular patches
img1Rect = img1[r1[1]:r1[1] + r1[3], r1[0]:r1[0] + r1[2]]
size = (r2[2], r2[3])
img2Rect = applyAffineTransform(img1Rect, t1Rect, t2Rect, size)
img2Rect = img2Rect * mask
# Copy triangular region of the rectangular patch to the output image
img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] * ((1.0, 1.0, 1.0) - mask)
img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] + img2Rect
# detect facial landmarks in image
def getLandmarks(faceDetector, landmarkDetector, im, FACE_DOWNSAMPLE_RATIO = 1):
points = []
imSmall = cv2.resize(im,None,
fx=1.0/FACE_DOWNSAMPLE_RATIO,
fy=1.0/FACE_DOWNSAMPLE_RATIO,
interpolation = cv2.INTER_LINEAR)
faceRects = faceDetector(imSmall, 0)
if len(faceRects) > 0:
maxArea = 0
maxRect = None
# TODO: test on images with multiple faces
for face in faceRects:
if face.area() > maxArea:
maxArea = face.area()
maxRect = [face.left(),
face.top(),
face.right(),
face.bottom()
]
rect = dlib.rectangle(*maxRect)
scaledRect = dlib.rectangle(int(rect.left()*FACE_DOWNSAMPLE_RATIO),
int(rect.top()*FACE_DOWNSAMPLE_RATIO),
int(rect.right()*FACE_DOWNSAMPLE_RATIO),
int(rect.bottom()*FACE_DOWNSAMPLE_RATIO))
landmarks = landmarkDetector(im, scaledRect)
points = dlibLandmarksToPoints(landmarks)
return points
# Warps an image in a piecewise affine manner.
# The warp is defined by the movement of landmark points specified by pointsIn
# to a new location specified by pointsOut. The triangulation beween points is specified
# by their indices in delaunayTri.
def warpImage(imIn, pointsIn, pointsOut, delaunayTri):
h, w, ch = imIn.shape
# Output image
imOut = np.zeros(imIn.shape, dtype=imIn.dtype)
# Warp each input triangle to output triangle.
# The triangulation is specified by delaunayTri
for j in range(0, len(delaunayTri)):
# Input and output points corresponding to jth triangle
tin = []
tout = []
for k in range(0, 3):
# Extract a vertex of input triangle
pIn = pointsIn[delaunayTri[j][k]]
# Make sure the vertex is inside the image.
pIn = constrainPoint(pIn, w, h)
# Extract a vertex of the output triangle
pOut = pointsOut[delaunayTri[j][k]]
# Make sure the vertex is inside the image.
pOut = constrainPoint(pOut, w, h)
# Push the input vertex into input triangle
tin.append(pIn)
# Push the output vertex into output triangle
tout.append(pOut)
# Warp pixels inside input triangle to output triangle.
warpTriangle(imIn, imOut, tin, tout)
return imOut