MediapipeFacialSegment node support face_landmarker to compatible Python 3.12 and 3.13
This commit is contained in:
Binary file not shown.
@@ -1,41 +1,65 @@
|
|||||||
# layerstyle advance
|
import os
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .imagefunc import *
|
from .imagefunc import *
|
||||||
|
|
||||||
NODE_NAME = 'MediapipeFacialSegment'
|
NODE_NAME = 'MediapipeFacialSegment'
|
||||||
|
|
||||||
|
|
||||||
# 获取特征点的坐标
|
|
||||||
def get_points(indices, face_landmarks, width, height):
|
def get_points(indices, face_landmarks, width, height):
|
||||||
return [(int(face_landmarks.landmark[i].x * width), int(face_landmarks.landmark[i].y * height))
|
return [
|
||||||
for i in indices]
|
(
|
||||||
|
int(face_landmarks.landmark[i].x * width),
|
||||||
|
int(face_landmarks.landmark[i].y * height)
|
||||||
|
)
|
||||||
|
for i in indices
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# 绘制面部特征的多边形
|
|
||||||
def draw_feature(indices, mask, face_landmarks, width, height):
|
def draw_feature(indices, mask, face_landmarks, width, height):
|
||||||
points = get_points(indices, face_landmarks, width, height)
|
points = get_points(indices, face_landmarks, width, height)
|
||||||
points = np.array(points, dtype=np.int32)
|
points = np.array(points, dtype=np.int32)
|
||||||
cv2.fillPoly(mask, [points], 255)
|
cv2.fillPoly(mask, [points], 255)
|
||||||
|
|
||||||
|
|
||||||
|
class _Landmark:
|
||||||
|
__slots__ = ("x", "y")
|
||||||
|
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
|
||||||
|
class _FaceLandmarksWrapper:
|
||||||
|
def __init__(self, landmarks):
|
||||||
|
self.landmark = [_Landmark(lm.x, lm.y) for lm in landmarks]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_face_landmarker_model_path():
|
||||||
|
base_dir = os.path.dirname(__file__) # .../ComfyUI_LayerStyle_Advance/py
|
||||||
|
plugin_dir = os.path.dirname(base_dir) # .../ComfyUI_LayerStyle_Advance
|
||||||
|
return os.path.join(
|
||||||
|
plugin_dir,
|
||||||
|
"face_landmarker",
|
||||||
|
"face_landmarker.task"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FacialFeatureSegment:
|
class FacialFeatureSegment:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
self._tasks_landmarker = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(self):
|
def INPUT_TYPES(self):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"image": ("IMAGE",), #
|
"image": ("IMAGE",),
|
||||||
"left_eye": ("BOOLEAN", {"default": True}),
|
"left_eye": ("BOOLEAN", {"default": True}),
|
||||||
"left_eyebrow": ("BOOLEAN", {"default": True}),
|
"left_eyebrow": ("BOOLEAN", {"default": True}),
|
||||||
"right_eye": ("BOOLEAN", {"default": True}),
|
"right_eye": ("BOOLEAN", {"default": True}),
|
||||||
"right_eyebrow": ("BOOLEAN", {"default": True}),
|
"right_eyebrow": ("BOOLEAN", {"default": True}),
|
||||||
"lips": ("BOOLEAN", {"default": True}),
|
"lips": ("BOOLEAN", {"default": True}),
|
||||||
"tooth": ("BOOLEAN", {"default": True}),
|
"tooth": ("BOOLEAN", {"default": True}),
|
||||||
},
|
|
||||||
"optional": {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,42 +68,86 @@ class FacialFeatureSegment:
|
|||||||
FUNCTION = 'facial_feature_segment'
|
FUNCTION = 'facial_feature_segment'
|
||||||
CATEGORY = '😺dzNodes/LayerMask'
|
CATEGORY = '😺dzNodes/LayerMask'
|
||||||
|
|
||||||
def facial_feature_segment(self, image,
|
def facial_feature_segment(
|
||||||
|
self,
|
||||||
|
image,
|
||||||
left_eye, left_eyebrow, right_eye, right_eyebrow, lips, tooth
|
left_eye, left_eyebrow, right_eye, right_eyebrow, lips, tooth
|
||||||
):
|
):
|
||||||
|
|
||||||
import mediapipe as mp
|
import mediapipe as mp
|
||||||
# 定义面部特征索引
|
|
||||||
|
USE_SOLUTIONS = hasattr(mp, "solutions")
|
||||||
|
|
||||||
|
|
||||||
left_eye_indices = [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246]
|
left_eye_indices = [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246]
|
||||||
right_eye_indices = [263, 249, 390, 373, 374, 380, 381, 382, 362, 398, 384, 385, 386, 387, 388, 466]
|
right_eye_indices = [263, 249, 390, 373, 374, 380, 381, 382, 362, 398, 384, 385, 386, 387, 388, 466]
|
||||||
left_eyebrow_indices = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46]
|
left_eyebrow_indices = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46]
|
||||||
right_eyebrow_indices = [336, 296, 334, 293, 300, 276, 283, 282, 295, 285]
|
right_eyebrow_indices = [336, 296, 334, 293, 300, 276, 283, 282, 295, 285]
|
||||||
# upper_lip_indices = [61, 146, 91, 181, 84, 17, 314, 405, 321, 375, 291, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95, 78]
|
|
||||||
# lower_lip_indices = [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 308, 415, 310, 311, 312, 13, 82, 81, 80, 191, 78]
|
|
||||||
tooth_indices = [78, 95, 88, 178, 87, 14, 317, 402, 318, 324, 308, 415, 310, 311, 312, 13, 82, 81, 80, 191, 78]
|
tooth_indices = [78, 95, 88, 178, 87, 14, 317, 402, 318, 324, 308, 415, 310, 311, 312, 13, 82, 81, 80, 191, 78]
|
||||||
lips_indices = [61, 76, 62, 78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178,
|
lips_indices = [
|
||||||
88, 95, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146,
|
61, 76, 62, 78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308,
|
||||||
61]
|
324, 318, 402, 317, 14, 87, 178, 88, 95, 185, 40, 39, 37, 0,
|
||||||
|
267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91,
|
||||||
|
146, 61
|
||||||
|
]
|
||||||
|
|
||||||
ret_images = []
|
ret_images = []
|
||||||
ret_masks = []
|
ret_masks = []
|
||||||
scale_factor = 4
|
scale_factor = 4
|
||||||
|
|
||||||
|
|
||||||
|
if USE_SOLUTIONS:
|
||||||
|
mp_face_mesh = mp.solutions.face_mesh
|
||||||
|
face_mesh = mp_face_mesh.FaceMesh(
|
||||||
|
static_image_mode=True,
|
||||||
|
max_num_faces=1,
|
||||||
|
min_detection_confidence=0.5
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
from mediapipe.tasks.python import vision
|
||||||
|
from mediapipe.tasks.python import BaseOptions
|
||||||
|
|
||||||
|
if self._tasks_landmarker is None:
|
||||||
|
model_path = _get_face_landmarker_model_path()
|
||||||
|
|
||||||
|
if not os.path.exists(model_path):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"[{NODE_NAME}] FaceLandmarker model not found:\n{model_path}"
|
||||||
|
)
|
||||||
|
|
||||||
|
options = vision.FaceLandmarkerOptions(
|
||||||
|
base_options=BaseOptions(model_asset_path=model_path),
|
||||||
|
output_face_blendshapes=False,
|
||||||
|
output_facial_transformation_matrixes=False,
|
||||||
|
num_faces=1
|
||||||
|
)
|
||||||
|
|
||||||
|
self._tasks_landmarker = vision.FaceLandmarker.create_from_options(options)
|
||||||
|
|
||||||
|
|
||||||
for i in image:
|
for i in image:
|
||||||
face_image = tensor2pil(i.unsqueeze(0)).convert('RGB')
|
face_image = tensor2pil(i.unsqueeze(0)).convert('RGB')
|
||||||
width, height = face_image.size
|
width, height = face_image.size
|
||||||
width *= scale_factor
|
width *= scale_factor
|
||||||
height *= scale_factor
|
height *= scale_factor
|
||||||
|
|
||||||
cv2_image = pil2cv2(face_image)
|
cv2_image = pil2cv2(face_image)
|
||||||
mp_face_mesh = mp.solutions.face_mesh
|
|
||||||
fase_mesh = mp_face_mesh.FaceMesh(static_image_mode=True, max_num_faces=1, min_detection_confidence=0.5)
|
|
||||||
results = fase_mesh.process(cv2_image)
|
|
||||||
mask = np.zeros((height, width), dtype=np.uint8)
|
mask = np.zeros((height, width), dtype=np.uint8)
|
||||||
|
|
||||||
|
if USE_SOLUTIONS:
|
||||||
|
results = face_mesh.process(cv2_image)
|
||||||
|
faces = results.multi_face_landmarks or []
|
||||||
|
else:
|
||||||
|
mp_image = mp.Image(
|
||||||
|
image_format=mp.ImageFormat.SRGB,
|
||||||
|
data=cv2_image
|
||||||
|
)
|
||||||
|
result = self._tasks_landmarker.detect(mp_image)
|
||||||
|
faces = [
|
||||||
|
_FaceLandmarksWrapper(lms)
|
||||||
|
for lms in (result.face_landmarks or [])
|
||||||
|
]
|
||||||
|
|
||||||
if results.multi_face_landmarks:
|
for face_landmarks in faces:
|
||||||
for face_landmarks in results.multi_face_landmarks:
|
|
||||||
# 绘制各个面部特征
|
|
||||||
if left_eye:
|
if left_eye:
|
||||||
draw_feature(left_eye_indices, mask, face_landmarks, width, height)
|
draw_feature(left_eye_indices, mask, face_landmarks, width, height)
|
||||||
if right_eye:
|
if right_eye:
|
||||||
@@ -96,12 +164,14 @@ class FacialFeatureSegment:
|
|||||||
mask = cv22pil(mask).convert('L')
|
mask = cv22pil(mask).convert('L')
|
||||||
mask = gaussian_blur(mask, 2)
|
mask = gaussian_blur(mask, 2)
|
||||||
mask = mask.resize(face_image.size, Image.BILINEAR)
|
mask = mask.resize(face_image.size, Image.BILINEAR)
|
||||||
|
|
||||||
ret_images.append(pil2tensor(RGB2RGBA(face_image, mask)))
|
ret_images.append(pil2tensor(RGB2RGBA(face_image, mask)))
|
||||||
ret_masks.append(image2mask(mask))
|
ret_masks.append(image2mask(mask))
|
||||||
|
|
||||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||||
return (torch.cat(ret_images, dim=0), torch.cat(ret_masks, dim=0),)
|
return (torch.cat(ret_images, dim=0), torch.cat(ret_masks, dim=0),)
|
||||||
|
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"LayerMask: MediapipeFacialSegment": FacialFeatureSegment
|
"LayerMask: MediapipeFacialSegment": FacialFeatureSegment
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "ComfyUI_LayerStyle_Advance"
|
name = "ComfyUI_LayerStyle_Advance"
|
||||||
description = "The nodes detached from ComfyUI Layer Style are mainly those with complex requirements for dependency packages."
|
description = "The nodes detached from ComfyUI Layer Style are mainly those with complex requirements for dependency packages."
|
||||||
version = "2.0.34"
|
version = "2.0.35"
|
||||||
license = { text = "MIT License" }
|
license = { text = "MIT License" }
|
||||||
dependencies = ["numpy", "matplotlib", "scikit_image", "scikit_learn", "opencv-contrib-python", "pymatting", "timm", "blend_modes", "transformers", "diffusers", "loguru", "colour-science", "huggingface_hub", "segment_anything", "addict", "omegaconf", "yapf", "wget", "iopath", "mediapipe", "typer_config", "fastapi", "rich", "google-generativeai", "ultralytics", "transparent-background", "accelerate", "onnxruntime", "bitsandbytes", "peft", "protobuf", "hydra-core", "blind-watermark", "qrcode", "pyzbar", "psd-tools", "wandb", "zhipuai", "openai","google-genai", "fastapi","typer-config"]
|
dependencies = ["numpy", "matplotlib", "scikit_image", "scikit_learn", "opencv-contrib-python", "pymatting", "timm", "blend_modes", "transformers", "diffusers", "loguru", "colour-science", "huggingface_hub", "segment_anything", "addict", "omegaconf", "yapf", "wget", "iopath", "mediapipe", "typer_config", "fastapi", "rich", "google-generativeai", "ultralytics", "transparent-background", "accelerate", "onnxruntime", "bitsandbytes", "peft", "protobuf", "hydra-core", "blind-watermark", "qrcode", "pyzbar", "psd-tools", "wandb", "zhipuai", "openai","google-genai", "fastapi","typer-config"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user