DWPose Estimator
Whole-Body Pose Without the ONNX Stack
- image
- image
- pose_keypoint
- Pose Overlay Mask
DWPose is the pose detector people switched to because OpenPose's hands are bad - better finger detection, whole-body output, and it has been the default in comfyui_controlnet_aux for years. It's also slow, and "slow" is the number one complaint you'll find about it in the wild, usually from someone trying to pose a whole video.
UC_DWPoseEstimator is silveroxides' reimplementation: YOLOX-L for person detection, RTMPose for the 133 whole-body keypoints, run eagerly in PyTorch, batched, with an optional temporal filter for video. Detection and pose crops go through separate batches, and partial batches aren't padded, so the last chunk of a clip doesn't pay for a full batch of empty frames.
How it works
The detector (a converted yolox_l TorchScript checkpoint) runs on each frame at a fixed 640 square, which is why resolution doesn't make it "see more detail" - it only changes the image you get back. Detected person boxes are NMS'd, expanded to crops, and each crop goes through the RTMPose model at 288x384 (the dwpose_ucoco_384 weights), which returns SimCC coordinate distributions that get decoded to 133 keypoints.
The two thresholds do different jobs. detection_threshold (0.45) decides whether a person exists at all; keypoint_threshold (0.45) decides whether an individual body, hand or face point survives. nms_threshold (0.45) is the duplicate-box cleanup - lower it and you can lose people standing close together, raise it and you'll get the same person twice.
One important difference from the OpenPose node in the same pack: detect_hand, detect_body and detect_face are drawing toggles only here. Turning off hands makes the map prettier, not the run faster. Hand and face keypoints still come out on pose_keypoint. Same for hands in the OpenPose node, where the toggle does skip the model - don't assume the two behave alike.
Inputs, outputs, wiring
Beyond the above: image (a batch, or consecutive frames for the temporal options), resolution (0 keeps your dimensions, otherwise the shortest edge), batch_size (5, your VRAM dial), and scale_stick_for_xinsr_cn, which thickens the drawn body lines at larger sizes for Xinsir-style maps. Drawing only; joint coordinates don't move.
The six temporal options run the same as the pack's other pose nodes: temporal_filter off by default, temporal_radius 2, temporal_min_support 2, temporal_max_distance 0.1 of the person's box diagonal, temporal_match_iou 0.3. It deletes joints that neighbouring frames can't vouch for and keeps the person entries. It's flicker removal, not interpolation - nothing gets invented, and pruning spans your batch chunks. Turn it off for unrelated stills.
Outputs: image (the pose map, in order - into your pose ControlNet), pose_keypoint (OpenPose-format, thresholded and filtered), and Pose Overlay Mask, which is white on drawn lines and points so you can composite the map over the original footage.
Install
Manager → search "ComfyUI-UtilsCollection", or:
cd ComfyUI/custom_nodes
git clone https://github.com/silveroxides/ComfyUI-UtilsCollection
# restart ComfyUI
Two models, downloaded on first execution into ComfyUI/models/controlnet/preprocessors (flat filenames, even though the HF repo nests them): dwpose_yolox_l.safetensors and dwpose_ucoco_384.safetensors, from silveroxides/ComfyUI-UtilsCollection-Models. No ONNX Runtime, no MMPose, no TorchScript at runtime - the pack converts the original .pth/TorchScript sources to safetensors and validates keys, shapes, dtypes and values before shipping them. The honest caveat from the README: verified conversion, unverified prediction parity and unmeasured speed. If you're squeezing every percent, benchmark it against the aux preprocessor you're replacing.
Troubleshooting
The pack imports kornia when it loads and kornia is not in its requirements.txt. If another pack hasn't already pulled it in, the entire pack fails to import and every node from it goes missing - pip install kornia in ComfyUI's Python environment fixes it.
If detection is failing on dark clothing, backlit shots or motion blur, detection_threshold at 0.45 is a reasonable default but YOLOX has never loved those frames; drop it to 0.3 first before you go rebuilding the pipeline.
And if your downstream ControlNet isn't a DWPose/OpenPose-trained one, remember you're feeding it a skeleton - pose conditioning is spatial, so the prompt still has to describe the subject.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Image or image batch to process. For temporal filtering, supply consecutive video frames in order, not unrelated images. | |
| detect_hand | BOOLEAN | true | Draw hand joints in the pose map. Turning off only hides the drawing; hand inference and keypoint output remain enabled. |
| detect_body | BOOLEAN | true | Draw body skeletons in the pose map. Turning off only hides the drawing; body inference and keypoint output remain enabled. |
| detect_face | BOOLEAN | true | Draw facial landmarks in the pose map. Turning off only hides the drawing; face inference and keypoint output remain enabled. |
| resolution | INT | 00–4096 | 0 preserves the input dimensions. A positive value sets the output's shortest edge in pixels, preserving aspect ratio. Larger maps do not increase the model's fixed input size. |
| batch_size | INT | 51–64 | Maximum images or person crops processed together. Higher values may improve throughput but use more memory; lower if VRAM runs out. Does not change temporal-filter range. |
| scale_stick_for_xinsr_cn | BOOLEAN | false | Scale body-line thickness with image size for Xinsir-style pose maps. Changes drawing only, not detected coordinates. |
| detection_threshold | FLOAT | 0.450–1 | Minimum person-detection confidence. Raise to reject weak detections such as shadows; may also remove real people. |
| keypoint_threshold | FLOAT | 0.450–1 | Minimum confidence for body, hand, and face keypoints. Raise to hide uncertain joints; this does not track or smooth motion. |
| temporal_filter | BOOLEAN | false | Video frames only: remove joints unsupported by nearby frames from both pose maps and keypoints. Keeps person entries; does not smooth or fill missing joints. Leave off for unrelated images. |
| temporal_radius | INT | 21–30 | Temporal filter only: frames to inspect before and after each frame. 2 checks up to four neighbors, across batch_size chunks. Larger windows cost more and may reject fast motion. |
| temporal_min_support | INT | 21–60 | Temporal filter only: neighboring frames that must confirm a joint. Raise to remove more flicker; lower to retain brief poses. Limited to available neighbors, including at clip edges. |
| temporal_max_distance | FLOAT | 0.100.001–1 | Temporal filter only: allowed joint displacement relative to the person's box diagonal; 0.1 means 10%. Raise for faster motion; lower to reject sudden jumps. |
| temporal_match_iou | FLOAT | 0.300–1 | Temporal filter only: required overlap between detection boxes to match a person across frames. Raise to avoid mixing people; lower for faster movement or changing boxes. |
| nms_threshold | FLOAT | 0.450–1 | Overlap limit for removing duplicate person boxes. Lower removes more overlapping detections but can lose nearby people; higher keeps more and may leave duplicates. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | Pose maps in input-frame order, showing the enabled body, hand and face drawings. |
| pose_keypoint | POSE_KEYPOINT | Per-frame OpenPose-format keypoints after thresholding and optional temporal filtering. Drawing toggles do not remove body, hand or face keypoints. |
| Pose Overlay Mask | MASK | White on rendered pose lines and points; black elsewhere. Use to composite the pose map over an image or video. Matches pose-map size and frame order; not a person silhouette. |