icp_point2plane — 3D refine op

• Data kinds: points × points × normals → pose

• Call: import fullseye as fs; fs.ledger.icp_point2plane(src, dst, dst_normals, iters=30, tol=1e-09, init=None, trim=None, device='cpu', reproducible=False) (to call the implementation directly, import match3d; match3d.icp_point2plane(src, dst, dst_normals, iters=30, tol=1e-09, init=None, trim=None, device='cpu', reproducible=False); from the registry, ops3d.get("icp_point2plane"))

Usage

Refine the rigid transform to high accuracy with point-to-plane ICP (Gauss-Newton, small-angle approximation).

A refinement method that tightens the initial pose from a coarse match (integer NCC / Fourier-Mellin ±3° / Hough ±0.5voxel) by minimising point-to-plane distances on surface point clouds. In each iteration every src point is paired with its dst nearest neighbour, and the point-to-plane residual r_i = n_i·(R·p_i + t - q_i) is minimised. Linearising R with the small-angle approximation R ≈ I + [ω]×, the Jacobian of each correspondence is J_i = [p_i×n_i | n_i] (from the scalar triple product n·(ω×p)=ω·(p×n)), with constant term b_i = -n_i·(p_i - q_i). The normal equations (JᵀJ)x = Jᵀb are solved as a 6×6 system to obtain the increment x=[ω|t], which is turned back into a rotation with Rodrigues and accumulated. Point-to-plane allows sliding within the tangent plane, so it converges tightly to the surface in fewer iterations than point-to-point (Low 2004).

Measured (wavy surface N=2025, CPU float64): from an initial 6°/translation 0.06 it recovers in 4 iterations to euclid RMSE 1.7e-16 and rotation error 0° (point-to-point stalls after 17 iterations at RMSE 4e-2 and rotation 1.9°). Even with initial angles of 3-20° it reaches machine precision in 4-5 iterations.

Arguments:

src (N,3): point cloud to be moved (initial pose after the coarse match).

dst (M,3): reference point cloud (fixed).

dst_normals (M,3): unit normals of dst (normalised internally even if not normalised).

If unknown, estimate beforehand with pointcloud.estimate_normals(dst) etc.

iters: maximum number of iterations.

tol: stops as converged when the RMSE change is below this value.

init ((R0,t0)): initial pose (pass the R,t of the coarse match). None means identity.

trim (float|None): fraction in [0,1). Trimmed ICP that discards the top share with the largest point-to-plane residual

in every iteration (robust to partial overlap and outliers).

device: "cpu"/"cuda" etc. torch device string (device-independent).

reproducible: if True, JᵀJ and Jᵀb of the normal equations (reductions over N points) are computed with ozakimm (Ozaki scheme),

the point-to-plane residual and the pose application are element-wise operations, and the RMSE uses math.fsum. Regardless of the src point order and the number of BLAS threads,

R, t and rmse are bit-identical. The default False is as before (J.T @ J).

Returns:

R (3,3), t (3,), aligned (N,3)=R·src+t, rmse (point-to-plane RMSE over the used points),

n_iter (actual number of iterations).

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

Runnable examples (verified samples that actually call this op)

• refinement — py -3.11 examples_3d/refinement.py

Ops the type connects to (they accept pose as input)

fuse_to_voxel · pose_error · bundle_adjust · mean_reprojection_error · optimize_pose_graph · relative_pose · mean_edge_error · rotation_translation_error

Same category (refine)

refine_peak_newton · refine_translation_lk · refine_lm · refine_rotation_z · icp_point2point_3d


*Provenance: match3d.py — 3D operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.