CV Disparity Filter (WLS)
Fills the holes without dragging your edges
- disparity
- guide
- disparity_right
- disparity
- confidence
- valid
CV Disparity Filter (WLS) takes a disparity map you already have - from any matcher - and cleans it up: fills the holes, smooths the noise, but follows the edges of a guide image while doing it.
That last clause is what makes it worth having. Every raw stereo matcher leaves you with a map that is riddled with invalid pixels: occlusions, textureless regions, and every left-band column that could never be matched in the first place. Blend it or blur it and you smear depth across every object silhouette. The weighted-least-squares filter instead solves for a smoothed map that's penalised for deviating from the guide's edges, so it interpolates within surfaces and stops at boundaries.
It's a ximgproc node, so it's contrib-only. And it's deliberately not matcher-bound: StereoBM, quasi-dense stereo, a depth network, an earlier refinement pass - feed it whatever. On the pack's own example stereo pair a StereoBM map goes from 22% to 54% valid pixels. That's a real number from a real pair, and it's typical.
How it works
ximgproc.createDisparityWLSFilter is a factory that needs a search range and a block size, and the generic wrapper route gets the derived geometry wrong. This node derives them properly: the region of interest skips the left band implied by num_disparities/min_disparity (the columns that can never have been matched - letting them into the solve corrupts the whole map), and it sets the depth-discontinuity radius to ceil(block_size / 2) exactly as the matcher-bound filter does. Those two details are not cosmetic; the tooltip is blunt that being one step off there changes the filtered disparity by 2000+ pixels at the worst pixel.
If you wire disparity_right - the right-to-left map, negative as ximgproc.createRightMatcher produces it - the filter also runs a left-right consistency check. That's where the confidence output comes from; without it, confidence comes back as all zeros because there's nothing to check against.
Inputs and outputs
Required: disparity - HxW float32 in pixels, the format every disparity node in this pack emits, not the raw 1/16-scale int16 map cv2 returns - plus guide (the left rectified frame whose edges are followed) and two filter settings: wls_lambda (8000, OpenCV's recommended default; larger means smoother and more bleeding across depth edges) and wls_sigma_color (1.5; the useful range is 0.8–2.0, low leaks disparity across object boundaries, high follows image noise).
Optional: disparity_right, num_disparities (64), min_disparity (0), block_size (7, forced odd), lrc_threshold (24, in 1/16-pixel units), depth_discontinuity_radius (0 = derive it), and roi - leave it on auto, which skips the invalid left band. full frame reproduces cv2's own generic default and is there to demonstrate the difference; it corrupts a real map.
Outputs: disparity (HxW float32, filtered), confidence (0–255, low where the two matching directions disagreed, all zeros unless you wired disparity_right), and valid (uint8 0/255 mask above min_disparity).
Install
From comfyui_cv (bmad4ever/comfyui_cv). ComfyUI Manager → "ComfyUI CV", or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Restart ComfyUI. Python ≥ 3.12, recent V3-API ComfyUI. ximgproc is a contrib module, so the contrib wheel is load-bearing here - a plain opencv-python install leaves you with no filter at all. All four OpenCV distributions share one site-packages/cv2, so any pack that installs a non-contrib wheel over yours takes the contrib nodes out with it; tools/repair_opencv_contrib.py --check tells you, --apply fixes it.
Common issues
- The whole filtered map is garbage. Your
block_sizeor search range doesn't match the matcher that produced the disparity. The node's tooltip says it plainly: leave these matching the matcher. Set them to what the disparity map was actually computed with. - Confidence is zero everywhere.
disparity_rightisn't wired. That's expected, not broken. - Depth edges bleed into the background. Lower
wls_sigma_color, or lowerwls_lambda- remember the prior here is fronto-parallel, so big lambda smooths aggressively. - There's a staircase on a slanted road. This is the documented failure of this filter rather than a misconfiguration. WLS assumes disparity is locally constant, so a textureless slanted surface gets bent towards steps.
CV Disparity Interpolate (Edge-Aware)fits a local plane instead, and on the shipped driving pair it keeps 0.06 m RMS where the plane fit through the road band degrades from 0.06 m to 0.36 m. If your scene is mostly ground plane, use that node. - The disparity input is in the wrong scale. If you fed the raw
cv2int16 map rather than this pack's pixel-unit float map, everything downstream of it is nonsense. This is not a detail you can fix with a lambda tweak.
Inputs (11)
| Name | Type | Default | Description |
|---|---|---|---|
| disparity | NPARRAY | HxW float32 disparity in PIXELS (what every disparity node in this pack outputs), not the raw 1/16 int16 map cv2 returns. Invalid pixels are the ones below min_disparity. | |
| guide | NPARRAY,IMAGE | The left rectified frame the disparity belongs to; its edges are what the filter follows. 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. | |
| wls_lambda | FLOAT | 80000–100000 | Regularization strength: how strongly the filtered disparity is smoothed towards the edges of the guide 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 guide-image edges. 0.8-2.0 is the useful range: too low leaks disparity across object boundaries, too high makes it follow image noise. |
| disparity_rightopt | NPARRAY | Optional HxW float32 right-to-left disparity in pixels, NEGATIVE as ximgproc.createRightMatcher produces it. With it the filter runs its left-right consistency check and the confidence output is real; without it confidence comes back as zeros. | |
| num_disparitiesopt | INT | 6416–512 | The search range the disparity map was computed with. Used ONLY to place the region to filter - the leftmost min_disparity + num_disparities columns can never have been matched, and letting them into the solve corrupts the whole map. |
| min_disparityopt | INT | 0-256–256 | The smallest disparity the map was searched from. Also the threshold for the 'valid' output. |
| block_sizeopt | INT | 71–21 | The block size the map was matched with (forced odd). It sets depth_discontinuity_radius to ceil(block_size / 2), exactly as the matcher-bound filter does - and that is not a cosmetic detail: one step off there changes the filtered disparity by 2000+ px at the worst pixel. Leave it matching the matcher. |
| 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. Only does anything when disparity_right is wired. |
| depth_discontinuity_radiusopt | INT | 00–32 | Radius (px) around depth discontinuities used when computing confidence. 0 = ceil(block_size / 2), which is what the matcher-bound filter picks. |
| roiopt | COMBO | auto (skip the invalid left band) | 'auto' filters everything except the invalid left band implied by num_disparities/min_disparity, which is what the matcher-bound filter does. 'full frame' reproduces cv2's own generic default - keep it only to demonstrate the difference, it corrupts a real map. |
Outputs (3)
| 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. ALL ZEROS when disparity_right is not wired - the filter has nothing to check against. |
| valid | NPARRAY | uint8 0/255 mask of pixels whose filtered disparity is at or above min_disparity. |