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"))
Estimate the rigid alignment (R,t) without an initial guess using FPFH descriptors and RANSAC.
The 3D version of sparse feature matching, corresponding to Harris/SIFT in 2D. Both clouds are represented by FPFH descriptors, correspondences are set up by nearest neighbours in descriptor space (filtered by mutual nearest neighbour + Lowe ratio), and RANSAC solves a rigid pose robust to outliers. Unlike dense matching (NCC/Hough) or PCA, its value is that it yields the pose without an initial estimate even under large rotation (50-70°) +translation + partial overlap. The obtained (R,t) can be passed to ICP as the initial value (coarse init) to tighten to surface accuracy (ICP convention dst ≈ src @ R.T + t).
Pipeline:
1) voxel downsampling (brings the neighbourhoods of 2 independently sampled clouds to a common resolution; an essential preprocessing step that strongly affects the FPFH correspondence accuracy).
2) normal estimation (local PCA). If src_normals/dst_normals are passed, they are used.
3) FPFH descriptor (SPFH → distance-weighted combination).
4) descriptor NN matching (mutual + ratio).
5) RANSAC: 3-point sample → edge-length consistency prefilter → Kabsch → scoring by all-point fitness (the fraction of all src points that, once transformed, have their dst nearest neighbour within the threshold). Scoring only the correspondences (~100 points) tends to stick to wrong poses, so all-point fitness makes it robust.
6) refit on the correspondence inliers of the best pose.
Args:
src (N,3), dst (M,3): the 2 point clouds to register (numpy/torch).
src_normals, dst_normals ((N,3)/(M,3) or None): precomputed normals. None means internal estimation
(but when voxel_size>0 the coordinates change, so they are always re-estimated).
voxel_size (float or None): downsampling edge length. None automatically uses dst resolution ×2.5.
0/None disables it (raw point clouds as is; not recommended for independent sampling).
normal_k (int): number of neighbours for normal estimation.
feature_k (int): number of FPFH neighbours (larger makes the descriptor more stable and discriminative; around 60 recommended).
n_bins (int): histogram bins per 1 feature (descriptor dimension = 3*n_bins).
ransac_iters (int): number of RANSAC iterations.
inlier_thr (float or None): inlier distance threshold. None uses (post-downsampling) resolution ×3.
edge_sim (float): tolerance of the edge-length ratio of triples (0<edge_sim<=1; the closer to 1, the stricter).
mutual (bool): mutual nearest-neighbour filter. ratio (float or None): Lowe ratio.
seed (int): RANSAC random seed (change it on restart). device (str): device of the returned tensors.
Returns:
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 = the criterion for choosing among restarts), "rmse" (inlier RMSE),
"inlier_thr", "src_corr","dst_corr" (correspondence indices)}.
Notes (honest): FPFH is coarse registration; under hard conditions of independent sampling + noise + partial overlap a rotation error of a few degrees remains (measured: with 62° rotation, overlap ~64% and noise 0.5× resolution, after RANSAC the median is ~4.4°, ~90% are <8°, and the remaining ~10% sit at the 8-9° boundary. Tightening with ICP brings 100% to <8° with a median of ~0.5°). Once the noise is on the order of the point spacing (resolution) or larger, the normals and angular features break down and the descriptor loses its discriminative power (measured: the success rate drops at noise ≥1.0× resolution). RANSAC is randomised, so in practice restart it a few times and take the result with the largest info["fitness"].
• blas_threads_and_memory — Why matrix decompositions are slow — BLAS threads, caches, memory layout
• 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.
• feature_register — py -3.11 examples_3d/feature_register.py
pose as input)fuse_to_voxel · pose_error · bundle_adjust · mean_reprojection_error · optimize_pose_graph · relative_pose · mean_edge_error · rotation_translation_error
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.