Global Registration with FPFH Features and RANSAC
When two LiDAR clouds have no shared pose prior — a loop closure, or two scans with large relative rotation — a local method like ICP has no basin to converge into and diverges. This task recovers a coarse alignment from scratch: compute pose-invariant FPFH descriptors, match them across the clouds, and use RANSAC to find the transform with the largest consensus of agreeing matches. Its output is deliberately coarse, sized to seed the local refiner. It is the from-scratch pass that the registration method selection guide calls for when no good initial guess exists, feeding ICP refinement.
FPFH descriptors are matched across clouds and RANSAC selects the transform with the most inliers to seed ICP:
Prerequisites #
- Python 3.10+, open3d 0.18+ (
registration_ransac_based_on_feature_matching,compute_fpfh_feature), NumPy 1.24+. - Input: two point clouds with no reliable relative pose, in comparable units.
- Upstream stage: clouds are motion-compensated; the result seeds the ICP pass from accelerating ICP.
- Output: a coarse source→target transform good enough to initialize local refinement.
Step-by-Step #
1. Downsample and estimate normals #
FPFH needs surface normals; compute them on a voxel-downsampled cloud so the descriptors are stable and cheap.
import open3d as o3d
def prepare(cloud: o3d.geometry.PointCloud, voxel=0.3):
down = cloud.voxel_down_sample(voxel)
down.estimate_normals(
o3d.geometry.KDTreeSearchParamHybrid(radius=voxel * 2, max_nn=30))
return down
Key parameters: voxel (0.3 m) sets feature density; the normal search radius is a couple of voxels so each normal averages a stable local patch.
2. Compute FPFH descriptors #
Compute the histogram at each downsampled point over a larger radius than the normals, so the descriptor captures neighbourhood structure.
def fpfh(down, voxel=0.3):
return o3d.pipelines.registration.compute_fpfh_feature(
down, o3d.geometry.KDTreeSearchParamHybrid(radius=voxel * 5, max_nn=100))
Key parameter: the FPFH radius (5× voxel) is deliberately larger than the normal radius so the histogram describes the surrounding geometry, not just the immediate point.
3. Match and run RANSAC #
Match descriptors and fit a transform on random consensus subsets, keeping the maximum-inlier result.
def global_register(src_d, tgt_d, src_f, tgt_f, voxel=0.3):
dist = voxel * 1.5
return o3d.pipelines.registration.registration_ransac_based_on_feature_matching(
src_d, tgt_d, src_f, tgt_f, True, dist,
o3d.pipelines.registration.TransformationEstimationPointToPoint(False), 3,
[o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(dist)],
o3d.pipelines.registration.RANSACConvergenceCriteria(100000, 0.999))
Key parameters: dist (1.5× voxel) is the inlier threshold; sampling 3 correspondences per RANSAC iteration is the minimum for a rigid transform; the convergence criteria bound the iterations.
Verification & Acceptance Criteria #
The global result is a seed, so it is gated on being good enough to refine, not on final accuracy.
def assert_global(result, min_fitness=0.3, coarse_rmse=0.6):
assert result.fitness >= min_fitness, f"fitness {result.fitness:.2f} too low"
assert result.inlier_rmse <= coarse_rmse, f"coarse RMSE {result.inlier_rmse:.2f} m high"
print(f"global seed: fitness {result.fitness:.2f}, RMSE {result.inlier_rmse:.2f} m")
Acceptance gate: fitness (inlier fraction) above a floor and coarse RMSE ≤0.6 m — enough to land inside ICP's basin; the transform is a proper rigid motion; and the result is stable across a couple of RANSAC seeds. A fitness below the floor means too few distinctive features — fall back to an odometry-seeded local method.
Common Errors & Fixes #
RANSAC returns garbage on a highway scene. Low-texture scenes yield few distinctive FPFH features, so no consensus forms. Fall back to an odometry prior with NDT, per registration method selection.
Descriptors are unstable and matches are all wrong. Normals were computed on the raw, noisy cloud. Downsample first and estimate normals on the downsampled points with a stable radius.
Global align is coarse and ICP still fails. The RANSAC result is outside ICP's basin. Loosen ICP's initial correspondence radius, or tighten the FPFH inlier threshold so the seed is closer.
Runtime is very slow. The clouds were not downsampled before feature computation. FPFH on full-resolution clouds is expensive; downsample to a scene-appropriate voxel first.
FAQ #
What is an FPFH descriptor? #
A Fast Point Feature Histogram is a compact description of the local surface geometry around a point, built from the angular relationships between the point's normal and those of its neighbours. It is designed to be pose-invariant, so the same physical surface patch produces a similar histogram regardless of how the cloud is oriented. That invariance is what lets a patch in one cloud be matched to the same patch in another cloud with an unknown relative pose.
Why is RANSAC needed after feature matching? #
Feature matches are noisy — many are wrong because similar surfaces produce similar descriptors. Fitting a transform to all matches would be dominated by those outliers. RANSAC repeatedly samples a small random set of matches, fits a candidate transform, and counts how many other matches agree with it, keeping the transform with the largest consensus. This rejects the wrong matches and recovers the correct coarse alignment from a sea of noise.
How accurate is the global result? #
Coarse — typically within a few tenths of a metre, not the final 0.05 m budget. Global registration is not meant to be precise; it is meant to land inside the basin of convergence of a local refiner. The RANSAC transform is always followed by ICP or NDT, which take the coarse alignment to full accuracy. A global result good enough to seed refinement, roughly under 0.6 m, is the acceptance target.
Related #
- Accelerating ICP with a KD-Tree and Voxel Downsampling — the local refiner this coarse transform seeds.
- Choosing a Point-Cloud Registration Method: ICP, NDT & Global — when a global pass is mandatory.
- Point Cloud Registration with ICP Algorithm in Python — the ICP refinement that follows.
Up one level: Point Cloud Registration Techniques — the parent workflow whose from-scratch alignment this global pass provides.