anyantudre's picture
moved from training repo to inference
caa56d6
'''
Create face mask and face boundary mask according to face landmarks,
so as to supervize the activation of Conv layer.
'''
import os
import numpy as np
import cv2
import dlib
import random
import argparse
from tqdm import tqdm
import time
from skimage import transform as trans
# from color_transfer import color_transfer
from .warp import gen_warp_params, warp_by_params, warp_mask
def crop_img_bbox(img, bbox, res, scale=1.3):
x, y, w, h = bbox
left, right = x, x+w
top, bottom = y, y+h
H, W, C = img.shape
cx, cy = (left+right)//2, (top+bottom)//2
w, h = (right-left)//2, (bottom-top)//2
x1 = max(0, int(cx-w*scale))
x2 = min(W, int(cx+w*scale))
y1 = max(0, int(cy-h*scale))
y2 = min(H, int(cy+h*scale))
roi = img[y1:y2, x1:x2]
roi = cv2.resize(roi, (res, res))
return roi
def get_mask_center(mask):
l, t, w, h = cv2.boundingRect(mask[:, :, 0:1].astype(np.uint8))
center = int(l+w/2), int(t+h/2)
return center
def get_5_keypoint(shape):
def get_point(idx):
# return [shape.part(idx).x, shape.part(idx).y]
return shape[idx]
def center(pt1, pt2):
return [(pt1[0]+pt2[0])//2, (pt1[1]+pt2[1])//2]
leye = np.array(center(get_point(36), get_point(39)),
dtype=int).reshape(-1, 2)
reye = np.array(center(get_point(45), get_point(42)),
dtype=int).reshape(-1, 2)
nose = np.array(get_point(30), dtype=int).reshape(-1, 2)
lmouth = np.array(get_point(48),
dtype=int).reshape(-1, 2)
rmouth = np.array(get_point(54),
dtype=int).reshape(-1, 2)
pts = np.concatenate([leye, reye, nose, lmouth, rmouth], axis=0)
return pts
def get_boundary(mask):
if len(mask.shape) == 3:
mask = mask[:, :, 0]
mask = cv2.GaussianBlur(mask, (3, 3), 0)
boundary = mask / 255.
boundary = 4*boundary*(1.-boundary)
return boundary
# def get_boundary(mask):
# if len(mask.shape) == 3:
# mask = mask[:, :, 0]
# mask = cv2.GaussianBlur(mask, (3, 3), 0)
# mask = mask.astype(np.uint8)
# # Dilation and Erosion to find the boundary
# dilated = cv2.dilate(mask, None, iterations=1)
# boundary = cv2.subtract(dilated, mask)
# # normalize the boundary to have values between 0 and 1
# boundary = boundary / 255.
# return boundary
def blur_mask(mask):
blur_k = 2*np.random.randint(1, 10)-1
#kernel = np.ones((blur_k+1, blur_k+1), np.uint8)
#mask = cv2.erode(mask, kernel)
mask = cv2.GaussianBlur(mask, (blur_k, blur_k), 0)
return mask
def random_deform(pt, tgt, scale=0.3):
x1, y1 = pt
x2, y2 = tgt
x = x1+(x2-x1)*np.random.rand()*scale
y = y1+(y2-y1)*np.random.rand()*scale
#print('before:', pt, ' after:', [int(x), int(y)])
return [int(x), int(y)]
def get_specific_mask(img, shape, mtype='mouth', random_side=False):
if mtype == 'eyes':
landmarks = shape[42:45] if random.choice([True, False]) else shape[36:39]
elif mtype == 'nose':
landmarks = shape[27:35]
elif mtype == 'mouth':
landmarks = shape[48:60]
elif mtype == 'eyebrows':
landmarks = shape[22:26] if random.choice([True, False]) else shape[17:21]
else:
raise ValueError(f"Invalid mtype. Choose from 'eyes', 'nose', 'mouth', or 'eyebrows', but got {mtype}")
# find convex hull
hull = cv2.convexHull(landmarks)
hull = hull.astype(int)
# mask
hull_mask = np.zeros_like(img)
cv2.fillPoly(hull_mask, [hull], (255, 255, 255))
mask = hull_mask
return mask
def get_hull_mask(img, shape, mtype='hull'):
if mtype == 'normal-hull':
landmarks = np.array(shape)
# find convex hull
hull = cv2.convexHull(landmarks)
hull = hull.astype(int)
# full face mask
hull_mask = np.zeros_like(img)
cv2.fillPoly(hull_mask, [hull], (255, 255, 255))
mask = hull_mask
elif mtype == 'inner-hull':
landmarks = shape[17:]
landmarks = np.array(landmarks)
# find convex hull
hull = cv2.convexHull(landmarks)
hull = hull.astype(int)
# full face mask
hull_mask = np.zeros_like(img)
cv2.fillPoly(hull_mask, [hull], (255, 255, 255))
mask = hull_mask
elif mtype == 'inner-hull-no-eyebrow':
landmarks = shape[27:]
landmarks = np.array(landmarks)
# find convex hull
hull = cv2.convexHull(landmarks)
hull = hull.astype(int)
# full face mask
hull_mask = np.zeros_like(img)
cv2.fillPoly(hull_mask, [hull], (255, 255, 255))
mask = hull_mask
elif mtype == 'mouth-hull':
landmarks = shape[2:15]
#landmarks.append(shape[29])
landmarks = np.concatenate([landmarks, shape[29].reshape(1, -1)], axis=0)
# find convex hull
hull = cv2.convexHull(landmarks)
hull = hull.astype(int)
# full face mask
hull_mask = np.zeros_like(img)
cv2.fillPoly(hull_mask, [hull], (255, 255, 255))
# kernel = np.ones((2, 2), np.uint8)
# c_mask = cv2.dilate(hull_mask, kernel, iterations=1)
mask = hull_mask
elif mtype == 'whole-hull':
face_height = shape[9][1] - shape[22][1]
landmarks = []
for i in range(27):
lmk = shape[i]
if i >= 5 and i <= 11:
x, y = lmk[0], lmk[1]
lmk = [x, max(0, y+15)]
# lift the eyebrows to get a larger landmark convex hull
if i >= 18 and i <= 27:
x, y = lmk[0], lmk[1]
lmk = [x, max(0, y-face_height//4)]
landmarks.append(lmk)
# find convex hull
landmarks = np.array(landmarks, dtype=np.int32)
hull = cv2.convexHull(landmarks)
hull = np.reshape(hull, (1, -1, 2))
# full face mask
hull_mask = np.zeros_like(img)
cv2.fillPoly(hull_mask, [hull], (255, 255, 255))
# kernel = np.ones((2, 2), np.uint8)
# c_mask = cv2.dilate(hull_mask, kernel, iterations=1)
mask = hull_mask
'''
elif mtype == 'rect':
cnt = []
for idx in [5, 11, 17, 26]:
cnt.append(shape[idx])
x, y, w, h = cv2.boundingRect(np.array(cnt))
rect_mask = np.zeros_like(img)
cv2.rectangle(rect_mask, (x, y), (x+w, y+h),
(255, 255, 255), cv2.FILLED)
mask = rect_mask
'''
return mask
def get_mask(shape, img, std=20, deform=True, restrict_mask=None):
mask_type = [
'normal-hull',
'inner-hull',
'inner-hull-no-eyebrow',
'mouth-hull',
'whole-hull'
]
max_mask = get_hull_mask(img, shape, 'whole-hull')
if deform:
mtype = mask_type[np.random.randint(len(mask_type))]
if mtype == 'rect':
mask = get_hull_mask(img, shape, 'inner-hull-no-eyebrow')
x, y, w, h = cv2.boundingRect(mask[:,:,0])
for i in range(y, y+h):
for j in range(x, x+w):
for k in range(mask.shape[2]):
mask[i, j, k] = 255
else:
mask = get_hull_mask(img, shape, mtype)
# random deform
if np.random.rand() < 0.9:
mask = warp_mask(mask, std=std)
# # random erode/dilate
# prob = np.random.rand()
# if prob < 0.3:
# erode_k = 2*np.random.randint(1, 10)+1
# kernel = np.ones((erode_k, erode_k), np.uint8)
# mask = cv2.erode(mask, kernel)
# elif prob < 0.6:
# erode_k = 2*np.random.randint(1, 10)+1
# kernel = np.ones((erode_k, erode_k), np.uint8)
# mask = cv2.dilate(mask, kernel)
else:
mask = max_mask.copy()
if restrict_mask is not None:
mask = mask*(restrict_mask//255)
# restrict mask range
mask = mask *(max_mask//255)
# random blur
if deform and np.random.rand() < 0.9:
mask = blur_mask(mask)
return mask[:,:,0]
def mask_postprocess(mask):
# random erode/dilate
prob = np.random.rand()
if prob < 0.3:
erode_k = 2*np.random.randint(1, 10)+1
kernel = np.ones((erode_k, erode_k), np.uint8)
mask = cv2.erode(mask, kernel)
elif prob < 0.6:
erode_k = 2*np.random.randint(1, 10)+1
kernel = np.ones((erode_k, erode_k), np.uint8)
mask = cv2.dilate(mask, kernel)
# random blur
if np.random.rand() < 0.9:
mask = blur_mask(mask)
return mask
def get_affine_param(from_, to_):
# use skimage tranformation
tform = trans.SimilarityTransform()
tform.estimate(from_.astype(np.float32), to_.astype(
np.float32)) # tform.estimate(from_, to_)
M = tform.params[0:2, :]
return M
def random_sharpen_img(img):
cand = ['bsharpen', 'gsharpen'] # , 'none']
mode = cand[np.random.randint(len(cand))]
# print('sharpen mode:', mode)
if mode == "bsharpen":
# Sharpening using filter2D
kernel = np.ones((3, 3)) * (-1)
kernel[1, 1] = 9
#kernel /= 9.
out = cv2.filter2D(img, -1, kernel)
elif mode == "gsharpen":
# Sharpening using Weighted Method
gaussain_blur = cv2.GaussianBlur(img, (0, 0), 3.0)
out = cv2.addWeighted(
img, 1.5, gaussain_blur, -0.5, 0, img)
else:
out = img
return out
def random_blur_img(img):
cand = ['avg', 'gaussion', 'med'] # , 'none']
mode = cand[np.random.randint(len(cand))]
# print('blur mode:', mode)
ksize = 2*np.random.randint(1, 5)+1
if mode == 'avg':
# Averaging
out = cv2.blur(img, (ksize, ksize))
elif mode == 'gaussion':
# Gaussian Blurring
out = cv2.GaussianBlur(img, (ksize, ksize), 0)
elif mode == 'med':
# Median blurring
out = cv2.medianBlur(img, ksize)
else:
out = img
# elif mode == 'bilateral'
# # Bilateral Filtering
# out = cv2.bilateralFilter(img,9,75,75)
return out
def random_warp_img(img, prob=0.5):
H, W, C = img.shape
param = gen_warp_params(W, flip=False)
choice = [True, False]
out = warp_by_params(param, img,
can_flip=False, # choice[np.random.randint(2)],
can_transform=False, # choice[np.random.randint(2)],
can_warp=(np.random.randint(10) < int(prob*10)),
border_replicate=choice[np.random.randint(2)])
return out
def main(args):
np.random.seed(int(time.time()))
detector = dlib.get_frontal_face_detector()
landmark_predictor = dlib.shape_predictor(args.model)
src_im = cv2.imread(args.src)
tgt_im = cv2.imread(args.tgt)
H, W, C = tgt_im.shape
src_im = cv2.resize(src_im, (W, H))
def get_shape(img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
dets = detector(img, 1)
det = dets[0]
shape = landmark_predictor(img, det)
return shape, det
src_shape, src_det = get_shape(src_im)
src_5_pts = get_5_keypoint(src_shape)
src_mask = get_mask(src_shape, src_im, whole=True, deform=False)
tgt_shape, tgt_det = get_shape(tgt_im)
tgt_5_pts = get_5_keypoint(tgt_shape)
tgt_mask = get_mask(tgt_shape, tgt_im, whole=False, deform=True)
#aff_param = get_affine_param(src_5_pts, tgt_5_pts)
# color transfer:
mask = src_mask[:, :, 0:1]/255.
ct_modes = ['lct', 'rct', 'idt', 'idt-m', 'mkl', 'mkl-m',
'sot', 'sot-m', 'mix-m'] # , 'seamless-hist-match']
for mode in ct_modes:
colored_src = color_transfer(mode, src_im, tgt_im, mask)
cv2.imwrite('{}_colored.png'.format(mode), colored_src)
src_im = colored_src
w1, h1 = src_det.right()-src_det.left(), src_det.bottom()-src_det.top()
w2, h2 = tgt_det.right()-tgt_det.left(), tgt_det.bottom()-tgt_det.top()
w_scale, h_scale = w2/w1, h2/h1
scaled_src = cv2.resize(src_im, (int(W*w_scale), int(H*h_scale)))
scaled_mask = cv2.resize(src_mask, (int(W*w_scale), int(H*h_scale)))
src_5_pts[:, 0] = src_5_pts[:, 0]*w_scale
src_5_pts[:, 1] = src_5_pts[:, 1]*h_scale
aff_param = get_affine_param(src_5_pts, tgt_5_pts)
aligned_src = cv2.warpAffine(
scaled_src, aff_param, (W, H), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT)
aligned_mask = cv2.warpAffine(
scaled_mask, aff_param, (W, H), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT)
center = get_mask_center(aligned_mask)
print('mask center:', center)
# colored_src = transfer_color(aligned_src, tgt_im)
init_blend = cv2.seamlessClone(
aligned_src, tgt_im, aligned_mask, center, cv2.NORMAL_CLONE)
cv2.imwrite('init_blended.png', init_blend)
# aligned_blend = cv2.warpAffine(
# colored_blend, aff_param, (W, H), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT)
b_mask = tgt_mask[:, :, 0:1]/255.
out_blend = init_blend*b_mask + tgt_im*(1. - b_mask)
cv2.imwrite('out_blended.png', out_blend)
res = 256
blend_crop = crop_img_bbox(out_blend, tgt_det, res, scale=1.5)
mask_crop = crop_img_bbox(tgt_mask, tgt_det, res, scale=1.5)
boundary = get_boundary(mask_crop)
cv2.imwrite('crop_blend.png', blend_crop)
cv2.imwrite('crop_mask.png', mask_crop)
cv2.imwrite('crop_bound.png', boundary*255)
if __name__ == "__main__":
p = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
p.add_argument('-s', '--src', type=str,
help='src image')
p.add_argument('-t', '--tgt', type=str,
help='tgt image')
p.add_argument('--model', type=str, default='/data1/yuchen/download/face_landmark/shape_predictor_68_face_landmarks.dat',
help="path to downloaded detector")
args = p.parse_args()
print(args)
main(args)