CV Stereo Disparity (WLS filtered)
WLS Is What Turns a SGBM Depth Map Into Something Usable
- left
- right
- disparity
- confidence
- valid
- disparity_raw
Plain SGBM gives you a disparity map with big holes wherever the scene is smooth. The usual fixes are all bad - inpaint them and you invent geometry that was never there, blur them and you smear depth edges. The weighted-least-squares filter is the third option: it smooths the disparity while sticking to the left image's edges, so shading gradients that happen to align with real object boundaries stay sharp and flat regions get filled with a smooth interpolation instead of a guess.
In practice this is the node you reach for when SGBM's output is a checkerboard of holes. On the pack's own example driving pair the author reports the valid fraction going from roughly 65% to roughly 95% - same matcher underneath, much more usable map on top.
How it works
WLS only makes sense as a trio, and that's why this is one node rather than three wired together. SGBM is created, a right matcher is created from the same instance, both directions are computed (left→right and right→left), and createDisparityWLSFilter consumes both maps plus the left image. The filter runs a left-right consistency check, then smooths the surviving values with an edge-aware weighting. Three nodes would have to pass live cv2 objects through the graph, which is exactly what you can't do in ComfyUI.
The payoff beyond the filled holes: the filter hands you a confidence map. That's what lets a downstream step keep only the disparity values worth trusting rather than thresholding blindly.
Two knobs live in the required block:
wls_lambda (default 8000, OpenCV's own recommendation) is how hard the filter smooths towards image edges. Higher fills more and bleeds more across depth discontinuities.
wls_sigma_color (default 1.5) is how sensitive it is to the left image's edges - the useful band is roughly 0.8–2.0. Too low and disparity leaks across object boundaries; too high and the filter starts chasing noise as if it were structure.
In the advanced group, lrc_threshold is the consistency tolerance the filter uses, depth_discontinuity_radius (0 = off) widens the discontinuity handling, and disp12_max_diff passes through to SGBM itself. The rest of the matcher settings mirror CV Stereo Disparity (SGBM) exactly: num_disparities, block_size, min_disparity, mode, uniqueness_ratio, speckle_window_size, speckle_range.
Outputs are disparity (WLS-filtered, float32 pixels), confidence (HxW float32, 0–255, low where the two matching directions disagreed - occlusions and textureless areas), valid (uint8 0/255 mask) and disparity_raw, the unfiltered left-to-right SGBM map. Keep the raw one; flipping between the two previews is how you tell whether the filter is helping or quietly hallucinating a smooth wall where there should be a hole.
Preview with Preview CV Array (normalize or heatmap). Both frames must be rectified first - cv2_stereoRectify + initUndistortRectifyMap + cv2_remap.
Install
ComfyUI Manager → search ComfyUI CV (publisher bmad4ever), or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Restart. Python ≥ 3.12 and a recent V3-API ComfyUI.
This node is the one where the contrib requirement really bites, because the WLS filter lives in cv2.ximgproc, which is contrib-only. Install a plain opencv-python on top of the contrib wheel and the two share site-packages/cv2; the contrib submodules go empty and this node fails where the plain SGBM sibling still works. If that sounds like your situation, tools/repair_opencv_contrib.py --check will confirm it and --apply fixes it.
workflows/58_stereo_bm_vs_sgbm.json runs BM, SGBM and this node side by side on the same rectified pair, and 59_disparity_refinement.json shows the cleaning chain. Run 01_install_example_inputs.json once and reload to get the sample frames.
Where it bites
Turn wls_lambda up until a smooth wall appears and you have quietly undone the point of the filter - check disparity_raw before believing a suspiciously complete map.
Then there's the honest framing: the author's README says this pack is a personal, heavily LLM-generated project with no support promised and stereo tuning overfitted to a specific dataset (StereoGeo-CARLA). The nodes are real OpenCV calls and they behave like them; the defaults are one person's footage, not yours. Re-tune wls_sigma_color on your own pair and watch the confidence map while you do it.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| left | NPARRAY,IMAGE | Left rectified frame (grayscaled internally). Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| right | NPARRAY,IMAGE | Right rectified frame; resized to left if the sizes differ. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| num_disparities | INT | 6416–512 | Disparity search range (0..num_disparities), rounded up to a multiple of 16. Larger covers nearer objects / wider baselines but is slower. |
| block_size | INT | 71–21 | Matched block size in pixels (forced odd; 3-11 is typical). Smaller = more detail but noisier. |
| min_disparity | INT | 0-256–256 | Smallest disparity to search from (usually 0). |
| mode | COMBO | SGBM (balanced) | SGBM is the standard; 3-way is faster at lower quality; HH runs the full-scale two-pass (best, slowest). |
| wls_lambda | FLOAT | 80000–100000 | Regularization strength: how strongly the filtered disparity is smoothed towards the edges of the left image. 8000 is the OpenCV-recommended default; larger = smoother, more hole filling, more bleeding across depth edges. |
| wls_sigma_color | FLOAT | 1.50–10 | How sensitive the filter is to left-image edges. 0.8-2.0 is the useful range: too low leaks disparity across object boundaries, too high makes it follow image noise. |
| uniqueness_ratioopt | INT | 100–100 | Margin (%) by which the best match must beat the runner-up to be accepted. |
| speckle_window_sizeopt | INT | 1000–1000 | Largest smooth disparity blob treated as speckle noise and invalidated (0 = off). |
| speckle_rangeopt | INT | 20–64 | Max disparity variation within a speckle component. |
| lrc_thresholdopt | FLOAT | 240–256 | Left-right consistency tolerance in 1/16 pixel units: disagreements above it are marked unconfident and re-filled by the filter. |
| depth_discontinuity_radiusopt | INT | 00–32 | Radius (px) around depth discontinuities used when computing confidence. 0 = the filter's own heuristic (block_size / 2). |
| disp12_max_diffopt | INT | -1-1–64 | SGBM's OWN left-right check, in pixels: matches that disagree by more than this are invalidated before the WLS filter ever sees them. -1 (cv2's default) turns it off and leaves occlusion junk in the map; 1 is the usual setting when the disparity feeds an interpolation or a point cloud. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| disparity | NPARRAY | HxW float32 WLS-filtered disparity in pixels. |
| confidence | NPARRAY | HxW float32 confidence map, 0-255; low where the two matching directions disagreed (occlusions, texture-less areas). Sample it at your points to reject unreliable 3D. |
| valid | NPARRAY | uint8 0/255 mask of pixels with a valid filtered disparity. |
| disparity_raw | NPARRAY | The unfiltered left-to-right SGBM disparity (float32 pixels) - keep it to show what the filter changed. |