| import logging |
| import cv2 |
| import numpy as np |
|
|
| logger = logging.getLogger(__name__) |
|
|
| class Mask(): |
| """ Parent class for masks |
| the output mask will be <mask_type>.mask |
| channels: 1, 3 or 4: |
| 1 - Returns a single channel mask |
| 3 - Returns a 3 channel mask |
| 4 - Returns the original image with the mask in the alpha channel """ |
|
|
| def __init__(self, landmarks, face, channels=4): |
| logger.info("Initializing %s: (face_shape: %s, channels: %s, landmarks: %s)", |
| self.__class__.__name__, face.shape, channels, landmarks) |
| self.landmarks = landmarks |
| self.face = face |
| self.channels = channels |
|
|
| mask = self.build_mask() |
| self.mask = self.merge_mask(mask) |
| logger.info("Initialized %s", self.__class__.__name__) |
|
|
| def build_mask(self): |
| """ Override to build the mask """ |
| raise NotImplementedError |
|
|
| def merge_mask(self, mask): |
| """ Return the mask in requested shape """ |
| logger.info("mask_shape: %s", mask.shape) |
| assert self.channels in (1, 3, 4), "Channels should be 1, 3 or 4" |
| assert mask.shape[2] == 1 and mask.ndim == 3, "Input mask be 3 dimensions with 1 channel" |
|
|
| if self.channels == 3: |
| retval = np.tile(mask, 3) |
| elif self.channels == 4: |
| retval = np.concatenate((self.face, mask), -1) |
| else: |
| retval = mask |
|
|
| logger.info("Final mask shape: %s", retval.shape) |
| return retval |
|
|
|
|
| class dfl_full(Mask): |
| """ DFL facial mask """ |
| def build_mask(self): |
| mask = np.zeros(self.face.shape[0:2] + (1, ), dtype=np.float32) |
|
|
| nose_ridge = (self.landmarks[27:31], self.landmarks[33:34]) |
| jaw = (self.landmarks[0:17], |
| self.landmarks[48:68], |
| self.landmarks[0:1], |
| self.landmarks[8:9], |
| self.landmarks[16:17]) |
| eyes = (self.landmarks[17:27], |
| self.landmarks[0:1], |
| self.landmarks[27:28], |
| self.landmarks[16:17], |
| self.landmarks[33:34]) |
| parts = [jaw, nose_ridge, eyes] |
|
|
| for item in parts: |
| merged = np.concatenate(item) |
| cv2.fillConvexPoly(mask, cv2.convexHull(merged), 255.) |
| return mask |
|
|
|
|
| class components(Mask): |
| """ Component model mask """ |
| def build_mask(self): |
| mask = np.zeros(self.face.shape[0:2] + (1, ), dtype=np.float32) |
|
|
| r_jaw = (self.landmarks[0:9], self.landmarks[17:18]) |
| l_jaw = (self.landmarks[8:17], self.landmarks[26:27]) |
| r_cheek = (self.landmarks[17:20], self.landmarks[8:9]) |
| l_cheek = (self.landmarks[24:27], self.landmarks[8:9]) |
| nose_ridge = (self.landmarks[19:25], self.landmarks[8:9],) |
| r_eye = (self.landmarks[17:22], |
| self.landmarks[27:28], |
| self.landmarks[31:36], |
| self.landmarks[8:9]) |
| l_eye = (self.landmarks[22:27], |
| self.landmarks[27:28], |
| self.landmarks[31:36], |
| self.landmarks[8:9]) |
| nose = (self.landmarks[27:31], self.landmarks[31:36]) |
| parts = [r_jaw, l_jaw, r_cheek, l_cheek, nose_ridge, r_eye, l_eye, nose] |
|
|
| for item in parts: |
| merged = np.concatenate(item) |
| cv2.fillConvexPoly(mask, cv2.convexHull(merged), 255.) |
| return mask |
|
|
|
|
| class extended(Mask): |
| """ Extended mask |
| Based on components mask. Attempts to extend the eyebrow points up the forehead |
| """ |
| def build_mask(self): |
| mask = np.zeros(self.face.shape[0:2] + (1, ), dtype=np.float32) |
|
|
| landmarks = self.landmarks.copy() |
| |
| ml_pnt = (landmarks[36] + landmarks[0]) // 2 |
| mr_pnt = (landmarks[16] + landmarks[45]) // 2 |
|
|
| |
| ql_pnt = (landmarks[36] + ml_pnt) // 2 |
| qr_pnt = (landmarks[45] + mr_pnt) // 2 |
|
|
| |
| bot_l = np.array((ql_pnt, landmarks[36], landmarks[37], landmarks[38], landmarks[39])) |
| bot_r = np.array((landmarks[42], landmarks[43], landmarks[44], landmarks[45], qr_pnt)) |
|
|
| |
| top_l = landmarks[17:22] |
| top_r = landmarks[22:27] |
|
|
| |
| landmarks[17:22] = top_l + ((top_l - bot_l) // 2) |
| landmarks[22:27] = top_r + ((top_r - bot_r) // 2) |
|
|
| r_jaw = (landmarks[0:9], landmarks[17:18]) |
| l_jaw = (landmarks[8:17], landmarks[26:27]) |
| r_cheek = (landmarks[17:20], landmarks[8:9]) |
| l_cheek = (landmarks[24:27], landmarks[8:9]) |
| nose_ridge = (landmarks[19:25], landmarks[8:9],) |
| r_eye = (landmarks[17:22], landmarks[27:28], landmarks[31:36], landmarks[8:9]) |
| l_eye = (landmarks[22:27], landmarks[27:28], landmarks[31:36], landmarks[8:9]) |
| nose = (landmarks[27:31], landmarks[31:36]) |
| parts = [r_jaw, l_jaw, r_cheek, l_cheek, nose_ridge, r_eye, l_eye, nose] |
|
|
| for item in parts: |
| merged = np.concatenate(item) |
| cv2.fillConvexPoly(mask, cv2.convexHull(merged), 255.) |
| return mask |
|
|
|
|
| class facehull(Mask): |
| """ Basic face hull mask """ |
| def build_mask(self): |
| mask = np.zeros(self.face.shape[0:2] + (1, ), dtype=np.float32) |
| hull = cv2.convexHull( |
| np.array(self.landmarks).reshape((-1, 2))) |
| cv2.fillConvexPoly(mask, hull, 255.0, lineType=cv2.LINE_AA) |
| return mask |