Library / SDK
koide3/small_gicp avatar
koide3/small_gicp

small_gicp: header-only point cloud registration without pulling in PCL

Efficient and parallel algorithms for point cloud registration [C++, Python]

1,064 stars138 forksC++MIT

At a glance

What is it?
small_gicp is a C++17 header-only library for fine point cloud registration, rewritten from fast_gicp with parallel preprocessing, only Eigen required, and Python bindings that are portable enough to sit alongside Open3D.
Who is it for?
small_gicp suits a robotics or perception team that needs accurate point cloud registration and cannot afford to adopt the full PCL dependency tree, and that wants the same algorithm available from Python and from C++ without two implementations. It is a poor fit if you need GPU acceleration, since the project states that GPU-based implementations are not included, and a poor fit if you need a full SLAM stack, since it registers clouds and does not estimate a trajectory.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 24 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A rewrite of fast_gicp with the dependency tree cut down

small_gicp is a header-only C++ library for efficient and parallelised algorithms for fine point cloud registration, and it is a refined and optimised rewrite of its predecessor fast_gicp, written from scratch.

The dependency situation is the reason it exists in this form. It requires only Eigen, along with nanoflann and Sophus which are bundled into the repository rather than installed separately. The nanoflann copy lives at include/small_gicp/ann/kdtree.hpp and the Sophus one at include/small_gicp/util/lie.hpp. PCL is optional, and only for the registration interface that lets the library act as a drop-in replacement in an existing PCL pipeline.

The performance claim is stated in the project description as up to a 2x speed gain over fast_gicp on the core registration algorithm, and there is a BENCHMARK.md in the repository along with a data directory, so the comparison is documented rather than asserted in passing.

The other structural choice is the customisation mechanism. Any custom point cloud class can be integrated into the registration algorithm through traits, and because the implementation is template-based, the registration process itself can be customised with original correspondence estimators and registration factors. That is the part that distinguishes a library from a black box: the pieces a research group wants to replace, the correspondence finding and the error metric, are the pieces it can replace.

The author is Kenji Koide, the package metadata lists version 1.0.1, the licence is MIT, and the project was published in the Journal of Open Source Software with a v1.0.0 release on 2024-08-09.

Parallelism covers the preprocessing, not just the solver

The parallelism claim in the project description is more specific than the word multithreaded usually implies, and it is worth understanding exactly where the threads go.

The whole registration process is parallelised, because the preprocessing algorithms have parallel implementations. The named stages are downsampling, KdTree construction, and normal and covariance estimation. Those are the three operations that dominate the cost of a typical registration run and that a naive implementation does serially: building the search tree over a large cloud, and estimating a normal and a covariance for every point.

Two parallelism backends are supported, OpenMP and Intel TBB. OpenMP is the default in most Linux setups, and the Python packaging makes that explicit, with libomp-dev in the C++ install instructions and a cibuildwheel step that installs libomp-devel on Linux and sets OpenMP_ROOT on macOS with a matching MACOSX_DEPLOYMENT_TARGET of 14.0. Choosing a backend is therefore a build-time decision, not a runtime one.

The thread count is set in the registration settings rather than being inherited from the environment, which is the right design for a library that may be called from inside a larger parallel framework. The example sets setting.num_threads = 4 alongside setting.downsampling_resolution = 0.25 and setting.max_correspondence_distance = 1.0, so a caller controls both the parallelism and the two parameters that determine what a correspondence is.

What the project explicitly does not have is GPU support. The note that GPU-based implementations are NOT included in this package is stated plainly, which is worth knowing before you look for a CUDA path here.

Building the helper library, and pip installing the bindings

Because small_gicp is header-only, you can download it and drop it into your project directory to use it. That is the entire install for the core library, and for a header-only design that is the point.

If you want only the basic point cloud registration functions, you can build and install the helper library instead:

bash
sudo apt-get install libeigen3-dev libomp-dev

cd small_gicp
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release && make -j
sudo make install

The Python side installs from PyPI with a single command:

bash
pip install small_gicp

And from source:

bash
cd small_gicp
pip install .

# [Optional (linux)] Install stubs for autocomplete (If you know a better way, let me know...)
pip install pybind11-stubgen
cd ~/.local/lib/python3.10/site-packages
pybind11-stubgen -o . --ignore-invalid=all small_gicp

The stub generation step is optional and Linux specific in the instructions, and the comment invites better alternatives, which tells you it is a workaround rather than a designed feature.

The packaging itself is scikit-build-core with pybind11, and the CMake defines show the build switches: BUILD_PYTHON_BINDINGS is true, while BUILD_HELPER and BUILD_SHARED_LIBS are both false. So the pip wheel ships the Python bindings and not the C++ helper library, and the shared library is off, meaning the extension is built statically into the wheel.

The reason the Python bindings are more portable than PCL's is stated directly: by being isolated from PCL they can be used seamlessly with other libraries such as Open3D.

The helper API, and how the result object is shaped

The easiest way in is the helper library, registration_helper.hpp, which handles point clouds represented as std::vector<Eigen::Vector(3|4)(f|d)>.

cpp
#include <small_gicp/registration/registration_helper.hpp>

std::vector<Eigen::Vector3d> target_points = ...;   // Any of Eigen::Vector(3|4)(f|d) can be used
std::vector<Eigen::Vector3d> source_points = ...;   // 

RegistrationSetting setting;
setting.num_threads = 4;                    // Number of threads to be used
setting.downsampling_resolution = 0.25;     // Downsampling resolution
setting.max_correspondence_distance = 1.0;  // Maximum correspondence distance between points (e.g., triming threshold)

Eigen::Isometry3d init_T_target_source = Eigen::Isometry3d::Identity();
RegistrationResult result = align(target_points, source_points, init_T_target_source, setting);

Eigen::Isometry3d T = result.T_target_source;  // Estimated transformation
size_t num_inliers = result.num_inliers;       // Number of inlier source points
Eigen::Matrix<double, 6, 6> H = result.H;      // Final Hessian matrix (6x6)

Three things in the result are worth noting. T_target_source is the estimated transformation, and the naming makes the direction explicit, which matters because getting the source and target the wrong way round is a classic silent bug. num_inliers reports how many source points were counted as inliers, which is the number to look at when a registration looks wrong, since a low count with a plausible-looking transform usually means the correspondences were poor rather than the solver being wrong. And H is the final 6x6 Hessian, so the result carries the linearisation information, not just an answer.

The initial guess is a parameter, init_T_target_source, and it starts as the identity. For scan matching in a moving frame, an identity initial guess is rarely a good one, and the fact that it is an explicit argument rather than an implicit default is the API making that visible.

The docs note one cost of the helper approach: it causes an overhead for duplicated preprocessing. The alternative is to perform preprocessing and registration separately, which saves time when registration is performed several times for the same point cloud, for example typical odometry estimation based on scan-to-scan matching.

Where the scope stops

The first limitation is the absence of GPU implementations, which the project states outright. The parallel work is on the CPU, through OpenMP or TBB, and the model-based approaches that would map onto a GPU are not here.

The second limitation is what the library does not do at all. It registers point clouds. It does not estimate a trajectory, does not do loop closure, does not build a map, and does not handle the sensor model. If your problem is SLAM, this is the front end of it and you need the rest from somewhere else.

The third limitation is the C++ standard and the PCL constraint. The library uses C++17 features, and the PCL interface is not compatible with PCL older than 1.11, because that version uses boost::shared_ptr. If your pipeline is on an older PCL, the drop-in replacement path is closed and you would need to use the Eigen-only interface.

The fourth is the scope of the accuracy claim. The speed comparison against fast_gicp is documented in BENCHMARK.md, and the 2x figure is a headline, but the number depends on the cloud size, the downsampling resolution and the correspondence distance, all of which are parameters the caller sets. The parallelism is only as effective as the preprocessing stage, and for a small cloud the thread startup cost can outweigh the gain.

On maintenance, the repository is not archived and the last push was on 2026-09-29, with the JOSS publication tagged as v1.0.0 on 2024-08-09. The project asks users to leave a comment on an issue if the package proved useful, and to cite the corresponding software paper for academic work, and the CITATION.cff file in the repository supports that request.

Editorial conclusion

small_gicp suits a robotics or perception team that needs accurate point cloud registration and cannot afford to adopt the full PCL dependency tree, and that wants the same algorithm available from Python and from C++ without two implementations. It is a poor fit if you need GPU acceleration, since the project states that GPU-based implementations are not included, and a poor fit if you need a full SLAM stack, since it registers clouds and does not estimate a trajectory. Before adopting, check that your compiler is C++17 capable and that Eigen is available, decide whether you need the PCL interface at all, since that is what pulls the dependency in, and read BENCHMARK.md for the measured comparison against the predecessor rather than taking the speed claim on faith.

Frequently asked questions

What is small_gicp and what does it provide?

small_gicp is a header-only C++ library providing efficient and parallelized algorithms for fine point cloud registration, including ICP, Point-to-Plane ICP, GICP and VGICP. It is a rewrite of fast_gicp with parallel preprocessing, and it requires only Eigen along with bundled nanoflann and Sophus.

How do I install small_gicp for C++ and for Python?

As a header-only library you can download it and drop it into your project directory. To build and install the helper library, install libeigen3-dev and libomp-dev, then run cmake .. -DCMAKE_BUILD_TYPE=Release && make -j and sudo make install. In Python, pip install small_gicp installs it from PyPI, and pip install . builds it from source.

Does small_gicp use the GPU?

No. The project states that GPU-based implementations are not included in this package. The parallelism is on the CPU, through OpenMP or Intel TBB, and it covers downsampling, KdTree construction and normal and covariance estimation.

Which PCL versions work with the small_gicp PCL interface?

The PCL interface is not compatible with PCL older than 1.11, because those versions use boost::shared_ptr. PCL is optional in any case, since the library only requires Eigen along with the bundled nanoflann and Sophus.

What does the small_gicp registration result contain?

The result carries the estimated transformation as T_target_source, the number of inlier source points as num_inliers, and the final 6x6 Hessian matrix as H. The helper's align call takes the target and source points, an initial transformation guess, and a RegistrationSetting with the thread count, downsampling resolution and maximum correspondence distance.

Official sources

  1. Issues
  2. koide3/small_gicp on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/koide3-small-gicp.svg)](https://hysenlabs.com/projects/koide3-small-gicp)

Was this useful?