Face Keypoints Filter (OpenPose)
Selectively mute facial keypoints in your pose ControlNet
- pose_kps
- pose_kps
Here's the problem this node solves: a pose ControlNet (DWPose/OpenPose) conditions on the whole skeleton. Body, face, hands - all of it - gets baked into your generation. So when you feed it a reference portrait and the model renders the face with a locked-in expression you didn't want, you can't just dial the ControlNet down; that weakens the pose everywhere, not just the mouth.
Face Keypoints Filter (OpenPose) is a tiny knife for that. It takes the POSE_KEYPOINT output of a DWPose preprocessor and lets you mute individual facial parts - jaw, eyebrows, eyes, nose, mouth - before it ever reaches ControlNet Apply. The pose still says "face here, roughly this shape," but the parts you mute stop dictating the details. Handy for re-expressing a character while keeping the bone structure, or keeping the eyes locked on camera while letting the prompt invent a new mouth.
How it works
DWPose (comfyui_controlnet_aux's DWPreprocessor) emits OpenPose-compatible JSON: a list of frames, each with face_keypoints_2d - a flat array of 68 face points in x, y, confidence triplets. This node walks that list and, for every part you toggle on, zeros out the (x, y, score) of that point range. Score zero reads as "not detected," so downstream the openpose ControlNet just doesn't have that feature to work with.
The body and hand keypoints are left completely alone. That's the part people miss - you're surgically editing the face landmarks, not weakening the pose condition as a whole.
Under the hood there are two honest limitations worth knowing (they're in the code, not the README). First, it only processes people[0] - if your detector found multiple people in the frame, only the first person's face gets filtered. Second, it checks that the face list actually has 68 points; if it's shorter, the frame passes through untouched. Both are fine for the usual single-subject portrait.
The inputs that matter
Six booleans, and the semantics are inverted from what you'd guess: checking a box removes that part.
remove_jaw(points 0–16) - the jaw/face contourremove_eyebrows(17–26)remove_nose(27–35)remove_eyes(36–47)remove_mouth_outer(48–59) - the lip contourremove_mouth_inner(60–67) - the open-mouth/teeth line
pose_kps is the POSE_KEYPOINT input from your DWPose preprocessor. The output is pose_kps, same format, wired straight into ControlNet Apply (openpose mode) - or anywhere else that takes a POSE_KEYPOINT. Nothing else changes in your workflow; you just slot this between the preprocessor and the Apply node.
Install
No dependencies, no model downloads, no requirements.txt to fight with - it's a single Python file with zero imports beyond the standard library. Either way takes a restart:
ComfyUI Manager: search "FaceKeypointsFilter" and install.
Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/smartvain/ComfyUI-FaceKeypointsFilter.git
Restart ComfyUI and the node appears under Pose/Face Utils > Face Keypoints Filter (OpenPose). You'll still need the ControlNet Auxiliary Preprocessors pack installed for DWPose itself - that's the real dependency here, just not one this repo ships.
Where people get burned
The trap nobody warns about: remove_mouth_outer defaults to True. Plug this node in, run a generation, and the mouth silently stops being controlled - expressions get loose, lips go wherever the prompt takes them. If your first run looks "wrong," check the toggles before you blame the sampler. The mouth-inner line (60–67) is off by default, so a closed mouth usually still reads fine; it's the outer contour that's muted.
Beyond that: make sure your DWPose preprocessor is in openpose_full (or at least outputting face keypoints), because if there's no face_keypoints_2d to work with, the node just passes frames through and you'll wonder why nothing changed. And remember the 68-point guard - a cropped or partial face detection may silently skip filtering.
It's a one-job node with no knobs past the six toggles, which is exactly why it's good at what it does. If your pose ControlNet keeps fighting you over facial features, this is the whole fix.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| pose_kps | POSE_KEYPOINT | — | |
| remove_jaw | BOOLEAN | false | — |
| remove_eyebrows | BOOLEAN | false | — |
| remove_eyes | BOOLEAN | false | — |
| remove_nose | BOOLEAN | false | — |
| remove_mouth_outer | BOOLEAN | true | — |
| remove_mouth_inner | BOOLEAN | false | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| pose_kps | POSE_KEYPOINT | — |