Nodes/ComfyUI CV/CV Register Point Clouds (3D)
ComfyUI Node

CV Register Point Clouds (3D)

The 3D counterpart of findHomography

By bmad4ever·Created 4 months ago·Updated 16 days ago· 1
CV Register Point Clouds (3D)
  • source
  • target
  • matrix
  • inliers
  • inlier_count
  • scale
  • found
◄methodRANSAC + rigid refit►
◄ransac_threshold0.30►
◄confidence0.999►

If you've ever aligned two photos by matching features and solving a homography, this is the same move in three dimensions. You give it two point clouds with known correspondences - point 1 in A maps to point 1 in B, and so on - and it returns the 3x4 [R|t] that moves the second onto the first. That's cv2.estimateAffine3D, wrapped with the outlier handling stereo depth actually needs.

The use case it was written for

Two stereo pairs of the same static scene. You've already got this far:

  1. Match features between the two left views (CV Match Features).
  2. Look up the 3D position of each match in each pair's XYZ map (CV Sample Array At Points on the output of cv2.reprojectImageTo3D).
  3. Now you have two clouds with one-to-one correspondences - and this node tells you how to bring them together.

source is the Nx3 cloud to be moved (the second view's), target is the Nx3 cloud to move onto, one per source point in the same order. Correspondences, not a search - this is not ICP, it won't align two clouds that don't share known point identities. (The pack has a separate ICP node for that case.)

Then method decides how much to trust the data:

  • RANSAC + rigid refit (the default) rejects outliers, then re-fits a clean rotation+translation(+uniform scale) on the inliers alone. That's the right choice for stereo clouds, because stereo depth noise grows with distance and there will be outliers.
  • RANSAC (affine) keeps the general affine fit, which can absorb depth error as shear - usually a bug, occasionally a feature.
  • rigid (all points) does no rejection at all. Only for already-filtered, clean correspondences.

ransac_threshold (0.3) is in the cloud's own units - metres for a calibrated stereo reconstruction. The tooltip is blunt about the trade: too tight finds no inliers, too loose accepts mismatches. Stereo depth noise of a few tenths of a metre is realistic, which is where the default comes from. confidence (0.999) is the standard RANSAC probability guarantee.

Outputs

matrix is the 3x4 [R|t] mapping source → target; feed it to CV Transform Points 3D. inliers is an Nx1 uint8 mask of what RANSAC kept - all zero when it failed, all 255 for the no-rejection method - and it's your best debugging view, because a registration that "worked" on 12 of 900 points is not a registration.

inlier_count and found are the gate. scale is the interesting one: it's the uniform scale of the rigid fit, and 1.0 means a pure rigid motion. Far from 1 means the two clouds disagree about depth - which in a stereo context almost always means a wrong baseline, not a misregistration. That single float is a surprisingly good sanity check on your calibration.

Failure is soft: fewer than 3 correspondences, a count mismatch, or a degenerate configuration returns the identity transform with found=false rather than raising. That keeps a graph running, and it also means a failed registration merges your clouds unaligned instead of stopping. Branch on found with a Basic data handling: IfElse if you'd rather skip the second cloud than produce a confident-looking wrong merge.

Install

Part of ComfyUI CV (bmad4ever/comfyui_cv), GPL-3.0 fork of opencv-comfyui:

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

Manager: search the pack title. Python ≥ 3.12 and a V3-API ComfyUI build. Note the pack's own disclaimers: it wraps OpenCV, it's a personal project built with heavy AI assistance, and the maintainer doesn't recommend production use without reviewing the source yourself.

Practical warnings

Order and count must match. There's no correspondence search and no partial matching. If your two match sets have drifted out of sync, you'll get a plausible-looking matrix built on wrong pairs - filter both with the same inlier_mask rather than one.

Do your filtering before this, not after. CV Filter Point Cloud and the depth filtering nodes go in front. This node's RANSAC is a safety net, not a substitute for dropping points where reprojectImageTo3D returned garbage.

Units matter for the threshold, and nothing else. The transform is scale-consistent with your data, but the inlier threshold is in whatever units the cloud is in. Swapping from metres to millimetres means the threshold needs to move too.

The pack's workflows/exercise_stereo_multiview.json builds the whole pipeline this node lives in, from stereo calibration to a merged cloud. Start there if the chain above sounds like a lot of plumbing - because it is, and it's already wired for you.

Categoryimage/CV/features

Inputs (5)

NameTypeDefaultDescription
sourceNPARRAYNx3 points to be MOVED (the second view's cloud).
targetNPARRAYNx3 points to move ONTO, one per source point in the same order (the reference view's cloud).
methodCOMBORANSAC + rigid refit'RANSAC + rigid refit': robust outlier rejection, then a rotation+translation(+uniform scale) fit on the inliers - the right choice for stereo clouds. 'RANSAC (affine)': keeps the general affine fit, which can absorb depth errors as shear. 'rigid (all points)': no outlier rejection (only for clean, already-filtered correspondences).
ransac_thresholdFLOAT0.300–1000000Inlier distance in the cloud's own unit (metres for a calibrated stereo reconstruction). Stereo depth noise grows with distance, so a few tenths of a metre is realistic; too tight finds no inliers, too loose accepts mismatches.
confidenceFLOAT0.9990–1RANSAC confidence: probability that the returned fit comes from an all-inlier sample.

Outputs (5)

NameTypeDescription
matrixNPARRAY3x4 [R|t] mapping source -> target (identity when found=false). Feed 'CV Transform Points 3D'.
inliersNPARRAYNx1 uint8 mask of the correspondences RANSAC kept (all zero when found=false; all 255 for the 'rigid (all points)' method).
inlier_countINT—
scaleFLOATUniform scale of the rigid fit (1.0 for a pure rigid motion; far from 1 means the two clouds disagree about depth, e.g. a wrong baseline).
foundBOOLEAN—