CV Point Cloud Nearest Distance
Did your registration actually work? Median nearest distance is the honest answer
- points
- reference
- distance
- near_mask
- median_distance
- matched_fraction
Two point clouds, one aligned onto the other. The alignment node says it converged. Does that mean anything? ICP's residual does not answer this - it measures how well the optimizer liked its own answer, which can look great while the clouds are a metre apart in a direction the solver didn't explore.
CV Point Cloud Nearest Distance answers the real question: for every point, how far is the nearest point of the reference cloud? Build a FLANN k-d tree index over the reference (a few randomized trees, a handful of leaves visited per query) and you get an Nx1 distance array plus two summary numbers that tell you whether the registration is any good.
The community pattern this fits is the 3D-reconstruction one: point clouds from multi-view or stereo geometry are cheap to produce and hard to evaluate, so the measurement step is what makes the rest trustworthy.
Two uses, one mechanism
Cross-view agreement. After registering a second stereo/omnidir cloud against the first, points the first view confirms have a tiny nearest distance; stereo blunders sit alone in space with a large one. That's a filter and a confidence score in one array.
Registration quality. median_distance drops as alignment improves, and unlike an optimizer residual it actually measures the thing you care about. If you're sweeping a parameter - an ICP iteration count, a threshold, a downsample grid - this is the number to watch, and it belongs on a CV Plot 2D curve.
Inputs and outputs
points- the Nx3 query cloud.reference- the Mx3 cloud to measure against. Both must be in the same frame. An empty reference gives infinite distances and an all-zero mask rather than an error.threshold(default 0.25) - the distance below which a point counts as confirmed. Same unit as your cloud, and for a calibrated stereo reconstruction that unit is metres, so 0.25 is 25 cm. Set it relative to your actual noise floor, not by vibe.checks(32) andtrees(4) - FLANN search effort and index size. More is more exact and slower; leave them unless you have a reason.
Outputs: distance (Nx1 float32, nearest reference distance per point), near_mask (Nx1 uint8, 255 where distance ≤ threshold), median_distance (the quality number), and matched_fraction (the fraction of points within threshold, 0–1).
near_mask is designed to be combined: CV Filter Points 3D By Mask to keep the confirmed points, or invert it to keep only the new coverage a second view adds. That's the merge-two-scans workflow, done with two nodes.
Install
Manager → search ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
cd comfyui_cv && pip install "opencv-contrib-python-headless~=5.0.0.93"
Restart. Python ≥ 3.12, V3 node API. It uses cv2.flann, which is core OpenCV - but the pack declares the contrib headless wheel and is curated against that version.
Common issues
- Distances are all infinite / mask is all zero - the reference cloud was empty. That's a valid outcome, not a crash; check the upstream node that was supposed to produce the second cloud.
median_distanceis huge even after registration - either the registration is genuinely bad, or the two clouds are in different units or a different frame (world vs camera). Wrong frame is the more common cause and looks identical from here.- Everything sits right at the threshold - your scan noise is at the scale of the threshold. Raise it, or accept that
matched_fractionwill be a fuzzy number. - Very slow on a big cloud - the k-d tree build plus queries is O(N log M)-ish;
treesandchecksare multiplier knobs. Downsample first withCV Downsample Point Cloudif you're just iterating on parameters. - The mask connects but the point filter does nothing - check the shape: the mask is Nx1, and the consumer usually wants it flattened.
Last thing: this node outputs data only, by design - nothing in this pack draws unless you ask it to. Preview the cloud with the pack's 3D viewer or the PLY writer when you want to see what the numbers mean. And the standard pack caveat applies: heavy LLM-assisted development, no production guarantee, updates not promised.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| points | NPARRAY | Nx3 cloud to measure (the query points). | |
| reference | NPARRAY | Mx3 cloud to measure AGAINST. Empty reference -> infinite distances and an all-zero mask, not an error. | |
| threshold | FLOAT | 0.25 | Distance (same unit as the cloud, metres for a calibrated stereo reconstruction) below which a point counts as confirmed by the reference cloud. |
| checksopt | INT | 321–4096 | FLANN search effort: leaves visited per query. Higher is more exact and slower. |
| treesopt | INT | 41–16 | Randomized k-d trees in the index. More = more accurate approximate search, slower build. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| distance | NPARRAY | Nx1 float32 distance to the nearest reference point. |
| near_mask | NPARRAY | Nx1 uint8 mask, 255 where distance <= threshold. |
| median_distance | FLOAT | Median nearest distance - the registration quality number to watch (lower = better aligned). |
| matched_fraction | FLOAT | Fraction of points within the threshold (0..1). |