Skip to content

Commit

Permalink
replace face landmark detector
Browse files Browse the repository at this point in the history
  • Loading branch information
wuhuikai committed Mar 26, 2019
1 parent c5f32f8 commit 53c3028
Show file tree
Hide file tree
Showing 12 changed files with 16 additions and 126 deletions.
24 changes: 0 additions & 24 deletions face_detection.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import cv2
import dlib
import json
import argparse

## Face detection
def face_detection(img,upsample_times=1):
Expand All @@ -12,24 +9,3 @@ def face_detection(img,upsample_times=1):
faces = detector(img, upsample_times)

return faces

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Face Detection')
parser.add_argument('--img_path', required=True, help='Path for input image')
parser.add_argument('--out', required=True, help='Path for storing bboxes of faces')
args = parser.parse_args()

# ## Debug
# args.img_path = 'imgs/multi_faces.jpg'
# args.out = 'results/multi_faces.faces.json'

# Read images
img = cv2.imread(args.img_path)
faces = face_detection(img)
bboxs = []

for face in faces:
bboxs.append((face.left(), face.top(), face.right(), face.bottom()))

with open(args.out, 'w') as f:
json.dump(bboxs, f)
30 changes: 1 addition & 29 deletions face_points_detection.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
#! /usr/bin/env python
import json
import cv2
import dlib
import argparse
import numpy as np

PREDICTOR_PATH = 'models/shape_predictor_68_face_landmarks.dat'
PREDICTOR_PATH = 'models/shape_predictor_81_face_landmarks.dat'
predictor = dlib.shape_predictor(PREDICTOR_PATH)
## Face and points detection
def face_points_detection(img, bbox:dlib.rectangle):
Expand All @@ -18,28 +15,3 @@ def face_points_detection(img, bbox:dlib.rectangle):

# return the array of (x, y)-coordinates
return coords

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='FaceSwap Demo')
parser.add_argument('--img_path', required=True, help='Path for input image')
parser.add_argument('--bbox_path', required=True, help='Path for bboxes')
parser.add_argument('--index', type=int, default=0, help='Which bbox to use')
parser.add_argument('--out', required=True, help='Path for storing face points')
args = parser.parse_args()

# ## Debug
# args.img_path = 'imgs/multi_faces.jpg'
# args.bbox_path = 'results/multi_faces.faces.json'
# args.index = 0
# args.out = 'results/multi_faces.points.json'

# Read images
img = cv2.imread(args.img_path)
with open(args.bbox_path) as f:
bbox = dlib.rectangle(*json.load(f)[args.index])

# Array of corresponding points
points = face_points_detection(img, bbox)

with open(args.out, 'w') as f:
json.dump(points, f)
86 changes: 14 additions & 72 deletions face_swap.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
#! /usr/bin/env python
import os
import cv2
import json
import argparse
import numpy as np
import scipy.spatial as spatial
import logging


## 3D Transform
def bilinear_interpolate(img, coords):
""" Interpolates over every image channel
Expand Down Expand Up @@ -39,9 +38,11 @@ def grid_coordinates(points):
xmax = np.max(points[:, 0]) + 1
ymin = np.min(points[:, 1])
ymax = np.max(points[:, 1]) + 1

return np.asarray([(x, y) for y in range(ymin, ymax)
for x in range(xmin, xmax)], np.uint32)


def process_warp(src_img, result_img, tri_affines, dst_points, delaunay):
"""
Warp each triangle from the src_image only within the
Expand All @@ -59,9 +60,9 @@ def process_warp(src_img, result_img, tri_affines, dst_points, delaunay):
x, y = coords.T
result_img[y, x] = bilinear_interpolate(src_img, out_coords)


return None


def triangular_affine_matrices(vertices, src_points, dst_points):
"""
Calculate the affine transformation matrix for each
Expand All @@ -78,6 +79,7 @@ def triangular_affine_matrices(vertices, src_points, dst_points):
mat = np.dot(src_tri, np.linalg.inv(dst_tri))[:2, :]
yield mat


def warp_image_3d(src_img, src_points, dst_points, dst_shape, dtype=np.uint8):
rows, cols = dst_shape[:2]
result_img = np.zeros((rows, cols, 3), dtype=dtype)
Expand All @@ -90,6 +92,7 @@ def warp_image_3d(src_img, src_points, dst_points, dst_shape, dtype=np.uint8):

return result_img


## 2D Transform
def transformation_from_points(points1, points2):
points1 = points1.astype(np.float64)
Expand All @@ -112,6 +115,7 @@ def transformation_from_points(points1, points2):
(c2.T - np.dot(s2 / s1 * R, c1.T))[:, np.newaxis]]),
np.array([[0., 0., 1.]])])


def warp_image_2d(im, M, dshape):
output_im = np.zeros(dshape, dtype=im.dtype)
cv2.warpAffine(im,
Expand All @@ -120,8 +124,10 @@ def warp_image_2d(im, M, dshape):
dst=output_im,
borderMode=cv2.BORDER_TRANSPARENT,
flags=cv2.WARP_INVERSE_MAP)

return output_im


## Generate Mask
def mask_from_points(size, points,erode_flag=1):
radius = 10 # kernel size
Expand All @@ -134,6 +140,7 @@ def mask_from_points(size, points,erode_flag=1):

return mask


## Color Correction
def correct_colours(im1, im2, landmarks1):
COLOUR_CORRECT_BLUR_FRAC = 0.75
Expand All @@ -158,6 +165,7 @@ def correct_colours(im1, im2, landmarks1):

return result


## Copy-and-paste
def apply_mask(img, mask):
""" Apply mask to supplied image
Expand All @@ -169,6 +177,7 @@ def apply_mask(img, mask):

return masked_img


## Alpha blending
def alpha_feathering(src_img, dest_img, img_mask, blur_radius=15):
mask = cv2.blur(img_mask, (blur_radius, blur_radius))
Expand All @@ -180,78 +189,11 @@ def alpha_feathering(src_img, dest_img, img_mask, blur_radius=15):

return result_img


def check_points(img,points):
# Todo: I just consider one situation.
if points[8,1]>img.shape[0]:
logging.error("Jaw part out of image")
else:
return True
return False

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='FaceSwap Demo')
parser.add_argument('--src_img', required=True, help='Path for source image')
parser.add_argument('--dst_img', required=True, help='Path for target image')
parser.add_argument('--mask_img', default=None, help='Path for mask image')
parser.add_argument('--src_points', required=True, help='Path for source face points')
parser.add_argument('--dst_points', required=True, help='Path for target face points')
parser.add_argument('--out', required=True, help='Path for storing output image')
args = parser.parse_args()

# ## Debug
# args.src_img = 'imgs/I.jpg'
# args.src_points = 'results/I.points.json'
# args.dst_img = 'imgs/multi_faces.jpg'
# args.dst_points = 'results/multi_faces.points.json'
# args.mask_img = None
# args.out = 'results/output.jpg'

# Read images
src_img = cv2.imread(args.src_img)
dst_img = cv2.imread(args.dst_img)
# Array of corresponding points
with open(args.src_points) as f:
src_points = np.asarray(json.load(f))
with open(args.dst_points) as f:
dst_points = np.asarray(json.load(f))

h, w = dst_img.shape[:2]
## 2d warp
src_mask = mask_from_points(src_img.shape[:2], src_points)

src_img = apply_mask(src_img, src_mask)
# Correct Color for 2d warp
warped_dst_img = warp_image_3d(dst_img, dst_points[:48], src_points[:48], src_img.shape[:2])
src_img = correct_colours(warped_dst_img, src_img, src_points)
# Warp
warped_src_img = warp_image_2d(src_img, transformation_from_points(dst_points, src_points), (h, w, 3))
## Mask for blending
if args.mask_img:
mask = cv2.cvtColor(cv2.imread(args.mask_img), cv2.COLOR_BGR2GRAY)
else:
mask = mask_from_points((h, w), dst_points)
mask_src = np.mean(warped_src_img, axis=2) > 0
mask = np.asarray(mask*mask_src, dtype=np.uint8)
## Shrink the mask
## But this steps makes no difference to mask
# kernel = np.ones((1, 1), np.uint8)
# mask = cv2.erode(mask, kernel, iterations=1)
## Poisson Blending
r = cv2.boundingRect(mask)
center = ((r[0] + int(r[2] / 2), r[1] + int(r[3] / 2)))
output = cv2.seamlessClone(warped_src_img, dst_img, mask, center, cv2.NORMAL_CLONE)

dir_path = os.path.dirname(args.out)
if not os.path.isdir(dir_path):
os.makedirs(dir_path)

cv2.imwrite(args.out, output)

# ##For debug
# cv2.imshow("Face Warped", warped_src_img)
# cv2.imshow("Face Swapped(A)", src_img)
# cv2.imshow("Face Swapped(B)", dst_img)
# cv2.imshow("Face Swapped(A->B)", output)
# cv2.waitKey(0)
#
# cv2.destroyAllWindows()
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#! /usr/bin/env python
import os
import cv2
import dlib
import argparse

import numpy as np
Expand All @@ -10,6 +9,7 @@
from face_points_detection import face_points_detection
from face_swap import warp_image_2d, warp_image_3d, mask_from_points, apply_mask, correct_colours, transformation_from_points


def select_face(im, r=10):
faces = face_detection(im)

Expand Down
Binary file added models/shape_predictor_81_face_landmarks.dat
Binary file not shown.
Binary file modified results/output6_1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified results/output6_2_2d.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified results/output6_3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified results/output6_4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified results/output6_7.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified results/output6_7_2d.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified results/output7_4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 53c3028

Please sign in to comment.