Nodes/ComfyUI CV/CV Disparity Filter (WLS)
ComfyUI Node

CV Disparity Filter (WLS)

Fills the holes without dragging your edges

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Disparity Filter (WLS)
  • disparity
  • guide
  • disparity_right
  • disparity
  • confidence
  • valid
◄wls_lambda8000►
◄wls_sigma_color1.5►
◄num_disparities64►
◄min_disparity0►
◄block_size7►
◄lrc_threshold24►
◄depth_discontinuity_radius0►
◄roiauto (skip the invalid left band)►

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_size or 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_right isn't wired. That's expected, not broken.
  • Depth edges bleed into the background. Lower wls_sigma_color, or lower wls_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 cv2 int16 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.
Categoryimage/CV/contrib

Inputs (11)

NameTypeDefaultDescription
disparityNPARRAYHxW 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.
guideNPARRAY,IMAGEThe 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_lambdaFLOAT80000–100000Regularization 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_colorFLOAT1.50–10How 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_rightoptNPARRAYOptional 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_disparitiesoptINT6416–512The 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_disparityoptINT0-256–256The smallest disparity the map was searched from. Also the threshold for the 'valid' output.
block_sizeoptINT71–21The 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_thresholdoptFLOAT240–256Left-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_radiusoptINT00–32Radius (px) around depth discontinuities used when computing confidence. 0 = ceil(block_size / 2), which is what the matcher-bound filter picks.
roioptCOMBOauto (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)

NameTypeDescription
disparityNPARRAYHxW float32 WLS-filtered disparity in pixels.
confidenceNPARRAYHxW 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.
validNPARRAYuint8 0/255 mask of pixels whose filtered disparity is at or above min_disparity.