Nodes/lip_mask_from_pose/Lip Mask from Pose Keypoints
ComfyUI Node

Lip Mask from Pose Keypoints

Steal DWPose's face keypoints and turn them into a mouth mask for video inpainting

By Jalen-Brunson·Created 5 months ago·Updated 21 days ago· 2
Lip Mask from Pose Keypoints
  • pose_keypoint
  • mask
  • centroid_x_list
  • centroid_y_list
  • fallback_active_list
mask_modeinner_mouth
blur_radius5
grow_pixels0
person_index0
confidence_threshold0.30
auto_fallbacktrue
fallback_area_threshold0.15
fallback_pad_h1.20
fallback_pad_v1.30

The flagship node this pack is named after - and honestly the one that earned the name. LipMaskFromPose takes the POSE_KEYPOINT output of a DWPose preprocessor and turns it into a precise mouth/lip mask you feed straight into video inpainting. Fix a smile, swap a tooth, change lipstick across a clip, re-dub a take where the mouth moves wrong: mask the mouth, let Wan 2.2 VACE (or a KSampler) regenerate only that region, leave everything else untouched.

That's the whole point of it in the wider workflow. VACE needs a mask to know where to edit, and drawing a mouth mask by hand across 120 frames is misery. This node does it from the keypoints you're already computing for pose control, at every frame, automatically.

How it works

DWPose returns face landmarks in the iBUG 300-W 68-point convention, and the lip region is a clean subset of it: the outer lip contour is landmarks 48–59 (12 points) and the inner mouth opening is 60–67 (8 points). The node builds polygons from those, fills them on a blank canvas at the video's resolution, and - here's the part people miss - the mask_mode dropdown decides which polygon math you get:

  • inner_mouth - the mouth cavity only (teeth/tongue/dark interior). Ideal for teeth work without touching lip shape.
  • outer_lips - the full lip region including lip skin.
  • lips_only - outer minus inner: just the lip flesh, for re-coloring lips while preserving the interior.
  • outer_expanded, top_lip_only, bottom_lip_only - the more surgical variants.

Beyond that, it's defensive by design. When someone puckers, kisses, or sticks a lollipop in front of the camera, the inner mouth polygon collapses to near nothing and a normal mask would implode. With auto_fallback on (default), the node detects that collapse via fallback_area_threshold - inner area as a ratio of outer area, default 0.15 - and swaps in an ellipse built from stable lip anchors (mouth corners, cupid's bow, lower lip bottom), padded by fallback_pad_h/fallback_pad_v. Same trick repeats across the pack's nodes, and it's the difference between a mask that survives a weird frame and one that doesn't.

The inputs and outputs that matter

Required: pose_keypoint - wire it from DWPreprocessor's POSE_KEYPOINT output with detect_face enabled. mask_mode as above. Then the handful you'll actually touch:

  • blur_radius (default 5) - Gaussian feathering on mask edges; 0 = hard edge. Feather before video inpainting or you'll see the seam.
  • grow_pixels (default 0) - dilate the mask outward; a couple of pixels stops the inpaint from hugging the lip boundary too tight.
  • person_index (default 0) - which detected person; -1 unions all people, which is the setting you want for a group shot.
  • confidence_threshold (default 0.3) - skip frames where average lip keypoint confidence drops below this.

Outputs: mask (the one you wire into VACE Inpaint / KSampler), plus three lists: centroid_x_list/centroid_y_list (per-frame lip centroids - the README's suggestion is feeding these as SAM-2 point prompts to refine the mask) and fallback_active_list (which frames actually used the fallback ellipse, so you can see when the mask is really an ellipse).

Installing it

Manager (search "lip_mask_from_pose") or:

cd ComfyUI/custom_nodes
git clone https://github.com/Jalen-Brunson/lip_mask_from_pose

restart, done. This node is pure numpy/cv2/torch - genuinely zero extra dependencies. The dependency you do need for its input is ComfyUI ControlNet Auxiliary Preprocessors for DWPreprocessor; that pack is basically required for any serious ControlNet workflow anyway.

Troubleshooting

  • Blank mask out of nowhere: you connected POSE_KEYPOINT but forgot detect_face=enable on the DWPreprocessor - the face landmarks aren't in the payload, so there's nothing to build.
  • Mask looks like a blob on certain frames: that's fallback_active_list doing its job; widen fallback_pad_h/fallback_pad_v if the ellipse undershoots the lips.
  • Wrong person masked: set person_index correctly, or use -1 for everyone.
  • Edgy, visible seams: raise blur_radius and grow_pixels a little - hard masks are the classic VACE seam cause.
Categorymask/pose

Inputs (10)

NameTypeDefaultDescription
pose_keypointPOSE_KEYPOINT
mask_modeCOMBOinner_mouth6 options: inner_mouth, outer_lips, lips_only, outer_expanded, top_lip_only, bottom_lip_only
blur_radiusINT50–100Gaussian blur radius for feathering mask edges. 0 = hard edge.
grow_pixelsINT00–200Dilate/grow the mask outward by this many pixels.
person_indexINT0-1–99Which detected person to extract lips from (0 = first/largest). -1 = all people, masks unioned together.
confidence_thresholdFLOAT0.300–1Skip frames where average lip keypoint confidence is below this.
auto_fallbackBOOLEANtrueAuto-switch to elliptical face-anchor mask when inner mouth polygon collapses (puckered lips, lollipop, kissing).
fallback_area_thresholdFLOAT0.150.01–1Inner mouth area as ratio of outer lip area. Below this = collapsed/puckered, triggers fallback. 0.15 means if inner mouth is less than 15% of outer lip area.
fallback_pad_hFLOAT1.200.5–3Horizontal scale for fallback ellipse. 1.0 = tight to mouth corners.
fallback_pad_vFLOAT1.300.5–3Vertical scale for fallback ellipse. 1.0 = tight to upper lip top / lower lip bottom.

Outputs (4)

NameTypeDescription
maskMASK
centroid_x_listFLOAT
centroid_y_listFLOAT
fallback_active_listBOOLEAN