A Person Face Landmark Mask Generator¶
Documentation¶
- Class name:
APersonFaceLandmarkMaskGenerator
- Category:
A Person Mask Generator - David Bielejeski
- Output node:
False
The APersonFaceLandmarkMaskGenerator node is designed to generate facial landmark masks for images. It utilizes the MediaPipe Face Mesh solution to detect facial landmarks and then creates masks for specific facial features such as the face oval, lips, eyes, and eyebrows. This node is capable of processing multiple faces within an image, allowing for the generation of masks for each detected face.
Input types¶
Required¶
images
- The input images for which facial landmark masks are to be generated. This parameter is essential for the detection and mask generation process, as the images undergo preprocessing and are fed into the MediaPipe Face Mesh model for landmark detection.
- Comfy dtype:
IMAGE
- Python dtype:
torch.Tensor
Optional¶
face
- A boolean flag indicating whether to generate a mask for the face oval. This option allows for selective mask generation based on the user's requirements.
- Comfy dtype:
BOOLEAN
- Python dtype:
bool
left_eyebrow
- A boolean flag indicating whether to generate a mask for the left eyebrow. This option contributes to the comprehensive facial feature masking by including the left eyebrow.
- Comfy dtype:
BOOLEAN
- Python dtype:
bool
right_eyebrow
- A boolean flag indicating whether to generate a mask for the right eyebrow. Including this option ensures the right eyebrow is also considered in the mask generation process.
- Comfy dtype:
BOOLEAN
- Python dtype:
bool
left_eye
- A boolean flag indicating whether to generate a mask for the left eye. This enhances the mask generation by including detailed masks for the left eye.
- Comfy dtype:
BOOLEAN
- Python dtype:
bool
right_eye
- A boolean flag indicating whether to generate a mask for the right eye. This option adds to the facial feature coverage by generating masks for the right eye.
- Comfy dtype:
BOOLEAN
- Python dtype:
bool
left_pupil
- A boolean flag indicating whether to generate a mask for the left pupil. Activating this option allows for the inclusion of the left pupil in the facial feature masks.
- Comfy dtype:
BOOLEAN
- Python dtype:
bool
right_pupil
- A boolean flag indicating whether to generate a mask for the right pupil. This ensures the right pupil is also covered in the generated facial feature masks.
- Comfy dtype:
BOOLEAN
- Python dtype:
bool
lips
- A boolean flag indicating whether to generate a mask for the lips. Enabling this option results in the creation of detailed masks for the lips, enhancing the overall mask generation.
- Comfy dtype:
BOOLEAN
- Python dtype:
bool
number_of_faces
- Specifies the maximum number of faces to detect in the input images. This parameter influences the scope of face detection, thereby affecting the number and detail of the generated masks.
- Comfy dtype:
INT
- Python dtype:
int
confidence
- The minimum confidence threshold for detecting faces. A higher value means that only faces with a higher likelihood of being correctly identified will be processed, impacting the accuracy and completeness of the generated masks.
- Comfy dtype:
FLOAT
- Python dtype:
float
Output types¶
masks
- Comfy dtype:
MASK
- The output is a tensor containing the generated masks for the specified facial features. Each mask corresponds to a different facial feature or face detected in the input images.
- Python dtype:
torch.Tensor
- Comfy dtype:
Usage tips¶
- Infra type:
GPU
- Common nodes: unknown
Source code¶
class APersonFaceLandmarkMaskGenerator:
# https://github.com/google-ai-edge/mediapipe/blob/master/mediapipe/python/solutions/face_mesh_connections.py
# order matters for these
FACEMESH_FACE_OVAL = [10, 338, 297, 332, 284, 251, 389, 356, 454, 323, 361, 288, 397, 365, 379, 378, 400, 377, 152, 148, 176, 149, 150, 136, 172, 58, 132, 93, 234, 127, 162,
21, 54, 103, 67, 109]
FACEMESH_LEFT_EYEBROW = [336, 296, 334, 293, 300, 276, 283, 282, 295, 285]
FACEMESH_RIGHT_EYEBROW = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46]
FACEMESH_LEFT_EYE = [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398]
FACEMESH_RIGHT_EYE = [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246]
FACEMESH_LEFT_PUPIL = [474, 475, 476, 477]
FACEMESH_RIGHT_PUPIL = [469, 470, 471, 472]
FACEMESH_LIPS = [61, 146, 91, 181, 84, 17, 314, 405, 321, 375, 291, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95, 185, 40, 39, 37, 0, 267, 269, 270, 409, 415, 310, 311, 312,
13, 82, 81, 42, 183, 78]
def __init__(self):
pass
@classmethod
def INPUT_TYPES(self):
false_widget = ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"})
true_widget = ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"})
return {
"required":
{
"images": ("IMAGE",),
},
"optional":
{
"face": false_widget,
"left_eyebrow": false_widget,
"right_eyebrow": false_widget,
"left_eye": true_widget,
"right_eye": true_widget,
"left_pupil": false_widget,
"right_pupil": false_widget,
"lips": true_widget,
"number_of_faces": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1},),
"confidence": ("FLOAT", {"default": 0.40, "min": 0.01, "max": 1.0, "step": 0.01},),
}
}
CATEGORY = "A Person Mask Generator - David Bielejeski"
RETURN_TYPES = ("MASK",)
RETURN_NAMES = ("masks",)
FUNCTION = "generate_mask"
def generate_mask(
self,
images,
face: bool,
left_eyebrow: bool,
right_eyebrow: bool,
left_eye: bool,
right_eye: bool,
left_pupil: bool,
right_pupil: bool,
lips: bool,
number_of_faces: int,
confidence: float,
):
"""Create a segmentation mask from an image
Args:
image (torch.Tensor): The image to create the mask for.
face (bool): create a mask for the face.
left_eyebrow (bool): create a mask for the left eyebrow.
right_eyebrow (bool): create a mask for the right eyebrow.
left_eye (bool): create a mask for the left eye.
right_eye (bool): create a mask for the right eye.
left_pupil (bool): create a mask for the left eye pupil.
right_pupil (bool): create a mask for the right eye pupil.
lips (bool): create a mask for the lips.
Returns:
torch.Tensor: The segmentation masks.
"""
res_masks = []
with mp.solutions.face_mesh.FaceMesh(
static_image_mode=True,
refine_landmarks=True,
max_num_faces=number_of_faces,
min_detection_confidence=confidence) as face_mesh:
for image in images:
# Convert the Tensor to a PIL image
i = 255. * image.cpu().numpy()
image_pil = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
# Process the image
results = face_mesh.process(np.asarray(image_pil))
img_width, img_height = image_pil.size
mesh_coords = [(int(point.x * img_width), int(point.y * img_height)) for point in results.multi_face_landmarks[0].landmark]
mask = np.zeros((img_height, img_width), dtype=np.uint8)
if face:
face_coords = [mesh_coords[p] for p in self.FACEMESH_FACE_OVAL]
cv.fillPoly(mask, [np.array(face_coords, dtype=np.int32)], 255)
else:
if left_eyebrow:
left_eyebrow_coords = [mesh_coords[p] for p in self.FACEMESH_LEFT_EYEBROW]
cv.fillPoly(mask, [np.array(left_eyebrow_coords, dtype=np.int32)], 255)
if right_eyebrow:
right_eyebrow_coords = [mesh_coords[p] for p in self.FACEMESH_RIGHT_EYEBROW]
cv.fillPoly(mask, [np.array(right_eyebrow_coords, dtype=np.int32)], 255)
if left_eye:
left_eye_coords = [mesh_coords[p] for p in self.FACEMESH_LEFT_EYE]
cv.fillPoly(mask, [np.array(left_eye_coords, dtype=np.int32)], 255)
if right_eye:
right_eye_coords = [mesh_coords[p] for p in self.FACEMESH_RIGHT_EYE]
cv.fillPoly(mask, [np.array(right_eye_coords, dtype=np.int32)], 255)
if left_pupil:
left_pupil_coords = [mesh_coords[p] for p in self.FACEMESH_LEFT_PUPIL]
cv.fillPoly(mask, [np.array(left_pupil_coords, dtype=np.int32)], 255)
if right_pupil:
right_pupil_coords = [mesh_coords[p] for p in self.FACEMESH_RIGHT_PUPIL]
cv.fillPoly(mask, [np.array(right_pupil_coords, dtype=np.int32)], 255)
if lips:
lips_coords = [mesh_coords[p] for p in self.FACEMESH_LIPS]
cv.fillPoly(mask, [np.array(lips_coords, dtype=np.int32)], 255)
# Create the image
mask_image = Image.fromarray(mask)
# convert PIL image to tensor image
tensor_mask = mask_image.convert("RGB")
tensor_mask = np.array(tensor_mask).astype(np.float32) / 255.0
tensor_mask = torch.from_numpy(tensor_mask)[None,]
tensor_mask = tensor_mask.squeeze(3)[..., 0]
res_masks.append(tensor_mask)
return (torch.cat(res_masks, dim=0),)