|
from enum import Enum |
|
from functools import reduce |
|
|
|
import cv2 |
|
import numpy as np |
|
from scipy.ndimage import binary_dilation |
|
|
|
from .DeepFakeMask import Mask |
|
|
|
|
|
def dist(a, b): |
|
x1, y1 = a |
|
x2, y2 = b |
|
return np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) |
|
|
|
|
|
|
|
def get_five_key(landmarks_68): |
|
|
|
leye_center = (landmarks_68[36] + landmarks_68[39]) * 0.5 |
|
reye_center = (landmarks_68[42] + landmarks_68[45]) * 0.5 |
|
nose = landmarks_68[33] |
|
lmouth = landmarks_68[48] |
|
rmouth = landmarks_68[54] |
|
leye_left = landmarks_68[36] |
|
leye_right = landmarks_68[39] |
|
reye_left = landmarks_68[42] |
|
reye_right = landmarks_68[45] |
|
out = [ |
|
tuple(x.astype("int32")) |
|
for x in [ |
|
leye_center, |
|
reye_center, |
|
nose, |
|
lmouth, |
|
rmouth, |
|
leye_left, |
|
leye_right, |
|
reye_left, |
|
reye_right, |
|
] |
|
] |
|
return out |
|
|
|
|
|
def remove_eyes(image, landmarks, opt): |
|
|
|
if opt == "l": |
|
(x1, y1), (x2, y2) = landmarks[5:7] |
|
elif opt == "r": |
|
(x1, y1), (x2, y2) = landmarks[7:9] |
|
elif opt == "b": |
|
(x1, y1), (x2, y2) = landmarks[:2] |
|
else: |
|
print("wrong region") |
|
mask = np.zeros_like(image[..., 0]) |
|
line = cv2.line(mask, (x1, y1), (x2, y2), color=(1), thickness=2) |
|
w = dist((x1, y1), (x2, y2)) |
|
dilation = int(w // 4) |
|
if opt != "b": |
|
dilation *= 4 |
|
line = binary_dilation(line, iterations=dilation) |
|
return line |
|
|
|
|
|
def remove_nose(image, landmarks): |
|
(x1, y1), (x2, y2) = landmarks[:2] |
|
x3, y3 = landmarks[2] |
|
mask = np.zeros_like(image[..., 0]) |
|
x4 = int((x1 + x2) / 2) |
|
y4 = int((y1 + y2) / 2) |
|
line = cv2.line(mask, (x3, y3), (x4, y4), color=(1), thickness=2) |
|
w = dist((x1, y1), (x2, y2)) |
|
dilation = int(w // 4) |
|
line = binary_dilation(line, iterations=dilation) |
|
return line |
|
|
|
|
|
def remove_mouth(image, landmarks): |
|
(x1, y1), (x2, y2) = landmarks[3:5] |
|
mask = np.zeros_like(image[..., 0]) |
|
line = cv2.line(mask, (x1, y1), (x2, y2), color=(1), thickness=2) |
|
w = dist((x1, y1), (x2, y2)) |
|
dilation = int(w // 3) |
|
line = binary_dilation(line, iterations=dilation) |
|
return line |
|
|
|
|
|
class SladdRegion(Enum): |
|
left_eye = 0 |
|
right_eye = 1 |
|
nose = 2 |
|
mouth = 3 |
|
|
|
both_eyes = left_eye + right_eye |
|
|
|
|
|
class SladdMasking(Mask): |
|
|
|
|
|
|
|
ALL_REGIONS = [ |
|
SladdRegion.left_eye, |
|
SladdRegion.right_eye, |
|
SladdRegion.nose, |
|
SladdRegion.mouth, |
|
] |
|
REGIONS = [ |
|
[SladdRegion.left_eye], |
|
[SladdRegion.right_eye], |
|
[SladdRegion.nose], |
|
[SladdRegion.mouth], |
|
[SladdRegion.left_eye, SladdRegion.right_eye], |
|
[SladdRegion.left_eye, SladdRegion.nose], |
|
[SladdRegion.right_eye, SladdRegion.nose], |
|
[SladdRegion.nose, SladdRegion.mouth], |
|
[SladdRegion.left_eye, SladdRegion.right_eye, SladdRegion.nose], |
|
ALL_REGIONS, |
|
] |
|
|
|
def init(self, compose: bool = False, single: bool = True, **kwargs): |
|
|
|
self.compose = compose |
|
if compose: |
|
self.regions = SladdMasking.REGIONS |
|
else: |
|
self.regions = [reg for reg in SladdMasking.REGIONS if len(reg) == 1] |
|
if single: |
|
self.regions = [self.ALL_REGIONS] |
|
|
|
@property |
|
def total(self) -> int: |
|
return len(self.regions) |
|
|
|
@staticmethod |
|
def parse(img, reg, landmarks) -> np.ndarray: |
|
five_key = get_five_key(landmarks) |
|
if reg is SladdRegion.left_eye: |
|
mask = remove_eyes(img, five_key, "l") |
|
elif reg is SladdRegion.right_eye: |
|
mask = remove_eyes(img, five_key, "r") |
|
elif reg is SladdRegion.nose: |
|
mask = remove_nose(img, five_key) |
|
elif reg is SladdRegion.mouth: |
|
mask = remove_mouth(img, five_key) |
|
else: |
|
raise ValueError("Invalid region") |
|
|
|
|
|
return mask |
|
|
|
def build_mask(self) -> np.ndarray: |
|
self.init() |
|
h, w = self.face.shape[:2] |
|
|
|
regs = [self.regions[0][self.idx]] |
|
|
|
|
|
masks = [SladdMasking.parse(self.face, reg, self.landmarks) for reg in regs] |
|
mask = reduce(np.maximum, masks) |
|
mask = mask.reshape([mask.shape[0],mask.shape[1], 1]) |
|
|
|
return mask |
|
|