VR Face Rectify
The node that makes face swapping on VR180 footage actually work
- image
- options
- patches
- rectify_map
- count
Face swappers like ReActor are trained on flat, camera-facing faces. VR180 faces are neither. An equirect or fisheye lens bends a face into a curve, and if you feed that to a swapper you get a smear - the swap technically happens, and then it sits there looking wrong under the lens distortion. VR Face Rectify is the first half of the fix: it detects faces in each eye of a VR frame and pulls each one into a flat, rectilinear patch a swapper can actually handle.
This is one half of a bracketed pair (see VR Face Unrectify for the other). The flow is:
VR frames --> VR Face Rectify --> patches --> ReActor --> swapped patches --> VR Face Unrectify --> VR frames
Note these two nodes shipped in a v2.0 update and the pack README hasn't caught up - they're in the code and in ComfyUI's node list, just not documented upstream.
How it works
For each frame in your batch, the node splits it by input_layout: sbs for the left|right halves of a VR180 side-by-side frame (most VR content), tb for top|bottom, or mono when the whole frame is a single eye (per-eye pipelines). It detects faces in each eye with insightface's buffalo_l model - the same detector ReActor uses - so if ReActor is already in your environment, there's nothing new to install. Face detection is imported lazily, only when a rectify actually runs.
For each detected face it converts the face's pixel position into a yaw/pitch on your source projection, then renders a flat patch. Two numbers matter here: patch_fov (default 80°) and patch_size (default 768px). The author's guidance is that ~75–80° FOV suits ReActor - wide enough to keep context, narrow enough that the face fills a reasonable portion of the patch. The geometry runs on torch if it's importable (GPU), falling back to numpy/cv2 otherwise; the node prints which backend it used, which is genuinely useful to spot-check.
The inputs that matter
- image (required) - VR180-SBS frame(s); a batch is allowed.
- input_layout - set this to sbs for VR180 side-by-side content. The default is
mono, which assumes one whole frame is a single eye; feed it full SBS footage and every "face" is actually two half-faces squished together. - projection -
equirect(default) orfisheye. Pick fisheye for actual fisheye lenses and the node auto-detects the lens circle;fisheye_fov(default 135°) only applies then. - patch_fov / patch_size - the flat patch geometry. Leave the defaults until you have a reason not to.
- input_faces_order and input_faces_index - which face(s) get rectified, ReActor-style. Default is
large-smallorder and index0, meaning "the dominant face only" - the usual one-character-per-job case.0,1grabs two,allgrabs everyone. - detect_gender_input - optional insightface gender filter (
no/female/male). - options - a wired ReActor-compatible
OPTIONSdict overrides those three selection widgets. Leave unwired unless you're feeding it from ReActor.
Selection happens here, at frame level, on purpose: once faces are sliced into single-face patches, ReActor's own order/index options can't distinguish characters anymore - ranking is only meaningful when the whole frame is in view.
Outputs
- patches - the
IMAGEbatch of flat face patches. Feed this into ReActor. - rectify_map - an opaque
VR_RECTIFY_MAPthat carries the original frames plus per-patch geometry. This is what Unrectify needs to put everything back. - count - number of patches produced.
Keep the map and the patches paired: Unrectify matches patch i to map entry i, so don't reorder or filter the batch between the two nodes.
Installing and troubleshooting
cd ComfyUI/custom_nodes && git clone https://github.com/zyquon/ComfyUI-Stash
or search "Stash" in ComfyUI Manager, then restart. No model download from this pack - the only runtime need beyond the pack's light pillow/numpy/requests deps is insightface + cv2, which ReActor installs for you.
The failure mode to know: no faces detected emits a single black patch and an empty map so the IMAGE wire stays valid - then Unrectify passes the frames through untouched. If that's happening, check input_layout first (the classic "full SBS frame fed as mono" trap), then frame resolution - the detector runs at 640×640, so tiny far-away faces can be missed.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | VR180-SBS frame(s); a batch is allowed | |
| input_layoutopt | COMBO | mono = whole frame is one eye (per-eye pipelines); sbs = left|right halves; tb = top|bottom halves | |
| projectionopt | COMBO | Source VR projection of each eye | |
| patch_fovopt | INT | 8020–150 | Field of view (deg) of the flat patch; ~75-80 suits ReActor |
| patch_sizeopt | INT | 768128–2048 | Square patch edge in pixels |
| fisheye_fovopt | FLOAT | 13590–220 | Full fisheye FOV (deg); only used for fisheye projection |
| input_faces_orderopt | COMBO | large-small | How to rank faces in each eye before picking by index (matches ReActor). Ignored if an OPTIONS input is wired. |
| input_faces_indexopt | STRING | 0 | Which ranked face(s) to swap: "0" (default = dominant face), "0,1", or "all". Selection is frame-level -> one patch per selected face. Ignored if an OPTIONS input is wired. |
| detect_gender_inputopt | COMBO | no | Only rectify faces of this gender (insightface). "no" = any. Ignored if an OPTIONS input is wired. |
| optionsopt | OPTIONS | Optional ReActor-compatible OPTIONS dict; when wired it overrides the three selection widgets above. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| patches | IMAGE | — |
| rectify_map | VR_RECTIFY_MAP | — |
| count | INT | — |