Based on GitHub Issue #1410 (WanVideoPreProcessor) and #10 (WanAnimatePreProcessor)
Special thanks: @kijai and @steven850
- Overview
- Installation
- Model Downloads
- Nodes Reference
- Parameter Deep-Dive
- Processing Pipeline
- Output Reference
- Iris & Gaze Detection
- Debug Image Guide
- Recommended Settings by Scenario
- Troubleshooting
- FAQ
This ComfyUI custom node package provides production-quality pose and face preprocessing for the Wan Video 2.2 Animate model. It takes input video frames and extracts:
- Full-body pose data (body, hands, face keypoints) in the format expected by Wan Animate
- Stabilised face crops (512Γ512) for expression-driven animation
- Iris/pupil positions and gaze direction for accurate eye animation
- Debug visualisation images showing every detection overlaid on the original frame
| Feature | V1 (Original) | V2 (This Version) |
|---|---|---|
| Pose stability | Frequent jitter, vanishing limbs | CLAHE + blur preprocessing eliminates jitter |
| Face crops | Raw bbox from keypoints; clips forehead/chin | Constant-size box with temporal smoothing |
| Iris detection | None | Image-based pupil detection with gradient voting |
| Debug output | None | Full diagnostic overlay image |
| Temporal smoothing | None | Motion-adaptive smoothing for face box and iris |
| Lighting robustness | Poor in low light | CLAHE enhancement adapts to any lighting |
pip install -r requirements.txtKey dependencies: numpy, opencv-python, onnxruntime (or onnxruntime-gpu), torch, tqdm.
- Clone or copy this folder into
ComfyUI/custom_nodes/:ComfyUI/custom_nodes/ComfyUI-WanAnimatePreprocessV2/ - Download the required models (see below).
- Restart ComfyUI.
- The three nodes will appear under the WanAnimatePreprocess_V2 category.
All models go into:
ComfyUI/models/detection/
| Model | Link |
|---|---|
| YOLOv10m (recommended) | yolov10m.onnx |
| Model | Size | Link |
|---|---|---|
| ViTPose-Large (faster) | ~350 MB | onnx/wholebody |
| ViTPose-Huge (more accurate) | ~1 GB | onnx |
Note for ViTPose-Huge: It is split into two files (
vitpose_h_wholebody_model.onnx+vitpose_h_wholebody_data.bin). Both must be in the same folder. Select the.onnxfile in the model loader.
Purpose: Loads the YOLO and ViTPose ONNX models into memory.
| Parameter | Type | Default | Description |
|---|---|---|---|
vitpose_model |
Dropdown | β | Select your ViTPose .onnx file from ComfyUI/models/detection/ |
yolo_model |
Dropdown | β | Select your YOLO .onnx file from ComfyUI/models/detection/ |
onnx_device |
Dropdown | CUDAExecutionProvider |
Which device to run inference on |
Device selection:
CUDAExecutionProviderβ Use your NVIDIA GPU. Much faster. Requiresonnxruntime-gpu.CPUExecutionProviderβ Use CPU only. Works everywhere but significantly slower.
Tip: If you get CUDA errors, ensure your
onnxruntime-gpuversion matches your CUDA toolkit version. You can check withpython -c "import onnxruntime; print(onnxruntime.get_available_providers())".
Output: A POSEMODEL object containing both loaded models, to be connected to the Pose and Face Detection (V2) node.
Purpose: The main processing node. Takes input frames and produces pose data, face crops, iris tracking, and debug images.
This is where all the magic happens. See the Parameter Deep-Dive section below for detailed explanation of every input.
Inputs:
| Parameter | Type | Default | Range |
|---|---|---|---|
model |
POSEMODEL | β | From Model Loader |
images |
IMAGE | β | Video frames (BΓHΓWΓ3) |
width |
INT | 832 | 64β2048 |
height |
INT | 480 | 64β2048 |
detection_threshold |
FLOAT | 0.05 | 0.0β1.0 |
pose_threshold |
FLOAT | 0.3 | 0.0β1.0 |
use_clahe |
BOOLEAN | True | β |
use_blur_for_pose |
BOOLEAN | True | β |
blur_radius |
INT | 5 | 1β20 |
blur_sigma |
FLOAT | 2.0 | 0.1β5.0 |
use_face_smoothing |
BOOLEAN | True | β |
face_smoothing_strength |
FLOAT | 0.6 | 0.0β1.0 |
use_constant_face_box |
BOOLEAN | True | β |
face_box_size_px |
INT | 224 | 64β1024 |
use_iris_smoothing |
BOOLEAN | True | β |
iris_smoothing_strength |
FLOAT | 0.4 | 0.0β1.0 |
Outputs:
| Output | Type | Description |
|---|---|---|
pose_data |
POSEDATA | Full pose metadata (body, hands, face, iris) for Wan Animate |
face_images |
IMAGE | Stabilised 512Γ512 face crops, one per frame |
key_frame_body_points |
STRING | JSON array of key body points from the first frame |
bboxes |
BBOX | Person bounding box from the first frame |
face_bboxes |
BBOX | Face bounding box per frame (x1, x2, y1, y2) |
iris_data |
STRING | JSON array of per-frame iris positions and gaze vectors |
debug_image |
IMAGE | Diagnostic overlay showing landmarks, iris, bboxes |
Purpose: Renders the detected pose as a coloured skeleton image, suitable as a conditioning input for Wan Animate.
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
pose_data |
POSEDATA | β | β | From Pose and Face Detection output |
width |
INT | 832 | 64β2048 | Output image width (should match your generation width) |
height |
INT | 480 | 64β2048 | Output image height (should match your generation height) |
retarget_padding |
INT | 16 | 0β512 | Padding around the pose figure in pixels |
body_stick_width |
INT | -1 | -1β20 | Width of body bone lines. -1 = auto-scale |
hand_stick_width |
INT | -1 | -1β20 | Width of hand bone lines. -1 = auto-scale. 0 = don't draw hands |
draw_head |
BOOLEAN | True | β | Whether to draw the head circle/arc |
pose_draw_threshold |
FLOAT | 0.3 | 0.0β1.0 | Confidence threshold for drawing joints |
Output: pose_images β A batch of skeleton images (BΓHΓWΓ3) as float tensors.
What it does: Controls the minimum confidence score for YOLO person detections to be accepted.
How it works: YOLO outputs bounding boxes with confidence scores. Any detection below this threshold is discarded. This is applied during YOLO post-processing before pose estimation begins.
How to use:
| Value | Behaviour | When to use |
|---|---|---|
0.01β0.05 |
Accept almost all detections | Partial occlusion, person at edge of frame, small person in wide shot |
0.05 (default) |
Balanced | Most videos |
0.1β0.3 |
Strict filtering | Clean, well-lit single-person videos. Reduces false positives |
0.5+ |
Very strict | Only use if you're getting phantom detections from background objects |
Best practice: Start with 0.05. Only increase if you see YOLO detecting non-human objects (chairs, mannequins, etc.). Decrease if the person occasionally disappears, especially in frames where they're partially off-screen.
β οΈ Setting this too high causes the person to "flicker" in and out of detection across frames.
What it does: Zeroes out any keypoint with confidence below this value. Affects both the stored pose data and drawn skeletons.
How it works: After ViTPose predicts all 133 keypoints (body + face + hands), any keypoint with confidence < pose_threshold gets its confidence set to 0. This means:
- The keypoint won't be drawn by Draw ViTPose
- The keypoint won't contribute to face bbox calculations
- The keypoint will still exist in the data but marked as "not visible"
How to use:
| Value | Behaviour | When to use |
|---|---|---|
0.0 |
Keep all keypoints regardless of confidence | Maximum data retention; useful if downstream processing handles filtering |
0.1β0.2 |
Very lenient | Difficult poses, people facing away, occluded limbs |
0.3 (default) |
Balanced | Standard forward-facing or side-view footage |
0.5β0.7 |
Strict | Only want highly confident keypoints. May cause limbs to disappear |
0.8+ |
Extreme | Not recommended. Most keypoints will be filtered out |
Best practice: 0.3 works for most content. Lower to 0.1 if limbs keep vanishing. The pose draw node has its own separate pose_draw_threshold that controls what gets rendered visually.
What it does: Enables CLAHE (Contrast Limited Adaptive Histogram Equalisation) on the cropped image fed to ViTPose.
How it works: CLAHE divides the image into small tiles and equalises the histogram in each tile independently, then blends the boundaries. This dramatically improves local contrast without blowing out already-bright areas. It helps ViTPose "see" keypoints in challenging lighting.
When to enable (True):
- Low-light or night-time footage
- Subject wearing dark clothing against a dark background
- Subject wearing white clothing against a bright background
- Indoor scenes with mixed lighting (window + overhead)
- Any video where pose detection is inconsistent
When to disable (False):
- Already well-lit, high-contrast studio footage
- If you notice artifacts in the pose output (very rare)
- Marginally faster without it (saving ~1ms per frame)
Best practice: Leave it True. It helps in nearly all situations and the overhead is negligible. The enhancement is only applied to the detection pipeline β your face crops and output images remain unaffected.
What it does: Applies a Gaussian blur to the frames before they're fed to YOLO (person detection) and ViTPose (keypoint estimation).
How it works: A Gaussian kernel smooths high-frequency details (fabric texture, hair strands, skin pores, compression artifacts). Pose models are trained to detect joint positions, not texture β blurring aligns the input distribution closer to what the model expects.
Why it helps:
- Removes compression noise (especially from H.264/H.265 video)
- Eliminates micro-texture that causes multiple weak heatmap peaks
- Produces cleaner, more confident keypoint predictions
- Stabilises YOLO bounding boxes across frames
Important: Blur is only applied to the detection pipeline. Face crops come from the original unblurred frames.
When to enable (True):
- Video input (any codec/compression)
- Images with visible noise or grain
- Textured clothing or busy backgrounds
When to disable (False):
- High-resolution studio photography with no noise
- Already-downscaled/blurry input
- If you want to experiment and compare results
Best practice: Leave it True. The improvement is substantial and consistent.
What it does: Controls the size of the Gaussian blur kernel used for pose detection.
How it works: The actual OpenCV kernel size is blur_radius * 2 + 1. So blur_radius=5 produces an 11Γ11 kernel. Larger kernels produce more smoothing.
| blur_radius | Kernel size | Effect |
|---|---|---|
| 1 | 3Γ3 | Minimal smoothing |
| 3 | 7Γ7 | Light smoothing |
| 5 (default) | 11Γ11 | Good balance |
| 7 | 15Γ15 | Strong smoothing |
| 10+ | 21Γ21+ | Heavy smoothing; may lose small-joint detail |
Best practice: 5 is optimal for 720pβ1080p video. For 4K input you might increase to 7. For low-resolution (480p or below), try 3.
β οΈ Extremely high values (>10) can blur away hand and face keypoints.
What it does: Controls the "spread" of the Gaussian blur. Higher sigma = softer, wider blur.
How it works: Sigma (Ο) determines how much each pixel's neighbours influence it. The kernel size sets the maximum range; sigma sets how quickly the influence drops off. A large sigma relative to the kernel size makes the blur uniform across the kernel.
| blur_sigma | Effect |
|---|---|
| 0.5β1.0 | Barely visible; only removes very fine noise |
| 2.0 (default) | Removes texture noise while preserving body shape |
| 3.0β4.0 | Stronger smoothing; good for very noisy input |
| 5.0 | Maximum; significant detail loss |
Best practice: 2.0 with blur_radius=5 is the sweet spot for standard video. If your input is very grainy (e.g., ISO 3200+ phone footage at night), try 3.0.
What it does: Enables temporal smoothing of the face bounding box centre across frames.
How it works: Without smoothing, the face bbox jitters frame-to-frame because ViTPose keypoints have small per-frame variations. This smoothing applies a motion-adaptive exponential filter to the face box centre:
- When the face is still β heavy smoothing (nearly static)
- When the face moves slightly β moderate smoothing
- When the face moves quickly (turning, nodding) β minimal smoothing (immediate tracking)
This prevents the generated face crops from jittering while still tracking rapid motion.
When to enable (True):
- Any video where the subject's face appears across multiple frames
- Talking-head videos
- Dance/movement videos
When to disable (False):
- Single-image processing (no temporal context)
- Rapidly switching between different subjects
- If you want raw detection output for further processing
Best practice: Leave it True for video. The motion-adaptive behaviour ensures it never lags behind real movement.
What it does: Controls how much temporal smoothing is applied to the face box centre. Only active when use_face_smoothing is True.
How it works: This is the "base" smoothing strength. The actual per-frame smoothing is dynamically reduced when motion is detected. 0.0 means no smoothing; 1.0 means maximum smoothing (when the face is stationary).
| Value | Behaviour | When to use |
|---|---|---|
0.0 |
No smoothing (same as disabling) | If you want raw bbox per frame |
0.2β0.3 |
Light smoothing | Fast-moving subjects; sports footage |
0.4β0.6 |
Moderate smoothing | Standard talking-head content |
0.6 (default) |
Good balance | Most videos |
0.8β1.0 |
Heavy smoothing | Very stable face (news anchor, interview) |
Best practice: 0.6 works well for most content. Increase to 0.8 for interview-style footage where the face barely moves. Decrease to 0.3 for action sequences.
What it does: Locks the face crop to a fixed pixel size instead of computing a variable-size bounding box from keypoints each frame.
How it works: ViTPose only detects eye, nose, and mouth landmarks β it has no forehead or chin keypoints. This means a bbox computed from keypoints alone will:
- Clip the forehead (especially with hair up or hats)
- Miss the chin in downward head tilts
- Change size erratically when keypoints move
With constant face box enabled, the system:
- Computes the face bbox centre from keypoints
- Applies temporal smoothing to that centre
- Creates a square box of exactly
face_box_size_pxcentred on the smoothed point
The result is a perfectly stable zoom level where only the centre moves.
When to enable (True):
- Standard Wan Animate workflows (recommended)
- Any scenario where you want consistent face framing across frames
- Preventing sudden zoom-in/zoom-out in face crops
When to disable (False):
- If the subject distance changes dramatically (walking toward/away from camera)
- Multi-person scenes where you want the bbox to adapt to different face sizes
- Creative workflows where variable framing is desired
Best practice: Leave it True for Wan Animate. The consistent framing produces much better animation results.
What it does: Sets the pixel size of the square face crop region (before the final 512Γ512 resize). Only applies when use_constant_face_box is True.
How it works: This is essentially a zoom level control. The face region is always cropped as a square of this size from the original frame, then resized to 512Γ512 for the Animate model.
- Smaller value = more zoomed in (only eyes/nose/mouth visible)
- Larger value = more zoomed out (full head + shoulders visible)
Size guide based on framing:
| Shot Type | Subject Coverage | Recommended Size | Notes |
|---|---|---|---|
| Extreme close-up | Face fills >70% of frame | 320β448 |
Preserves forehead and chin |
| Close-up | Head and shoulders | 256β320 |
Good default range |
| Medium shot (chest up) | Most common | 224β256 |
Start here |
| Medium-wide (waist up) | Half body | 192β256 |
Still captures head turns |
| Full body | Entire person | 128β192 |
Small face; may lose detail |
| Wide shot | Person is small | 96β128 |
May need to increase |
How to find the right value:
- Start with
224(default) - Run the workflow and check the
face_imagesoutput - If the forehead or chin is clipped β increase by 32β64
- If there's too much background in the face crop β decrease by 32β64
- Check that the ears are visible for profile views
Tip: The output face crop is always 512Γ512 regardless of this setting. A smaller
face_box_size_pxmeans the face is upscaled more, which can introduce blur. Keep it large enough to capture the full head at native resolution.
What it does: Enables temporal smoothing of detected iris/pupil positions across video frames.
How it works: Without smoothing, the detected pupil centre can jump slightly between frames due to image noise, lighting changes, or slight inaccuracies in the detection pipeline. Iris smoothing applies an exponential moving average to the (x, y) iris positions:
smoothed_pos = alpha Γ current_pos + (1 - alpha) Γ previous_smoothed_pos
where alpha = 1.0 - iris_smoothing_strength.
When to enable (True):
- Video sequences where eye gaze should appear smooth
- Talking-head videos where the subject looks at the camera
- Any footage where you're using iris/gaze data for animation
When to disable (False):
- Single-image processing
- Footage with very rapid eye movements you want to preserve (rare)
- If you don't use the iris_data output at all
Best practice: Leave it True for video. Eye movement is naturally smooth in real life, so temporal smoothing produces more realistic results.
What it does: Controls the amount of temporal smoothing applied to iris positions when use_iris_smoothing is True.
How it works: Higher values smooth more aggressively. The filter is applied independently to left and right iris positions.
| Value | Behaviour | When to use |
|---|---|---|
0.0 |
No smoothing | If you want raw detection per frame |
0.1β0.2 |
Very light | Fast eye movements that need to be preserved |
0.3β0.4 |
Moderate | Most standard footage |
0.4 (default) |
Good balance | Standard talking-head videos |
0.6β0.8 |
Strong smoothing | Subject looking steadily at camera; minimises jitter |
1.0 |
Maximum (iris barely moves) | Not recommended for natural results |
Best practice: 0.4 for most content. Increase to 0.6 if you notice iris jitter in the debug image. Keep below 0.7 to preserve natural eye movement.
What it does: Adds padding (in pixels) around the entire pose figure on the output canvas.
How it works: Before drawing the skeleton, the pose is retargeted to fit within the output dimensions minus this padding on each side. More padding = smaller skeleton centred on the canvas.
| Value | Effect |
|---|---|
0 |
Skeleton fills the entire canvas |
16 (default) |
Small margin; good for most cases |
32β64 |
Noticeable margin; prevents limbs touching edges |
128+ |
Large margin; tiny skeleton in the centre |
Best practice: 16 is fine for Wan Animate. Increase if limbs get cut off at the frame edges.
What it does: Width (in pixels) of the lines connecting body joints.
| Value | Effect |
|---|---|
-1 (default) |
Auto-calculated based on output resolution |
0 |
Don't draw body at all |
1β5 |
Thin lines |
6β10 |
Medium (good for 720pβ1080p) |
11β20 |
Thick lines (good for 4K or emphasis) |
What it does: Width (in pixels) of lines connecting hand/finger joints.
| Value | Effect |
|---|---|
-1 (default) |
Auto-calculated |
0 |
Don't draw hands at all |
1β3 |
Thin (hands are small; thin lines work well) |
Tip: Set to
0if hand tracking is unreliable or not needed.
What it does: Whether to draw the head circle/arc on the pose image.
True(default): Draws a head representation based on face keypointsFalse: Omits the head; useful if you only need body pose
What it does: Controls which keypoints/bones are drawn based on their confidence.
This is separate from pose_threshold β the detection node stores all keypoints, while this threshold controls which ones are rendered as visual output.
| Value | Effect |
|---|---|
0.0 |
Draw everything, even uncertain joints (may look messy) |
0.05β0.1 |
Draw most joints; prevents fully random points from showing |
0.2β0.3 |
Draw confident joints; some minor limbs may disappear |
0.3 (default) |
Balanced; matches detection threshold |
0.5+ |
Only very confident joints; may lose fingers, toes, obscured limbs |
Best practice: Use 0.3 for clean results. Lower to 0.05 if limbs keep vanishing in the pose output.
Input Frames (B Γ H Γ W Γ 3)
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 1: PREPROCESSING β
β β’ Optional CLAHE enhancement β
β β’ Optional Gaussian blur β
β β Blurred frames (for detect) β
β β Original frames (for crops) β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 2: YOLO PERSON DETECT β
β β’ Runs on blurred frames β
β β’ Outputs person bounding box β
β β’ Filters by detection_thresh β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 3: ViTPose INFERENCE β
β β’ Crops person from blurred β
β β’ CLAHE on crop (optional) β
β β’ Normalises input β
β β’ Predicts 133 keypoints: β
β 23 body + 21 LH + 21 RH β
β + 68 face landmarks β
β β’ Filters by pose_threshold β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 4: POSE META EXTRACTION β
β β’ Normalise keypoints to [0,1] β
β β’ Split into body/face/hands β
β β’ Build pose meta structures β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 5: FACE BBOX BUILD β
β β’ Compute raw bbox from face β
β keypoints (scale=1.3) β
β β’ Extract centres β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 6: FACE TEMPORAL SMOOTH β
β β’ Motion-adaptive exponential β
β smoothing of bbox centres β
β β’ Constant-size box from β
β smoothed centre β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 7: FACE CROPPING β
β β’ Crop from ORIGINAL frames β
β β’ Resize to 512Γ512 β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 8: IRIS / GAZE DETECT β
β β’ Extract eye ROI per frame β
β β’ CLAHE + upper-region mask β
β β’ Gradient voting (primary) β
β β’ Contour analysis (secondary) β
β β’ Temporal smoothing β
β β’ Compute gaze vectors β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 9: DEBUG VISUALISATION β
β β’ Draw all 68 face landmarks β
β β’ Eye contour polylines β
β β’ Iris crosshairs + confidence β
β β’ Gaze direction arrows β
β β’ Face and body bounding boxes β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Stage 10: PACKAGING β
β β’ PoseData with iris metadata β
β β’ face_images tensor β
β β’ JSON outputs (keypoints, β
β iris/gaze data) β
β β’ Debug image tensor β
βββββββββββββββββββββββββββββββββββ
| Output | Type | Format | Usage |
|---|---|---|---|
pose_data |
POSEDATA | Dict with pose_metas, pose_metas_original, iris_data |
Connect to Draw ViTPose and/or Wan Animate nodes |
face_images |
IMAGE | Float32 tensor [B, 512, 512, 3] | Connect to Wan Animate face input |
key_frame_body_points |
STRING | JSON: [{"x": 123, "y": 456}, ...] |
8 key body points from frame 0 for reference |
bboxes |
BBOX | Tuple of 4 ints (x1, y1, x2, y2) | Person bounding box from frame 0 |
face_bboxes |
BBOX | List of (x1, x2, y1, y2) per frame | Face crop regions used |
iris_data |
STRING | JSON array (see below) | Per-frame iris + gaze |
debug_image |
IMAGE | Float32 tensor [B, H, W, 3] | Connect to Preview Image for debugging |
[
{
"frame": 0,
"right_iris": {"x": 234.5, "y": 167.2, "confidence": 0.85},
"left_iris": {"x": 345.1, "y": 165.8, "confidence": 0.82},
"right_gaze": {"dx": 0.1234, "dy": -0.0567},
"left_gaze": {"dx": 0.0987, "dy": -0.0432}
},
...
]x,yβ Pixel coordinates of the detected pupil centreconfidenceβ Detection confidence (0.0β1.0). Below 0.1 indicates fallback to geometric centredx,dyβ Normalised gaze direction unit vector (iris offset from geometric eye centre)dx > 0β looking right;dx < 0β looking leftdy > 0β looking down;dy < 0β looking up- Magnitude near 0 β looking straight ahead
The iris detection pipeline uses a multi-strategy approach to robustly locate the pupil centre within each eye:
Inspired by the Timm-Barth (2011) algorithm. Edge gradients around the circular pupil boundary naturally point radially outward. By casting rays in the negative gradient direction from every strong-edge pixel, votes accumulate at the true pupil centre.
Advantages:
- Threshold-free β doesn't depend on absolute pixel darkness
- Robust to varying lighting, iris colour, and reflections
- Naturally finds circular features (the pupil)
Tries multiple intensity percentiles (10th, 20th, 30th, 40th) to binarise the eye region, then evaluates each dark blob by:
- Circularity β pupil is round
- Proximity β pupil is near the geometric eye centre (with asymmetric vertical penalty to avoid eyelid shadows)
- Size β pupil is 8β45% of the visible eye area
Computes the weighted centroid of all dark pixels within the eye mask, biased toward the upper region of the eye opening.
-
Upper-region restriction: The search mask is limited to the upper 65% of the eye opening. The pupil is biologically at or above the vertical midpoint β lower regions contain eyelid shadows and eyelash darkness that contaminate detection.
-
CLAHE preprocessing: Local histogram equalisation on the eye ROI dramatically improves pupil-iris-sclera contrast, especially under uneven lighting.
-
Eye Aspect Ratio (EAR) check: Closed or nearly-closed eyes (EAR < 0.12) are detected early and assigned the geometric centre with low confidence, avoiding false detections on eyelid creases.
-
Asymmetric vertical scoring: Candidates below the geometric eye centre are penalised more heavily than those above, reflecting the natural anatomy where the pupil centre is typically at or slightly above the midline of the visible eye opening.
The debug_image output shows annotations overlaid on the original frame:
| Element | Colour | Description |
|---|---|---|
| Jawline dots | Yellow (255, 200, 0) |
Landmarks 1β17 |
| Eyebrow dots | Yellow-Green (200, 255, 0) |
Landmarks 18β27 |
| Nose dots | Blue (0, 0, 255) |
Landmarks 28β36 |
| Eye dots | Green (0, 255, 0) |
Landmarks 37β48 |
| Outer mouth dots | Cyan (0, 255, 255) |
Landmarks 49β60 |
| Inner mouth dots | Teal (0, 255, 200) |
Landmarks 61β68 |
| Eye contour polylines | Green | Closed polygon connecting 6 eye contour points |
| Iris crosshair | Magenta (255, 0, 255) |
Cross marker at detected pupil centre |
| Iris circle | Magenta | Small circle around detected pupil |
| Confidence number | Magenta | Iris detection confidence (0.00β1.00) |
| Gaze arrow | Orange (0, 200, 255) |
Arrow from iris centre showing gaze direction |
| Face bounding box | Yellow (255, 255, 0) |
"FACE" labelled rectangle |
| Body bounding box | Green (0, 255, 0) |
"BODY" labelled rectangle |
-
Landmarks correctly placed? Each coloured dot should sit on the corresponding facial feature. If dots are wildly off, check your
detection_thresholdandpose_threshold. -
Eye contour reasonable? The green polygon should outline each eye. If it's skewed, the ViTPose detection may be struggling β try enabling CLAHE and blur.
-
Iris crosshair on the pupil? The magenta cross should be centred on the dark pupil. If it's offset, the confidence number tells you how sure the detector is.
-
Gaze arrow direction makes sense? For a person looking at the camera, arrows should be very short (near zero). For a person looking left, arrows point left. If arrows consistently point the wrong way, the iris detection may need different parameters.
-
Face box captures the full head? The yellow rectangle should include forehead and chin. Adjust
face_box_size_pxif it's too tight or too loose.
detection_threshold: 0.05
pose_threshold: 0.3
use_clahe: True
use_blur_for_pose: True
blur_radius: 5
blur_sigma: 2.0
use_face_smoothing: True
face_smoothing_strength: 0.6
use_constant_face_box: True
face_box_size_px: 224
use_iris_smoothing: True
iris_smoothing_strength: 0.4
detection_threshold: 0.03
pose_threshold: 0.2
use_clahe: True
use_blur_for_pose: True
blur_radius: 5
blur_sigma: 2.0
use_face_smoothing: True
face_smoothing_strength: 0.3 β less smoothing for fast movement
use_constant_face_box: True
face_box_size_px: 256 β slightly larger to handle tilts
use_iris_smoothing: True
iris_smoothing_strength: 0.2 β preserve quick eye movements
detection_threshold: 0.03 β more lenient for noisy detections
pose_threshold: 0.2
use_clahe: True β critical for dark footage
use_blur_for_pose: True
blur_radius: 7 β stronger to counter noise
blur_sigma: 3.0
use_face_smoothing: True
face_smoothing_strength: 0.7 β more smoothing (detections noisier)
use_constant_face_box: True
face_box_size_px: 256
use_iris_smoothing: True
iris_smoothing_strength: 0.5 β more smoothing for noisy iris detect
detection_threshold: 0.05
pose_threshold: 0.3
use_clahe: True
use_blur_for_pose: True
blur_radius: 3 β less blur needed at high resolution
blur_sigma: 1.5
use_face_smoothing: True
face_smoothing_strength: 0.6
use_constant_face_box: True
face_box_size_px: 384 β larger to capture full head
use_iris_smoothing: True
iris_smoothing_strength: 0.4
detection_threshold: 0.02 β very lenient to catch small person
pose_threshold: 0.15 β keep uncertain keypoints
use_clahe: True
use_blur_for_pose: True
blur_radius: 3 β less blur (person already small)
blur_sigma: 1.5
use_face_smoothing: True
face_smoothing_strength: 0.5
use_constant_face_box: True
face_box_size_px: 128 β small crop since face is small
use_iris_smoothing: True
iris_smoothing_strength: 0.5
detection_threshold: 0.05
pose_threshold: 0.3
use_clahe: True
use_blur_for_pose: True
blur_radius: 5
blur_sigma: 2.0
use_face_smoothing: False β no temporal context
face_smoothing_strength: 0.0
use_constant_face_box: True
face_box_size_px: 224
use_iris_smoothing: False β no temporal context
iris_smoothing_strength: 0.0
Symptom: YOLO doesn't detect the person in some frames, or detection flickers on/off.
Fix:
- Lower
detection_thresholdto0.02or0.01 - Ensure
use_claheis True - Ensure
use_blur_for_poseis True with default blur settings - Check if the person is very small in frame β YOLO struggles with small subjects
Symptom: In the pose output, some limbs disappear intermittently.
Fix:
- Lower
pose_thresholdto0.15or0.1 - Lower
pose_draw_thresholdin the Draw ViTPose node to0.1or0.05 - Enable
use_claheanduse_blur_for_pose - Ensure the person is fully visible and not heavily occluded
Symptom: Face crops jitter/jump between frames.
Fix:
- Enable
use_face_smoothingwith strength0.6β0.8 - Enable
use_constant_face_boxβ this dramatically reduces crop instability - If jittering persists, increase
face_smoothing_strength
Symptom: The 512Γ512 face crop cuts off the top of the head or the chin.
Fix:
- Increase
face_box_size_px(try256,320, or384) - Enable
use_constant_face_boxto prevent erratic sizing
Symptom: In the debug image, the magenta iris crosshair is not on the pupil, or the gaze arrow points in a wrong direction.
Fix:
- Ensure
use_claheis True (iris detection uses CLAHE internally, but overall image quality affects landmark placement) - Check if the eye landmarks (green dots) are correctly placed first β if not, the iris detector has no good reference
- For low-resolution faces (small or distant subjects), iris detection will be less accurate. Increase
face_box_size_pxor use closer footage - Enable
use_iris_smoothingto smooth out per-frame noise
Symptom: Errors mentioning onnxruntime, CUDA, or DLL issues.
Fix:
- Check
onnx_devicein the Model Loader β switch toCPUExecutionProviderif you don't have a compatible GPU - Ensure
onnxruntime-gpumatches your CUDA version:pip install onnxruntime-gpu==1.17.0(for CUDA 12.x) - If you get "DLL access denied" errors, close all Python processes, then reinstall:
pip install --force-reinstall onnxruntime-gpu
Symptom: Face landmark dots are placed on wrong locations (e.g., nose landmarks on the ear).
Fix:
- This usually means the ViTPose model is returning poor predictions. Enable both
use_claheanduse_blur_for_pose - Ensure you're using a whole-body ViTPose model (not a body-only model)
- Try using the ViTPose-Huge model for better accuracy
Q: Do I need all three nodes? A: The Model Loader and Pose & Face Detection are required. Draw ViTPose is only needed if you want to generate pose skeleton images for conditioning.
Q: Can I use this with Wan 2.1 or only 2.2? A: This is designed for Wan 2.2 Animate, which expects specific pose meta formats. It may work with other versions but is not guaranteed.
Q: What resolution should width and height be?
A: These should match your target generation resolution. Common values: 832Γ480, 1280Γ720, 512Γ512. The pose data is normalised so it scales to any resolution.
Q: Why is the blur only applied to detection, not to face crops? A: The blur helps pose models detect joints more reliably. But face crops need to be sharp and detailed for the Animate model to read facial expressions. That's why face crops always come from the original unblurred frames.
Q: How accurate is the iris detection? A: It works best when the eye occupies at least 20β30 pixels in width. Close-up to medium shots produce reliable results. For very distant subjects (eye width < 10 pixels), it falls back to the geometric eye centre with low confidence.
Q: Can I process thousands of frames? A: Yes. Memory usage scales linearly with batch size. For very long videos (1000+ frames), process in segments if you run out of VRAM.
Q: What's the performance like? A: On an RTX 3080 with CUDA, expect ~20β30 FPS for detection and pose estimation combined. CPU mode is significantly slower (~2β5 FPS).
Tracking quality with V2 shows significant improvement:
- β Stable bones β no jitter or vanishing limbs
- β Clean face crops β forehead and chin included
- β Accurate iris/gaze detection β pupils correctly located
- β Robust performance under difficult lighting
- β Smooth temporal transitions across frames
See LICENSE file.
Attached:
nodes.py,onnx_models.py(minor changes to pass thresholds correctly)
π Thanks for all the work on this project. This contribution is shared in the hope it can be merged upstream and help improve Animate preprocessing for everyone.
This V2 pack ships three nodes β OnnxDetectionModelLoaderV2 (loads YOLOv8 + ViTPose ONNX checkpoints into a unified detector bundle), PoseAndFaceDetectionV2 (per-frame whole-body 133-keypoint detection plus face crop and bbox extraction), and DrawViTPoseV2 (renders the COCO-WholeBody skeleton with optional face/hand stick widths and head toggles). The POSEDATA bundle and rendered IMAGE skeleton match the schema the Wan-Animate sampler family expects.
| Model family | Applicability |
|---|---|
| Wan 2.x Animate (human) | Native. The skeleton IMAGE from DrawViTPoseV2 is the human-pose conditioning channel for Wan-Animate; face_bboxes / face_images from PoseAndFaceDetectionV2 feed the face conditioning channel. This is the V2 of that pipeline β improved face handling and hand keypoints over V1. |
| Wan 2.x Animate (animal) | Not applicable here β use the sister pack ComfyUI-WanAnimalPreprocess (AP10k/APT36k animal ViTPose) for quadruped subjects. |
| Flux | Indirect. The rendered skeleton is a generic conditioning IMAGE β wire it into a Flux ControlNet pose model when generating human stills with pose control. The face crops are useful as ID/IP-Adapter input. |
| Qwen-Image | Indirect. Same pattern as Flux: use the skeleton as ControlNet pose conditioning if a Qwen-compatible pose ControlNet is loaded. |
| Z-Image | Indirect. Use the skeleton as a conditioning image where Z-Image supports pose ControlNet. |
| GLM-Image | Not currently applicable; GLM-Image does not yet expose a pose ControlNet path. |
| ERNIE-VL | Not applicable; ERNIE-VL is a multimodal LLM rather than a diffusion sampler. |
Wiring pattern for the canonical Wan-Animate V2 graph:
[Load Video Frames] ββ> [Pose and Face Detection V2] βββ¬ββ> POSEDATA ββ> [Draw ViT Pose V2] ββ> skeleton IMAGE
βββ> face_bboxes
βββ> face_images
β
[ONNX Detection Model Loader V2] ββ> POSEMODEL βββ β
βΌ
[Wan-Animate Sampler]
Bounding-box outputs are also useful as input to crop / inpaint pipelines (e.g. ComfyUI-CustomNodePacks Inpaint Crop Pro) when refining only the body or face region.
A complete, auto-generated reference covering every node, return type and widget exposed by this pack lives in NODE_REFERENCE.md. It is regenerated whenever INPUT_TYPES change.
