register_fpfh — 3D feature_register op

• Data kinds: points × points → pose

• Call: import fullseye as fs; fs.ledger.register_fpfh(src, dst, src_normals=None, dst_normals=None, voxel_size=None, normal_k=16, feature_k=60, n_bins=11, ransac_iters=8000, inlier_thr=None, edge_sim=0.9, mutual=True, ratio=0.95, seed=0, device='cpu') (to call the implementation directly, import feat_fpfh; feat_fpfh.register_fpfh(src, dst, src_normals=None, dst_normals=None, voxel_size=None, normal_k=16, feature_k=60, n_bins=11, ransac_iters=8000, inlier_thr=None, edge_sim=0.9, mutual=True, ratio=0.95, seed=0, device='cpu'); from the registry, ops3d.get("register_fpfh"))

• GPU: none (CPU reference implementation only)

Usage

Estimate the rigid alignment (R,t) without an initial guess using FPFH descriptors and RANSAC.

A 3D version of sparse feature matching, equivalent to the 2D Harris/SIFT. Both clouds are represented by FPFH descriptors, Create a correspondence using the nearest neighbors in the descriptor space (mutual nearest neighbors + filter by Lowe ratio), and use RANSAC to Solving rigid body poses that are robust to outliers. Unlike close match (NCC/Hough) and PCA, large rotation (50-70°) The value is that the pose can be determined without initial estimation even with +translation + partial overlap. The obtained (R,t) is the initial value of ICP (coarse init) tightens to surface precision (ICP convention of dst ≈ src @ R.T + t).

Pipeline:

1) Voxel downsampling (independent sampling 2 Align the vicinity of clouds to a common resolution. (Required preprocessing that greatly affects the correct response rate of FPFH).

2) Normal estimation (local PCA). If you pass src_normals/dst_normals, it will be used.

3) FPFH descriptor (SPFH → distance weight synthesis).

4) Descriptor NN match (mutual + ratio).

5) RANSAC: 3-point sample → edge-length matched prefilter → Kabsch → All-point fitness (src convert all points and dst the rate at which the nearest neighbor falls within the threshold). Compatible (~100 points) only Scoring tends to be fixed on incorrect postures, so it is made robust with all-point fitness.

6) Refit with the corresponding inlier for the best posture.

Arguments:

src (N,3), dst (M,3): Two point groups to align (numpy/torch).

src_normals, dst_normals ((N,3)/(M,3) or None): Prior normals. If None, internal estimation

(However, when voxel_size>0, the coordinates change, so it is always re-estimated).

voxel_size (float or None): Downsample side length. Automatically adopts DST resolution x 2.5 with None.

Disable with 0 (remains raw point cloud; not recommended for independent sampling).

normal_k (int): Number of neighbors for normal estimation.

feature_k (int): Number of neighbors of FPFH (the larger the number, the more stable and discriminative the descriptor is. Around 60 is recommended).

n_bins (int): Number of histogram bins per feature (descriptor dimension = 3*n_bins).

ransac_iters (int): RANSAC iteration number.

inlier_thr (float or None): Inlier distance threshold. None (after downsampling) resolution x 3.

edge_sim (float): Tolerance of triple edge length ratio (0<edge_sim<=1, closer to 1 is stricter).

mutual (bool): Mutual nearest neighbor filter. ratio (float or None): Lowe ratio.

seed (int): RANSAC random number seed (change at restart). device (str): device of return tensor.

Return value:

R (3,3) torch.Tensor(on device), t (3,) torch.Tensor,

info dict: {"n_corr"(number of correspondences), "inliers"(number of inliers), "inlier_ratio",

"fitness" (all-point fitness = restart selection index), "rmse" (inlier RMSE),

"inlier_thr", "src_corr", "dst_corr" (corresponding index)}.

Note (honest): FPFH is coarse registration, independent sampling + noise + partial overlap Under difficult conditions, a rotation error of several degrees remains (actual measurement: 62° rotation, overlap ~64%, noise 0.5 × resolution) Post-RANSAC median ~4.4°, ~90% <8°, remaining ~10% bordering 8-9°. 100% when tightened with ICP <8°, median ~0.5°). When the noise becomes equal to or higher than the point spacing (resolution), the normal and corner features become The collapsed descriptor loses its discriminative power (actual measurement: success rate decreases when noise ≥ 1.0 × resolution). RANSAC is random selection Therefore, in actual operation, it is recommended to restart several times and take the maximum result info["fitness"].

Background guides (the physics and conventions behind this op)

• blas_threads_and_memory — Why matrix decompositions are slow — BLAS threads, caches, memory layout

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)

• feature_register — py -3.11 examples_3d/feature_register.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 (feature_register)

harris3d_keypoints · iss_keypoints · compute_fpfh · shot_descriptor · register_spin · register_shot


*Provenance: feat_fpfh.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.