This tutorial covers advanced point cloud registration methods that address limitations of standard ICP: Generalized ICP (GICP), Normal Distributions Transform (NDT), TEASER++ (robust global registration), and KISS-ICP (simple modern LiDAR odometry).
Standard ICP has limitations that advanced methods address:
| Method | Strength | Best For |
|---|---|---|
| GICP | Probabilistic, handles uncertainty | Accurate local registration |
| NDT | Fast, grid-based | Real-time odometry |
| TEASER++ | Globally optimal, outlier-robust | Initial alignment, loop closure |
| KISS-ICP | Simple, modern, real-time | Production LiDAR odometry |
GICP combines point-to-point and point-to-plane ICP in a probabilistic framework.
Models both source and target points as Gaussian distributions:
p_i ~ N(p_i, C_i^A)
q_i ~ N(q_i, C_i^B)
Minimize: sum d_i^T (C_i^B + R*C_i^A*R^T)^(-1) d_i
where d_i = q_i - (R*p_i + t)
- Plane-to-plane: Elongated along surface normal
- Point-to-point: Isotropic (standard ICP)
- Point-to-plane: Intermediate
#include <pcl/registration/gicp.h>
pcl::GeneralizedIterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> gicp;
gicp.setInputSource(source);
gicp.setInputTarget(target);
// GICP-specific parameters
gicp.setMaximumIterations(50);
gicp.setTransformationEpsilon(1e-8);
gicp.setMaxCorrespondenceDistance(1.0);
gicp.setCorrespondenceRandomness(20); // Number of neighbors for covariance
gicp.setMaximumOptimizerIterations(20);
pcl::PointCloud<pcl::PointXYZ>::Ptr aligned(
new pcl::PointCloud<pcl::PointXYZ>);
gicp.align(*aligned);
std::cout << "GICP fitness: " << gicp.getFitnessScore() << std::endl;
std::cout << "Transform:\n" << gicp.getFinalTransformation() << std::endl;NDT represents the target cloud as a grid of Gaussian distributions.
- Divide target space into cells (voxels)
- For each cell, compute mean and covariance of points
- For source points, maximize likelihood under target distributions
- Very fast (no per-point correspondences)
- Smooth cost function
- Works well with sparse data
#include <pcl/registration/ndt.h>
pcl::NormalDistributionsTransform<pcl::PointXYZ, pcl::PointXYZ> ndt;
ndt.setInputSource(source);
ndt.setInputTarget(target);
// NDT-specific parameters
ndt.setResolution(1.0); // Cell size (meters)
ndt.setStepSize(0.1); // Newton optimization step size
ndt.setTransformationEpsilon(0.01);
ndt.setMaximumIterations(50);
// Provide initial guess (important for NDT)
Eigen::Matrix4f initial_guess = Eigen::Matrix4f::Identity();
pcl::PointCloud<pcl::PointXYZ>::Ptr aligned(
new pcl::PointCloud<pcl::PointXYZ>);
ndt.align(*aligned, initial_guess);
std::cout << "NDT converged: " << ndt.hasConverged() << std::endl;
std::cout << "Fitness: " << ndt.getFitnessScore() << std::endl;| Parameter | Effect | Typical Value |
|---|---|---|
| Resolution | Cell size, affects smoothness | 0.5-2.0m |
| StepSize | Optimization step, affects speed | 0.05-0.5 |
| MaxIterations | Convergence limit | 30-100 |
TEASER++ is a globally optimal, certifiably robust registration method.
- Outlier-robust: Works with >95% outliers
- Certifiable: Provides optimality guarantee
- No initial guess: Global registration
- Loop closure verification
- Relocalization
- Initial alignment for ICP
#include <teaser/registration.h>
// Prepare correspondences
teaser::PointCloud src_cloud, tgt_cloud;
for (const auto& p : source->points) {
src_cloud.push_back({p.x, p.y, p.z});
}
for (const auto& p : target->points) {
tgt_cloud.push_back({p.x, p.y, p.z});
}
// Setup TEASER++
teaser::RobustRegistrationSolver::Params params;
params.noise_bound = 0.05; // Noise threshold
params.cbar2 = 1.0; // TLS parameter
params.estimate_scaling = false;
params.rotation_max_iterations = 100;
params.rotation_gnc_factor = 1.4;
params.rotation_estimation_algorithm =
teaser::RobustRegistrationSolver::ROTATION_ESTIMATION_ALGORITHM::GNC_TLS;
params.rotation_cost_threshold = 0.005;
teaser::RobustRegistrationSolver solver(params);
solver.solve(src_cloud, tgt_cloud);
// Get result
auto solution = solver.getSolution();
Eigen::Matrix4d transform = Eigen::Matrix4d::Identity();
transform.block<3, 3>(0, 0) = solution.rotation;
transform.block<3, 1>(0, 3) = solution.translation;
std::cout << "TEASER++ valid: " << solution.valid << std::endl;
std::cout << "Transform:\n" << transform << std::endl;KISS-ICP is a modern, simple LiDAR odometry pipeline that achieves state-of-the-art results.
- Adaptive threshold: Automatically adjusts correspondence threshold
- Voxel-based correspondences: Fast approximate nearest neighbors
- Point-to-point ICP: Simple but effective
- Motion compensation: Handles sensor motion during scan
Raw LiDAR Scan
|
v
+------------------+
| Motion Compen- | Deskew points using predicted motion
| sation |
+------------------+
|
v
+------------------+
| Voxel Down- | Efficient representation
| sampling |
+------------------+
|
v
+------------------+
| Adaptive ICP | Point-to-point with adaptive threshold
+------------------+
|
v
+------------------+
| Local Map | Maintain sliding window map
| Management |
+------------------+
|
v
Pose Estimate
#include <kiss_icp/pipeline/KissICP.hpp>
// Configuration
kiss_icp::pipeline::KISSConfig config;
config.max_range = 100.0;
config.min_range = 5.0;
config.deskew = true;
config.voxel_size = 1.0;
config.max_points_per_voxel = 20;
config.initial_threshold = 2.0;
// Create pipeline
kiss_icp::pipeline::KissICP kiss_icp(config);
// Process frames
for (const auto& cloud : lidar_sequence) {
// Convert to kiss-icp format
std::vector<Eigen::Vector3d> points;
for (const auto& p : cloud->points) {
points.emplace_back(p.x, p.y, p.z);
}
// Register
kiss_icp.RegisterFrame(points);
// Get pose
Sophus::SE3d pose = kiss_icp.pose();
std::cout << "Position: "
<< pose.translation().transpose() << std::endl;
}from kiss_icp.pipeline import OdometryPipeline
from kiss_icp.config import KISSConfig
config = KISSConfig()
config.data.max_range = 100.0
config.data.min_range = 5.0
pipeline = OdometryPipeline(config)
for cloud in lidar_sequence:
pose = pipeline.process(cloud)
print(f"Position: {pose[:3, 3]}")| Method | Translation Error | Rotation Error | FPS |
|---|---|---|---|
| ICP | 1.5% | 0.006 deg/m | 20 |
| GICP | 1.0% | 0.004 deg/m | 10 |
| NDT | 1.2% | 0.005 deg/m | 30 |
| KISS-ICP | 0.8% | 0.003 deg/m | 50+ |
| Scenario | Recommended Method |
|---|---|
| Real-time odometry | KISS-ICP or NDT |
| High accuracy needed | GICP |
| Unknown initial pose | TEASER++ + GICP |
| Loop closure | TEASER++ |
| Structured environments | NDT |
| Unstructured outdoor | KISS-ICP |
2_30/
├── README.md
├── CMakeLists.txt
├── Dockerfile
├── data/
│ └── sample_sequences/
└── examples/
├── gicp_demo.cpp # GICP registration
├── ndt_demo.cpp # NDT registration
├── teaser_demo.cpp # TEASER++ global registration
├── kiss_icp_demo.cpp # KISS-ICP odometry
└── method_comparison.cpp # Compare all methods
- PCL 1.12+
- Eigen3
- TEASER++ (optional)
- KISS-ICP (optional)
mkdir build && cd build
cmake ..
make -j4docker build . -t slam_zero_to_hero:2_30# GICP
./build/gicp_demo source.pcd target.pcd
# NDT
./build/ndt_demo source.pcd target.pcd --resolution 1.0
# TEASER++
./build/teaser_demo source.pcd target.pcd
# KISS-ICP
./build/kiss_icp_demo /path/to/kitti/velodyne/
# Compare methods
./build/method_comparison source.pcd target.pcdclass LidarOdometryAdvanced {
public:
Eigen::Matrix4f registerClouds(
pcl::PointCloud<pcl::PointXYZ>::Ptr source,
pcl::PointCloud<pcl::PointXYZ>::Ptr target) {
// 1. Coarse alignment with TEASER++ (if needed)
Eigen::Matrix4f initial = Eigen::Matrix4f::Identity();
if (!hasGoodInitialGuess()) {
initial = runTEASER(source, target);
}
// 2. Fine alignment with GICP
pcl::GeneralizedIterativeClosestPoint<
pcl::PointXYZ, pcl::PointXYZ> gicp;
gicp.setInputSource(source);
gicp.setInputTarget(target);
gicp.setMaximumIterations(30);
pcl::PointCloud<pcl::PointXYZ>::Ptr aligned(
new pcl::PointCloud<pcl::PointXYZ>);
gicp.align(*aligned, initial);
return gicp.getFinalTransformation();
}
};- Segal et al., "Generalized-ICP", RSS 2009
- Biber & Strasser, "The Normal Distributions Transform", IROS 2003
- Yang et al., "TEASER: Fast and Certifiable Point Cloud Registration", IEEE T-RO 2020
- Vizzo et al., "KISS-ICP: In Defense of Point-to-Point ICP", IEEE RA-L 2023
- KISS-ICP GitHub
- TEASER++ GitHub