Nodes/ComfyUI CV/cv2.filterSpeckles
ComfyUI Node

cv2.filterSpeckles

Killing Speckles with cv2.filterSpeckles

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.filterSpeckles
  • img
  • img
  • buf
◄newVal0.0000►
◄maxSpeckleSize0►
◄maxDiff0.0000►

If you've ever turned a stereo pair into depth, you know the shape of the problem: block matching hands back a crisp-looking disparity map that, on close inspection, is salted with tiny islands of wrong values. A couple of pixels where the matcher locked onto texture instead of geometry. Those islands become spikes in the point cloud and holes in your depth-driven compositing. cv2.filterSpeckles is the cleanup pass that OpenCV has shipped for exactly this since forever, and this pack wraps it as a raw node.

What it actually does

The algorithm is blunt, which is why it works. It groups neighbouring disparity pixels into blobs - two pixels belong to the same blob when their disparity differs by less than maxDiff - and any blob with fewer than maxSpeckleSize pixels is declared a speckle and painted over with newVal, the value your pipeline treats as "no measurement". Big regions, real geometry, are left alone. It's a size filter, not a smoother, so it won't blur your depth edges.

That's also why the node has two outputs and you only care about one. img is your filtered disparity map. buf is the scratch buffer cv2 allocates internally to avoid a malloc per call - it's a working array, not a picture. Nothing useful to do with it. Yes, you can ignore an output socket; the graph doesn't mind.

The inputs that matter

img is the one to get right, and it's the classic trap. cv2 wants a 16-bit signed disparity image. The socket happily accepts a ComfyUI IMAGE or MASK - every image-ish input in this pack does, because IMAGE gets converted to uint8 BGR frame 0 - but that conversion destroys a 16-bit signed map, and cv2 will then refuse the type outright. Feed this from an NPARRAY: the pack's CV Stereo Disparity (SGBM) or CV Stereo Disparity (BM) nodes hand you exactly that. Never from Load Image.

maxSpeckleSize and maxDiff both default to 0, which does nothing useful, so set them. Speckle size is in pixels - 100–500 is the normal range for a 640×480 map. And now the bit that catches everyone: StereoBM and StereoSGBM return fixed-point disparity, multiplied by 16. So a one-pixel disparity step is a maxDiff of 16, not 1. If your values look absurdly off, that scale factor is why.

newVal is the value painted over the speckles. 0 is the default and usually right; if the rest of your graph uses a sentinel like -16 or 255 for "invalid", match it, or the holes you just cleaned will read as real measurements.

Where it sits in the graph

Between the matcher and whatever consumes the map. Downstream that's usually cv2_reprojectImageTo3D / CV Depth To 3D for a cloud, or just Preview CV Array to look at it - which, usefully, paints non-finite pixels in a colour no colormap can produce, so NaN holes can't masquerade as data. Inspect CV Data shows you the dtype and shape, which is how you confirm you actually have CV_16S and not something the wrappers quietly cast.

Worth saying plainly: for a normal stereo pipeline you may not need this node at all. The curated CV Stereo Disparity (WLS) node filters internally, and CV Disparity Filter (WLS) runs the ximgproc smoother over a raw BM map. The shipped 59_disparity_refinement.json compares those routes side by side. Reach for raw filterSpeckles when you want this one specific, cheap, deterministic pass in the chain and nothing else.

Installing it

The whole pack installs the same way - this node comes with it, there's nothing separate to fetch and no model to download.

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"

Or install "ComfyUI CV" through ComfyUI Manager and restart. Two hard requirements: Python 3.12 or newer, and a recent ComfyUI on the V3 node API - an older core just won't load the pack.

Common issues

"cv2.error (-215) ... type": your input isn't 16-bit signed. Check the dtype with Inspect CV Data before blaming the parameters.

Everything got worse: maxSpeckleSize too high is the usual cause - you start deleting legitimate thin structures (fence wire, hair, small objects). Bisect downward.

A node that used to work disappeared: installing a plain opencv-python or opencv-python-headless wheel over a contrib one empties the contrib submodules, because all four distributions share one site-packages/cv2. The community hits this constantly - version and wheel conflicts between custom nodes are one of the top support threads in r/comfyui. The pack ships tools/repair_opencv_contrib.py with --check and --apply for exactly this, and it's worth knowing the check exists before you start uninstalling things by hand.

Categoryimage/CV/low-level/cv2 F

Inputs (4)

NameTypeDefaultDescription
imgNPARRAY,IMAGE,MASKThe input 16-bit signed disparity image 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.
newValFLOAT0.0000-1e+38–1e+38The disparity value used to paint-off the speckles
maxSpeckleSizeINT0-2147483648–2147483647The maximum speckle size to consider it a speckle. Larger blobs are not affected by the algorithm
maxDiffFLOAT0.0000-1e+38–1e+38Maximum difference between neighbor disparity pixels to put them into the same blob. Note that since StereoBM, StereoSGBM and may be other algorithms return a fixed-point disparity map, where disparity values are multiplied by 16, this scale factor should be taken into account when specifying this parameter value.

Outputs (2)

NameTypeDescription
imgNPARRAY—
bufNPARRAY—