Compare commits

...

14 Commits

Author SHA1 Message Date
unclearness 1d8cce8add
Fix VS2022 build (OpenMP optional) and debugger working directory for examples/tests (#119)
* Pass compile on Windows

* Set debugger working diretoriy as project root for examples and tests on Windows
2025-06-10 12:48:47 +09:00
koide3 08bc50beff
fix typo (#115) 2025-05-07 14:51:07 +09:00
koide3 9befefb198
sequential voxelmap accessor (#113)
* sequential voxelmap accessor

* capital
2025-05-07 11:35:58 +09:00
koide3 3466ea263a
change accessing order for column major (#112) 2025-05-07 11:25:04 +09:00
koide3 df145cbb15
use ubuntu 24.04 runner to avoid packaging issue (#111)
* use ubuntu 24.04 runner to avoid packaging issue

* ignore errors mismatch
2025-05-07 10:02:51 +09:00
atm db2f8e6646
Fix typos and enable links (#109)
* chore: enable youtube link

* chore: correct typos in comments
2025-05-07 09:31:31 +09:00
koide3 2c5e9e6092
improve batch_knn_search performance (#101) 2025-01-12 13:30:09 +09:00
koide3 ff63d5ef76
add ninja-build (#99)
* add ninja-build

* add ninja-build

* add ninja-build for coverage
2024-12-30 05:51:45 +09:00
fateshelled 13e0a75cc1
fix: update error value (#97) 2024-12-30 05:23:05 +09:00
koide3 8e665814a9
projective search (#92) 2024-09-24 18:04:55 +09:00
Artem Voronov 4e8e745800
feat: add rotation and translation epsilon to adjust convergence criteria in python interface (#91) 2024-09-22 07:52:42 +09:00
koide3 e669301de3
Update README.md 2024-08-10 23:40:06 +09:00
koide3 f127ae7a51
Update README.md 2024-08-10 23:38:23 +09:00
koide3 d6b0cb8e99
Create CITATION.cff 2024-08-10 23:32:29 +09:00
18 changed files with 437 additions and 29 deletions

View File

@ -13,7 +13,7 @@ on:
jobs: jobs:
coverage: coverage:
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -23,7 +23,7 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get install --no-install-recommends -y build-essential cmake python3-pip pybind11-dev libeigen3-dev libfmt-dev libtbb-dev libomp-dev libpcl-dev libgtest-dev lcov sudo apt-get install --no-install-recommends -y build-essential cmake python3-pip pybind11-dev libeigen3-dev libfmt-dev libtbb-dev libomp-dev libpcl-dev libgtest-dev lcov ninja-build
pip install -U setuptools pytest pytest-cov numpy scipy pip install -U setuptools pytest pytest-cov numpy scipy
- name: Build (C++) - name: Build (C++)

View File

@ -13,7 +13,7 @@ on:
jobs: jobs:
test: test:
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -23,7 +23,7 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get install --no-install-recommends -y build-essential cmake python3-pip pybind11-dev libeigen3-dev libfmt-dev libtbb-dev libomp-dev libpcl-dev libgtest-dev sudo apt-get install --no-install-recommends -y build-essential cmake python3-pip pybind11-dev libeigen3-dev libfmt-dev libtbb-dev libomp-dev libpcl-dev libgtest-dev ninja-build
pip install -U setuptools pytest numpy scipy pip install -U setuptools pytest numpy scipy
- name: Build - name: Build

View File

@ -129,5 +129,5 @@ small_vgicp : APE=5.956 +- 2.725 RPE(100)=1.315 +- 0.762 RPE(400)=6.8
[Code](https://github.com/koide3/small_gicp/blob/pybench/src/benchmark/odometry_benchmark.py) [Code](https://github.com/koide3/small_gicp/blob/pybench/src/benchmark/odometry_benchmark.py)
Processing speed comparison between small_gicp and Open3D ([youtube]((https://youtu.be/LNESzGXPr4c?feature=shared))). Processing speed comparison between small_gicp and Open3D ([youtube](https://youtu.be/LNESzGXPr4c?feature=shared)).
[![small_comp](https://github.com/koide3/small_gicp/assets/31344317/7959edd6-f0e4-4318-b4c1-a3f8755c407f)](https://youtu.be/LNESzGXPr4c?feature=shared) [![small_comp](https://github.com/koide3/small_gicp/assets/31344317/7959edd6-f0e4-4318-b4c1-a3f8755c407f)](https://youtu.be/LNESzGXPr4c?feature=shared)

32
CITATION.cff Normal file
View File

@ -0,0 +1,32 @@
cff-version: "1.2.0"
authors:
- family-names: Koide
given-names: Kenji
orcid: "https://orcid.org/0000-0001-5361-1428"
contact:
- family-names: Koide
given-names: Kenji
orcid: "https://orcid.org/0000-0001-5361-1428"
doi: 10.5281/zenodo.13283012
message: If you use this software, please cite our article in the
Journal of Open Source Software.
preferred-citation:
authors:
- family-names: Koide
given-names: Kenji
orcid: "https://orcid.org/0000-0001-5361-1428"
date-published: 2024-08-10
doi: 10.21105/joss.06948
issn: 2475-9066
issue: 100
journal: Journal of Open Source Software
publisher:
name: Open Journals
start: 6948
title: "small_gicp: Efficient and parallel algorithms for point cloud
registration"
type: article
url: "https://joss.theoj.org/papers/10.21105/joss.06948"
volume: 9
title: "small_gicp: Efficient and parallel algorithms for point cloud
registration"

View File

@ -97,7 +97,10 @@ endif()
if(MSVC) if(MSVC)
add_compile_definitions(_USE_MATH_DEFINES) add_compile_definitions(_USE_MATH_DEFINES)
# add_compile_options(/openmp:llvm) add_compile_options(/bigobj)
if(BUILD_WITH_OPENMP)
add_compile_options(/openmp:llvm)
endif()
endif() endif()
############## ##############
@ -111,8 +114,8 @@ if(ENABLE_COVERAGE)
find_program(GENHTML genhtml REQUIRED) find_program(GENHTML genhtml REQUIRED)
add_custom_target(coverage add_custom_target(coverage
COMMAND ${LCOV} --directory . --capture --output-file coverage.info COMMAND ${LCOV} --directory . --capture --output-file coverage.info --ignore-errors mismatch
COMMAND ${LCOV} --remove coverage.info -o coverage.info '/usr/*' COMMAND ${LCOV} --remove coverage.info -o coverage.info '/usr/*' --ignore-errors mismatch
COMMAND ${GENHTML} --demangle-cpp -o coverage coverage.info COMMAND ${GENHTML} --demangle-cpp -o coverage coverage.info
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
endif() endif()
@ -247,6 +250,12 @@ if(BUILD_EXAMPLES)
TBB::tbbmalloc TBB::tbbmalloc
PCL::PCL PCL::PCL
) )
if(MSVC)
set_target_properties(${EXAMPLE_NAME}
PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY
"${CMAKE_SOURCE_DIR}"
)
endif()
endforeach() endforeach()
endif() endif()
@ -273,6 +282,13 @@ if(BUILD_TESTS)
) )
gtest_discover_tests(${TEST_NAME} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) gtest_discover_tests(${TEST_NAME} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
if(MSVC)
set_target_properties(${TEST_NAME}
PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY
"${CMAKE_SOURCE_DIR}"
)
endif()
endforeach() endforeach()
endif() endif()

View File

@ -10,10 +10,10 @@
Note that GPU-based implementations are NOT included in this package. Note that GPU-based implementations are NOT included in this package.
If you find this package useful for your project, please consider leaving a comment [here](https://github.com/koide3/small_gicp/issues/3). It would help the author receive recognition in his organization and keep working on this project. If you find this package useful for your project, please consider leaving a comment [here](https://github.com/koide3/small_gicp/issues/3). It would help the author receive recognition in his organization and keep working on this project. Please also cite [the corresponding software paper](https://joss.theoj.org/papers/10.21105/joss.06948) if you use this software in an academic work.
[![Build(Linux)](https://github.com/koide3/small_gicp/actions/workflows/build-linux.yml/badge.svg)](https://github.com/koide3/small_gicp/actions/workflows/build-linux.yml) [![macos](https://github.com/koide3/small_gicp/actions/workflows/build-macos.yml/badge.svg)](https://github.com/koide3/small_gicp/actions/workflows/build-macos.yml) [![Build(Windows)](https://github.com/koide3/small_gicp/actions/workflows/build-windows.yml/badge.svg)](https://github.com/koide3/small_gicp/actions/workflows/build-windows.yml) [![Test](https://github.com/koide3/small_gicp/actions/workflows/test.yml/badge.svg)](https://github.com/koide3/small_gicp/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/koide3/small_gicp/graph/badge.svg?token=PCVIUP2Z33)](https://codecov.io/gh/koide3/small_gicp) [![status](https://joss.theoj.org/papers/059b017c823ed9fd211fc91373cdc2cc/status.svg)](https://joss.theoj.org/papers/059b017c823ed9fd211fc91373cdc2cc) [![Build(Linux)](https://github.com/koide3/small_gicp/actions/workflows/build-linux.yml/badge.svg)](https://github.com/koide3/small_gicp/actions/workflows/build-linux.yml) [![macos](https://github.com/koide3/small_gicp/actions/workflows/build-macos.yml/badge.svg)](https://github.com/koide3/small_gicp/actions/workflows/build-macos.yml) [![Build(Windows)](https://github.com/koide3/small_gicp/actions/workflows/build-windows.yml/badge.svg)](https://github.com/koide3/small_gicp/actions/workflows/build-windows.yml) [![Test](https://github.com/koide3/small_gicp/actions/workflows/test.yml/badge.svg)](https://github.com/koide3/small_gicp/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/koide3/small_gicp/graph/badge.svg?token=PCVIUP2Z33)](https://codecov.io/gh/koide3/small_gicp)
## Requirements ## Requirements
@ -431,7 +431,7 @@ python3 src/example/basic_registration.py
## [Benchmark](BENCHMARK.md) ## [Benchmark](BENCHMARK.md)
Processing speed comparison between small_gicp and Open3D ([youtube]((https://youtu.be/LNESzGXPr4c?feature=shared))). Processing speed comparison between small_gicp and Open3D ([youtube](https://youtu.be/LNESzGXPr4c?feature=shared)).
[![small_comp](https://github.com/koide3/small_gicp/assets/31344317/7959edd6-f0e4-4318-b4c1-a3f8755c407f)](https://youtu.be/LNESzGXPr4c?feature=shared) [![small_comp](https://github.com/koide3/small_gicp/assets/31344317/7959edd6-f0e4-4318-b4c1-a3f8755c407f)](https://youtu.be/LNESzGXPr4c?feature=shared)
### Downsampling ### Downsampling
@ -465,6 +465,22 @@ This package is released under the MIT license.
If you find this package useful for your project, please consider leaving a comment [here](https://github.com/koide3/small_gicp/issues/3). It would help the author receive recognition in his organization and keep working on this project. If you find this package useful for your project, please consider leaving a comment [here](https://github.com/koide3/small_gicp/issues/3). It would help the author receive recognition in his organization and keep working on this project.
Please also cite [the following article](https://joss.theoj.org/papers/10.21105/joss.06948) if you use this software in an academic work.
```
@article{small_gicp,
author = {Kenji Koide},
title = {{small\_gicp: Efficient and parallel algorithms for point cloud registration}},
journal = {Journal of Open Source Software},
month = aug,
number = {100},
pages = {6948},
volume = {9},
year = {2024},
doi = {10.21105/joss.06948}
}
```
## Contact ## Contact
[Kenji Koide](https://staff.aist.go.jp/k.koide/), National Institute of Advanced Industrial Science and Technology (AIST) [Kenji Koide](https://staff.aist.go.jp/k.koide/), National Institute of Advanced Industrial Science and Technology (AIST)

View File

@ -7,7 +7,7 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --no-install-recommends -y \ RUN apt-get update && apt-get install --no-install-recommends -y \
&& apt-get install --no-install-recommends -y \ && apt-get install --no-install-recommends -y \
wget nano build-essential git cmake python3-dev python3-pip pybind11-dev \ wget nano build-essential git cmake python3-dev python3-pip pybind11-dev \
libeigen3-dev libomp-dev libeigen3-dev libomp-dev ninja-build
RUN mkdir -p ~/.config/pip RUN mkdir -p ~/.config/pip
RUN echo "[global]\nbreak-system-packages=true" > ~/.config/pip/pip.conf RUN echo "[global]\nbreak-system-packages=true" > ~/.config/pip/pip.conf

View File

@ -7,7 +7,7 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --no-install-recommends -y \ RUN apt-get update && apt-get install --no-install-recommends -y \
&& apt-get install --no-install-recommends -y \ && apt-get install --no-install-recommends -y \
wget nano build-essential git cmake python3-dev python3-pip pybind11-dev \ wget nano build-essential git cmake python3-dev python3-pip pybind11-dev \
libeigen3-dev libomp-dev libeigen3-dev libomp-dev ninja-build
RUN apt-get update && apt-get install --no-install-recommends -y \ RUN apt-get update && apt-get install --no-install-recommends -y \
&& apt-get install --no-install-recommends -y \ && apt-get install --no-install-recommends -y \

View File

@ -34,6 +34,7 @@ public:
/// This class supports incremental point cloud insertion and LRU-based voxel deletion that removes voxels that are not recently referenced. /// This class supports incremental point cloud insertion and LRU-based voxel deletion that removes voxels that are not recently referenced.
/// @note This class can be used as a point cloud as well as a neighbor search structure. /// @note This class can be used as a point cloud as well as a neighbor search structure.
/// @note This class can handle arbitrary number of voxels and arbitrary range of voxel coordinates (in 32-bit int range). /// @note This class can handle arbitrary number of voxels and arbitrary range of voxel coordinates (in 32-bit int range).
/// @note To use this as a source point cloud for registration, use `SequentialVoxelMapAccessor`.
template <typename VoxelContents> template <typename VoxelContents>
struct IncrementalVoxelMap { struct IncrementalVoxelMap {
public: public:

View File

@ -0,0 +1,219 @@
// SPDX-FileCopyrightText: Copyright 2024 Kenji Koide
// SPDX-License-Identifier: MIT
#pragma once
#include <cmath>
#include <Eigen/Core>
#include <small_gicp/points/traits.hpp>
#include <small_gicp/ann/knn_result.hpp>
namespace small_gicp {
/// @brief Equirectangular projection.
struct EquirectangularProjection {
public:
/// @brief Project the point into the normalized image coordinates. (u, v) in ([0, 1], [0, 1])
Eigen::Vector2d operator()(const Eigen::Vector3d& pt_3d) const {
if (pt_3d.squaredNorm() < 1e-3) {
return Eigen::Vector2d(0.5, 0.5);
}
const Eigen::Vector3d bearing = pt_3d.normalized();
const double lat = -std::asin(bearing[1]);
const double lon = std::atan2(bearing[0], bearing[2]);
return Eigen::Vector2d(lon / (2.0 * M_PI) + 0.5, lat / M_PI + 0.5);
};
};
/// @brief Border clamp mode. Points out of the border are discarded.
struct BorderClamp {
public:
int operator()(int x, int width) const { return x; }
};
/// @brief Border repeat mode. Points out of the border are wrapped around.
struct BorderRepeat {
public:
int operator()(int x, int width) const { return x < 0 ? x + width : (x >= width ? x - width : x); }
};
/// @brief "Unsafe" projective search. This class does not hold the ownership of the target point cloud.
template <typename PointCloud, typename Projection = EquirectangularProjection, typename BorderModeH = BorderRepeat, typename BorderModeV = BorderClamp>
struct UnsafeProjectiveSearch {
public:
/// @brief Constructor.
/// @param width Index map width
/// @param height Index map height
/// @param points Target point cloud
UnsafeProjectiveSearch(int width, int height, const PointCloud& points) : points(points), index_map(height, width), search_window_h(10), search_window_v(5) {
index_map.setConstant(invalid_index);
Projection project;
for (size_t i = 0; i < traits::size(points); ++i) {
const Eigen::Vector4d pt = traits::point(points, i);
const Eigen::Vector2d uv = project(pt.head<3>());
const int u = uv[0] * index_map.cols();
const int v = uv[1] * index_map.rows();
if (u < 0 || u >= index_map.cols() || v < 0 || v >= index_map.rows()) {
continue;
}
index_map(v, u) = i;
}
}
/// @brief Find the nearest neighbor.
/// @param query Query point
/// @param k_indices Index of the nearest neighbor (uninitialized if not found)
/// @param k_sq_dists Squared distance to the nearest neighbor (uninitialized if not found)
/// @param setting KNN search setting
/// @return Number of found neighbors (0 or 1)
size_t nearest_neighbor_search(const Eigen::Vector4d& query, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
return knn_search<1>(query, k_indices, k_sq_dists, setting);
}
/// @brief Find k-nearest neighbors. This method uses dynamic memory allocation.
/// @param query Query point
/// @param k Number of neighbors
/// @param k_indices Indices of neighbors
/// @param k_sq_dists Squared distances to neighbors (sorted in ascending order)
/// @param setting KNN search setting
/// @return Number of found neighbors
size_t knn_search(const Eigen::Vector4d& query, int k, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
KnnResult<-1> result(k_indices, k_sq_dists, k);
knn_search(query, result, setting);
return result.num_found();
}
/// @brief Find k-nearest neighbors. This method uses fixed and static memory allocation. Might be faster for small k.
/// @param query Query point
/// @param k_indices Indices of neighbors
/// @param k_sq_dists Squared distances to neighbors (sorted in ascending order)
/// @param setting KNN search setting
/// @return Number of found neighbors
template <int N>
size_t knn_search(const Eigen::Vector4d& query, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
KnnResult<N> result(k_indices, k_sq_dists);
knn_search(query, result, setting);
return result.num_found();
}
private:
template <typename Result>
void knn_search(const Eigen::Vector4d& query, Result& result, const KnnSetting& setting) const {
BorderModeH border_h;
BorderModeV border_v;
Projection project;
const Eigen::Vector2d uv = project(query.head<3>());
const int u = uv[0] * index_map.cols();
const int v = uv[1] * index_map.rows();
for (int du = -search_window_h; du <= search_window_h; du++) {
const int u_clamped = border_h(u + du, index_map.cols());
if (u_clamped < 0 || u_clamped >= index_map.cols()) {
continue;
}
for (int dv = -search_window_v; dv <= search_window_v; dv++) {
const int v_clamped = border_v(v + dv, index_map.rows());
if (v_clamped < 0 || v_clamped >= index_map.rows()) {
continue;
}
const auto index = index_map(v_clamped, u_clamped);
if (index == invalid_index) {
continue;
}
const double sq_dist = (traits::point(points, index) - query).squaredNorm();
result.push(index, sq_dist);
if (setting.fulfilled(result)) {
return;
}
}
}
}
public:
static constexpr std::uint32_t invalid_index = std::numeric_limits<std::uint32_t>::max();
const PointCloud& points;
Eigen::Matrix<std::uint32_t, -1, -1> index_map;
int search_window_h;
int search_window_v;
};
/// @brief "Safe" projective search. This class holds the ownership of the target point cloud.
template <typename PointCloud, typename Projection = EquirectangularProjection, typename BorderModeH = BorderRepeat, typename BorderModeV = BorderClamp>
struct ProjectiveSearch {
public:
using Ptr = std::shared_ptr<ProjectiveSearch<PointCloud, Projection>>;
using ConstPtr = std::shared_ptr<const ProjectiveSearch<PointCloud, Projection>>;
explicit ProjectiveSearch(int width, int height, std::shared_ptr<const PointCloud> points) : points(points), search(width, height, *points) {}
/// @brief Find k-nearest neighbors. This method uses dynamic memory allocation.
/// @param query Query point
/// @param k Number of neighbors
/// @param k_indices Indices of neighbors
/// @param k_sq_dists Squared distances to neighbors (sorted in ascending order)
/// @param setting KNN search setting
/// @return Number of found neighbors
size_t nearest_neighbor_search(const Eigen::Vector4d& query, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
return search.nearest_neighbor_search(query, k_indices, k_sq_dists, setting);
}
/// @brief Find k-nearest neighbors. This method uses dynamic memory allocation.
/// @param query Query point
/// @param k Number of neighbors
/// @param k_indices Indices of neighbors
/// @param k_sq_dists Squared distances to neighbors (sorted in ascending order)
/// @param setting KNN search setting
/// @return Number of found neighbors
size_t knn_search(const Eigen::Vector4d& query, size_t k, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
return search.knn_search(query, k, k_indices, k_sq_dists, setting);
}
public:
const std::shared_ptr<const PointCloud> points; ///< Points
const UnsafeProjectiveSearch<PointCloud, Projection, BorderModeH, BorderModeV> search; ///< Search
};
namespace traits {
template <typename PointCloud, typename Projection, typename BorderModeH, typename BorderModeV>
struct Traits<UnsafeProjectiveSearch<PointCloud, Projection, BorderModeH, BorderModeV>> {
static size_t nearest_neighbor_search(
const UnsafeProjectiveSearch<PointCloud, Projection, BorderModeH, BorderModeV>& tree,
const Eigen::Vector4d& point,
size_t* k_indices,
double* k_sq_dists) {
return tree.nearest_neighbor_search(point, k_indices, k_sq_dists);
}
static size_t
knn_search(const UnsafeProjectiveSearch<PointCloud, Projection, BorderModeH, BorderModeV>& tree, const Eigen::Vector4d& point, size_t k, size_t* k_indices, double* k_sq_dists) {
return tree.knn_search(point, k, k_indices, k_sq_dists);
}
};
template <typename PointCloud, typename Projection, typename BorderModeH, typename BorderModeV>
struct Traits<ProjectiveSearch<PointCloud, Projection, BorderModeH, BorderModeV>> {
static size_t
nearest_neighbor_search(const ProjectiveSearch<PointCloud, Projection, BorderModeH, BorderModeV>& tree, const Eigen::Vector4d& point, size_t* k_indices, double* k_sq_dists) {
return tree.nearest_neighbor_search(point, k_indices, k_sq_dists);
}
static size_t
knn_search(const ProjectiveSearch<PointCloud, Projection, BorderModeH, BorderModeV>& tree, const Eigen::Vector4d& point, size_t k, size_t* k_indices, double* k_sq_dists) {
return tree.knn_search(point, k, k_indices, k_sq_dists);
}
};
} // namespace traits
} // namespace small_gicp

View File

@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: Copyright 2024 Kenji Koide
// SPDX-License-Identifier: MIT
#pragma once
#include <small_gicp/points/traits.hpp>
#include <small_gicp/ann/incremental_voxelmap.hpp>
namespace small_gicp {
/**
* @brief A wrapper class to sequentially access points in a voxelmap (e.g., using points in a voxelmap as source point cloud for registration).
* @note If the contents of the voxelmap are changed, the accessor must be recreated.
* @example
small_gicp::IncrementalVoxelMap<small_gicp::FlatContainerCov>::Ptr last_voxelmap = ...;
small_gicp::IncrementalVoxelMap<small_gicp::FlatContainerCov>::Ptr voxelmap = ...;
// Create a sequential accessor
const auto accessor = small_gicp::create_sequential_accessor(*voxelmap);
// Use the voxelmap as a source point cloud
small_gicp::Registration<small_gicp::GICPFactor, small_gicp::ParallelReductionOMP> registration;
auto result = registration.align(*last_voxelmap, accessor, *last_voxelmap, Eigen::Isometry3d::Identity());
*/
template <typename VoxelMap>
class SequentialVoxelMapAccessor {
public:
/// @brief Constructor.
/// @param voxelmap Voxelmap
SequentialVoxelMapAccessor(const VoxelMap& voxelmap) : voxelmap(voxelmap), indices(traits::point_indices(voxelmap)) {}
/// @brief Number of points in the voxelmap.
size_t size() const { return indices.size(); }
public:
const VoxelMap& voxelmap; ///< Voxelmap
const std::vector<size_t> indices; ///< Indices of points in the voxelmap
};
/// @brief Create a sequential voxelmap accessor.
template <typename VoxelMap>
SequentialVoxelMapAccessor<VoxelMap> create_sequential_accessor(const VoxelMap& voxelmap) {
return SequentialVoxelMapAccessor<VoxelMap>(voxelmap);
}
template <typename VoxelMap>
struct traits::Traits<SequentialVoxelMapAccessor<VoxelMap>> {
static size_t size(const SequentialVoxelMapAccessor<VoxelMap>& accessor) { return accessor.size(); }
static bool has_points(const SequentialVoxelMapAccessor<VoxelMap>& accessor) { return traits::has_points(accessor.voxelmap); }
static bool has_normals(const SequentialVoxelMapAccessor<VoxelMap>& accessor) { return traits::has_normals(accessor.voxelmap); }
static bool has_covs(const SequentialVoxelMapAccessor<VoxelMap>& accessor) { return traits::has_covs(accessor.voxelmap); }
static Eigen::Vector4d point(const SequentialVoxelMapAccessor<VoxelMap>& accessor, size_t i) { return traits::point(accessor.voxelmap, accessor.indices[i]); }
static Eigen::Vector4d normal(const SequentialVoxelMapAccessor<VoxelMap>& accessor, size_t i) { return traits::normal(accessor.voxelmap, accessor.indices[i]); }
static Eigen::Matrix4d cov(const SequentialVoxelMapAccessor<VoxelMap>& accessor, size_t i) { return traits::cov(accessor.voxelmap, accessor.indices[i]); }
};
} // namespace small_gicp

View File

@ -99,8 +99,8 @@ protected:
std::string registration_type_; ///< Registration type ("GICP" or "VGICP"). std::string registration_type_; ///< Registration type ("GICP" or "VGICP").
bool verbose_; ///< Verbosity flag. bool verbose_; ///< Verbosity flag.
std::shared_ptr<KdTree<pcl::PointCloud<PointSource>>> target_tree_; ///< KdTree for target point cloud. std::shared_ptr<small_gicp::KdTree<pcl::PointCloud<PointSource>>> target_tree_; ///< KdTree for target point cloud.
std::shared_ptr<KdTree<pcl::PointCloud<PointSource>>> source_tree_; ///< KdTree for source point cloud. std::shared_ptr<small_gicp::KdTree<pcl::PointCloud<PointSource>>> source_tree_; ///< KdTree for source point cloud.
std::shared_ptr<GaussianVoxelMap> target_voxelmap_; ///< VoxelMap for target point cloud. std::shared_ptr<GaussianVoxelMap> target_voxelmap_; ///< VoxelMap for target point cloud.
std::shared_ptr<GaussianVoxelMap> source_voxelmap_; ///< VoxelMap for source point cloud. std::shared_ptr<GaussianVoxelMap> source_voxelmap_; ///< VoxelMap for source point cloud.

View File

@ -44,7 +44,7 @@ void RegistrationPCL<PointSource, PointTarget>::setInputSource(const PointCloudS
} }
pcl::Registration<PointSource, PointTarget, Scalar>::setInputSource(cloud); pcl::Registration<PointSource, PointTarget, Scalar>::setInputSource(cloud);
source_tree_ = std::make_shared<KdTree<pcl::PointCloud<PointSource>>>(input_, KdTreeBuilderOMP(num_threads_)); source_tree_ = std::make_shared<small_gicp::KdTree<pcl::PointCloud<PointSource>>>(input_, KdTreeBuilderOMP(num_threads_));
source_covs_.clear(); source_covs_.clear();
source_voxelmap_.reset(); source_voxelmap_.reset();
} }
@ -56,7 +56,7 @@ void RegistrationPCL<PointSource, PointTarget>::setInputTarget(const PointCloudT
} }
pcl::Registration<PointSource, PointTarget, Scalar>::setInputTarget(cloud); pcl::Registration<PointSource, PointTarget, Scalar>::setInputTarget(cloud);
target_tree_ = std::make_shared<KdTree<pcl::PointCloud<PointTarget>>>(target_, KdTreeBuilderOMP(num_threads_)); target_tree_ = std::make_shared<small_gicp::KdTree<pcl::PointCloud<PointTarget>>>(target_, KdTreeBuilderOMP(num_threads_));
target_covs_.clear(); target_covs_.clear();
target_voxelmap_.reset(); target_voxelmap_.reset();
} }
@ -214,7 +214,7 @@ void RegistrationPCL<PointSource, PointTarget>::computeTransformation(PointCloud
estimate_covariances_omp(target_proxy, *target_tree_, k_correspondences_, num_threads_); estimate_covariances_omp(target_proxy, *target_tree_, k_correspondences_, num_threads_);
} }
Registration<GICPFactor, ParallelReductionOMP> registration; small_gicp::Registration<GICPFactor, ParallelReductionOMP> registration;
registration.criteria.rotation_eps = rotation_epsilon_; registration.criteria.rotation_eps = rotation_epsilon_;
registration.criteria.translation_eps = transformation_epsilon_; registration.criteria.translation_eps = transformation_epsilon_;
registration.reduction.num_threads = num_threads_; registration.reduction.num_threads = num_threads_;

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#pragma once #pragma once
#include <vector>
#include <Eigen/Core> #include <Eigen/Core>
#include <small_gicp/points/traits.hpp> #include <small_gicp/points/traits.hpp>
@ -15,5 +16,30 @@ struct Traits<Eigen::MatrixXd> {
static Eigen::Vector4d point(const Eigen::MatrixXd& points, size_t i) { return Eigen::Vector4d(points(i, 0), points(i, 1), points(i, 2), 1.0); } static Eigen::Vector4d point(const Eigen::MatrixXd& points, size_t i) { return Eigen::Vector4d(points(i, 0), points(i, 1), points(i, 2), 1.0); }
}; };
template <typename Scalar, int D>
struct Traits<std::vector<Eigen::Matrix<Scalar, D, 1>>> {
static_assert(D == 3 || D == 4, "Only 3D and 4D (homogeneous) points are supported");
static_assert(std::is_floating_point<Scalar>::value, "Only floating point types are supported");
using Point = Eigen::Matrix<Scalar, D, 1>;
static size_t size(const std::vector<Point>& points) { return points.size(); }
static bool has_points(const std::vector<Point>& points) { return points.size(); }
static Eigen::Vector4d point(const std::vector<Point>& points, size_t i) {
if constexpr (std::is_same_v<Scalar, double>) {
if constexpr (D == 3) {
return points[i].homogeneous();
} else {
return points[i];
}
} else {
if constexpr (D == 3) {
return points[i].homogeneous().template cast<double>();
} else {
return points[i].template cast<double>();
}
}
}
};
} // namespace traits } // namespace traits
} // namespace small_gicp } // namespace small_gicp

View File

@ -108,7 +108,7 @@ struct LevenbergMarquardtOptimizer {
// Solve with damping // Solve with damping
const Eigen::Matrix<double, 6, 1> delta = (H + lambda * Eigen::Matrix<double, 6, 6>::Identity()).ldlt().solve(-b); const Eigen::Matrix<double, 6, 1> delta = (H + lambda * Eigen::Matrix<double, 6, 6>::Identity()).ldlt().solve(-b);
// Validte new solution // Validate new solution
const Eigen::Isometry3d new_T = result.T_target_source * se3_exp(delta); const Eigen::Isometry3d new_T = result.T_target_source * se3_exp(delta);
double new_e = reduction.error(target, source, new_T, factors); double new_e = reduction.error(target, source, new_T, factors);
general_factor.update_error(target, source, new_T, &e); general_factor.update_error(target, source, new_T, &e);
@ -124,6 +124,7 @@ struct LevenbergMarquardtOptimizer {
result.T_target_source = new_T; result.T_target_source = new_T;
lambda /= lambda_factor; lambda /= lambda_factor;
success = true; success = true;
e = new_e;
break; break;
} else { } else {
@ -149,7 +150,7 @@ struct LevenbergMarquardtOptimizer {
bool verbose; ///< If true, print debug messages bool verbose; ///< If true, print debug messages
int max_iterations; ///< Max number of optimization iterations int max_iterations; ///< Max number of optimization iterations
int max_inner_iterations; ///< Max number of inner iterations (lambda-trial) int max_inner_iterations; ///< Max number of inner iterations (lambda-trial)
double init_lambda; ///< Initial lambda (damping factor) double init_lambda; ///< Initial lambda (damping factor)
double lambda_factor; ///< Lambda increase factor double lambda_factor; ///< Lambda increase factor
}; };

View File

@ -7,7 +7,11 @@
namespace small_gicp { namespace small_gicp {
#ifndef _OPENMP #ifndef _OPENMP
#ifdef _WIN32
#pragma message(__FILE__, " OpenMP is not available.Parallel reduction will be disabled.")
#else
#warning "OpenMP is not available. Parallel reduction will be disabled." #warning "OpenMP is not available. Parallel reduction will be disabled."
#endif
inline int omp_get_thread_num() { inline int omp_get_thread_num() {
return 0; return 0;
} }

View File

@ -33,6 +33,8 @@ void define_align(py::module& m) {
double max_correspondence_distance, double max_correspondence_distance,
int num_threads, int num_threads,
int max_iterations, int max_iterations,
double rotation_epsilon,
double translation_epsilon,
bool verbose) { bool verbose) {
if (target_points.cols() != 3 && target_points.cols() != 4) { if (target_points.cols() != 3 && target_points.cols() != 4) {
std::cerr << "target_points must be Nx3 or Nx4" << std::endl; std::cerr << "target_points must be Nx3 or Nx4" << std::endl;
@ -62,6 +64,8 @@ void define_align(py::module& m) {
setting.max_correspondence_distance = max_correspondence_distance; setting.max_correspondence_distance = max_correspondence_distance;
setting.num_threads = num_threads; setting.num_threads = num_threads;
setting.max_iterations = max_iterations; setting.max_iterations = max_iterations;
setting.rotation_eps = rotation_epsilon;
setting.translation_eps = translation_epsilon;
setting.verbose = verbose; setting.verbose = verbose;
std::vector<Eigen::Vector4d> target(target_points.rows()); std::vector<Eigen::Vector4d> target(target_points.rows());
@ -97,6 +101,8 @@ void define_align(py::module& m) {
py::arg("max_correspondence_distance") = 1.0, py::arg("max_correspondence_distance") = 1.0,
py::arg("num_threads") = 1, py::arg("num_threads") = 1,
py::arg("max_iterations") = 20, py::arg("max_iterations") = 20,
py::arg("rotation_epsilon") = 0.1 * M_PI / 180.0,
py::arg("translation_epsilon") = 1e-3,
py::arg("verbose") = false, py::arg("verbose") = false,
R"pbdoc( R"pbdoc(
Align two point clouds using various ICP-like algorithms. Align two point clouds using various ICP-like algorithms.
@ -125,6 +131,10 @@ void define_align(py::module& m) {
Number of threads to use for parallel processing. Number of threads to use for parallel processing.
max_iterations : int = 20 max_iterations : int = 20
Maximum number of iterations for the optimization algorithm. Maximum number of iterations for the optimization algorithm.
rotation_epsilon: double = 0.1 * M_PI / 180.0
Convergence criteria for rotation change
translation_epsilon: double = 1e-3
Convergence criteria for transformation change
verbose : bool = False verbose : bool = False
If True, print debug information during the optimization process. If True, print debug information during the optimization process.
@ -146,6 +156,8 @@ void define_align(py::module& m) {
double max_correspondence_distance, double max_correspondence_distance,
int num_threads, int num_threads,
int max_iterations, int max_iterations,
double rotation_epsilon,
double translation_epsilon,
bool verbose) { bool verbose) {
RegistrationSetting setting; RegistrationSetting setting;
if (registration_type == "ICP") { if (registration_type == "ICP") {
@ -161,6 +173,8 @@ void define_align(py::module& m) {
setting.max_correspondence_distance = max_correspondence_distance; setting.max_correspondence_distance = max_correspondence_distance;
setting.num_threads = num_threads; setting.num_threads = num_threads;
setting.max_iterations = max_iterations; setting.max_iterations = max_iterations;
setting.rotation_eps = rotation_epsilon;
setting.translation_eps = translation_epsilon;
setting.verbose = verbose; setting.verbose = verbose;
if (target_tree == nullptr) { if (target_tree == nullptr) {
@ -176,6 +190,8 @@ void define_align(py::module& m) {
py::arg("max_correspondence_distance") = 1.0, py::arg("max_correspondence_distance") = 1.0,
py::arg("num_threads") = 1, py::arg("num_threads") = 1,
py::arg("max_iterations") = 20, py::arg("max_iterations") = 20,
py::arg("rotation_epsilon") = 0.1 * M_PI / 180.0,
py::arg("translation_epsilon") = 1e-3,
py::arg("verbose") = false, py::arg("verbose") = false,
R"pbdoc( R"pbdoc(
Align two point clouds using specified ICP-like algorithms, utilizing point cloud and KD-tree inputs. Align two point clouds using specified ICP-like algorithms, utilizing point cloud and KD-tree inputs.
@ -200,6 +216,10 @@ void define_align(py::module& m) {
Number of threads to use for computation. Number of threads to use for computation.
max_iterations : int = 20 max_iterations : int = 20
Maximum number of iterations for the optimization algorithm. Maximum number of iterations for the optimization algorithm.
rotation_epsilon: double = 0.1 * M_PI / 180.0
Convergence criteria for rotation change
translation_epsilon: double = 1e-3
Convergence criteria for transformation change
verbose : bool = False verbose : bool = False
If True, print debug information during the optimization process. If True, print debug information during the optimization process.
@ -219,11 +239,15 @@ void define_align(py::module& m) {
double max_correspondence_distance, double max_correspondence_distance,
int num_threads, int num_threads,
int max_iterations, int max_iterations,
double rotation_epsilon,
double translation_epsilon,
bool verbose) { bool verbose) {
Registration<GICPFactor, ParallelReductionOMP> registration; Registration<GICPFactor, ParallelReductionOMP> registration;
registration.rejector.max_dist_sq = max_correspondence_distance * max_correspondence_distance; registration.rejector.max_dist_sq = max_correspondence_distance * max_correspondence_distance;
registration.reduction.num_threads = num_threads; registration.reduction.num_threads = num_threads;
registration.optimizer.max_iterations = max_iterations; registration.optimizer.max_iterations = max_iterations;
registration.criteria.rotation_eps = rotation_epsilon;
registration.criteria.translation_eps = translation_epsilon;
registration.optimizer.verbose = verbose; registration.optimizer.verbose = verbose;
return registration.align(target_voxelmap, source, target_voxelmap, Eigen::Isometry3d(init_T_target_source)); return registration.align(target_voxelmap, source, target_voxelmap, Eigen::Isometry3d(init_T_target_source));
@ -234,6 +258,8 @@ void define_align(py::module& m) {
py::arg("max_correspondence_distance") = 1.0, py::arg("max_correspondence_distance") = 1.0,
py::arg("num_threads") = 1, py::arg("num_threads") = 1,
py::arg("max_iterations") = 20, py::arg("max_iterations") = 20,
py::arg("rotation_epsilon") = 0.1 * M_PI / 180.0,
py::arg("translation_epsilon") = 1e-3,
py::arg("verbose") = false, py::arg("verbose") = false,
R"pbdoc( R"pbdoc(
Align two point clouds using voxel-based GICP algorithm, utilizing a Gaussian Voxel Map. Align two point clouds using voxel-based GICP algorithm, utilizing a Gaussian Voxel Map.
@ -254,6 +280,10 @@ void define_align(py::module& m) {
Number of threads to use for computation. Number of threads to use for computation.
max_iterations : int = 20 max_iterations : int = 20
Maximum number of iterations for the optimization algorithm. Maximum number of iterations for the optimization algorithm.
rotation_epsilon: double = 0.1 * M_PI / 180.0
Convergence criteria for rotation change
translation_epsilon: double = 1e-3
Convergence criteria for transformation change
verbose : bool = False verbose : bool = False
If True, print debug information during the optimization process. If True, print debug information during the optimization process.

View File

@ -129,7 +129,7 @@ void define_kdtree(py::module& m) {
std::vector<size_t> k_indices(pts.rows(), -1); std::vector<size_t> k_indices(pts.rows(), -1);
std::vector<double> k_sq_dists(pts.rows(), std::numeric_limits<double>::max()); std::vector<double> k_sq_dists(pts.rows(), std::numeric_limits<double>::max());
#pragma omp parallel for num_threads(num_threads) #pragma omp parallel for num_threads(num_threads) schedule(guided, 4)
for (int i = 0; i < pts.rows(); ++i) { for (int i = 0; i < pts.rows(); ++i) {
const size_t found = traits::nearest_neighbor_search(kdtree, Eigen::Vector4d(pts(i, 0), pts(i, 1), pts(i, 2), 1.0), &k_indices[i], &k_sq_dists[i]); const size_t found = traits::nearest_neighbor_search(kdtree, Eigen::Vector4d(pts(i, 0), pts(i, 1), pts(i, 2), 1.0), &k_indices[i], &k_sq_dists[i]);
if (!found) { if (!found) {
@ -154,9 +154,9 @@ void define_kdtree(py::module& m) {
Returns Returns
------- -------
k_indices : numpy.ndarray, shape (n,) k_indices : numpy.ndarray, shape (n, k)
The indices of the nearest neighbors for each input point. If a neighbor was not found, the index is -1. The indices of the nearest neighbors for each input point. If a neighbor was not found, the index is -1.
k_sq_dists : numpy.ndarray, shape (n,) k_sq_dists : numpy.ndarray, shape (n, k)
The squared distances to the nearest neighbors for each input point. The squared distances to the nearest neighbors for each input point.
)""") )""")
@ -167,16 +167,21 @@ void define_kdtree(py::module& m) {
throw std::invalid_argument("pts must have shape (n, 3) or (n, 4)"); throw std::invalid_argument("pts must have shape (n, 3) or (n, 4)");
} }
std::vector<std::vector<size_t>> k_indices(pts.rows(), std::vector<size_t>(k, -1)); Eigen::Matrix<size_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> k_indices(pts.rows(), k);
std::vector<std::vector<double>> k_sq_dists(pts.rows(), std::vector<double>(k, std::numeric_limits<double>::max())); Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> k_sq_dists(pts.rows(), k);
k_indices.setConstant(-1);
k_sq_dists.setConstant(std::numeric_limits<double>::max());
#pragma omp parallel for num_threads(num_threads) #pragma omp parallel for num_threads(num_threads) schedule(guided, 4)
for (int i = 0; i < pts.rows(); ++i) { for (int i = 0; i < pts.rows(); ++i) {
const size_t found = traits::knn_search(kdtree, Eigen::Vector4d(pts(i, 0), pts(i, 1), pts(i, 2), 1.0), k, k_indices[i].data(), k_sq_dists[i].data()); size_t* k_indices_begin = k_indices.data() + i * k;
double* k_sq_dists_begin = k_sq_dists.data() + i * k;
const size_t found = traits::knn_search(kdtree, Eigen::Vector4d(pts(i, 0), pts(i, 1), pts(i, 2), 1.0), k, k_indices_begin, k_sq_dists_begin);
if (found < k) { if (found < k) {
for (size_t j = found; j < k; ++j) { for (size_t j = found; j < k; ++j) {
k_indices[i][j] = -1; k_indices_begin[j] = -1;
k_sq_dists[i][j] = std::numeric_limits<double>::max(); k_sq_dists_begin[j] = std::numeric_limits<double>::max();
} }
} }
} }