CV Transform Points 3D
Moving a point cloud into someone else's coordinate frame
- points
- matrix
- points
Reconstructing 3D from images gives you points in some coordinate frame, and it is almost never the frame your next step wants. Left camera, right camera, world, model-local - every stage of a 3D pipeline speaks its own dialect, and the translation between them is a 3×4 matrix.
CV Transform Points 3D applies one. Give it an Nx3 cloud and a matrix, get the cloud back in the other frame. It's cv2.transform with the plumbing handled, which is the sort of node you don't notice until you try to build something without it.
The inputs
- points -
Nx3, orNx1x3/HxWx3(a depth-image-shaped cloud flattens fine). AnNx6cloud fromCV Point Cloud NormalsorCV Mesh Vertex Normalsis also accepted, and its positions come back asNx3- the normals don't survive. That's deliberate and it's a real geometric fact: rotating a cloud rotates its normals too, and the honest thing is to recompute them after the move rather than rotate stale ones. The pack's own workflow 81 wires exactly that chain: transform → normals → ICP. - matrix - a
3x4[R|t](whatcv2.estimateAffine3Dreturns), a4x4homogeneous matrix (whatCV ICP Registerreturns), or a bare3x3rotation. - direction -
forwardmeansp' = R p + t;inversemeansp' = R⁻¹(p − t). For a rigid matrix the inverse is built exactly fromRᵀand−Rᵀtrather than inverted numerically, which is both faster and more accurate.
That direction switch is worth more than it looks. Which way a matrix points depends entirely on who produced it: a matrix that maps cloud A into frame B, used on cloud B, gives you garbage that still looks plausible as a scatter plot. When a registration result looks "sort of right but offset", this is the first thing to flip.
Output is a single points array - Nx3 float32, same order in as out, so you can keep a parallel colour array or index list aligned.
Where it fits
Three jobs, in rough order of how often they come up:
Merging two clouds. Reconstruct the same scene from two stereo pairs, register the second to the first, move it, then concatenate. Without the transform step the two halves just sit in the same file overlapping wrongly - the classic symptom is a scan that looks doubled.
Camera-frame to world. Odometry and depth reconstruction produce points relative to the camera. Invert the pose that rendered them and you're in world space - the pack's CV Pick Point On Mesh blueprint does this: OpenCV Pose To Matrix → cv2_invert → this node.
Depth to a height field. Take a depth-derived cloud, apply a rotation that lays it flat, and the Y values become a height map you can print or displace with.
Installing it
Manager → ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart. Python ≥ 3.12, recent ComfyUI on the V3 node API, and opencv-contrib-python-headless~=5.0.0.93 - install the contrib wheel, because overwriting it with plain opencv-python empties the shared cv2 and takes a chunk of this pack with it. GPL-3.0, forked from opencv-comfyui, LLM-assisted and explicitly not production-blessed. No model files needed.
Where people get burned
Failure tolerance that hides mistakes. An empty or None cloud passes through empty, and a singular matrix returns the points unchanged. Sensible for a pipeline, quietly dangerous for you: if your registration produced a degenerate matrix, nothing errors and you just get your input cloud back. Check the cloud actually moved.
Units and scale. This node moves points, it doesn't rescale them. A matrix that came out of a similarity fit with a scale factor won't be applied as such - that's the difference between rigid and similarity registration, and it lives in whichever node produced the matrix.
Normals you forgot about. If your points have normals and you transform them, they're now wrong. Recompute them (depth-derived normals are the fiddly part; the KB's depth-estimation.md covers why) instead of carrying the old ones into a lighting or ICP step.
Point order is assumed stable. Nothing sorts or dedupes. Feed it a cloud and its colour array, transform the points, and the colours still line up - that's the invariant everything downstream relies on.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| points | NPARRAY | Nx3 (or Nx1x3 / HxWx3) point cloud. An Nx6 cloud from 'CV Point Cloud Normals' / 'CV Mesh Vertex Normals' is accepted and its POSITIONS come back Nx3: rotating a cloud rotates its normals too, so recompute them after moving it (which is exactly the transform -> normals -> ICP chain workflow 81 wires). | |
| matrix | NPARRAY | 3x4 [R|t], 4x4 homogeneous, or 3x3 rotation. | |
| direction | COMBO | forward | forward: p' = R p + t (use for a matrix that already maps THIS cloud into the target frame). inverse: p' = R^-1 (p - t) (use when the matrix maps the target frame into this one). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| points | NPARRAY | Transformed Nx3 float32 points, same order as the input. |