Files

195 lines
6.4 KiB
Python

# face_keypoints_filter.py
#
# DWPose / OpenPose の POSE_KEYPOINT から
# 顔パーツ(あご / 眉 / 目 / 鼻 / 口外周 / 口内周)を部分的に無効化するノード
#
# 前提:
# pose_kps は comfyui_controlnet_aux DWPose(DWPreprocessor) が出す
# OpenPose 互換 JSON 形式:
# [
# {
# "version": 1.3,
# "people": [
# {
# "pose_keypoints_2d": [...],
# "face_keypoints_2d": [...],
# "hand_left_keypoints_2d": [...],
# "hand_right_keypoints_2d": [...]
# }
# ],
# "canvas_width": 512,
# "canvas_height": 512
# },
# ...
# ]
class FaceKeypointsFilter:
"""
POSE_KEYPOINT 内の face_keypoints_2d から
特定の顔パーツの信頼度(score)を 0 にして無効化するノード。
DWPose / OpenPose の JSON 構造 (frame["people"][0]["face_keypoints_2d"]) に対応。
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pose_kps": ("POSE_KEYPOINT",),
# True のパーツは「無効化」される(score=0)
"remove_jaw": ("BOOLEAN", {"default": False}),
"remove_eyebrows": ("BOOLEAN", {"default": False}),
"remove_eyes": ("BOOLEAN", {"default": False}),
"remove_nose": ("BOOLEAN", {"default": False}),
# ★ 口を外周・内周で分ける
"remove_mouth_outer": ("BOOLEAN", {"default": True}),
"remove_mouth_inner": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("POSE_KEYPOINT",)
RETURN_NAMES = ("pose_kps",)
FUNCTION = "filter_keypoints"
CATEGORY = "Pose/Face Utils"
# ------------------ internal helpers ------------------ #
def _zero_indices(self, flat_list, indices):
"""
flat_list: [x0,y0,c0, x1,y1,c1, ...] の配列
indices: 無効化したい「点 index」のリスト (0〜67)
該当 index の (x,y,score) を 0 にする
"""
if flat_list is None:
return flat_list
if len(flat_list) % 3 != 0:
# 想定外の形式はそのまま返す
return flat_list
num_points = len(flat_list) // 3
if num_points < 68:
# 顔68点が入っていない場合は何もしない
return flat_list
out = list(flat_list)
for idx in indices:
if idx < 0 or idx >= num_points:
continue
base = idx * 3
out[base] = 0.0
out[base + 1] = 0.0
out[base + 2] = 0.0
return out
def _get_face_list_from_frame(self, frame):
"""
frame から face_keypoints_2d のフラット配列を取得する。
DWPose 形式 (frame["people"][0]["face_keypoints_2d"]) と
旧形式 (frame["face_keypoints_2d"]) の両方に対応。
"""
if not isinstance(frame, dict):
return None, None, None
# DWPose / OpenPose 標準 JSON:
# frame["people"][0]["face_keypoints_2d"]
if "people" in frame and isinstance(frame["people"], list) and frame["people"]:
person = frame["people"][0]
if isinstance(person, dict) and "face_keypoints_2d" in person:
return frame, person, person["face_keypoints_2d"]
# 互換用:トップレベルに face_keypoints_2d がいる場合
if "face_keypoints_2d" in frame:
return frame, None, frame["face_keypoints_2d"]
return None, None, None
# ------------------ main function ------------------ #
def filter_keypoints(
self,
pose_kps,
remove_jaw,
remove_eyebrows,
remove_eyes,
remove_nose,
remove_mouth_outer,
remove_mouth_inner,
):
"""
pose_kps: DWPose / OpenPose 由来の POSE_KEYPOINT
"""
# OpenPose 68point face のインデックス
jaw_idx = list(range(0, 17)) # あご輪郭
eyebrow_idx = list(range(17, 27)) # 眉
nose_idx = list(range(27, 36)) # 鼻
eye_idx = list(range(36, 48)) # 両目
# ★ 口を外周・内周に分割
mouth_outer_idx = list(range(48, 60)) # 48〜59: 唇の輪郭
mouth_inner_idx = list(range(60, 68)) # 60〜67: 口の開閉ライン
if not isinstance(pose_kps, list):
# 想定:pose_kps は frame のリスト
return (pose_kps,)
new_pose_kps = []
for frame in pose_kps:
# フレームを浅いコピー
if isinstance(frame, dict):
frame_copy = dict(frame)
else:
new_pose_kps.append(frame)
continue
frame_ref, person_ref, face_flat = self._get_face_list_from_frame(frame_copy)
if face_flat is None:
# 顔情報が無ければそのまま
new_pose_kps.append(frame_copy)
continue
indices_to_zero = []
if remove_jaw:
indices_to_zero += jaw_idx
if remove_eyebrows:
indices_to_zero += eyebrow_idx
if remove_eyes:
indices_to_zero += eye_idx
if remove_nose:
indices_to_zero += nose_idx
if remove_mouth_outer:
indices_to_zero += mouth_outer_idx
if remove_mouth_inner:
indices_to_zero += mouth_inner_idx
if indices_to_zero:
face_flat_new = self._zero_indices(list(face_flat), indices_to_zero)
# person 内か frame 直下かで代入先を変える
if person_ref is not None:
person_copy = dict(person_ref)
person_copy["face_keypoints_2d"] = face_flat_new
people_list = list(frame_copy.get("people", []))
if people_list:
people_list[0] = person_copy
frame_copy["people"] = people_list
else:
frame_copy["face_keypoints_2d"] = face_flat_new
new_pose_kps.append(frame_copy)
return (new_pose_kps,)
NODE_CLASS_MAPPINGS = {
"FaceKeypointsFilter": FaceKeypointsFilter,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FaceKeypointsFilter": "Face Keypoints Filter (OpenPose)",
}