BD MP Mouth Parts
Lips, teeth, tongue into one viseme atlas — BD MP Mouth Parts
- image
- model
- pom
- lips
- teeth
- tongue
- rgb_packed
- rgba_packed
- debug_overlay
- status
If you've ever rigged a mouth for animation, you know the viseme atlas workflow: separate the mouth render into lips, teeth, and tongue, then pack them into an RGBA texture where the game engine reads each from its own channel. Doing that used to mean a five-node chain - SAM3 ×3 for the three parts, fill holes, resolve, pack channels. BD MP Mouth Parts collapses all of it into one node: it separates an isolated mouth render into lips / teeth / tongue and packs them straight into the Unreal viseme-atlas contract - R = lips, G = teeth, B = tongue, A = POM (parallax occlusion, if you wire a depth map in).
The clever part is the default engine, color, needs no model at all. It's pure HSV classification tuned on the pack's own PinkF1 viseme set: the mouth render comes in on a near-black background with magenta/purple lips, white teeth, and a salmon/rose tongue - a clean color-separation problem. Teeth are bright and desaturated (low saturation, high value); the tongue is the warm-hue region (closer to red, ~338°) gated to the eroded mouth interior so warm lip-corner highlights don't read as tongue; lips are the saturated remainder, with a saturation floor that keeps the dark mouth cavity out.
The engine choice
engine defaults to color, but flip it to sam3 when color alone fails - e.g. the lit front of the tongue reads as saturated magenta and bleeds into the lips. In sam3 mode, the color split becomes the prior: each part's color mask seeds SAM3 (bounding box + interior positive points + the other parts' centroids as negatives), SAM3 grows the true object boundary, and the result gets clipped to the mouth foreground and cleaned. If you leave model unwired, it auto-loads the official SAM3 checkpoint in-house.
Then there's the threshold dial cluster for the color engine - these are tuned for the PinkF1 palette and exposed so you can adapt to other renders:
bg_v_min(25) - background cutoff; raise if the render has a lifted grey background.teeth_s_max(60) /teeth_v_min(140) - teeth = desaturated and bright.tongue_h_lo/hi(325/358),tongue_s_min/max(80/170),tongue_v_min(120) - the warm tongue band.lips_s_min(85) - the cavity guard.interior_frac(0.06) - the tongue must lie inside the mouth interior eroded by this fraction; kills false tongues from warm highlights.
The cleanup trio - edge_smooth, fill_holes, despeckle - seals jagged edges, fills interior holes, and drops tiny components (specular glints that read as teeth). And edge_refine (off → guided → matting → vitmatte) snaps part edges to the image, each degrading gracefully to 'off' if its backend is missing.
Outputs and install
You get lips, teeth, tongue as MASKs; rgb_packed (R/G/B, no alpha); rgba_packed (R/G/B/A with POM - the viseme cell contract); a debug_overlay tinted by part for QC; and a status string. Wire pom with a depth map (e.g. Lotus2 depth → normalize → center luma) and it rides the A channel; leave it and A = the mouth foreground, so the packed image is still usable.
It's in ComfyUI-BrainDead: Manager search "BrainDead", or clone + pip install -r requirements.txt, restart (V3-API pack - keep ComfyUI current). It needs cv2 (numpy/opencv) and, in sam3 mode, auto-downloads the SAM3 checkpoint.
The honest word: it's tuned on a specific render style, so if your mouth renders use a completely different palette, expect to touch the HSV thresholds (the debug_overlay makes that painless). And it's a separation and packing node only - saving to disk and POM derivation live in their own nodes. But for its lane - PinkF1-style visemes straight into a Unreal atlas - it replaces a six-node chain with one node and no model, which is exactly the kind of thing this pack does well.
Inputs (24)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Isolated mouth render (background near-black). Only image[0] is used. | |
| engineopt | COMBO | color | color — HSV classification, no model (default). sam3 — colour split SEEDS SAM3 (box + interior + sibling-negative points) for object-accurate boundaries. Auto-loads SAM3 in-house if `model` is unwired. |
| modelopt | MODEL | Comfy-core SAM3 model (override). Leave UNWIRED to auto-load + auto-download the official SAM3 checkpoint in-house (bd_sam3). Only used when engine='sam3'. | |
| pomopt | IMAGE | Optional POM / depth map (e.g. Lotus2 depth → NormLuma → CenterLuma) → packed into the A channel. Its luminance is used. If unwired, A = mouth foreground (lips∪teeth∪tongue). | |
| bg_v_minopt | INT | 250–128 | Background cutoff: pixels with HSV value ≤ this are background (black). Raise if the render has a lifted/grey background. |
| teeth_s_maxopt | INT | 600–255 | Teeth = saturation BELOW this (white/grey is desaturated). Raise to grab tinted teeth; lower if pale lip facets leak into teeth. |
| teeth_v_minopt | INT | 1400–255 | Teeth = value (brightness) ABOVE this. Raise to require brighter teeth. |
| tongue_h_loopt | INT | 3250–360 | Tongue warm-hue band low edge (°). The tongue is warmer (closer to red, ~338°) than the magenta lips (~310°); the |H≤6° wrap is always included. |
| tongue_h_hiopt | INT | 3580–360 | Tongue warm-hue band high edge (°). |
| tongue_s_minopt | INT | 800–255 | Tongue core min saturation (excludes near-grey). |
| tongue_s_maxopt | INT | 1700–255 | Tongue core MAX saturation — the tongue is LESS saturated than the lips, so this ceiling separates the salmon tongue from saturated magenta lips. |
| tongue_v_minopt | INT | 1200–255 | Tongue core min value (brightness). |
| lips_s_minopt | INT | 850–255 | Lips min saturation. Acts as the cavity guard: the dark, desaturated mouth interior falls below this so it is NOT labelled lips. Lower to include darker lip facets; raise if cavity bleeds in. |
| interior_fracopt | FLOAT | 0.0600–0.25 | Mouth-interior erosion (fraction of the longer side). The tongue core must lie inside the foreground eroded by this much — kills warm lip-corner highlights that would otherwise grow into a false tongue. 0 disables the gate. |
| sam3_itersopt | INT | 10–5 | engine='sam3' only: SAM decoder refinement passes. 1 = single pass (fullest part — extra passes tend to shrink small parts on stylized renders); a collapse-guard reverts any part the loop over-shrinks. |
| bleed_guardopt | INT | 240–200 | engine='sam3' only: dilate the colour mouth foreground by this many px, then clip each SAM3 part to it — stops SAM3 from escaping onto skin/specular. 0 = clip exactly to the colour foreground. |
| edge_smoothopt | INT | 30–15 | Morphological close radius (px) to seal jagged lip/tongue edges. 0 = none. |
| fill_holesopt | BOOLEAN | true | Fill interior holes so each part is solid (e.g. specular gaps in the tongue, gaps between teeth in the lips ring). |
| despeckleopt | FLOAT | 0.00080–0.05 | Drop connected components smaller than this fraction of the image (removes specular glints on the tongue that read as teeth and punch holes in it). 0 disables. Keep small so real (separate) teeth aren't removed. |
| edge_refineopt | COMBO | off | Snap each part's edge to the image colour/edges (shared with BD MP SAM3): off — colour masks as-is (fast) guided — guided filter (fast, edge-aware) matting — PyMatting closed-form (CPU, no model) vitmatte — VitMatte deep matting (GPU, auto-downloads). Each degrades to 'off' if its backend is missing. |
| refine_radiusopt | INT | 61–40 | Guided-filter radius / matting trimap band width (px). |
| refine_epsopt | FLOAT | 0.00010.000001–0.1 | Guided-filter edge sensitivity (smaller hugs edges harder). Ignored by matting. |
| refine_thresholdopt | FLOAT | 0.500.05–0.95 | Binarize the refined alpha at this level. |
| vitmatte_modelopt | COMBO | small | VitMatte variant for edge_refine='vitmatte' (auto-downloaded from the HF hub). |
Outputs (7)
| Name | Type | Description |
|---|---|---|
| lips | MASK | — |
| teeth | MASK | — |
| tongue | MASK | — |
| rgb_packed | IMAGE | R=lips, G=teeth, B=tongue (no alpha). The PackChannels equivalent. |
| rgba_packed | IMAGE | R=lips, G=teeth, B=tongue, A=POM (from `pom`, else mouth foreground). The viseme-atlas cell contract. |
| debug_overlay | IMAGE | Render tinted by part (lips=R, teeth=G, tongue=B) for QC. |
| status | STRING | — |