├── .gitignore
├── CMakeLists.txt
├── README.md
├── Scancontext
├── KDTreeVectorOfVectorsAdaptor.h
├── Scancontext.cpp
├── Scancontext.h
├── nanoflann.hpp
└── tictoc.h
├── config
└── sc_config.yaml
├── img
├── loop.png
└── origin.png
├── launch
└── sc_optimization.launch
├── package.xml
└── src
└── SC_Optimization_node.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.1)
2 |
3 | project(sc_aloam)
4 |
5 | set(CMAKE_CXX_STANDARD 14)# for the problem of error: ‘make_unique’ is not a member of ‘std’
6 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
7 | set(CMAKE_CXX_FLAGS "-std=c++11")
8 | set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g")
9 |
10 | find_package(Eigen3 REQUIRED)
11 | find_package(GTSAM REQUIRED)
12 | find_package(PCL REQUIRED)
13 | include_directories(
14 | ${EIGEN3_INCLUDE_DIR}
15 | ${PCL_INCLUDE_DIRS}
16 | ${GTSAM_INCLUDE_DIRS}
17 | ${PROJECT_SOURCE_DIR}
18 | ${PROJECT_SOURCE_DIR}/include
19 | ${PROJECT_SOURCE_DIR}/Scancontext
20 | )
21 |
22 | find_package(
23 | catkin REQUIRED COMPONENTS
24 | roscpp
25 | tf
26 | sensor_msgs
27 | pcl_conversions
28 | nav_msgs
29 | geometry_msgs
30 | cv_bridge
31 | )
32 | include_directories(${catkin_INCLUDE_DIRS})
33 |
34 | catkin_package()
35 |
36 | add_executable(loopclosure_aloam src/SC_Optimization_node.cpp
37 | Scancontext/Scancontext.cpp)
38 | target_link_libraries(loopclosure_aloam ${catkin_LIBRARIES} ${PCL_LIBRARIES} gtsam)
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Loopclosure for A-LOAM
2 |
3 | This is a basic implementation of loopclosure detection for A-LOAM without optimization.
4 |
5 | ## 1. Dependency
6 | - GTSAM
7 | - ROS
8 | - PCL
9 | - [Scancontext](https://github.com/irapkaist/scancontext)
10 |
11 | ## 2. Results
12 |
13 | - Before performing loop closure
14 |
15 |
16 | - After performing loop closure
17 |
18 |
19 | ## 3. Acknowledgements
20 | Thanks for [A-LOAM](https://github.com/HKUST-Aerial-Robotics/A-LOAM) and LOAM(J. Zhang and S. Singh. LOAM: Lidar Odometry and Mapping in Real-time) and [Scancontext](https://github.com/irapkaist/scancontext).
21 |
--------------------------------------------------------------------------------
/Scancontext/KDTreeVectorOfVectorsAdaptor.h:
--------------------------------------------------------------------------------
1 | /***********************************************************************
2 | * Software License Agreement (BSD License)
3 | *
4 | * Copyright 2011-16 Jose Luis Blanco (joseluisblancoc@gmail.com).
5 | * All rights reserved.
6 | *
7 | * Redistribution and use in source and binary forms, with or without
8 | * modification, are permitted provided that the following conditions
9 | * are met:
10 | *
11 | * 1. Redistributions of source code must retain the above copyright
12 | * notice, this list of conditions and the following disclaimer.
13 | * 2. Redistributions in binary form must reproduce the above copyright
14 | * notice, this list of conditions and the following disclaimer in the
15 | * documentation and/or other materials provided with the distribution.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | *************************************************************************/
28 |
29 | #pragma once
30 |
31 | #include
32 |
33 | #include
34 |
35 | // ===== This example shows how to use nanoflann with these types of containers: =======
36 | //typedef std::vector > my_vector_of_vectors_t;
37 | //typedef std::vector my_vector_of_vectors_t; // This requires #include
38 | // =====================================================================================
39 |
40 |
41 | /** A simple vector-of-vectors adaptor for nanoflann, without duplicating the storage.
42 | * The i'th vector represents a point in the state space.
43 | *
44 | * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality for the points in the data set, allowing more compiler optimizations.
45 | * \tparam num_t The type of the point coordinates (typically, double or float).
46 | * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc.
47 | * \tparam IndexType The type for indices in the KD-tree index (typically, size_t of int)
48 | */
49 | template
50 | struct KDTreeVectorOfVectorsAdaptor
51 | {
52 | typedef KDTreeVectorOfVectorsAdaptor self_t;
53 | typedef typename Distance::template traits::distance_t metric_t;
54 | typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,DIM,IndexType> index_t;
55 |
56 | index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index.
57 |
58 | /// Constructor: takes a const ref to the vector of vectors object with the data points
59 | KDTreeVectorOfVectorsAdaptor(const size_t /* dimensionality */, const VectorOfVectorsType &mat, const int leaf_max_size = 10) : m_data(mat)
60 | {
61 | assert(mat.size() != 0 && mat[0].size() != 0);
62 | const size_t dims = mat[0].size();
63 | if (DIM>0 && static_cast(dims) != DIM)
64 | throw std::runtime_error("Data set dimensionality does not match the 'DIM' template argument");
65 | index = new index_t( static_cast(dims), *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) );
66 | index->buildIndex();
67 | }
68 |
69 | ~KDTreeVectorOfVectorsAdaptor() {
70 | delete index;
71 | }
72 |
73 | const VectorOfVectorsType &m_data;
74 |
75 | /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]).
76 | * Note that this is a short-cut method for index->findNeighbors().
77 | * The user can also call index->... methods as desired.
78 | * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface.
79 | */
80 | inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED = 10) const
81 | {
82 | nanoflann::KNNResultSet resultSet(num_closest);
83 | resultSet.init(out_indices, out_distances_sq);
84 | index->findNeighbors(resultSet, query_point, nanoflann::SearchParams());
85 | }
86 |
87 | /** @name Interface expected by KDTreeSingleIndexAdaptor
88 | * @{ */
89 |
90 | const self_t & derived() const {
91 | return *this;
92 | }
93 | self_t & derived() {
94 | return *this;
95 | }
96 |
97 | // Must return the number of data points
98 | inline size_t kdtree_get_point_count() const {
99 | return m_data.size();
100 | }
101 |
102 | // Returns the dim'th component of the idx'th point in the class:
103 | inline num_t kdtree_get_pt(const size_t idx, const size_t dim) const {
104 | return m_data[idx][dim];
105 | }
106 |
107 | // Optional bounding-box computation: return false to default to a standard bbox computation loop.
108 | // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again.
109 | // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds)
110 | template
111 | bool kdtree_get_bbox(BBOX & /*bb*/) const {
112 | return false;
113 | }
114 |
115 | /** @} */
116 |
117 | }; // end of KDTreeVectorOfVectorsAdaptor
118 |
--------------------------------------------------------------------------------
/Scancontext/Scancontext.cpp:
--------------------------------------------------------------------------------
1 | #include "Scancontext.h"
2 |
3 | // namespace SC2
4 | // {
5 |
6 | void coreImportTest (void)
7 | {
8 | cout << "scancontext lib is successfully imported." << endl;
9 | } // coreImportTest
10 |
11 |
12 | float rad2deg(float radians)
13 | {
14 | return radians * 180.0 / M_PI;
15 | }
16 |
17 | float deg2rad(float degrees)
18 | {
19 | return degrees * M_PI / 180.0;
20 | }
21 |
22 |
23 | float xy2theta( const float & _x, const float & _y )
24 | {
25 | if ( _x >= 0 & _y >= 0)
26 | return (180/M_PI) * atan(_y / _x);
27 |
28 | if ( _x < 0 & _y >= 0)
29 | return 180 - ( (180/M_PI) * atan(_y / (-_x)) );
30 |
31 | if ( _x < 0 & _y < 0)
32 | return 180 + ( (180/M_PI) * atan(_y / _x) );
33 |
34 | if ( _x >= 0 & _y < 0)
35 | return 360 - ( (180/M_PI) * atan((-_y) / _x) );
36 | } // xy2theta
37 |
38 |
39 | MatrixXd circshift( MatrixXd &_mat, int _num_shift )
40 | {
41 | // shift columns to right direction
42 | assert(_num_shift >= 0);
43 |
44 | if( _num_shift == 0 )
45 | {
46 | MatrixXd shifted_mat( _mat );
47 | return shifted_mat; // Early return
48 | }
49 |
50 | MatrixXd shifted_mat = MatrixXd::Zero( _mat.rows(), _mat.cols() );
51 | for ( int col_idx = 0; col_idx < _mat.cols(); col_idx++ )
52 | {
53 | int new_location = (col_idx + _num_shift) % _mat.cols();
54 | shifted_mat.col(new_location) = _mat.col(col_idx);
55 | }
56 |
57 | return shifted_mat;
58 |
59 | } // circshift
60 |
61 |
62 | std::vector eig2stdvec( MatrixXd _eigmat )
63 | {
64 | std::vector vec( _eigmat.data(), _eigmat.data() + _eigmat.size() );
65 | return vec;
66 | } // eig2stdvec
67 |
68 |
69 | double SCManager::distDirectSC ( MatrixXd &_sc1, MatrixXd &_sc2 )
70 | {
71 | int num_eff_cols = 0; // i.e., to exclude all-nonzero sector
72 | double sum_sector_similarity = 0;
73 | for ( int col_idx = 0; col_idx < _sc1.cols(); col_idx++ )
74 | {
75 | VectorXd col_sc1 = _sc1.col(col_idx);
76 | VectorXd col_sc2 = _sc2.col(col_idx);
77 |
78 | if( col_sc1.norm() == 0 | col_sc2.norm() == 0 )
79 | continue; // don't count this sector pair.
80 |
81 | double sector_similarity = col_sc1.dot(col_sc2) / (col_sc1.norm() * col_sc2.norm());
82 |
83 | sum_sector_similarity = sum_sector_similarity + sector_similarity;
84 | num_eff_cols = num_eff_cols + 1;
85 | }
86 |
87 | double sc_sim = sum_sector_similarity / num_eff_cols;
88 | return 1.0 - sc_sim;
89 |
90 | } // distDirectSC
91 |
92 |
93 | int SCManager::fastAlignUsingVkey( MatrixXd & _vkey1, MatrixXd & _vkey2)
94 | {
95 | int argmin_vkey_shift = 0;
96 | double min_veky_diff_norm = 10000000;
97 | for ( int shift_idx = 0; shift_idx < _vkey1.cols(); shift_idx++ )
98 | {
99 | MatrixXd vkey2_shifted = circshift(_vkey2, shift_idx);
100 |
101 | MatrixXd vkey_diff = _vkey1 - vkey2_shifted;
102 |
103 | double cur_diff_norm = vkey_diff.norm();
104 | if( cur_diff_norm < min_veky_diff_norm )
105 | {
106 | argmin_vkey_shift = shift_idx;
107 | min_veky_diff_norm = cur_diff_norm;
108 | }
109 | }
110 |
111 | return argmin_vkey_shift;
112 |
113 | } // fastAlignUsingVkey
114 |
115 |
116 | std::pair SCManager::distanceBtnScanContext( MatrixXd &_sc1, MatrixXd &_sc2 )
117 | {
118 | // 1. fast align using variant key (not in original IROS18)
119 | MatrixXd vkey_sc1 = makeSectorkeyFromScancontext( _sc1 );
120 | MatrixXd vkey_sc2 = makeSectorkeyFromScancontext( _sc2 );
121 | int argmin_vkey_shift = fastAlignUsingVkey( vkey_sc1, vkey_sc2 );
122 |
123 | const int SEARCH_RADIUS = round( 0.5 * SEARCH_RATIO * _sc1.cols() ); // a half of search range
124 | std::vector shift_idx_search_space { argmin_vkey_shift };
125 | for ( int ii = 1; ii < SEARCH_RADIUS + 1; ii++ )
126 | {
127 | shift_idx_search_space.push_back( (argmin_vkey_shift + ii + _sc1.cols()) % _sc1.cols() );
128 | shift_idx_search_space.push_back( (argmin_vkey_shift - ii + _sc1.cols()) % _sc1.cols() );
129 | }
130 | std::sort(shift_idx_search_space.begin(), shift_idx_search_space.end());
131 |
132 | // 2. fast columnwise diff
133 | int argmin_shift = 0;
134 | double min_sc_dist = 10000000;
135 | for ( int num_shift: shift_idx_search_space )
136 | {
137 | MatrixXd sc2_shifted = circshift(_sc2, num_shift);
138 | double cur_sc_dist = distDirectSC( _sc1, sc2_shifted );
139 | if( cur_sc_dist < min_sc_dist )
140 | {
141 | argmin_shift = num_shift;
142 | min_sc_dist = cur_sc_dist;
143 | }
144 | }
145 |
146 | return make_pair(min_sc_dist, argmin_shift);
147 |
148 | } // distanceBtnScanContext
149 |
150 |
151 | MatrixXd SCManager::makeScancontext( pcl::PointCloud & _scan_down )
152 | {
153 | TicToc t_making_desc;
154 |
155 | int num_pts_scan_down = _scan_down.points.size();
156 |
157 | // main
158 | const int NO_POINT = -1000;
159 | MatrixXd desc = NO_POINT * MatrixXd::Ones(PC_NUM_RING, PC_NUM_SECTOR);
160 |
161 | SCPointType pt;
162 | float azim_angle, azim_range; // wihtin 2d plane
163 | int ring_idx, sctor_idx;
164 | for (int pt_idx = 0; pt_idx < num_pts_scan_down; pt_idx++)
165 | {
166 | pt.x = _scan_down.points[pt_idx].x;
167 | pt.y = _scan_down.points[pt_idx].y;
168 | pt.z = _scan_down.points[pt_idx].z + LIDAR_HEIGHT; // naive adding is ok (all points should be > 0).
169 |
170 | // xyz to ring, sector
171 | azim_range = sqrt(pt.x * pt.x + pt.y * pt.y);
172 | azim_angle = xy2theta(pt.x, pt.y);
173 |
174 | // if range is out of roi, pass
175 | if( azim_range > PC_MAX_RADIUS )
176 | continue;
177 |
178 | ring_idx = std::max( std::min( PC_NUM_RING, int(ceil( (azim_range / PC_MAX_RADIUS) * PC_NUM_RING )) ), 1 );
179 | sctor_idx = std::max( std::min( PC_NUM_SECTOR, int(ceil( (azim_angle / 360.0) * PC_NUM_SECTOR )) ), 1 );
180 |
181 | // taking maximum z
182 | if ( desc(ring_idx-1, sctor_idx-1) < pt.z ) // -1 means cpp starts from 0
183 | desc(ring_idx-1, sctor_idx-1) = pt.z; // update for taking maximum value at that bin
184 | }
185 |
186 | // reset no points to zero (for cosine dist later)
187 | for ( int row_idx = 0; row_idx < desc.rows(); row_idx++ )
188 | for ( int col_idx = 0; col_idx < desc.cols(); col_idx++ )
189 | if( desc(row_idx, col_idx) == NO_POINT )
190 | desc(row_idx, col_idx) = 0;
191 |
192 | t_making_desc.toc("PolarContext making");
193 |
194 | return desc;
195 | } // SCManager::makeScancontext
196 |
197 |
198 | MatrixXd SCManager::makeRingkeyFromScancontext( Eigen::MatrixXd &_desc )
199 | {
200 | /*
201 | * summary: rowwise mean vector
202 | */
203 | Eigen::MatrixXd invariant_key(_desc.rows(), 1);
204 | for ( int row_idx = 0; row_idx < _desc.rows(); row_idx++ )
205 | {
206 | Eigen::MatrixXd curr_row = _desc.row(row_idx);
207 | invariant_key(row_idx, 0) = curr_row.mean();
208 | }
209 |
210 | return invariant_key;
211 | } // SCManager::makeRingkeyFromScancontext
212 |
213 |
214 | MatrixXd SCManager::makeSectorkeyFromScancontext( Eigen::MatrixXd &_desc )
215 | {
216 | /*
217 | * summary: columnwise mean vector
218 | */
219 | Eigen::MatrixXd variant_key(1, _desc.cols());
220 | for ( int col_idx = 0; col_idx < _desc.cols(); col_idx++ )
221 | {
222 | Eigen::MatrixXd curr_col = _desc.col(col_idx);
223 | variant_key(0, col_idx) = curr_col.mean();
224 | }
225 |
226 | return variant_key;
227 | } // SCManager::makeSectorkeyFromScancontext
228 |
229 |
230 | void SCManager::makeAndSaveScancontextAndKeys( pcl::PointCloud & _scan_down )
231 | {
232 | Eigen::MatrixXd sc = makeScancontext(_scan_down); // v1
233 | Eigen::MatrixXd ringkey = makeRingkeyFromScancontext( sc );
234 | Eigen::MatrixXd sectorkey = makeSectorkeyFromScancontext( sc );
235 | std::vector polarcontext_invkey_vec = eig2stdvec( ringkey );
236 |
237 | polarcontexts_.push_back( sc );
238 | polarcontext_invkeys_.push_back( ringkey );
239 | polarcontext_vkeys_.push_back( sectorkey );
240 | polarcontext_invkeys_mat_.push_back( polarcontext_invkey_vec );
241 |
242 | // cout < SCManager::detectLoopClosureID ( void )
248 | {
249 | int loop_id { -1 }; // init with -1, -1 means no loop (== LeGO-LOAM's variable "closestHistoryFrameID")
250 |
251 | auto curr_key = polarcontext_invkeys_mat_.back(); // current observation (query)
252 | auto curr_desc = polarcontexts_.back(); // current observation (query)
253 |
254 | /*
255 | * step 1: candidates from ringkey tree_
256 | */
257 | if( polarcontext_invkeys_mat_.size() < NUM_EXCLUDE_RECENT + 1)
258 | {
259 | std::pair result {loop_id, 0.0};
260 | return result; // Early return
261 | }
262 |
263 | // tree_ reconstruction (not mandatory to make everytime)
264 | if( tree_making_period_conter % TREE_MAKING_PERIOD_ == 0) // to save computation cost
265 | {
266 | TicToc t_tree_construction;
267 |
268 | polarcontext_invkeys_to_search_.clear();
269 | polarcontext_invkeys_to_search_.assign( polarcontext_invkeys_mat_.begin(), polarcontext_invkeys_mat_.end() - NUM_EXCLUDE_RECENT ) ;
270 |
271 | polarcontext_tree_.reset();
272 | polarcontext_tree_ = std::make_unique(PC_NUM_RING /* dim */, polarcontext_invkeys_to_search_, 10 /* max leaf */ );
273 | // tree_ptr_->index->buildIndex(); // inernally called in the constructor of InvKeyTree (for detail, refer the nanoflann and KDtreeVectorOfVectorsAdaptor)
274 | t_tree_construction.toc("Tree construction");
275 | }
276 | tree_making_period_conter = tree_making_period_conter + 1;
277 |
278 | double min_dist = 10000000; // init with somthing large
279 | int nn_align = 0;
280 | int nn_idx = 0;
281 |
282 | // knn search
283 | std::vector candidate_indexes( NUM_CANDIDATES_FROM_TREE );
284 | std::vector out_dists_sqr( NUM_CANDIDATES_FROM_TREE );
285 |
286 | TicToc t_tree_search;
287 | nanoflann::KNNResultSet knnsearch_result( NUM_CANDIDATES_FROM_TREE );
288 | knnsearch_result.init( &candidate_indexes[0], &out_dists_sqr[0] );
289 | polarcontext_tree_->index->findNeighbors( knnsearch_result, &curr_key[0] /* query */, nanoflann::SearchParams(10) );
290 | t_tree_search.toc("Tree search");
291 |
292 | /*
293 | * step 2: pairwise distance (find optimal columnwise best-fit using cosine distance)
294 | */
295 | TicToc t_calc_dist;
296 | for ( int candidate_iter_idx = 0; candidate_iter_idx < NUM_CANDIDATES_FROM_TREE; candidate_iter_idx++ )
297 | {
298 | MatrixXd polarcontext_candidate = polarcontexts_[ candidate_indexes[candidate_iter_idx] ];
299 | std::pair sc_dist_result = distanceBtnScanContext( curr_desc, polarcontext_candidate );
300 |
301 | double candidate_dist = sc_dist_result.first;
302 | int candidate_align = sc_dist_result.second;
303 |
304 | if( candidate_dist < min_dist )
305 | {
306 | min_dist = candidate_dist;
307 | nn_align = candidate_align;
308 |
309 | nn_idx = candidate_indexes[candidate_iter_idx];
310 | }
311 | }
312 | t_calc_dist.toc("Distance calc");
313 |
314 | /*
315 | * loop threshold check
316 | */
317 | if( min_dist < SC_DIST_THRES )
318 | {
319 | loop_id = nn_idx;
320 |
321 | //std::cout.precision(3);
322 | //cout << "[Loop found] Nearest distance: " << min_dist << " btn " << polarcontexts_.size()-1 << " and " << nn_idx << "." << endl;
323 | //cout << "[Loop found] yaw diff: " << nn_align * PC_UNIT_SECTORANGLE << " deg." << endl;
324 | }
325 | else
326 | {
327 | //std::cout.precision(3);
328 | //cout << "[Not loop] Nearest distance: " << min_dist << " btn " << polarcontexts_.size()-1 << " and " << nn_idx << "." << endl;
329 | //cout << "[Not loop] yaw diff: " << nn_align * PC_UNIT_SECTORANGLE << " deg." << endl;
330 | }
331 |
332 | // To do: return also nn_align (i.e., yaw diff)
333 | float yaw_diff_rad = deg2rad(nn_align * PC_UNIT_SECTORANGLE);
334 | std::pair result {loop_id, yaw_diff_rad};
335 |
336 | return result;
337 |
338 | } // SCManager::detectLoopClosureID
339 |
340 | // } // namespace SC2
--------------------------------------------------------------------------------
/Scancontext/Scancontext.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include "nanoflann.hpp"
26 | #include "KDTreeVectorOfVectorsAdaptor.h"
27 |
28 | #include "tictoc.h"
29 |
30 | using namespace Eigen;
31 | using namespace nanoflann;
32 |
33 | using std::cout;
34 | using std::endl;
35 | using std::make_pair;
36 |
37 | using std::atan2;
38 | using std::cos;
39 | using std::sin;
40 |
41 | using SCPointType = pcl::PointXYZI; // using xyz only. but a user can exchange the original bin encoding function (i.e., max hegiht) to max intensity (for detail, refer 20 ICRA Intensity Scan Context)
42 | using KeyMat = std::vector >;
43 | using InvKeyTree = KDTreeVectorOfVectorsAdaptor< KeyMat, float >;
44 |
45 |
46 | // namespace SC2
47 | // {
48 |
49 | void coreImportTest ( void );
50 |
51 |
52 | // sc param-independent helper functions
53 | float xy2theta( const float & _x, const float & _y );
54 | MatrixXd circshift( MatrixXd &_mat, int _num_shift );
55 | std::vector eig2stdvec( MatrixXd _eigmat );
56 |
57 |
58 | class SCManager
59 | {
60 | public:
61 | SCManager( ) = default; // reserving data space (of std::vector) could be considered. but the descriptor is lightweight so don't care.
62 |
63 | Eigen::MatrixXd makeScancontext( pcl::PointCloud & _scan_down );
64 | Eigen::MatrixXd makeRingkeyFromScancontext( Eigen::MatrixXd &_desc );
65 | Eigen::MatrixXd makeSectorkeyFromScancontext( Eigen::MatrixXd &_desc );
66 |
67 | int fastAlignUsingVkey ( MatrixXd & _vkey1, MatrixXd & _vkey2 );
68 | double distDirectSC ( MatrixXd &_sc1, MatrixXd &_sc2 ); // "d" (eq 5) in the original paper (IROS 18)
69 | std::pair distanceBtnScanContext ( MatrixXd &_sc1, MatrixXd &_sc2 ); // "D" (eq 6) in the original paper (IROS 18)
70 |
71 | // User-side API
72 | void makeAndSaveScancontextAndKeys( pcl::PointCloud & _scan_down );
73 | std::pair detectLoopClosureID( void ); // int: nearest node index, float: relative yaw
74 |
75 | public:
76 | // hyper parameters ()
77 | const double LIDAR_HEIGHT = 2.0; // lidar height : add this for simply directly using lidar scan in the lidar local coord (not robot base coord) / if you use robot-coord-transformed lidar scans, just set this as 0.
78 |
79 | const int PC_NUM_RING = 20; // 20 in the original paper (IROS 18)
80 | const int PC_NUM_SECTOR = 60; // 60 in the original paper (IROS 18)
81 | const double PC_MAX_RADIUS = 80.0; // 80 meter max in the original paper (IROS 18)
82 | const double PC_UNIT_SECTORANGLE = 360.0 / double(PC_NUM_SECTOR);
83 | const double PC_UNIT_RINGGAP = PC_MAX_RADIUS / double(PC_NUM_RING);
84 |
85 | // tree
86 | const int NUM_EXCLUDE_RECENT = 500; // simply just keyframe gap, but node position distance-based exclusion is ok.
87 | const int NUM_CANDIDATES_FROM_TREE = 10; // 10 is enough. (refer the IROS 18 paper)
88 |
89 | // loop thres
90 | const double SEARCH_RATIO = 0.1; // for fast comparison, no Brute-force, but search 10 % is okay. // but may well work for same-direction-revisits, not for reverse-revisits
91 | // const double SC_DIST_THRES = 0.13; // empirically 0.1-0.2 is fine (rare false-alarms) for 20x60 polar context (but for 0.15 <, DCS or ICP fit score check (e.g., in LeGO-LOAM) should be required for robustness)
92 | const double SC_DIST_THRES = 0.11; // 0.4-0.6 is good choice for using with robust kernel (e.g., Cauchy, DCS) + icp fitness threshold
93 |
94 | // config
95 | const int TREE_MAKING_PERIOD_ = 50; // i.e., remaking tree frequency, to avoid non-mandatory every remaking, to save time cost
96 | int tree_making_period_conter = 0;
97 |
98 | // data
99 | std::vector polarcontexts_timestamp_; // optional.
100 | std::vector polarcontexts_;
101 | std::vector polarcontext_invkeys_;
102 | std::vector polarcontext_vkeys_;
103 |
104 | KeyMat polarcontext_invkeys_mat_;
105 | KeyMat polarcontext_invkeys_to_search_;
106 | std::unique_ptr polarcontext_tree_;
107 |
108 | }; // SCManager
109 |
110 | // } // namespace SC2
111 |
--------------------------------------------------------------------------------
/Scancontext/nanoflann.hpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************
2 | * Software License Agreement (BSD License)
3 | *
4 | * Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
5 | * Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
6 | * Copyright 2011-2016 Jose Luis Blanco (joseluisblancoc@gmail.com).
7 | * All rights reserved.
8 | *
9 | * THE BSD LICENSE
10 | *
11 | * Redistribution and use in source and binary forms, with or without
12 | * modification, are permitted provided that the following conditions
13 | * are met:
14 | *
15 | * 1. Redistributions of source code must retain the above copyright
16 | * notice, this list of conditions and the following disclaimer.
17 | * 2. Redistributions in binary form must reproduce the above copyright
18 | * notice, this list of conditions and the following disclaimer in the
19 | * documentation and/or other materials provided with the distribution.
20 | *
21 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 | *************************************************************************/
32 |
33 | /** \mainpage nanoflann C++ API documentation
34 | * nanoflann is a C++ header-only library for building KD-Trees, mostly
35 | * optimized for 2D or 3D point clouds.
36 | *
37 | * nanoflann does not require compiling or installing, just an
38 | * #include in your code.
39 | *
40 | * See:
41 | * - C++ API organized by modules
42 | * - Online README
43 | * - Doxygen
44 | * documentation
45 | */
46 |
47 | #ifndef NANOFLANN_HPP_
48 | #define NANOFLANN_HPP_
49 |
50 | #include
51 | #include
52 | #include
53 | #include // for abs()
54 | #include // for fwrite()
55 | #include // for abs()
56 | #include
57 | #include // std::reference_wrapper
58 | #include
59 | #include
60 |
61 | /** Library version: 0xMmP (M=Major,m=minor,P=patch) */
62 | #define NANOFLANN_VERSION 0x132
63 |
64 | // Avoid conflicting declaration of min/max macros in windows headers
65 | #if !defined(NOMINMAX) && \
66 | (defined(_WIN32) || defined(_WIN32_) || defined(WIN32) || defined(_WIN64))
67 | #define NOMINMAX
68 | #ifdef max
69 | #undef max
70 | #undef min
71 | #endif
72 | #endif
73 |
74 | namespace nanoflann {
75 | /** @addtogroup nanoflann_grp nanoflann C++ library for ANN
76 | * @{ */
77 |
78 | /** the PI constant (required to avoid MSVC missing symbols) */
79 | template T pi_const() {
80 | return static_cast(3.14159265358979323846);
81 | }
82 |
83 | /**
84 | * Traits if object is resizable and assignable (typically has a resize | assign
85 | * method)
86 | */
87 | template struct has_resize : std::false_type {};
88 |
89 | template
90 | struct has_resize().resize(1), 0)>
91 | : std::true_type {};
92 |
93 | template struct has_assign : std::false_type {};
94 |
95 | template
96 | struct has_assign().assign(1, 0), 0)>
97 | : std::true_type {};
98 |
99 | /**
100 | * Free function to resize a resizable object
101 | */
102 | template
103 | inline typename std::enable_if::value, void>::type
104 | resize(Container &c, const size_t nElements) {
105 | c.resize(nElements);
106 | }
107 |
108 | /**
109 | * Free function that has no effects on non resizable containers (e.g.
110 | * std::array) It raises an exception if the expected size does not match
111 | */
112 | template
113 | inline typename std::enable_if::value, void>::type
114 | resize(Container &c, const size_t nElements) {
115 | if (nElements != c.size())
116 | throw std::logic_error("Try to change the size of a std::array.");
117 | }
118 |
119 | /**
120 | * Free function to assign to a container
121 | */
122 | template
123 | inline typename std::enable_if::value, void>::type
124 | assign(Container &c, const size_t nElements, const T &value) {
125 | c.assign(nElements, value);
126 | }
127 |
128 | /**
129 | * Free function to assign to a std::array
130 | */
131 | template
132 | inline typename std::enable_if::value, void>::type
133 | assign(Container &c, const size_t nElements, const T &value) {
134 | for (size_t i = 0; i < nElements; i++)
135 | c[i] = value;
136 | }
137 |
138 | /** @addtogroup result_sets_grp Result set classes
139 | * @{ */
140 | template
142 | class KNNResultSet {
143 | public:
144 | typedef _DistanceType DistanceType;
145 | typedef _IndexType IndexType;
146 | typedef _CountType CountType;
147 |
148 | private:
149 | IndexType *indices;
150 | DistanceType *dists;
151 | CountType capacity;
152 | CountType count;
153 |
154 | public:
155 | inline KNNResultSet(CountType capacity_)
156 | : indices(0), dists(0), capacity(capacity_), count(0) {}
157 |
158 | inline void init(IndexType *indices_, DistanceType *dists_) {
159 | indices = indices_;
160 | dists = dists_;
161 | count = 0;
162 | if (capacity)
163 | dists[capacity - 1] = (std::numeric_limits::max)();
164 | }
165 |
166 | inline CountType size() const { return count; }
167 |
168 | inline bool full() const { return count == capacity; }
169 |
170 | /**
171 | * Called during search to add an element matching the criteria.
172 | * @return true if the search should be continued, false if the results are
173 | * sufficient
174 | */
175 | inline bool addPoint(DistanceType dist, IndexType index) {
176 | CountType i;
177 | for (i = count; i > 0; --i) {
178 | #ifdef NANOFLANN_FIRST_MATCH // If defined and two points have the same
179 | // distance, the one with the lowest-index will be
180 | // returned first.
181 | if ((dists[i - 1] > dist) ||
182 | ((dist == dists[i - 1]) && (indices[i - 1] > index))) {
183 | #else
184 | if (dists[i - 1] > dist) {
185 | #endif
186 | if (i < capacity) {
187 | dists[i] = dists[i - 1];
188 | indices[i] = indices[i - 1];
189 | }
190 | } else
191 | break;
192 | }
193 | if (i < capacity) {
194 | dists[i] = dist;
195 | indices[i] = index;
196 | }
197 | if (count < capacity)
198 | count++;
199 |
200 | // tell caller that the search shall continue
201 | return true;
202 | }
203 |
204 | inline DistanceType worstDist() const { return dists[capacity - 1]; }
205 | };
206 |
207 | /** operator "<" for std::sort() */
208 | struct IndexDist_Sorter {
209 | /** PairType will be typically: std::pair */
210 | template
211 | inline bool operator()(const PairType &p1, const PairType &p2) const {
212 | return p1.second < p2.second;
213 | }
214 | };
215 |
216 | /**
217 | * A result-set class used when performing a radius based search.
218 | */
219 | template
220 | class RadiusResultSet {
221 | public:
222 | typedef _DistanceType DistanceType;
223 | typedef _IndexType IndexType;
224 |
225 | public:
226 | const DistanceType radius;
227 |
228 | std::vector> &m_indices_dists;
229 |
230 | inline RadiusResultSet(
231 | DistanceType radius_,
232 | std::vector> &indices_dists)
233 | : radius(radius_), m_indices_dists(indices_dists) {
234 | init();
235 | }
236 |
237 | inline void init() { clear(); }
238 | inline void clear() { m_indices_dists.clear(); }
239 |
240 | inline size_t size() const { return m_indices_dists.size(); }
241 |
242 | inline bool full() const { return true; }
243 |
244 | /**
245 | * Called during search to add an element matching the criteria.
246 | * @return true if the search should be continued, false if the results are
247 | * sufficient
248 | */
249 | inline bool addPoint(DistanceType dist, IndexType index) {
250 | if (dist < radius)
251 | m_indices_dists.push_back(std::make_pair(index, dist));
252 | return true;
253 | }
254 |
255 | inline DistanceType worstDist() const { return radius; }
256 |
257 | /**
258 | * Find the worst result (furtherest neighbor) without copying or sorting
259 | * Pre-conditions: size() > 0
260 | */
261 | std::pair worst_item() const {
262 | if (m_indices_dists.empty())
263 | throw std::runtime_error("Cannot invoke RadiusResultSet::worst_item() on "
264 | "an empty list of results.");
265 | typedef
266 | typename std::vector>::const_iterator
267 | DistIt;
268 | DistIt it = std::max_element(m_indices_dists.begin(), m_indices_dists.end(),
269 | IndexDist_Sorter());
270 | return *it;
271 | }
272 | };
273 |
274 | /** @} */
275 |
276 | /** @addtogroup loadsave_grp Load/save auxiliary functions
277 | * @{ */
278 | template
279 | void save_value(FILE *stream, const T &value, size_t count = 1) {
280 | fwrite(&value, sizeof(value), count, stream);
281 | }
282 |
283 | template
284 | void save_value(FILE *stream, const std::vector &value) {
285 | size_t size = value.size();
286 | fwrite(&size, sizeof(size_t), 1, stream);
287 | fwrite(&value[0], sizeof(T), size, stream);
288 | }
289 |
290 | template
291 | void load_value(FILE *stream, T &value, size_t count = 1) {
292 | size_t read_cnt = fread(&value, sizeof(value), count, stream);
293 | if (read_cnt != count) {
294 | throw std::runtime_error("Cannot read from file");
295 | }
296 | }
297 |
298 | template void load_value(FILE *stream, std::vector &value) {
299 | size_t size;
300 | size_t read_cnt = fread(&size, sizeof(size_t), 1, stream);
301 | if (read_cnt != 1) {
302 | throw std::runtime_error("Cannot read from file");
303 | }
304 | value.resize(size);
305 | read_cnt = fread(&value[0], sizeof(T), size, stream);
306 | if (read_cnt != size) {
307 | throw std::runtime_error("Cannot read from file");
308 | }
309 | }
310 | /** @} */
311 |
312 | /** @addtogroup metric_grp Metric (distance) classes
313 | * @{ */
314 |
315 | struct Metric {};
316 |
317 | /** Manhattan distance functor (generic version, optimized for
318 | * high-dimensionality data sets). Corresponding distance traits:
319 | * nanoflann::metric_L1 \tparam T Type of the elements (e.g. double, float,
320 | * uint8_t) \tparam _DistanceType Type of distance variables (must be signed)
321 | * (e.g. float, double, int64_t)
322 | */
323 | template
324 | struct L1_Adaptor {
325 | typedef T ElementType;
326 | typedef _DistanceType DistanceType;
327 |
328 | const DataSource &data_source;
329 |
330 | L1_Adaptor(const DataSource &_data_source) : data_source(_data_source) {}
331 |
332 | inline DistanceType evalMetric(const T *a, const size_t b_idx, size_t size,
333 | DistanceType worst_dist = -1) const {
334 | DistanceType result = DistanceType();
335 | const T *last = a + size;
336 | const T *lastgroup = last - 3;
337 | size_t d = 0;
338 |
339 | /* Process 4 items with each loop for efficiency. */
340 | while (a < lastgroup) {
341 | const DistanceType diff0 =
342 | std::abs(a[0] - data_source.kdtree_get_pt(b_idx, d++));
343 | const DistanceType diff1 =
344 | std::abs(a[1] - data_source.kdtree_get_pt(b_idx, d++));
345 | const DistanceType diff2 =
346 | std::abs(a[2] - data_source.kdtree_get_pt(b_idx, d++));
347 | const DistanceType diff3 =
348 | std::abs(a[3] - data_source.kdtree_get_pt(b_idx, d++));
349 | result += diff0 + diff1 + diff2 + diff3;
350 | a += 4;
351 | if ((worst_dist > 0) && (result > worst_dist)) {
352 | return result;
353 | }
354 | }
355 | /* Process last 0-3 components. Not needed for standard vector lengths. */
356 | while (a < last) {
357 | result += std::abs(*a++ - data_source.kdtree_get_pt(b_idx, d++));
358 | }
359 | return result;
360 | }
361 |
362 | template
363 | inline DistanceType accum_dist(const U a, const V b, const size_t) const {
364 | return std::abs(a - b);
365 | }
366 | };
367 |
368 | /** Squared Euclidean distance functor (generic version, optimized for
369 | * high-dimensionality data sets). Corresponding distance traits:
370 | * nanoflann::metric_L2 \tparam T Type of the elements (e.g. double, float,
371 | * uint8_t) \tparam _DistanceType Type of distance variables (must be signed)
372 | * (e.g. float, double, int64_t)
373 | */
374 | template
375 | struct L2_Adaptor {
376 | typedef T ElementType;
377 | typedef _DistanceType DistanceType;
378 |
379 | const DataSource &data_source;
380 |
381 | L2_Adaptor(const DataSource &_data_source) : data_source(_data_source) {}
382 |
383 | inline DistanceType evalMetric(const T *a, const size_t b_idx, size_t size,
384 | DistanceType worst_dist = -1) const {
385 | DistanceType result = DistanceType();
386 | const T *last = a + size;
387 | const T *lastgroup = last - 3;
388 | size_t d = 0;
389 |
390 | /* Process 4 items with each loop for efficiency. */
391 | while (a < lastgroup) {
392 | const DistanceType diff0 = a[0] - data_source.kdtree_get_pt(b_idx, d++);
393 | const DistanceType diff1 = a[1] - data_source.kdtree_get_pt(b_idx, d++);
394 | const DistanceType diff2 = a[2] - data_source.kdtree_get_pt(b_idx, d++);
395 | const DistanceType diff3 = a[3] - data_source.kdtree_get_pt(b_idx, d++);
396 | result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;
397 | a += 4;
398 | if ((worst_dist > 0) && (result > worst_dist)) {
399 | return result;
400 | }
401 | }
402 | /* Process last 0-3 components. Not needed for standard vector lengths. */
403 | while (a < last) {
404 | const DistanceType diff0 = *a++ - data_source.kdtree_get_pt(b_idx, d++);
405 | result += diff0 * diff0;
406 | }
407 | return result;
408 | }
409 |
410 | template
411 | inline DistanceType accum_dist(const U a, const V b, const size_t) const {
412 | return (a - b) * (a - b);
413 | }
414 | };
415 |
416 | /** Squared Euclidean (L2) distance functor (suitable for low-dimensionality
417 | * datasets, like 2D or 3D point clouds) Corresponding distance traits:
418 | * nanoflann::metric_L2_Simple \tparam T Type of the elements (e.g. double,
419 | * float, uint8_t) \tparam _DistanceType Type of distance variables (must be
420 | * signed) (e.g. float, double, int64_t)
421 | */
422 | template
423 | struct L2_Simple_Adaptor {
424 | typedef T ElementType;
425 | typedef _DistanceType DistanceType;
426 |
427 | const DataSource &data_source;
428 |
429 | L2_Simple_Adaptor(const DataSource &_data_source)
430 | : data_source(_data_source) {}
431 |
432 | inline DistanceType evalMetric(const T *a, const size_t b_idx,
433 | size_t size) const {
434 | DistanceType result = DistanceType();
435 | for (size_t i = 0; i < size; ++i) {
436 | const DistanceType diff = a[i] - data_source.kdtree_get_pt(b_idx, i);
437 | result += diff * diff;
438 | }
439 | return result;
440 | }
441 |
442 | template
443 | inline DistanceType accum_dist(const U a, const V b, const size_t) const {
444 | return (a - b) * (a - b);
445 | }
446 | };
447 |
448 | /** SO2 distance functor
449 | * Corresponding distance traits: nanoflann::metric_SO2
450 | * \tparam T Type of the elements (e.g. double, float)
451 | * \tparam _DistanceType Type of distance variables (must be signed) (e.g.
452 | * float, double) orientation is constrained to be in [-pi, pi]
453 | */
454 | template
455 | struct SO2_Adaptor {
456 | typedef T ElementType;
457 | typedef _DistanceType DistanceType;
458 |
459 | const DataSource &data_source;
460 |
461 | SO2_Adaptor(const DataSource &_data_source) : data_source(_data_source) {}
462 |
463 | inline DistanceType evalMetric(const T *a, const size_t b_idx,
464 | size_t size) const {
465 | return accum_dist(a[size - 1], data_source.kdtree_get_pt(b_idx, size - 1),
466 | size - 1);
467 | }
468 |
469 | /** Note: this assumes that input angles are already in the range [-pi,pi] */
470 | template
471 | inline DistanceType accum_dist(const U a, const V b, const size_t) const {
472 | DistanceType result = DistanceType();
473 | DistanceType PI = pi_const();
474 | result = b - a;
475 | if (result > PI)
476 | result -= 2 * PI;
477 | else if (result < -PI)
478 | result += 2 * PI;
479 | return result;
480 | }
481 | };
482 |
483 | /** SO3 distance functor (Uses L2_Simple)
484 | * Corresponding distance traits: nanoflann::metric_SO3
485 | * \tparam T Type of the elements (e.g. double, float)
486 | * \tparam _DistanceType Type of distance variables (must be signed) (e.g.
487 | * float, double)
488 | */
489 | template
490 | struct SO3_Adaptor {
491 | typedef T ElementType;
492 | typedef _DistanceType DistanceType;
493 |
494 | L2_Simple_Adaptor distance_L2_Simple;
495 |
496 | SO3_Adaptor(const DataSource &_data_source)
497 | : distance_L2_Simple(_data_source) {}
498 |
499 | inline DistanceType evalMetric(const T *a, const size_t b_idx,
500 | size_t size) const {
501 | return distance_L2_Simple.evalMetric(a, b_idx, size);
502 | }
503 |
504 | template
505 | inline DistanceType accum_dist(const U a, const V b, const size_t idx) const {
506 | return distance_L2_Simple.accum_dist(a, b, idx);
507 | }
508 | };
509 |
510 | /** Metaprogramming helper traits class for the L1 (Manhattan) metric */
511 | struct metric_L1 : public Metric {
512 | template struct traits {
513 | typedef L1_Adaptor distance_t;
514 | };
515 | };
516 | /** Metaprogramming helper traits class for the L2 (Euclidean) metric */
517 | struct metric_L2 : public Metric {
518 | template struct traits {
519 | typedef L2_Adaptor distance_t;
520 | };
521 | };
522 | /** Metaprogramming helper traits class for the L2_simple (Euclidean) metric */
523 | struct metric_L2_Simple : public Metric {
524 | template struct traits {
525 | typedef L2_Simple_Adaptor distance_t;
526 | };
527 | };
528 | /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */
529 | struct metric_SO2 : public Metric {
530 | template struct traits {
531 | typedef SO2_Adaptor distance_t;
532 | };
533 | };
534 | /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */
535 | struct metric_SO3 : public Metric {
536 | template struct traits {
537 | typedef SO3_Adaptor distance_t;
538 | };
539 | };
540 |
541 | /** @} */
542 |
543 | /** @addtogroup param_grp Parameter structs
544 | * @{ */
545 |
546 | /** Parameters (see README.md) */
547 | struct KDTreeSingleIndexAdaptorParams {
548 | KDTreeSingleIndexAdaptorParams(size_t _leaf_max_size = 10)
549 | : leaf_max_size(_leaf_max_size) {}
550 |
551 | size_t leaf_max_size;
552 | };
553 |
554 | /** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */
555 | struct SearchParams {
556 | /** Note: The first argument (checks_IGNORED_) is ignored, but kept for
557 | * compatibility with the FLANN interface */
558 | SearchParams(int checks_IGNORED_ = 32, float eps_ = 0, bool sorted_ = true)
559 | : checks(checks_IGNORED_), eps(eps_), sorted(sorted_) {}
560 |
561 | int checks; //!< Ignored parameter (Kept for compatibility with the FLANN
562 | //!< interface).
563 | float eps; //!< search for eps-approximate neighbours (default: 0)
564 | bool sorted; //!< only for radius search, require neighbours sorted by
565 | //!< distance (default: true)
566 | };
567 | /** @} */
568 |
569 | /** @addtogroup memalloc_grp Memory allocation
570 | * @{ */
571 |
572 | /**
573 | * Allocates (using C's malloc) a generic type T.
574 | *
575 | * Params:
576 | * count = number of instances to allocate.
577 | * Returns: pointer (of type T*) to memory buffer
578 | */
579 | template inline T *allocate(size_t count = 1) {
580 | T *mem = static_cast(::malloc(sizeof(T) * count));
581 | return mem;
582 | }
583 |
584 | /**
585 | * Pooled storage allocator
586 | *
587 | * The following routines allow for the efficient allocation of storage in
588 | * small chunks from a specified pool. Rather than allowing each structure
589 | * to be freed individually, an entire pool of storage is freed at once.
590 | * This method has two advantages over just using malloc() and free(). First,
591 | * it is far more efficient for allocating small objects, as there is
592 | * no overhead for remembering all the information needed to free each
593 | * object or consolidating fragmented memory. Second, the decision about
594 | * how long to keep an object is made at the time of allocation, and there
595 | * is no need to track down all the objects to free them.
596 | *
597 | */
598 |
599 | const size_t WORDSIZE = 16;
600 | const size_t BLOCKSIZE = 8192;
601 |
602 | class PooledAllocator {
603 | /* We maintain memory alignment to word boundaries by requiring that all
604 | allocations be in multiples of the machine wordsize. */
605 | /* Size of machine word in bytes. Must be power of 2. */
606 | /* Minimum number of bytes requested at a time from the system. Must be
607 | * multiple of WORDSIZE. */
608 |
609 | size_t remaining; /* Number of bytes left in current block of storage. */
610 | void *base; /* Pointer to base of current block of storage. */
611 | void *loc; /* Current location in block to next allocate memory. */
612 |
613 | void internal_init() {
614 | remaining = 0;
615 | base = NULL;
616 | usedMemory = 0;
617 | wastedMemory = 0;
618 | }
619 |
620 | public:
621 | size_t usedMemory;
622 | size_t wastedMemory;
623 |
624 | /**
625 | Default constructor. Initializes a new pool.
626 | */
627 | PooledAllocator() { internal_init(); }
628 |
629 | /**
630 | * Destructor. Frees all the memory allocated in this pool.
631 | */
632 | ~PooledAllocator() { free_all(); }
633 |
634 | /** Frees all allocated memory chunks */
635 | void free_all() {
636 | while (base != NULL) {
637 | void *prev =
638 | *(static_cast(base)); /* Get pointer to prev block. */
639 | ::free(base);
640 | base = prev;
641 | }
642 | internal_init();
643 | }
644 |
645 | /**
646 | * Returns a pointer to a piece of new memory of the given size in bytes
647 | * allocated from the pool.
648 | */
649 | void *malloc(const size_t req_size) {
650 | /* Round size up to a multiple of wordsize. The following expression
651 | only works for WORDSIZE that is a power of 2, by masking last bits of
652 | incremented size to zero.
653 | */
654 | const size_t size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1);
655 |
656 | /* Check whether a new block must be allocated. Note that the first word
657 | of a block is reserved for a pointer to the previous block.
658 | */
659 | if (size > remaining) {
660 |
661 | wastedMemory += remaining;
662 |
663 | /* Allocate new storage. */
664 | const size_t blocksize =
665 | (size + sizeof(void *) + (WORDSIZE - 1) > BLOCKSIZE)
666 | ? size + sizeof(void *) + (WORDSIZE - 1)
667 | : BLOCKSIZE;
668 |
669 | // use the standard C malloc to allocate memory
670 | void *m = ::malloc(blocksize);
671 | if (!m) {
672 | fprintf(stderr, "Failed to allocate memory.\n");
673 | return NULL;
674 | }
675 |
676 | /* Fill first word of new block with pointer to previous block. */
677 | static_cast(m)[0] = base;
678 | base = m;
679 |
680 | size_t shift = 0;
681 | // int size_t = (WORDSIZE - ( (((size_t)m) + sizeof(void*)) &
682 | // (WORDSIZE-1))) & (WORDSIZE-1);
683 |
684 | remaining = blocksize - sizeof(void *) - shift;
685 | loc = (static_cast(m) + sizeof(void *) + shift);
686 | }
687 | void *rloc = loc;
688 | loc = static_cast(loc) + size;
689 | remaining -= size;
690 |
691 | usedMemory += size;
692 |
693 | return rloc;
694 | }
695 |
696 | /**
697 | * Allocates (using this pool) a generic type T.
698 | *
699 | * Params:
700 | * count = number of instances to allocate.
701 | * Returns: pointer (of type T*) to memory buffer
702 | */
703 | template T *allocate(const size_t count = 1) {
704 | T *mem = static_cast(this->malloc(sizeof(T) * count));
705 | return mem;
706 | }
707 | };
708 | /** @} */
709 |
710 | /** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff
711 | * @{ */
712 |
713 | /** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors
714 | * when DIM=-1. Fixed size version for a generic DIM:
715 | */
716 | template struct array_or_vector_selector {
717 | typedef std::array container_t;
718 | };
719 | /** Dynamic size version */
720 | template struct array_or_vector_selector<-1, T> {
721 | typedef std::vector container_t;
722 | };
723 |
724 | /** @} */
725 |
726 | /** kd-tree base-class
727 | *
728 | * Contains the member functions common to the classes KDTreeSingleIndexAdaptor
729 | * and KDTreeSingleIndexDynamicAdaptor_.
730 | *
731 | * \tparam Derived The name of the class which inherits this class.
732 | * \tparam DatasetAdaptor The user-provided adaptor (see comments above).
733 | * \tparam Distance The distance metric to use, these are all classes derived
734 | * from nanoflann::Metric \tparam DIM Dimensionality of data points (e.g. 3 for
735 | * 3D points) \tparam IndexType Will be typically size_t or int
736 | */
737 |
738 | template
740 | class KDTreeBaseClass {
741 |
742 | public:
743 | /** Frees the previously-built index. Automatically called within
744 | * buildIndex(). */
745 | void freeIndex(Derived &obj) {
746 | obj.pool.free_all();
747 | obj.root_node = NULL;
748 | obj.m_size_at_index_build = 0;
749 | }
750 |
751 | typedef typename Distance::ElementType ElementType;
752 | typedef typename Distance::DistanceType DistanceType;
753 |
754 | /*--------------------- Internal Data Structures --------------------------*/
755 | struct Node {
756 | /** Union used because a node can be either a LEAF node or a non-leaf node,
757 | * so both data fields are never used simultaneously */
758 | union {
759 | struct leaf {
760 | IndexType left, right; //!< Indices of points in leaf node
761 | } lr;
762 | struct nonleaf {
763 | int divfeat; //!< Dimension used for subdivision.
764 | DistanceType divlow, divhigh; //!< The values used for subdivision.
765 | } sub;
766 | } node_type;
767 | Node *child1, *child2; //!< Child nodes (both=NULL mean its a leaf node)
768 | };
769 |
770 | typedef Node *NodePtr;
771 |
772 | struct Interval {
773 | ElementType low, high;
774 | };
775 |
776 | /**
777 | * Array of indices to vectors in the dataset.
778 | */
779 | std::vector vind;
780 |
781 | NodePtr root_node;
782 |
783 | size_t m_leaf_max_size;
784 |
785 | size_t m_size; //!< Number of current points in the dataset
786 | size_t m_size_at_index_build; //!< Number of points in the dataset when the
787 | //!< index was built
788 | int dim; //!< Dimensionality of each data point
789 |
790 | /** Define "BoundingBox" as a fixed-size or variable-size container depending
791 | * on "DIM" */
792 | typedef
793 | typename array_or_vector_selector::container_t BoundingBox;
794 |
795 | /** Define "distance_vector_t" as a fixed-size or variable-size container
796 | * depending on "DIM" */
797 | typedef typename array_or_vector_selector::container_t
798 | distance_vector_t;
799 |
800 | /** The KD-tree used to find neighbours */
801 |
802 | BoundingBox root_bbox;
803 |
804 | /**
805 | * Pooled memory allocator.
806 | *
807 | * Using a pooled memory allocator is more efficient
808 | * than allocating memory directly when there is a large
809 | * number small of memory allocations.
810 | */
811 | PooledAllocator pool;
812 |
813 | /** Returns number of points in dataset */
814 | size_t size(const Derived &obj) const { return obj.m_size; }
815 |
816 | /** Returns the length of each point in the dataset */
817 | size_t veclen(const Derived &obj) {
818 | return static_cast(DIM > 0 ? DIM : obj.dim);
819 | }
820 |
821 | /// Helper accessor to the dataset points:
822 | inline ElementType dataset_get(const Derived &obj, size_t idx,
823 | int component) const {
824 | return obj.dataset.kdtree_get_pt(idx, component);
825 | }
826 |
827 | /**
828 | * Computes the inde memory usage
829 | * Returns: memory used by the index
830 | */
831 | size_t usedMemory(Derived &obj) {
832 | return obj.pool.usedMemory + obj.pool.wastedMemory +
833 | obj.dataset.kdtree_get_point_count() *
834 | sizeof(IndexType); // pool memory and vind array memory
835 | }
836 |
837 | void computeMinMax(const Derived &obj, IndexType *ind, IndexType count,
838 | int element, ElementType &min_elem,
839 | ElementType &max_elem) {
840 | min_elem = dataset_get(obj, ind[0], element);
841 | max_elem = dataset_get(obj, ind[0], element);
842 | for (IndexType i = 1; i < count; ++i) {
843 | ElementType val = dataset_get(obj, ind[i], element);
844 | if (val < min_elem)
845 | min_elem = val;
846 | if (val > max_elem)
847 | max_elem = val;
848 | }
849 | }
850 |
851 | /**
852 | * Create a tree node that subdivides the list of vecs from vind[first]
853 | * to vind[last]. The routine is called recursively on each sublist.
854 | *
855 | * @param left index of the first vector
856 | * @param right index of the last vector
857 | */
858 | NodePtr divideTree(Derived &obj, const IndexType left, const IndexType right,
859 | BoundingBox &bbox) {
860 | NodePtr node = obj.pool.template allocate(); // allocate memory
861 |
862 | /* If too few exemplars remain, then make this a leaf node. */
863 | if ((right - left) <= static_cast(obj.m_leaf_max_size)) {
864 | node->child1 = node->child2 = NULL; /* Mark as leaf node. */
865 | node->node_type.lr.left = left;
866 | node->node_type.lr.right = right;
867 |
868 | // compute bounding-box of leaf points
869 | for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {
870 | bbox[i].low = dataset_get(obj, obj.vind[left], i);
871 | bbox[i].high = dataset_get(obj, obj.vind[left], i);
872 | }
873 | for (IndexType k = left + 1; k < right; ++k) {
874 | for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {
875 | if (bbox[i].low > dataset_get(obj, obj.vind[k], i))
876 | bbox[i].low = dataset_get(obj, obj.vind[k], i);
877 | if (bbox[i].high < dataset_get(obj, obj.vind[k], i))
878 | bbox[i].high = dataset_get(obj, obj.vind[k], i);
879 | }
880 | }
881 | } else {
882 | IndexType idx;
883 | int cutfeat;
884 | DistanceType cutval;
885 | middleSplit_(obj, &obj.vind[0] + left, right - left, idx, cutfeat, cutval,
886 | bbox);
887 |
888 | node->node_type.sub.divfeat = cutfeat;
889 |
890 | BoundingBox left_bbox(bbox);
891 | left_bbox[cutfeat].high = cutval;
892 | node->child1 = divideTree(obj, left, left + idx, left_bbox);
893 |
894 | BoundingBox right_bbox(bbox);
895 | right_bbox[cutfeat].low = cutval;
896 | node->child2 = divideTree(obj, left + idx, right, right_bbox);
897 |
898 | node->node_type.sub.divlow = left_bbox[cutfeat].high;
899 | node->node_type.sub.divhigh = right_bbox[cutfeat].low;
900 |
901 | for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {
902 | bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low);
903 | bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high);
904 | }
905 | }
906 |
907 | return node;
908 | }
909 |
910 | void middleSplit_(Derived &obj, IndexType *ind, IndexType count,
911 | IndexType &index, int &cutfeat, DistanceType &cutval,
912 | const BoundingBox &bbox) {
913 | const DistanceType EPS = static_cast(0.00001);
914 | ElementType max_span = bbox[0].high - bbox[0].low;
915 | for (int i = 1; i < (DIM > 0 ? DIM : obj.dim); ++i) {
916 | ElementType span = bbox[i].high - bbox[i].low;
917 | if (span > max_span) {
918 | max_span = span;
919 | }
920 | }
921 | ElementType max_spread = -1;
922 | cutfeat = 0;
923 | for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {
924 | ElementType span = bbox[i].high - bbox[i].low;
925 | if (span > (1 - EPS) * max_span) {
926 | ElementType min_elem, max_elem;
927 | computeMinMax(obj, ind, count, i, min_elem, max_elem);
928 | ElementType spread = max_elem - min_elem;
929 | ;
930 | if (spread > max_spread) {
931 | cutfeat = i;
932 | max_spread = spread;
933 | }
934 | }
935 | }
936 | // split in the middle
937 | DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2;
938 | ElementType min_elem, max_elem;
939 | computeMinMax(obj, ind, count, cutfeat, min_elem, max_elem);
940 |
941 | if (split_val < min_elem)
942 | cutval = min_elem;
943 | else if (split_val > max_elem)
944 | cutval = max_elem;
945 | else
946 | cutval = split_val;
947 |
948 | IndexType lim1, lim2;
949 | planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2);
950 |
951 | if (lim1 > count / 2)
952 | index = lim1;
953 | else if (lim2 < count / 2)
954 | index = lim2;
955 | else
956 | index = count / 2;
957 | }
958 |
959 | /**
960 | * Subdivide the list of points by a plane perpendicular on axe corresponding
961 | * to the 'cutfeat' dimension at 'cutval' position.
962 | *
963 | * On return:
964 | * dataset[ind[0..lim1-1]][cutfeat]cutval
967 | */
968 | void planeSplit(Derived &obj, IndexType *ind, const IndexType count,
969 | int cutfeat, DistanceType &cutval, IndexType &lim1,
970 | IndexType &lim2) {
971 | /* Move vector indices for left subtree to front of list. */
972 | IndexType left = 0;
973 | IndexType right = count - 1;
974 | for (;;) {
975 | while (left <= right && dataset_get(obj, ind[left], cutfeat) < cutval)
976 | ++left;
977 | while (right && left <= right &&
978 | dataset_get(obj, ind[right], cutfeat) >= cutval)
979 | --right;
980 | if (left > right || !right)
981 | break; // "!right" was added to support unsigned Index types
982 | std::swap(ind[left], ind[right]);
983 | ++left;
984 | --right;
985 | }
986 | /* If either list is empty, it means that all remaining features
987 | * are identical. Split in the middle to maintain a balanced tree.
988 | */
989 | lim1 = left;
990 | right = count - 1;
991 | for (;;) {
992 | while (left <= right && dataset_get(obj, ind[left], cutfeat) <= cutval)
993 | ++left;
994 | while (right && left <= right &&
995 | dataset_get(obj, ind[right], cutfeat) > cutval)
996 | --right;
997 | if (left > right || !right)
998 | break; // "!right" was added to support unsigned Index types
999 | std::swap(ind[left], ind[right]);
1000 | ++left;
1001 | --right;
1002 | }
1003 | lim2 = left;
1004 | }
1005 |
1006 | DistanceType computeInitialDistances(const Derived &obj,
1007 | const ElementType *vec,
1008 | distance_vector_t &dists) const {
1009 | assert(vec);
1010 | DistanceType distsq = DistanceType();
1011 |
1012 | for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {
1013 | if (vec[i] < obj.root_bbox[i].low) {
1014 | dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].low, i);
1015 | distsq += dists[i];
1016 | }
1017 | if (vec[i] > obj.root_bbox[i].high) {
1018 | dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].high, i);
1019 | distsq += dists[i];
1020 | }
1021 | }
1022 | return distsq;
1023 | }
1024 |
1025 | void save_tree(Derived &obj, FILE *stream, NodePtr tree) {
1026 | save_value(stream, *tree);
1027 | if (tree->child1 != NULL) {
1028 | save_tree(obj, stream, tree->child1);
1029 | }
1030 | if (tree->child2 != NULL) {
1031 | save_tree(obj, stream, tree->child2);
1032 | }
1033 | }
1034 |
1035 | void load_tree(Derived &obj, FILE *stream, NodePtr &tree) {
1036 | tree = obj.pool.template allocate();
1037 | load_value(stream, *tree);
1038 | if (tree->child1 != NULL) {
1039 | load_tree(obj, stream, tree->child1);
1040 | }
1041 | if (tree->child2 != NULL) {
1042 | load_tree(obj, stream, tree->child2);
1043 | }
1044 | }
1045 |
1046 | /** Stores the index in a binary file.
1047 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when
1048 | * loading the index object it must be constructed associated to the same
1049 | * source of data points used while building it. See the example:
1050 | * examples/saveload_example.cpp \sa loadIndex */
1051 | void saveIndex_(Derived &obj, FILE *stream) {
1052 | save_value(stream, obj.m_size);
1053 | save_value(stream, obj.dim);
1054 | save_value(stream, obj.root_bbox);
1055 | save_value(stream, obj.m_leaf_max_size);
1056 | save_value(stream, obj.vind);
1057 | save_tree(obj, stream, obj.root_node);
1058 | }
1059 |
1060 | /** Loads a previous index from a binary file.
1061 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the
1062 | * index object must be constructed associated to the same source of data
1063 | * points used while building the index. See the example:
1064 | * examples/saveload_example.cpp \sa loadIndex */
1065 | void loadIndex_(Derived &obj, FILE *stream) {
1066 | load_value(stream, obj.m_size);
1067 | load_value(stream, obj.dim);
1068 | load_value(stream, obj.root_bbox);
1069 | load_value(stream, obj.m_leaf_max_size);
1070 | load_value(stream, obj.vind);
1071 | load_tree(obj, stream, obj.root_node);
1072 | }
1073 | };
1074 |
1075 | /** @addtogroup kdtrees_grp KD-tree classes and adaptors
1076 | * @{ */
1077 |
1078 | /** kd-tree static index
1079 | *
1080 | * Contains the k-d trees and other information for indexing a set of points
1081 | * for nearest-neighbor matching.
1082 | *
1083 | * The class "DatasetAdaptor" must provide the following interface (can be
1084 | * non-virtual, inlined methods):
1085 | *
1086 | * \code
1087 | * // Must return the number of data poins
1088 | * inline size_t kdtree_get_point_count() const { ... }
1089 | *
1090 | *
1091 | * // Must return the dim'th component of the idx'th point in the class:
1092 | * inline T kdtree_get_pt(const size_t idx, const size_t dim) const { ... }
1093 | *
1094 | * // Optional bounding-box computation: return false to default to a standard
1095 | * bbox computation loop.
1096 | * // Return true if the BBOX was already computed by the class and returned
1097 | * in "bb" so it can be avoided to redo it again.
1098 | * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3
1099 | * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const
1100 | * {
1101 | * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits
1102 | * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits
1103 | * ...
1104 | * return true;
1105 | * }
1106 | *
1107 | * \endcode
1108 | *
1109 | * \tparam DatasetAdaptor The user-provided adaptor (see comments above).
1110 | * \tparam Distance The distance metric to use: nanoflann::metric_L1,
1111 | * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM
1112 | * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType Will
1113 | * be typically size_t or int
1114 | */
1115 | template
1117 | class KDTreeSingleIndexAdaptor
1118 | : public KDTreeBaseClass<
1119 | KDTreeSingleIndexAdaptor,
1120 | Distance, DatasetAdaptor, DIM, IndexType> {
1121 | public:
1122 | /** Deleted copy constructor*/
1123 | KDTreeSingleIndexAdaptor(
1124 | const KDTreeSingleIndexAdaptor
1125 | &) = delete;
1126 |
1127 | /**
1128 | * The dataset used by this index
1129 | */
1130 | const DatasetAdaptor &dataset; //!< The source of our data
1131 |
1132 | const KDTreeSingleIndexAdaptorParams index_params;
1133 |
1134 | Distance distance;
1135 |
1136 | typedef typename nanoflann::KDTreeBaseClass<
1137 | nanoflann::KDTreeSingleIndexAdaptor,
1139 | Distance, DatasetAdaptor, DIM, IndexType>
1140 | BaseClassRef;
1141 |
1142 | typedef typename BaseClassRef::ElementType ElementType;
1143 | typedef typename BaseClassRef::DistanceType DistanceType;
1144 |
1145 | typedef typename BaseClassRef::Node Node;
1146 | typedef Node *NodePtr;
1147 |
1148 | typedef typename BaseClassRef::Interval Interval;
1149 | /** Define "BoundingBox" as a fixed-size or variable-size container depending
1150 | * on "DIM" */
1151 | typedef typename BaseClassRef::BoundingBox BoundingBox;
1152 |
1153 | /** Define "distance_vector_t" as a fixed-size or variable-size container
1154 | * depending on "DIM" */
1155 | typedef typename BaseClassRef::distance_vector_t distance_vector_t;
1156 |
1157 | /**
1158 | * KDTree constructor
1159 | *
1160 | * Refer to docs in README.md or online in
1161 | * https://github.com/jlblancoc/nanoflann
1162 | *
1163 | * The KD-Tree point dimension (the length of each point in the datase, e.g. 3
1164 | * for 3D points) is determined by means of:
1165 | * - The \a DIM template parameter if >0 (highest priority)
1166 | * - Otherwise, the \a dimensionality parameter of this constructor.
1167 | *
1168 | * @param inputData Dataset with the input features
1169 | * @param params Basically, the maximum leaf node size
1170 | */
1171 | KDTreeSingleIndexAdaptor(const int dimensionality,
1172 | const DatasetAdaptor &inputData,
1173 | const KDTreeSingleIndexAdaptorParams ¶ms =
1174 | KDTreeSingleIndexAdaptorParams())
1175 | : dataset(inputData), index_params(params), distance(inputData) {
1176 | BaseClassRef::root_node = NULL;
1177 | BaseClassRef::m_size = dataset.kdtree_get_point_count();
1178 | BaseClassRef::m_size_at_index_build = BaseClassRef::m_size;
1179 | BaseClassRef::dim = dimensionality;
1180 | if (DIM > 0)
1181 | BaseClassRef::dim = DIM;
1182 | BaseClassRef::m_leaf_max_size = params.leaf_max_size;
1183 |
1184 | // Create a permutable array of indices to the input vectors.
1185 | init_vind();
1186 | }
1187 |
1188 | /**
1189 | * Builds the index
1190 | */
1191 | void buildIndex() {
1192 | BaseClassRef::m_size = dataset.kdtree_get_point_count();
1193 | BaseClassRef::m_size_at_index_build = BaseClassRef::m_size;
1194 | init_vind();
1195 | this->freeIndex(*this);
1196 | BaseClassRef::m_size_at_index_build = BaseClassRef::m_size;
1197 | if (BaseClassRef::m_size == 0)
1198 | return;
1199 | computeBoundingBox(BaseClassRef::root_bbox);
1200 | BaseClassRef::root_node =
1201 | this->divideTree(*this, 0, BaseClassRef::m_size,
1202 | BaseClassRef::root_bbox); // construct the tree
1203 | }
1204 |
1205 | /** \name Query methods
1206 | * @{ */
1207 |
1208 | /**
1209 | * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored
1210 | * inside the result object.
1211 | *
1212 | * Params:
1213 | * result = the result object in which the indices of the
1214 | * nearest-neighbors are stored vec = the vector for which to search the
1215 | * nearest neighbors
1216 | *
1217 | * \tparam RESULTSET Should be any ResultSet
1218 | * \return True if the requested neighbors could be found.
1219 | * \sa knnSearch, radiusSearch
1220 | */
1221 | template
1222 | bool findNeighbors(RESULTSET &result, const ElementType *vec,
1223 | const SearchParams &searchParams) const {
1224 | assert(vec);
1225 | if (this->size(*this) == 0)
1226 | return false;
1227 | if (!BaseClassRef::root_node)
1228 | throw std::runtime_error(
1229 | "[nanoflann] findNeighbors() called before building the index.");
1230 | float epsError = 1 + searchParams.eps;
1231 |
1232 | distance_vector_t
1233 | dists; // fixed or variable-sized container (depending on DIM)
1234 | auto zero = static_cast(0);
1235 | assign(dists, (DIM > 0 ? DIM : BaseClassRef::dim),
1236 | zero); // Fill it with zeros.
1237 | DistanceType distsq = this->computeInitialDistances(*this, vec, dists);
1238 | searchLevel(result, vec, BaseClassRef::root_node, distsq, dists,
1239 | epsError); // "count_leaf" parameter removed since was neither
1240 | // used nor returned to the user.
1241 | return result.full();
1242 | }
1243 |
1244 | /**
1245 | * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1].
1246 | * Their indices are stored inside the result object. \sa radiusSearch,
1247 | * findNeighbors \note nChecks_IGNORED is ignored but kept for compatibility
1248 | * with the original FLANN interface. \return Number `N` of valid points in
1249 | * the result set. Only the first `N` entries in `out_indices` and
1250 | * `out_distances_sq` will be valid. Return may be less than `num_closest`
1251 | * only if the number of elements in the tree is less than `num_closest`.
1252 | */
1253 | size_t knnSearch(const ElementType *query_point, const size_t num_closest,
1254 | IndexType *out_indices, DistanceType *out_distances_sq,
1255 | const int /* nChecks_IGNORED */ = 10) const {
1256 | nanoflann::KNNResultSet resultSet(num_closest);
1257 | resultSet.init(out_indices, out_distances_sq);
1258 | this->findNeighbors(resultSet, query_point, nanoflann::SearchParams());
1259 | return resultSet.size();
1260 | }
1261 |
1262 | /**
1263 | * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius.
1264 | * The output is given as a vector of pairs, of which the first element is a
1265 | * point index and the second the corresponding distance. Previous contents of
1266 | * \a IndicesDists are cleared.
1267 | *
1268 | * If searchParams.sorted==true, the output list is sorted by ascending
1269 | * distances.
1270 | *
1271 | * For a better performance, it is advisable to do a .reserve() on the vector
1272 | * if you have any wild guess about the number of expected matches.
1273 | *
1274 | * \sa knnSearch, findNeighbors, radiusSearchCustomCallback
1275 | * \return The number of points within the given radius (i.e. indices.size()
1276 | * or dists.size() )
1277 | */
1278 | size_t
1279 | radiusSearch(const ElementType *query_point, const DistanceType &radius,
1280 | std::vector> &IndicesDists,
1281 | const SearchParams &searchParams) const {
1282 | RadiusResultSet resultSet(radius, IndicesDists);
1283 | const size_t nFound =
1284 | radiusSearchCustomCallback(query_point, resultSet, searchParams);
1285 | if (searchParams.sorted)
1286 | std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter());
1287 | return nFound;
1288 | }
1289 |
1290 | /**
1291 | * Just like radiusSearch() but with a custom callback class for each point
1292 | * found in the radius of the query. See the source of RadiusResultSet<> as a
1293 | * start point for your own classes. \sa radiusSearch
1294 | */
1295 | template
1296 | size_t radiusSearchCustomCallback(
1297 | const ElementType *query_point, SEARCH_CALLBACK &resultSet,
1298 | const SearchParams &searchParams = SearchParams()) const {
1299 | this->findNeighbors(resultSet, query_point, searchParams);
1300 | return resultSet.size();
1301 | }
1302 |
1303 | /** @} */
1304 |
1305 | public:
1306 | /** Make sure the auxiliary list \a vind has the same size than the current
1307 | * dataset, and re-generate if size has changed. */
1308 | void init_vind() {
1309 | // Create a permutable array of indices to the input vectors.
1310 | BaseClassRef::m_size = dataset.kdtree_get_point_count();
1311 | if (BaseClassRef::vind.size() != BaseClassRef::m_size)
1312 | BaseClassRef::vind.resize(BaseClassRef::m_size);
1313 | for (size_t i = 0; i < BaseClassRef::m_size; i++)
1314 | BaseClassRef::vind[i] = i;
1315 | }
1316 |
1317 | void computeBoundingBox(BoundingBox &bbox) {
1318 | resize(bbox, (DIM > 0 ? DIM : BaseClassRef::dim));
1319 | if (dataset.kdtree_get_bbox(bbox)) {
1320 | // Done! It was implemented in derived class
1321 | } else {
1322 | const size_t N = dataset.kdtree_get_point_count();
1323 | if (!N)
1324 | throw std::runtime_error("[nanoflann] computeBoundingBox() called but "
1325 | "no data points found.");
1326 | for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) {
1327 | bbox[i].low = bbox[i].high = this->dataset_get(*this, 0, i);
1328 | }
1329 | for (size_t k = 1; k < N; ++k) {
1330 | for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) {
1331 | if (this->dataset_get(*this, k, i) < bbox[i].low)
1332 | bbox[i].low = this->dataset_get(*this, k, i);
1333 | if (this->dataset_get(*this, k, i) > bbox[i].high)
1334 | bbox[i].high = this->dataset_get(*this, k, i);
1335 | }
1336 | }
1337 | }
1338 | }
1339 |
1340 | /**
1341 | * Performs an exact search in the tree starting from a node.
1342 | * \tparam RESULTSET Should be any ResultSet
1343 | * \return true if the search should be continued, false if the results are
1344 | * sufficient
1345 | */
1346 | template
1347 | bool searchLevel(RESULTSET &result_set, const ElementType *vec,
1348 | const NodePtr node, DistanceType mindistsq,
1349 | distance_vector_t &dists, const float epsError) const {
1350 | /* If this is a leaf node, then do check and return. */
1351 | if ((node->child1 == NULL) && (node->child2 == NULL)) {
1352 | // count_leaf += (node->lr.right-node->lr.left); // Removed since was
1353 | // neither used nor returned to the user.
1354 | DistanceType worst_dist = result_set.worstDist();
1355 | for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right;
1356 | ++i) {
1357 | const IndexType index = BaseClassRef::vind[i]; // reorder... : i;
1358 | DistanceType dist = distance.evalMetric(
1359 | vec, index, (DIM > 0 ? DIM : BaseClassRef::dim));
1360 | if (dist < worst_dist) {
1361 | if (!result_set.addPoint(dist, BaseClassRef::vind[i])) {
1362 | // the resultset doesn't want to receive any more points, we're done
1363 | // searching!
1364 | return false;
1365 | }
1366 | }
1367 | }
1368 | return true;
1369 | }
1370 |
1371 | /* Which child branch should be taken first? */
1372 | int idx = node->node_type.sub.divfeat;
1373 | ElementType val = vec[idx];
1374 | DistanceType diff1 = val - node->node_type.sub.divlow;
1375 | DistanceType diff2 = val - node->node_type.sub.divhigh;
1376 |
1377 | NodePtr bestChild;
1378 | NodePtr otherChild;
1379 | DistanceType cut_dist;
1380 | if ((diff1 + diff2) < 0) {
1381 | bestChild = node->child1;
1382 | otherChild = node->child2;
1383 | cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx);
1384 | } else {
1385 | bestChild = node->child2;
1386 | otherChild = node->child1;
1387 | cut_dist = distance.accum_dist(val, node->node_type.sub.divlow, idx);
1388 | }
1389 |
1390 | /* Call recursively to search next level down. */
1391 | if (!searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError)) {
1392 | // the resultset doesn't want to receive any more points, we're done
1393 | // searching!
1394 | return false;
1395 | }
1396 |
1397 | DistanceType dst = dists[idx];
1398 | mindistsq = mindistsq + cut_dist - dst;
1399 | dists[idx] = cut_dist;
1400 | if (mindistsq * epsError <= result_set.worstDist()) {
1401 | if (!searchLevel(result_set, vec, otherChild, mindistsq, dists,
1402 | epsError)) {
1403 | // the resultset doesn't want to receive any more points, we're done
1404 | // searching!
1405 | return false;
1406 | }
1407 | }
1408 | dists[idx] = dst;
1409 | return true;
1410 | }
1411 |
1412 | public:
1413 | /** Stores the index in a binary file.
1414 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when
1415 | * loading the index object it must be constructed associated to the same
1416 | * source of data points used while building it. See the example:
1417 | * examples/saveload_example.cpp \sa loadIndex */
1418 | void saveIndex(FILE *stream) { this->saveIndex_(*this, stream); }
1419 |
1420 | /** Loads a previous index from a binary file.
1421 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the
1422 | * index object must be constructed associated to the same source of data
1423 | * points used while building the index. See the example:
1424 | * examples/saveload_example.cpp \sa loadIndex */
1425 | void loadIndex(FILE *stream) { this->loadIndex_(*this, stream); }
1426 |
1427 | }; // class KDTree
1428 |
1429 | /** kd-tree dynamic index
1430 | *
1431 | * Contains the k-d trees and other information for indexing a set of points
1432 | * for nearest-neighbor matching.
1433 | *
1434 | * The class "DatasetAdaptor" must provide the following interface (can be
1435 | * non-virtual, inlined methods):
1436 | *
1437 | * \code
1438 | * // Must return the number of data poins
1439 | * inline size_t kdtree_get_point_count() const { ... }
1440 | *
1441 | * // Must return the dim'th component of the idx'th point in the class:
1442 | * inline T kdtree_get_pt(const size_t idx, const size_t dim) const { ... }
1443 | *
1444 | * // Optional bounding-box computation: return false to default to a standard
1445 | * bbox computation loop.
1446 | * // Return true if the BBOX was already computed by the class and returned
1447 | * in "bb" so it can be avoided to redo it again.
1448 | * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3
1449 | * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const
1450 | * {
1451 | * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits
1452 | * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits
1453 | * ...
1454 | * return true;
1455 | * }
1456 | *
1457 | * \endcode
1458 | *
1459 | * \tparam DatasetAdaptor The user-provided adaptor (see comments above).
1460 | * \tparam Distance The distance metric to use: nanoflann::metric_L1,
1461 | * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM
1462 | * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType Will
1463 | * be typically size_t or int
1464 | */
1465 | template
1467 | class KDTreeSingleIndexDynamicAdaptor_
1468 | : public KDTreeBaseClass,
1470 | Distance, DatasetAdaptor, DIM, IndexType> {
1471 | public:
1472 | /**
1473 | * The dataset used by this index
1474 | */
1475 | const DatasetAdaptor &dataset; //!< The source of our data
1476 |
1477 | KDTreeSingleIndexAdaptorParams index_params;
1478 |
1479 | std::vector &treeIndex;
1480 |
1481 | Distance distance;
1482 |
1483 | typedef typename nanoflann::KDTreeBaseClass<
1484 | nanoflann::KDTreeSingleIndexDynamicAdaptor_,
1486 | Distance, DatasetAdaptor, DIM, IndexType>
1487 | BaseClassRef;
1488 |
1489 | typedef typename BaseClassRef::ElementType ElementType;
1490 | typedef typename BaseClassRef::DistanceType DistanceType;
1491 |
1492 | typedef typename BaseClassRef::Node Node;
1493 | typedef Node *NodePtr;
1494 |
1495 | typedef typename BaseClassRef::Interval Interval;
1496 | /** Define "BoundingBox" as a fixed-size or variable-size container depending
1497 | * on "DIM" */
1498 | typedef typename BaseClassRef::BoundingBox BoundingBox;
1499 |
1500 | /** Define "distance_vector_t" as a fixed-size or variable-size container
1501 | * depending on "DIM" */
1502 | typedef typename BaseClassRef::distance_vector_t distance_vector_t;
1503 |
1504 | /**
1505 | * KDTree constructor
1506 | *
1507 | * Refer to docs in README.md or online in
1508 | * https://github.com/jlblancoc/nanoflann
1509 | *
1510 | * The KD-Tree point dimension (the length of each point in the datase, e.g. 3
1511 | * for 3D points) is determined by means of:
1512 | * - The \a DIM template parameter if >0 (highest priority)
1513 | * - Otherwise, the \a dimensionality parameter of this constructor.
1514 | *
1515 | * @param inputData Dataset with the input features
1516 | * @param params Basically, the maximum leaf node size
1517 | */
1518 | KDTreeSingleIndexDynamicAdaptor_(
1519 | const int dimensionality, const DatasetAdaptor &inputData,
1520 | std::vector &treeIndex_,
1521 | const KDTreeSingleIndexAdaptorParams ¶ms =
1522 | KDTreeSingleIndexAdaptorParams())
1523 | : dataset(inputData), index_params(params), treeIndex(treeIndex_),
1524 | distance(inputData) {
1525 | BaseClassRef::root_node = NULL;
1526 | BaseClassRef::m_size = 0;
1527 | BaseClassRef::m_size_at_index_build = 0;
1528 | BaseClassRef::dim = dimensionality;
1529 | if (DIM > 0)
1530 | BaseClassRef::dim = DIM;
1531 | BaseClassRef::m_leaf_max_size = params.leaf_max_size;
1532 | }
1533 |
1534 | /** Assignment operator definiton */
1535 | KDTreeSingleIndexDynamicAdaptor_
1536 | operator=(const KDTreeSingleIndexDynamicAdaptor_ &rhs) {
1537 | KDTreeSingleIndexDynamicAdaptor_ tmp(rhs);
1538 | std::swap(BaseClassRef::vind, tmp.BaseClassRef::vind);
1539 | std::swap(BaseClassRef::m_leaf_max_size, tmp.BaseClassRef::m_leaf_max_size);
1540 | std::swap(index_params, tmp.index_params);
1541 | std::swap(treeIndex, tmp.treeIndex);
1542 | std::swap(BaseClassRef::m_size, tmp.BaseClassRef::m_size);
1543 | std::swap(BaseClassRef::m_size_at_index_build,
1544 | tmp.BaseClassRef::m_size_at_index_build);
1545 | std::swap(BaseClassRef::root_node, tmp.BaseClassRef::root_node);
1546 | std::swap(BaseClassRef::root_bbox, tmp.BaseClassRef::root_bbox);
1547 | std::swap(BaseClassRef::pool, tmp.BaseClassRef::pool);
1548 | return *this;
1549 | }
1550 |
1551 | /**
1552 | * Builds the index
1553 | */
1554 | void buildIndex() {
1555 | BaseClassRef::m_size = BaseClassRef::vind.size();
1556 | this->freeIndex(*this);
1557 | BaseClassRef::m_size_at_index_build = BaseClassRef::m_size;
1558 | if (BaseClassRef::m_size == 0)
1559 | return;
1560 | computeBoundingBox(BaseClassRef::root_bbox);
1561 | BaseClassRef::root_node =
1562 | this->divideTree(*this, 0, BaseClassRef::m_size,
1563 | BaseClassRef::root_bbox); // construct the tree
1564 | }
1565 |
1566 | /** \name Query methods
1567 | * @{ */
1568 |
1569 | /**
1570 | * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored
1571 | * inside the result object.
1572 | *
1573 | * Params:
1574 | * result = the result object in which the indices of the
1575 | * nearest-neighbors are stored vec = the vector for which to search the
1576 | * nearest neighbors
1577 | *
1578 | * \tparam RESULTSET Should be any ResultSet
1579 | * \return True if the requested neighbors could be found.
1580 | * \sa knnSearch, radiusSearch
1581 | */
1582 | template
1583 | bool findNeighbors(RESULTSET &result, const ElementType *vec,
1584 | const SearchParams &searchParams) const {
1585 | assert(vec);
1586 | if (this->size(*this) == 0)
1587 | return false;
1588 | if (!BaseClassRef::root_node)
1589 | return false;
1590 | float epsError = 1 + searchParams.eps;
1591 |
1592 | // fixed or variable-sized container (depending on DIM)
1593 | distance_vector_t dists;
1594 | // Fill it with zeros.
1595 | assign(dists, (DIM > 0 ? DIM : BaseClassRef::dim),
1596 | static_cast(0));
1597 | DistanceType distsq = this->computeInitialDistances(*this, vec, dists);
1598 | searchLevel(result, vec, BaseClassRef::root_node, distsq, dists,
1599 | epsError); // "count_leaf" parameter removed since was neither
1600 | // used nor returned to the user.
1601 | return result.full();
1602 | }
1603 |
1604 | /**
1605 | * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1].
1606 | * Their indices are stored inside the result object. \sa radiusSearch,
1607 | * findNeighbors \note nChecks_IGNORED is ignored but kept for compatibility
1608 | * with the original FLANN interface. \return Number `N` of valid points in
1609 | * the result set. Only the first `N` entries in `out_indices` and
1610 | * `out_distances_sq` will be valid. Return may be less than `num_closest`
1611 | * only if the number of elements in the tree is less than `num_closest`.
1612 | */
1613 | size_t knnSearch(const ElementType *query_point, const size_t num_closest,
1614 | IndexType *out_indices, DistanceType *out_distances_sq,
1615 | const int /* nChecks_IGNORED */ = 10) const {
1616 | nanoflann::KNNResultSet resultSet(num_closest);
1617 | resultSet.init(out_indices, out_distances_sq);
1618 | this->findNeighbors(resultSet, query_point, nanoflann::SearchParams());
1619 | return resultSet.size();
1620 | }
1621 |
1622 | /**
1623 | * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius.
1624 | * The output is given as a vector of pairs, of which the first element is a
1625 | * point index and the second the corresponding distance. Previous contents of
1626 | * \a IndicesDists are cleared.
1627 | *
1628 | * If searchParams.sorted==true, the output list is sorted by ascending
1629 | * distances.
1630 | *
1631 | * For a better performance, it is advisable to do a .reserve() on the vector
1632 | * if you have any wild guess about the number of expected matches.
1633 | *
1634 | * \sa knnSearch, findNeighbors, radiusSearchCustomCallback
1635 | * \return The number of points within the given radius (i.e. indices.size()
1636 | * or dists.size() )
1637 | */
1638 | size_t
1639 | radiusSearch(const ElementType *query_point, const DistanceType &radius,
1640 | std::vector> &IndicesDists,
1641 | const SearchParams &searchParams) const {
1642 | RadiusResultSet resultSet(radius, IndicesDists);
1643 | const size_t nFound =
1644 | radiusSearchCustomCallback(query_point, resultSet, searchParams);
1645 | if (searchParams.sorted)
1646 | std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter());
1647 | return nFound;
1648 | }
1649 |
1650 | /**
1651 | * Just like radiusSearch() but with a custom callback class for each point
1652 | * found in the radius of the query. See the source of RadiusResultSet<> as a
1653 | * start point for your own classes. \sa radiusSearch
1654 | */
1655 | template
1656 | size_t radiusSearchCustomCallback(
1657 | const ElementType *query_point, SEARCH_CALLBACK &resultSet,
1658 | const SearchParams &searchParams = SearchParams()) const {
1659 | this->findNeighbors(resultSet, query_point, searchParams);
1660 | return resultSet.size();
1661 | }
1662 |
1663 | /** @} */
1664 |
1665 | public:
1666 | void computeBoundingBox(BoundingBox &bbox) {
1667 | resize(bbox, (DIM > 0 ? DIM : BaseClassRef::dim));
1668 |
1669 | if (dataset.kdtree_get_bbox(bbox)) {
1670 | // Done! It was implemented in derived class
1671 | } else {
1672 | const size_t N = BaseClassRef::m_size;
1673 | if (!N)
1674 | throw std::runtime_error("[nanoflann] computeBoundingBox() called but "
1675 | "no data points found.");
1676 | for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) {
1677 | bbox[i].low = bbox[i].high =
1678 | this->dataset_get(*this, BaseClassRef::vind[0], i);
1679 | }
1680 | for (size_t k = 1; k < N; ++k) {
1681 | for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) {
1682 | if (this->dataset_get(*this, BaseClassRef::vind[k], i) < bbox[i].low)
1683 | bbox[i].low = this->dataset_get(*this, BaseClassRef::vind[k], i);
1684 | if (this->dataset_get(*this, BaseClassRef::vind[k], i) > bbox[i].high)
1685 | bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[k], i);
1686 | }
1687 | }
1688 | }
1689 | }
1690 |
1691 | /**
1692 | * Performs an exact search in the tree starting from a node.
1693 | * \tparam RESULTSET Should be any ResultSet
1694 | */
1695 | template
1696 | void searchLevel(RESULTSET &result_set, const ElementType *vec,
1697 | const NodePtr node, DistanceType mindistsq,
1698 | distance_vector_t &dists, const float epsError) const {
1699 | /* If this is a leaf node, then do check and return. */
1700 | if ((node->child1 == NULL) && (node->child2 == NULL)) {
1701 | // count_leaf += (node->lr.right-node->lr.left); // Removed since was
1702 | // neither used nor returned to the user.
1703 | DistanceType worst_dist = result_set.worstDist();
1704 | for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right;
1705 | ++i) {
1706 | const IndexType index = BaseClassRef::vind[i]; // reorder... : i;
1707 | if (treeIndex[index] == -1)
1708 | continue;
1709 | DistanceType dist = distance.evalMetric(
1710 | vec, index, (DIM > 0 ? DIM : BaseClassRef::dim));
1711 | if (dist < worst_dist) {
1712 | if (!result_set.addPoint(
1713 | static_cast(dist),
1714 | static_cast(
1715 | BaseClassRef::vind[i]))) {
1716 | // the resultset doesn't want to receive any more points, we're done
1717 | // searching!
1718 | return; // false;
1719 | }
1720 | }
1721 | }
1722 | return;
1723 | }
1724 |
1725 | /* Which child branch should be taken first? */
1726 | int idx = node->node_type.sub.divfeat;
1727 | ElementType val = vec[idx];
1728 | DistanceType diff1 = val - node->node_type.sub.divlow;
1729 | DistanceType diff2 = val - node->node_type.sub.divhigh;
1730 |
1731 | NodePtr bestChild;
1732 | NodePtr otherChild;
1733 | DistanceType cut_dist;
1734 | if ((diff1 + diff2) < 0) {
1735 | bestChild = node->child1;
1736 | otherChild = node->child2;
1737 | cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx);
1738 | } else {
1739 | bestChild = node->child2;
1740 | otherChild = node->child1;
1741 | cut_dist = distance.accum_dist(val, node->node_type.sub.divlow, idx);
1742 | }
1743 |
1744 | /* Call recursively to search next level down. */
1745 | searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError);
1746 |
1747 | DistanceType dst = dists[idx];
1748 | mindistsq = mindistsq + cut_dist - dst;
1749 | dists[idx] = cut_dist;
1750 | if (mindistsq * epsError <= result_set.worstDist()) {
1751 | searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError);
1752 | }
1753 | dists[idx] = dst;
1754 | }
1755 |
1756 | public:
1757 | /** Stores the index in a binary file.
1758 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when
1759 | * loading the index object it must be constructed associated to the same
1760 | * source of data points used while building it. See the example:
1761 | * examples/saveload_example.cpp \sa loadIndex */
1762 | void saveIndex(FILE *stream) { this->saveIndex_(*this, stream); }
1763 |
1764 | /** Loads a previous index from a binary file.
1765 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the
1766 | * index object must be constructed associated to the same source of data
1767 | * points used while building the index. See the example:
1768 | * examples/saveload_example.cpp \sa loadIndex */
1769 | void loadIndex(FILE *stream) { this->loadIndex_(*this, stream); }
1770 | };
1771 |
1772 | /** kd-tree dynaimic index
1773 | *
1774 | * class to create multiple static index and merge their results to behave as
1775 | * single dynamic index as proposed in Logarithmic Approach.
1776 | *
1777 | * Example of usage:
1778 | * examples/dynamic_pointcloud_example.cpp
1779 | *
1780 | * \tparam DatasetAdaptor The user-provided adaptor (see comments above).
1781 | * \tparam Distance The distance metric to use: nanoflann::metric_L1,
1782 | * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM
1783 | * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType Will
1784 | * be typically size_t or int
1785 | */
1786 | template
1788 | class KDTreeSingleIndexDynamicAdaptor {
1789 | public:
1790 | typedef typename Distance::ElementType ElementType;
1791 | typedef typename Distance::DistanceType DistanceType;
1792 |
1793 | protected:
1794 | size_t m_leaf_max_size;
1795 | size_t treeCount;
1796 | size_t pointCount;
1797 |
1798 | /**
1799 | * The dataset used by this index
1800 | */
1801 | const DatasetAdaptor &dataset; //!< The source of our data
1802 |
1803 | std::vector treeIndex; //!< treeIndex[idx] is the index of tree in which
1804 | //!< point at idx is stored. treeIndex[idx]=-1
1805 | //!< means that point has been removed.
1806 |
1807 | KDTreeSingleIndexAdaptorParams index_params;
1808 |
1809 | int dim; //!< Dimensionality of each data point
1810 |
1811 | typedef KDTreeSingleIndexDynamicAdaptor_
1812 | index_container_t;
1813 | std::vector index;
1814 |
1815 | public:
1816 | /** Get a const ref to the internal list of indices; the number of indices is
1817 | * adapted dynamically as the dataset grows in size. */
1818 | const std::vector &getAllIndices() const { return index; }
1819 |
1820 | private:
1821 | /** finds position of least significant unset bit */
1822 | int First0Bit(IndexType num) {
1823 | int pos = 0;
1824 | while (num & 1) {
1825 | num = num >> 1;
1826 | pos++;
1827 | }
1828 | return pos;
1829 | }
1830 |
1831 | /** Creates multiple empty trees to handle dynamic support */
1832 | void init() {
1833 | typedef KDTreeSingleIndexDynamicAdaptor_
1834 | my_kd_tree_t;
1835 | std::vector index_(
1836 | treeCount, my_kd_tree_t(dim /*dim*/, dataset, treeIndex, index_params));
1837 | index = index_;
1838 | }
1839 |
1840 | public:
1841 | Distance distance;
1842 |
1843 | /**
1844 | * KDTree constructor
1845 | *
1846 | * Refer to docs in README.md or online in
1847 | * https://github.com/jlblancoc/nanoflann
1848 | *
1849 | * The KD-Tree point dimension (the length of each point in the datase, e.g. 3
1850 | * for 3D points) is determined by means of:
1851 | * - The \a DIM template parameter if >0 (highest priority)
1852 | * - Otherwise, the \a dimensionality parameter of this constructor.
1853 | *
1854 | * @param inputData Dataset with the input features
1855 | * @param params Basically, the maximum leaf node size
1856 | */
1857 | KDTreeSingleIndexDynamicAdaptor(const int dimensionality,
1858 | const DatasetAdaptor &inputData,
1859 | const KDTreeSingleIndexAdaptorParams ¶ms =
1860 | KDTreeSingleIndexAdaptorParams(),
1861 | const size_t maximumPointCount = 1000000000U)
1862 | : dataset(inputData), index_params(params), distance(inputData) {
1863 | treeCount = static_cast(std::log2(maximumPointCount));
1864 | pointCount = 0U;
1865 | dim = dimensionality;
1866 | treeIndex.clear();
1867 | if (DIM > 0)
1868 | dim = DIM;
1869 | m_leaf_max_size = params.leaf_max_size;
1870 | init();
1871 | const size_t num_initial_points = dataset.kdtree_get_point_count();
1872 | if (num_initial_points > 0) {
1873 | addPoints(0, num_initial_points - 1);
1874 | }
1875 | }
1876 |
1877 | /** Deleted copy constructor*/
1878 | KDTreeSingleIndexDynamicAdaptor(
1879 | const KDTreeSingleIndexDynamicAdaptor &) = delete;
1881 |
1882 | /** Add points to the set, Inserts all points from [start, end] */
1883 | void addPoints(IndexType start, IndexType end) {
1884 | size_t count = end - start + 1;
1885 | treeIndex.resize(treeIndex.size() + count);
1886 | for (IndexType idx = start; idx <= end; idx++) {
1887 | int pos = First0Bit(pointCount);
1888 | index[pos].vind.clear();
1889 | treeIndex[pointCount] = pos;
1890 | for (int i = 0; i < pos; i++) {
1891 | for (int j = 0; j < static_cast(index[i].vind.size()); j++) {
1892 | index[pos].vind.push_back(index[i].vind[j]);
1893 | if (treeIndex[index[i].vind[j]] != -1)
1894 | treeIndex[index[i].vind[j]] = pos;
1895 | }
1896 | index[i].vind.clear();
1897 | index[i].freeIndex(index[i]);
1898 | }
1899 | index[pos].vind.push_back(idx);
1900 | index[pos].buildIndex();
1901 | pointCount++;
1902 | }
1903 | }
1904 |
1905 | /** Remove a point from the set (Lazy Deletion) */
1906 | void removePoint(size_t idx) {
1907 | if (idx >= pointCount)
1908 | return;
1909 | treeIndex[idx] = -1;
1910 | }
1911 |
1912 | /**
1913 | * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored
1914 | * inside the result object.
1915 | *
1916 | * Params:
1917 | * result = the result object in which the indices of the
1918 | * nearest-neighbors are stored vec = the vector for which to search the
1919 | * nearest neighbors
1920 | *
1921 | * \tparam RESULTSET Should be any ResultSet
1922 | * \return True if the requested neighbors could be found.
1923 | * \sa knnSearch, radiusSearch
1924 | */
1925 | template
1926 | bool findNeighbors(RESULTSET &result, const ElementType *vec,
1927 | const SearchParams &searchParams) const {
1928 | for (size_t i = 0; i < treeCount; i++) {
1929 | index[i].findNeighbors(result, &vec[0], searchParams);
1930 | }
1931 | return result.full();
1932 | }
1933 | };
1934 |
1935 | /** An L2-metric KD-tree adaptor for working with data directly stored in an
1936 | * Eigen Matrix, without duplicating the data storage. Each row in the matrix
1937 | * represents a point in the state space.
1938 | *
1939 | * Example of usage:
1940 | * \code
1941 | * Eigen::Matrix mat;
1942 | * // Fill out "mat"...
1943 | *
1944 | * typedef KDTreeEigenMatrixAdaptor< Eigen::Matrix >
1945 | * my_kd_tree_t; const int max_leaf = 10; my_kd_tree_t mat_index(mat, max_leaf
1946 | * ); mat_index.index->buildIndex(); mat_index.index->... \endcode
1947 | *
1948 | * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality
1949 | * for the points in the data set, allowing more compiler optimizations. \tparam
1950 | * Distance The distance metric to use: nanoflann::metric_L1,
1951 | * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc.
1952 | */
1953 | template
1954 | struct KDTreeEigenMatrixAdaptor {
1955 | typedef KDTreeEigenMatrixAdaptor self_t;
1956 | typedef typename MatrixType::Scalar num_t;
1957 | typedef typename MatrixType::Index IndexType;
1958 | typedef
1959 | typename Distance::template traits::distance_t metric_t;
1960 | typedef KDTreeSingleIndexAdaptor
1962 | index_t;
1963 |
1964 | index_t *index; //! The kd-tree index for the user to call its methods as
1965 | //! usual with any other FLANN index.
1966 |
1967 | /// Constructor: takes a const ref to the matrix object with the data points
1968 | KDTreeEigenMatrixAdaptor(const size_t dimensionality,
1969 | const std::reference_wrapper &mat,
1970 | const int leaf_max_size = 10)
1971 | : m_data_matrix(mat) {
1972 | const auto dims = mat.get().cols();
1973 | if (size_t(dims) != dimensionality)
1974 | throw std::runtime_error(
1975 | "Error: 'dimensionality' must match column count in data matrix");
1976 | if (DIM > 0 && int(dims) != DIM)
1977 | throw std::runtime_error(
1978 | "Data set dimensionality does not match the 'DIM' template argument");
1979 | index =
1980 | new index_t(static_cast(dims), *this /* adaptor */,
1981 | nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size));
1982 | index->buildIndex();
1983 | }
1984 |
1985 | public:
1986 | /** Deleted copy constructor */
1987 | KDTreeEigenMatrixAdaptor(const self_t &) = delete;
1988 |
1989 | ~KDTreeEigenMatrixAdaptor() { delete index; }
1990 |
1991 | const std::reference_wrapper m_data_matrix;
1992 |
1993 | /** Query for the \a num_closest closest points to a given point (entered as
1994 | * query_point[0:dim-1]). Note that this is a short-cut method for
1995 | * index->findNeighbors(). The user can also call index->... methods as
1996 | * desired. \note nChecks_IGNORED is ignored but kept for compatibility with
1997 | * the original FLANN interface.
1998 | */
1999 | inline void query(const num_t *query_point, const size_t num_closest,
2000 | IndexType *out_indices, num_t *out_distances_sq,
2001 | const int /* nChecks_IGNORED */ = 10) const {
2002 | nanoflann::KNNResultSet resultSet(num_closest);
2003 | resultSet.init(out_indices, out_distances_sq);
2004 | index->findNeighbors(resultSet, query_point, nanoflann::SearchParams());
2005 | }
2006 |
2007 | /** @name Interface expected by KDTreeSingleIndexAdaptor
2008 | * @{ */
2009 |
2010 | const self_t &derived() const { return *this; }
2011 | self_t &derived() { return *this; }
2012 |
2013 | // Must return the number of data points
2014 | inline size_t kdtree_get_point_count() const {
2015 | return m_data_matrix.get().rows();
2016 | }
2017 |
2018 | // Returns the dim'th component of the idx'th point in the class:
2019 | inline num_t kdtree_get_pt(const IndexType idx, size_t dim) const {
2020 | return m_data_matrix.get().coeff(idx, IndexType(dim));
2021 | }
2022 |
2023 | // Optional bounding-box computation: return false to default to a standard
2024 | // bbox computation loop.
2025 | // Return true if the BBOX was already computed by the class and returned in
2026 | // "bb" so it can be avoided to redo it again. Look at bb.size() to find out
2027 | // the expected dimensionality (e.g. 2 or 3 for point clouds)
2028 | template bool kdtree_get_bbox(BBOX & /*bb*/) const {
2029 | return false;
2030 | }
2031 |
2032 | /** @} */
2033 |
2034 | }; // end of KDTreeEigenMatrixAdaptor
2035 | /** @} */
2036 |
2037 | /** @} */ // end of grouping
2038 | } // namespace nanoflann
2039 |
2040 | #endif /* NANOFLANN_HPP_ */
2041 |
--------------------------------------------------------------------------------
/Scancontext/tictoc.h:
--------------------------------------------------------------------------------
1 | // Author: Tong Qin qintonguav@gmail.com
2 | // Shaozu Cao saozu.cao@connect.ust.hk
3 |
4 | #pragma once
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | class TicToc
13 | {
14 | public:
15 | TicToc()
16 | {
17 | tic();
18 | }
19 |
20 | TicToc( bool _disp )
21 | {
22 | disp_ = _disp;
23 | tic();
24 | }
25 |
26 | void tic()
27 | {
28 | start = std::chrono::system_clock::now();
29 | }
30 |
31 | void toc( std::string _about_task )
32 | {
33 | end = std::chrono::system_clock::now();
34 | std::chrono::duration elapsed_seconds = end - start;
35 | double elapsed_ms = elapsed_seconds.count() * 1000;
36 |
37 | if( disp_ )
38 | {
39 | std::cout.precision(3); // 10 for sec, 3 for ms
40 | std::cout << _about_task << ": " << elapsed_ms << " msec." << std::endl;
41 | }
42 | }
43 |
44 | private:
45 | std::chrono::time_point start, end;
46 | bool disp_ = false;
47 | };
48 |
--------------------------------------------------------------------------------
/config/sc_config.yaml:
--------------------------------------------------------------------------------
1 | %YAML:1.0
2 |
3 | LeafSize: 0.1
--------------------------------------------------------------------------------
/img/loop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Freeecode/loopclosure_for_aloam/1a0d70cde6787b476a513ac7dbf0cb803e144ee5/img/loop.png
--------------------------------------------------------------------------------
/img/origin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Freeecode/loopclosure_for_aloam/1a0d70cde6787b476a513ac7dbf0cb803e144ee5/img/origin.png
--------------------------------------------------------------------------------
/launch/sc_optimization.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | sc_aloam
4 | 0.0.0
5 |
6 |
7 | This work is based on scancontext, which provided loopclosure detection for aloam
8 |
9 |
10 | Houzhan Zhang
11 |
12 | BSD
13 |
14 | catkin
15 | geometry_msgs
16 | nav_msgs
17 | roscpp
18 | rospy
19 | std_msgs
20 | rosbag
21 | sensor_msgs
22 | tf
23 | image_transport
24 | eigen_conversions
25 | message_generation
26 | message_runtime
27 | geometry_msgs
28 | nav_msgs
29 | sensor_msgs
30 | roscpp
31 | rospy
32 | std_msgs
33 | rosbag
34 | tf
35 | eigen_conversions
36 | image_transport
37 |
38 |
--------------------------------------------------------------------------------
/src/SC_Optimization_node.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * @brief loop closure detection for aloam
3 | *
4 | */
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | //PCL
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 |
21 | //ROS
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include "Scancontext/Scancontext.h"
28 |
29 | // GTSAM
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 |
40 | ros::Publisher pub_path;
41 |
42 | // Buffer
43 | std::queue fullPointsBuf;
44 | std::queue odometryBuf;
45 | std::vector::Ptr> laserCloudBuf;
46 | std::vector headerBuf;
47 |
48 | // Mutex
49 | std::mutex points_mutex;
50 | std::mutex odom_mutex;
51 | std::mutex buf_mutex;
52 | std::mutex graph_mutex;
53 | std::mutex flag_mutex;
54 |
55 | // median pointcloud
56 | pcl::PointCloud::Ptr laserCloudFull;
57 | pcl::PointCloud::Ptr final;
58 | pcl::PointCloud::Ptr tmp;
59 | pcl::VoxelGrid downSizeFilterScancontext;
60 |
61 | // Scancontext
62 | SCManager scManager;
63 |
64 | // gtsam optimization
65 | gtsam::NonlinearFactorGraph gtSAMgraph;
66 | gtsam::noiseModel::Diagonal::shared_ptr priorNoisemodel;
67 | gtsam::noiseModel::Diagonal::shared_ptr odomNoisemodel;
68 | gtsam::noiseModel::Diagonal::shared_ptr loopNoisemode;
69 | gtsam::noiseModel::Base::shared_ptr robustNoiseModel;
70 | gtsam::Values initials;
71 | gtsam::Values results;
72 | gtsam::Pose3 lastPose;
73 | gtsam::Pose3 currentPose;
74 |
75 | std_msgs::Header header;
76 | bool first_msg = true;
77 | bool optimize_flag = false;
78 | int odom_num = 0;
79 | int laser_num = 0;
80 | int history_id = -1;
81 | bool zero_flag = true;
82 |
83 | void init()
84 | {
85 | // allocate memory
86 | laserCloudFull.reset(new pcl::PointCloud);
87 | final.reset(new pcl::PointCloud);
88 | tmp.reset(new pcl::PointCloud);
89 |
90 | // set voxel filter
91 | float filter_size = 0.4;
92 | downSizeFilterScancontext.setLeafSize(filter_size, filter_size, filter_size);
93 |
94 | // noise model
95 | //gtsam::Vector6 PriorVector;
96 | gtsam::Vector PriorVector(6);
97 | PriorVector << 1e-6, 1e-6, 1e-6, 1e-6, 1e-6, 1e-6;
98 | priorNoisemodel = gtsam::noiseModel::Diagonal::Variances(PriorVector);
99 | gtsam::Vector6 Constraint(6);
100 | Constraint << 0.10, 0.10, 0.10, 0.10, 0.10, 0.10;
101 | odomNoisemodel = gtsam::noiseModel::Diagonal::Variances(Constraint);
102 | loopNoisemode = gtsam::noiseModel::Diagonal::Variances(Constraint);
103 | robustNoiseModel = gtsam::noiseModel::Robust::Create(
104 | gtsam::noiseModel::mEstimator::Cauchy::Create(1),
105 | loopNoisemode);
106 | }
107 |
108 | gtsam::Pose3 msg2Pose(nav_msgs::OdometryConstPtr &msg)
109 | {
110 | Eigen::Quaterniond q(msg->pose.pose.orientation.w, msg->pose.pose.orientation.x,
111 | msg->pose.pose.orientation.y, msg->pose.pose.orientation.z);
112 | Eigen::Vector3d t(msg->pose.pose.position.x, msg->pose.pose.position.y, msg->pose.pose.position.z);
113 |
114 | gtsam::Rot3 rot(q.w(), q.x(), q.y(), q.z());
115 | gtsam::Point3 trans(t.x(), t.y(), t.z());
116 | return gtsam::Pose3(rot, trans);
117 | }
118 |
119 | gtsam::Pose3 eigen2Pose(const Eigen::Matrix4f& transform)
120 | {
121 | Eigen::Matrix3d rotation;
122 | for(int i = 0; i < 3; i++)
123 | for(int j = 0; j < 3; j++)
124 | rotation(i,j) = transform(i,j);
125 |
126 | Eigen::Quaterniond q(rotation);
127 | Eigen::Vector3d t(transform(0,3), transform(1,3), transform(2,3));
128 |
129 | gtsam::Rot3 rot(q.w(), q.x(), q.y(), q.z());
130 | gtsam::Point3 trans(t.x(), t.y(), t.z());
131 | return gtsam::Pose3(rot, trans);
132 | }
133 |
134 | void GraphOptimization()
135 | {
136 | ros::Rate loop(1);
137 | while (ros::ok())
138 | {
139 | if(optimize_flag == false)
140 | continue;
141 |
142 | ROS_INFO("Start optimizing...");
143 | results.clear();
144 |
145 | graph_mutex.lock();
146 |
147 | gtsam::LevenbergMarquardtParams param;
148 | param.maxIterations = 60;
149 | param.setVerbosity("TERMINATION"); // this will show info about stopping conditions
150 | param.setVerbosity("SILENT");
151 | gtsam::LevenbergMarquardtOptimizer optimizer(gtSAMgraph, initials, param);
152 | results = optimizer.optimize();
153 |
154 | nav_msgs::Path path;
155 | int id = 0;
156 | for(const gtsam::Values::ConstKeyValuePair& key_value: results)
157 | {
158 | gtsam::Pose3 transform = key_value.value.cast();
159 | Eigen::Quaterniond q = transform.rotation().toQuaternion();
160 | Eigen::Vector3d t = transform.translation();
161 |
162 | initials.update(id, transform);
163 |
164 | geometry_msgs::PoseStamped posestamp;
165 | posestamp.header = headerBuf[id];
166 | posestamp.pose.orientation.w = q.w();
167 | posestamp.pose.orientation.x = q.x();
168 | posestamp.pose.orientation.y = q.y();
169 | posestamp.pose.orientation.z = q.z();
170 | posestamp.pose.position.x = t.x();
171 | posestamp.pose.position.y = t.y();
172 | posestamp.pose.position.z = t.z();
173 |
174 | path.poses.push_back(posestamp);
175 |
176 | id++;
177 | }
178 |
179 | path.header = headerBuf[id-1];
180 | pub_path.publish(path);
181 |
182 | graph_mutex.unlock();
183 |
184 | flag_mutex.lock();
185 | optimize_flag = false;
186 | flag_mutex.unlock();
187 |
188 | loop.sleep();
189 | }
190 | }
191 |
192 |
193 | bool geometryVerfication(int curr_id, int match_id, gtsam::Pose3& relative)
194 | {
195 | double residual_q = 0.0;
196 | double residual_t = 0.0;
197 |
198 | gtsam::Pose3 pose1 = initials.at(curr_id);
199 | gtsam::Pose3 pose2 = initials.at(match_id);
200 | gtsam::Pose3 transform1 = pose2.between(pose1);
201 | gtsam::Pose3 transform = transform1.between(relative);
202 | residual_t += std::abs(transform.translation().x()) + std::abs(transform.translation().y()) +
203 | std::abs(transform.translation().z());
204 | residual_q += std::abs(transform.rotation().toQuaternion().w()-1) + std::abs(transform.rotation().toQuaternion().x())
205 | + std::abs(transform.rotation().toQuaternion().y()) + std::abs(transform.rotation().toQuaternion().z());
206 |
207 | if(residual_q > 0.02 || residual_t > 0.5)
208 | return false;
209 |
210 | return true;
211 | }
212 |
213 | void odom_callback(const nav_msgs::OdometryConstPtr& msg)
214 | {
215 | odom_mutex.lock();
216 | odometryBuf.push(msg);
217 | odom_num++;
218 | odom_mutex.unlock();
219 | }
220 |
221 | void pointcloud_callback(const sensor_msgs::PointCloud2ConstPtr& msg)
222 | {
223 | points_mutex.lock();
224 | fullPointsBuf.push(msg);
225 | laser_num++;
226 | points_mutex.unlock();
227 | }
228 |
229 | void detect_loop_closure()
230 | {
231 | if(fullPointsBuf.empty() || odometryBuf.empty())
232 | return;
233 |
234 | buf_mutex.lock();
235 |
236 | double time1 = fullPointsBuf.front()->header.stamp.toSec();
237 | double time2 = odometryBuf.front()->header.stamp.toSec();
238 | if(!fullPointsBuf.empty() && std::abs(time1 - time2) > 0.005)
239 | {
240 | fullPointsBuf.pop();
241 | buf_mutex.unlock();
242 | return;
243 | }
244 |
245 | if(!odometryBuf.empty() && std::abs(time1 - time2) > 0.005)
246 | {
247 | odometryBuf.pop();
248 | buf_mutex.unlock();
249 | return;
250 | }
251 | //std::cout << "laser: " << laser_num << "\nodom: " << odom_num << std::endl;
252 |
253 | buf_mutex.unlock();
254 |
255 | // Process pointcloud msg
256 | tmp->clear();
257 | final->clear();
258 | laserCloudFull->clear();
259 |
260 | points_mutex.lock();
261 | pcl::fromROSMsg(*fullPointsBuf.front(), *laserCloudFull);
262 | fullPointsBuf.pop();
263 | points_mutex.unlock();
264 |
265 | // DownSample Pointcloud
266 | downSizeFilterScancontext.setInputCloud(laserCloudFull);
267 | downSizeFilterScancontext.filter(*tmp);
268 | laserCloudBuf.push_back(tmp);
269 |
270 | // Make Scancontext
271 | scManager.makeAndSaveScancontextAndKeys(*tmp);
272 | // end process pointcloud
273 |
274 | // Process odometry msg
275 | odom_mutex.lock();
276 | currentPose = msg2Pose(odometryBuf.front());
277 | header = odometryBuf.front()->header;
278 | odometryBuf.pop();
279 | odom_mutex.unlock();
280 | headerBuf.push_back(header);
281 | // end process odometry
282 |
283 | graph_mutex.lock();
284 | int key = laserCloudBuf.size()-1;
285 | initials.insert(key, currentPose);
286 |
287 | if(first_msg)
288 | {
289 | // add PriorFactor; to graph
290 | gtSAMgraph.add(gtsam::PriorFactor(key, currentPose, priorNoisemodel));
291 | first_msg = false;
292 | }
293 | else
294 | {
295 | //gtsam::Pose3 increment = lastPose.inverse() * currentPose;
296 | gtSAMgraph.add(gtsam::BetweenFactor(key-1, key, lastPose.between(currentPose), odomNoisemodel));
297 | }
298 | graph_mutex.unlock();
299 |
300 | int current_id = laserCloudBuf.size()-1;
301 |
302 | // Detect loop closure
303 | auto result = scManager.detectLoopClosureID();
304 | history_id = result.first;
305 |
306 | // Verify loop closure
307 | if(history_id != -1)
308 | {
309 | if(!zero_flag)
310 | return;
311 | if(history_id == 0)
312 | zero_flag = false;
313 |
314 | pcl::IterativeClosestPoint icp;
315 | icp.setMaxCorrespondenceDistance(100);
316 | icp.setMaximumIterations(20);
317 | icp.setTransformationEpsilon(1e-6);
318 | icp.setEuclideanFitnessEpsilon(1e-6);
319 | icp.setRANSACIterations(0);
320 |
321 | icp.setInputSource(tmp);
322 | icp.setInputTarget(laserCloudBuf[history_id]);
323 | icp.align(*final);
324 |
325 | if(icp.hasConverged() == true && icp.getFitnessScore() < 0.001)
326 | {
327 | ROS_INFO("[Loop info]: from %d to %d", current_id, history_id);
328 |
329 | Eigen::Matrix4f loop = icp.getFinalTransformation();
330 | gtsam::Pose3 poseFrom = eigen2Pose(loop);
331 | gtsam::Pose3 poseTo = gtsam::Pose3::identity();
332 |
333 | //if(!geometryVerfication(current_id, history_id, poseFrom))
334 | // return;
335 | graph_mutex.unlock();
336 | gtSAMgraph.add(gtsam::BetweenFactor(current_id, history_id, poseFrom.between(poseTo), loopNoisemode));
337 | graph_mutex.unlock();
338 |
339 | flag_mutex.lock();
340 | optimize_flag = true;
341 | flag_mutex.unlock();
342 | }
343 | }
344 |
345 | lastPose = currentPose;
346 | }
347 |
348 | int main(int argc, char* argv[])
349 | {
350 | ros::init(argc, argv, "sc_optimization");
351 | ros::NodeHandle nh("~");
352 |
353 | ros::Subscriber sub_odom = nh.subscribe("/odom", 1000, odom_callback);
354 | ros::Subscriber sub_pointcloud = nh.subscribe("/velodyne_points", 1000, pointcloud_callback);
355 |
356 | pub_path = nh.advertise("final_path", 10);
357 |
358 | init();
359 |
360 | std::thread optimization_thread{GraphOptimization};
361 |
362 | ros::Rate rate(30);
363 | while (ros::ok())
364 | {
365 | detect_loop_closure();
366 | ros::spinOnce();
367 | rate.sleep();
368 | }
369 |
370 | optimization_thread.join();
371 |
372 | return 0;
373 | }
374 |
--------------------------------------------------------------------------------