├── README.md ├── build └── CMakeLists.txt ├── include ├── deform_node.h ├── dual_quat.h ├── dynamic_fusion.h ├── frame.h ├── nanoflann │ └── include │ │ └── nanoflann.hpp ├── point.h └── warp_field.h ├── installDependencies.sh ├── projectBuild.sh ├── report ├── build.sh ├── report.bib └── report.tex └── src ├── deform_node.cpp ├── dual_quat.cpp ├── dynamic_fusion.cpp ├── main.cpp ├── point.cpp ├── saxpy.cu └── warp_field.cpp /README.md: -------------------------------------------------------------------------------- 1 | # dynamicfusion 2 | BEng Individual project - DynamicFusion: Reconstruction and Tracking of Non-rigid Scenes in Real-Time 3 | -------------------------------------------------------------------------------- /build/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | set (CMAKE_CXX_STANDARD 11) 3 | 4 | project(dynamicfusion) 5 | 6 | find_package(Ceres REQUIRED) 7 | 8 | set(INCLUDE_DIR 9 | ../include 10 | ../include/nanoflann/include 11 | ${CERES_INCLUDE_DIRS} 12 | ) 13 | 14 | include_directories(${INCLUDE_DIR}) 15 | 16 | set(SOURCES 17 | ../src/main.cpp 18 | 19 | ../include/point.h 20 | ../src/point.cpp 21 | 22 | ../include/dual_quat.h 23 | ../src/dual_quat.cpp 24 | 25 | ../include/dynamic_fusion.h 26 | ../src/dynamic_fusion.cpp 27 | 28 | ../include/deform_node.h 29 | ../src/deform_node.cpp 30 | 31 | ../include/frame.h 32 | 33 | ../include/warp_field.h 34 | ../src/warp_field.cpp 35 | 36 | ../include/nanoflann/include/nanoflann.hpp 37 | ) 38 | 39 | add_executable(dynamicfusion ${SOURCES}) 40 | target_link_libraries(dynamicfusion ${CERES_LIBRARIES}) 41 | 42 | -------------------------------------------------------------------------------- /include/deform_node.h: -------------------------------------------------------------------------------- 1 | #ifndef DEFORM_NODE_H 2 | #define DEFORM_NODE_H 3 | 4 | #include "point.h" 5 | #include "dual_quat.h" 6 | 7 | class warp_field_t; 8 | 9 | /* 10 | * a class representing one of the deformation nodes that make up the 11 | * warp field. 12 | */ 13 | class deform_node_t { 14 | public: 15 | // constructor 16 | deform_node_t(warp_field_t * wf, point_t p, dual_quat_t dq, float w); 17 | 18 | // energy function used for enforcing warp field regularisation 19 | float regularisation(float phi); 20 | 21 | // weight for altering radius of influence 22 | float weight(point_t p); 23 | 24 | // getters 25 | dual_quat_t get_transform(); 26 | point_t get_position(); 27 | 28 | private: 29 | // pointer to warp field to allow node to find neighbours 30 | warp_field_t * warp_field; 31 | 32 | // properties of deformation node as in dynamic fusion paper 33 | point_t position; 34 | dual_quat_t transform; 35 | float radial_weight; 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /include/dual_quat.h: -------------------------------------------------------------------------------- 1 | #ifndef DUAL_QUAT_H 2 | #define DUAL_QUAT_H 3 | 4 | #include 5 | 6 | typedef boost::math::quaternion quat_t; 7 | 8 | class dual_quat_t { 9 | public: 10 | /* 11 | * constructors 12 | */ 13 | dual_quat_t(); 14 | dual_quat_t(quat_t real, quat_t dual); 15 | 16 | /* 17 | * overloaded operators 18 | */ 19 | dual_quat_t operator*(float scale); 20 | void operator+=(const dual_quat_t & other); 21 | 22 | /* 23 | * public methods 24 | */ 25 | void normalise(); 26 | 27 | private: 28 | // Rotational part 29 | quat_t real; 30 | 31 | // translation part 32 | quat_t dual; 33 | }; 34 | #endif 35 | -------------------------------------------------------------------------------- /include/dynamic_fusion.h: -------------------------------------------------------------------------------- 1 | #ifndef DYNAMIC_FUSION_H 2 | #define DYNAMIC_FUSION_H 3 | 4 | #include "warp_field.h" 5 | 6 | class dynamic_fusion_t { 7 | public: 8 | /* 9 | constructors and destructors 10 | */ 11 | dynamic_fusion_t(); 12 | ~dynamic_fusion_t(); 13 | 14 | // main public method 15 | void execute(); 16 | 17 | private: 18 | warp_field_t warp_field; 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /include/frame.h: -------------------------------------------------------------------------------- 1 | #ifndef FRAME_H 2 | #define FRAME_H 3 | 4 | #include "point.h" 5 | 6 | #include 7 | 8 | /* 9 | a small container class representing a frame. 10 | contains a unique id, and the vertex positions and normals of the frame 11 | */ 12 | class frame_t { 13 | public: 14 | // constructor 15 | frame_t(int frame_id); 16 | 17 | // destructor 18 | ~frame_t(); 19 | 20 | private: 21 | // unique id for frame 22 | int frame_id; 23 | 24 | // point clouds storing position and normal data 25 | std::vector vertices; 26 | std::vector normals; 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /include/nanoflann/include/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 documentation 44 | */ 45 | 46 | #ifndef NANOFLANN_HPP_ 47 | #define NANOFLANN_HPP_ 48 | 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include // for fwrite() 54 | #define _USE_MATH_DEFINES // Required by MSVC to define M_PI,etc. in 55 | #include // for abs() 56 | #include // for abs() 57 | #include 58 | #include 59 | 60 | // Avoid conflicting declaration of min/max macros in windows headers 61 | #if !defined(NOMINMAX) && (defined(_WIN32) || defined(_WIN32_) || defined(WIN32) || defined(_WIN64)) 62 | # define NOMINMAX 63 | # ifdef max 64 | # undef max 65 | # undef min 66 | # endif 67 | #endif 68 | 69 | namespace nanoflann 70 | { 71 | /** @addtogroup nanoflann_grp nanoflann C++ library for ANN 72 | * @{ */ 73 | 74 | /** Library version: 0xMmP (M=Major,m=minor,P=patch) */ 75 | #define NANOFLANN_VERSION 0x123 76 | 77 | /** @addtogroup result_sets_grp Result set classes 78 | * @{ */ 79 | template 80 | class KNNResultSet 81 | { 82 | IndexType * indices; 83 | DistanceType* dists; 84 | CountType capacity; 85 | CountType count; 86 | 87 | public: 88 | inline KNNResultSet(CountType capacity_) : indices(0), dists(0), capacity(capacity_), count(0) 89 | { 90 | } 91 | 92 | inline void init(IndexType* indices_, DistanceType* dists_) 93 | { 94 | indices = indices_; 95 | dists = dists_; 96 | count = 0; 97 | if (capacity) 98 | dists[capacity-1] = (std::numeric_limits::max)(); 99 | } 100 | 101 | inline CountType size() const 102 | { 103 | return count; 104 | } 105 | 106 | inline bool full() const 107 | { 108 | return count == capacity; 109 | } 110 | 111 | 112 | /** 113 | * Called during search to add an element matching the criteria. 114 | * @return true if the search should be continued, false if the results are sufficient 115 | */ 116 | inline bool addPoint(DistanceType dist, IndexType index) 117 | { 118 | CountType i; 119 | for (i = count; i > 0; --i) { 120 | #ifdef NANOFLANN_FIRST_MATCH // If defined and two points have the same distance, the one with the lowest-index will be returned first. 121 | if ( (dists[i-1] > dist) || ((dist == dists[i-1]) && (indices[i-1] > index)) ) { 122 | #else 123 | if (dists[i-1] > dist) { 124 | #endif 125 | if (i < capacity) { 126 | dists[i] = dists[i-1]; 127 | indices[i] = indices[i-1]; 128 | } 129 | } 130 | else break; 131 | } 132 | if (i < capacity) { 133 | dists[i] = dist; 134 | indices[i] = index; 135 | } 136 | if (count < capacity) count++; 137 | 138 | // tell caller that the search shall continue 139 | return true; 140 | } 141 | 142 | inline DistanceType worstDist() const 143 | { 144 | return dists[capacity-1]; 145 | } 146 | }; 147 | 148 | /** operator "<" for std::sort() */ 149 | struct IndexDist_Sorter 150 | { 151 | /** PairType will be typically: std::pair */ 152 | template 153 | inline bool operator()(const PairType &p1, const PairType &p2) const { 154 | return p1.second < p2.second; 155 | } 156 | }; 157 | 158 | /** 159 | * A result-set class used when performing a radius based search. 160 | */ 161 | template 162 | class RadiusResultSet 163 | { 164 | public: 165 | const DistanceType radius; 166 | 167 | std::vector > &m_indices_dists; 168 | 169 | inline RadiusResultSet(DistanceType radius_, std::vector > &indices_dists) : radius(radius_), m_indices_dists(indices_dists) 170 | { 171 | init(); 172 | } 173 | 174 | inline void init() { clear(); } 175 | inline void clear() { m_indices_dists.clear(); } 176 | 177 | inline size_t size() const { return m_indices_dists.size(); } 178 | 179 | inline bool full() const { return true; } 180 | 181 | /** 182 | * Called during search to add an element matching the criteria. 183 | * @return true if the search should be continued, false if the results are sufficient 184 | */ 185 | inline bool addPoint(DistanceType dist, IndexType index) 186 | { 187 | if (dist < radius) 188 | m_indices_dists.push_back(std::make_pair(index, dist)); 189 | return true; 190 | } 191 | 192 | inline DistanceType worstDist() const { return radius; } 193 | 194 | /** 195 | * Find the worst result (furtherest neighbor) without copying or sorting 196 | * Pre-conditions: size() > 0 197 | */ 198 | std::pair worst_item() const 199 | { 200 | if (m_indices_dists.empty()) throw std::runtime_error("Cannot invoke RadiusResultSet::worst_item() on an empty list of results."); 201 | typedef typename std::vector >::const_iterator DistIt; 202 | DistIt it = std::max_element(m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter()); 203 | return *it; 204 | } 205 | }; 206 | 207 | 208 | /** @} */ 209 | 210 | 211 | /** @addtogroup loadsave_grp Load/save auxiliary functions 212 | * @{ */ 213 | template 214 | void save_value(FILE* stream, const T& value, size_t count = 1) 215 | { 216 | fwrite(&value, sizeof(value), count, stream); 217 | } 218 | 219 | template 220 | void save_value(FILE* stream, const std::vector& value) 221 | { 222 | size_t size = value.size(); 223 | fwrite(&size, sizeof(size_t), 1, stream); 224 | fwrite(&value[0], sizeof(T), size, stream); 225 | } 226 | 227 | template 228 | void load_value(FILE* stream, T& value, size_t count = 1) 229 | { 230 | size_t read_cnt = fread(&value, sizeof(value), count, stream); 231 | if (read_cnt != count) { 232 | throw std::runtime_error("Cannot read from file"); 233 | } 234 | } 235 | 236 | 237 | template 238 | void load_value(FILE* stream, std::vector& value) 239 | { 240 | size_t size; 241 | size_t read_cnt = fread(&size, sizeof(size_t), 1, stream); 242 | if (read_cnt != 1) { 243 | throw std::runtime_error("Cannot read from file"); 244 | } 245 | value.resize(size); 246 | read_cnt = fread(&value[0], sizeof(T), size, stream); 247 | if (read_cnt != size) { 248 | throw std::runtime_error("Cannot read from file"); 249 | } 250 | } 251 | /** @} */ 252 | 253 | 254 | /** @addtogroup metric_grp Metric (distance) classes 255 | * @{ */ 256 | 257 | struct Metric 258 | { 259 | }; 260 | 261 | /** Manhattan distance functor (generic version, optimized for high-dimensionality data sets). 262 | * Corresponding distance traits: nanoflann::metric_L1 263 | * \tparam T Type of the elements (e.g. double, float, uint8_t) 264 | * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) 265 | */ 266 | template 267 | struct L1_Adaptor 268 | { 269 | typedef T ElementType; 270 | typedef _DistanceType DistanceType; 271 | 272 | const DataSource &data_source; 273 | 274 | L1_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } 275 | 276 | inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size, DistanceType worst_dist = -1) const 277 | { 278 | DistanceType result = DistanceType(); 279 | const T* last = a + size; 280 | const T* lastgroup = last - 3; 281 | size_t d = 0; 282 | 283 | /* Process 4 items with each loop for efficiency. */ 284 | while (a < lastgroup) { 285 | const DistanceType diff0 = std::abs(a[0] - data_source.kdtree_get_pt(b_idx,d++)); 286 | const DistanceType diff1 = std::abs(a[1] - data_source.kdtree_get_pt(b_idx,d++)); 287 | const DistanceType diff2 = std::abs(a[2] - data_source.kdtree_get_pt(b_idx,d++)); 288 | const DistanceType diff3 = std::abs(a[3] - data_source.kdtree_get_pt(b_idx,d++)); 289 | result += diff0 + diff1 + diff2 + diff3; 290 | a += 4; 291 | if ((worst_dist > 0) && (result > worst_dist)) { 292 | return result; 293 | } 294 | } 295 | /* Process last 0-3 components. Not needed for standard vector lengths. */ 296 | while (a < last) { 297 | result += std::abs( *a++ - data_source.kdtree_get_pt(b_idx, d++) ); 298 | } 299 | return result; 300 | } 301 | 302 | template 303 | inline DistanceType accum_dist(const U a, const V b, int ) const 304 | { 305 | return std::abs(a-b); 306 | } 307 | }; 308 | 309 | /** Squared Euclidean distance functor (generic version, optimized for high-dimensionality data sets). 310 | * Corresponding distance traits: nanoflann::metric_L2 311 | * \tparam T Type of the elements (e.g. double, float, uint8_t) 312 | * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) 313 | */ 314 | template 315 | struct L2_Adaptor 316 | { 317 | typedef T ElementType; 318 | typedef _DistanceType DistanceType; 319 | 320 | const DataSource &data_source; 321 | 322 | L2_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } 323 | 324 | inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size, DistanceType worst_dist = -1) const 325 | { 326 | DistanceType result = DistanceType(); 327 | const T* last = a + size; 328 | const T* lastgroup = last - 3; 329 | size_t d = 0; 330 | 331 | /* Process 4 items with each loop for efficiency. */ 332 | while (a < lastgroup) { 333 | const DistanceType diff0 = a[0] - data_source.kdtree_get_pt(b_idx,d++); 334 | const DistanceType diff1 = a[1] - data_source.kdtree_get_pt(b_idx,d++); 335 | const DistanceType diff2 = a[2] - data_source.kdtree_get_pt(b_idx,d++); 336 | const DistanceType diff3 = a[3] - data_source.kdtree_get_pt(b_idx,d++); 337 | result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; 338 | a += 4; 339 | if ((worst_dist > 0) && (result > worst_dist)) { 340 | return result; 341 | } 342 | } 343 | /* Process last 0-3 components. Not needed for standard vector lengths. */ 344 | while (a < last) { 345 | const DistanceType diff0 = *a++ - data_source.kdtree_get_pt(b_idx, d++); 346 | result += diff0 * diff0; 347 | } 348 | return result; 349 | } 350 | 351 | template 352 | inline DistanceType accum_dist(const U a, const V b, int ) const 353 | { 354 | return (a - b) * (a - b); 355 | } 356 | }; 357 | 358 | /** Squared Euclidean (L2) distance functor (suitable for low-dimensionality datasets, like 2D or 3D point clouds) 359 | * Corresponding distance traits: nanoflann::metric_L2_Simple 360 | * \tparam T Type of the elements (e.g. double, float, uint8_t) 361 | * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) 362 | */ 363 | template 364 | struct L2_Simple_Adaptor 365 | { 366 | typedef T ElementType; 367 | typedef _DistanceType DistanceType; 368 | 369 | const DataSource &data_source; 370 | 371 | L2_Simple_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } 372 | 373 | inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { 374 | DistanceType result = DistanceType(); 375 | for (size_t i = 0; i < size; ++i) { 376 | const DistanceType diff = a[i] - data_source.kdtree_get_pt(b_idx, i); 377 | result += diff * diff; 378 | } 379 | return result; 380 | } 381 | 382 | template 383 | inline DistanceType accum_dist(const U a, const V b, int ) const 384 | { 385 | return (a - b) * (a - b); 386 | } 387 | }; 388 | 389 | /** SO2 distance functor 390 | * Corresponding distance traits: nanoflann::metric_SO2 391 | * \tparam T Type of the elements (e.g. double, float) 392 | * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double) 393 | * orientation is constrained to be in [-pi, pi] 394 | */ 395 | template 396 | struct SO2_Adaptor 397 | { 398 | typedef T ElementType; 399 | typedef _DistanceType DistanceType; 400 | 401 | const DataSource &data_source; 402 | 403 | SO2_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } 404 | 405 | inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { 406 | return accum_dist(a[size-1], data_source.kdtree_get_pt(b_idx, size - 1) , size - 1); 407 | } 408 | 409 | template 410 | inline DistanceType accum_dist(const U a, const V b, int ) const 411 | { 412 | DistanceType result = DistanceType(); 413 | result = b - a; 414 | if (result > M_PI) 415 | result -= 2. * M_PI; 416 | else if (result < -M_PI) 417 | result += 2. * M_PI; 418 | return result; 419 | } 420 | }; 421 | 422 | /** SO3 distance functor (Uses L2_Simple) 423 | * Corresponding distance traits: nanoflann::metric_SO3 424 | * \tparam T Type of the elements (e.g. double, float) 425 | * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double) 426 | */ 427 | template 428 | struct SO3_Adaptor 429 | { 430 | typedef T ElementType; 431 | typedef _DistanceType DistanceType; 432 | 433 | L2_Simple_Adaptor distance_L2_Simple; 434 | 435 | SO3_Adaptor(const DataSource &_data_source) : distance_L2_Simple(_data_source) { } 436 | 437 | inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { 438 | return distance_L2_Simple.evalMetric(a, b_idx, size); 439 | } 440 | 441 | template 442 | inline DistanceType accum_dist(const U a, const V b, int idx) const 443 | { 444 | return distance_L2_Simple.accum_dist(a, b, idx); 445 | } 446 | }; 447 | 448 | /** Metaprogramming helper traits class for the L1 (Manhattan) metric */ 449 | struct metric_L1 : public Metric 450 | { 451 | template 452 | struct traits { 453 | typedef L1_Adaptor distance_t; 454 | }; 455 | }; 456 | /** Metaprogramming helper traits class for the L2 (Euclidean) metric */ 457 | struct metric_L2 : public Metric 458 | { 459 | template 460 | struct traits { 461 | typedef L2_Adaptor distance_t; 462 | }; 463 | }; 464 | /** Metaprogramming helper traits class for the L2_simple (Euclidean) metric */ 465 | struct metric_L2_Simple : public Metric 466 | { 467 | template 468 | struct traits { 469 | typedef L2_Simple_Adaptor distance_t; 470 | }; 471 | }; 472 | /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ 473 | struct metric_SO2 : public Metric 474 | { 475 | template 476 | struct traits { 477 | typedef SO2_Adaptor distance_t; 478 | }; 479 | }; 480 | /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ 481 | struct metric_SO3 : public Metric 482 | { 483 | template 484 | struct traits { 485 | typedef SO3_Adaptor distance_t; 486 | }; 487 | }; 488 | 489 | /** @} */ 490 | 491 | /** @addtogroup param_grp Parameter structs 492 | * @{ */ 493 | 494 | /** Parameters (see README.md) */ 495 | struct KDTreeSingleIndexAdaptorParams 496 | { 497 | KDTreeSingleIndexAdaptorParams(size_t _leaf_max_size = 10) : 498 | leaf_max_size(_leaf_max_size) 499 | {} 500 | 501 | size_t leaf_max_size; 502 | }; 503 | 504 | /** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */ 505 | struct SearchParams 506 | { 507 | /** Note: The first argument (checks_IGNORED_) is ignored, but kept for compatibility with the FLANN interface */ 508 | SearchParams(int checks_IGNORED_ = 32, float eps_ = 0, bool sorted_ = true ) : 509 | checks(checks_IGNORED_), eps(eps_), sorted(sorted_) {} 510 | 511 | int checks; //!< Ignored parameter (Kept for compatibility with the FLANN interface). 512 | float eps; //!< search for eps-approximate neighbours (default: 0) 513 | bool sorted; //!< only for radius search, require neighbours sorted by distance (default: true) 514 | }; 515 | /** @} */ 516 | 517 | 518 | /** @addtogroup memalloc_grp Memory allocation 519 | * @{ */ 520 | 521 | /** 522 | * Allocates (using C's malloc) a generic type T. 523 | * 524 | * Params: 525 | * count = number of instances to allocate. 526 | * Returns: pointer (of type T*) to memory buffer 527 | */ 528 | template 529 | inline T* allocate(size_t count = 1) 530 | { 531 | T* mem = static_cast( ::malloc(sizeof(T)*count)); 532 | return mem; 533 | } 534 | 535 | 536 | /** 537 | * Pooled storage allocator 538 | * 539 | * The following routines allow for the efficient allocation of storage in 540 | * small chunks from a specified pool. Rather than allowing each structure 541 | * to be freed individually, an entire pool of storage is freed at once. 542 | * This method has two advantages over just using malloc() and free(). First, 543 | * it is far more efficient for allocating small objects, as there is 544 | * no overhead for remembering all the information needed to free each 545 | * object or consolidating fragmented memory. Second, the decision about 546 | * how long to keep an object is made at the time of allocation, and there 547 | * is no need to track down all the objects to free them. 548 | * 549 | */ 550 | 551 | const size_t WORDSIZE = 16; 552 | const size_t BLOCKSIZE = 8192; 553 | 554 | class PooledAllocator 555 | { 556 | /* We maintain memory alignment to word boundaries by requiring that all 557 | allocations be in multiples of the machine wordsize. */ 558 | /* Size of machine word in bytes. Must be power of 2. */ 559 | /* Minimum number of bytes requested at a time from the system. Must be multiple of WORDSIZE. */ 560 | 561 | 562 | size_t remaining; /* Number of bytes left in current block of storage. */ 563 | void* base; /* Pointer to base of current block of storage. */ 564 | void* loc; /* Current location in block to next allocate memory. */ 565 | 566 | void internal_init() 567 | { 568 | remaining = 0; 569 | base = NULL; 570 | usedMemory = 0; 571 | wastedMemory = 0; 572 | } 573 | 574 | public: 575 | size_t usedMemory; 576 | size_t wastedMemory; 577 | 578 | /** 579 | Default constructor. Initializes a new pool. 580 | */ 581 | PooledAllocator() { 582 | internal_init(); 583 | } 584 | 585 | /** 586 | * Destructor. Frees all the memory allocated in this pool. 587 | */ 588 | ~PooledAllocator() { 589 | free_all(); 590 | } 591 | 592 | /** Frees all allocated memory chunks */ 593 | void free_all() 594 | { 595 | while (base != NULL) { 596 | void *prev = *(static_cast( base)); /* Get pointer to prev block. */ 597 | ::free(base); 598 | base = prev; 599 | } 600 | internal_init(); 601 | } 602 | 603 | /** 604 | * Returns a pointer to a piece of new memory of the given size in bytes 605 | * allocated from the pool. 606 | */ 607 | void* malloc(const size_t req_size) 608 | { 609 | /* Round size up to a multiple of wordsize. The following expression 610 | only works for WORDSIZE that is a power of 2, by masking last bits of 611 | incremented size to zero. 612 | */ 613 | const size_t size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1); 614 | 615 | /* Check whether a new block must be allocated. Note that the first word 616 | of a block is reserved for a pointer to the previous block. 617 | */ 618 | if (size > remaining) { 619 | 620 | wastedMemory += remaining; 621 | 622 | /* Allocate new storage. */ 623 | const size_t blocksize = (size + sizeof(void*) + (WORDSIZE - 1) > BLOCKSIZE) ? 624 | size + sizeof(void*) + (WORDSIZE - 1) : BLOCKSIZE; 625 | 626 | // use the standard C malloc to allocate memory 627 | void* m = ::malloc(blocksize); 628 | if (!m) { 629 | fprintf(stderr, "Failed to allocate memory.\n"); 630 | return NULL; 631 | } 632 | 633 | /* Fill first word of new block with pointer to previous block. */ 634 | static_cast(m)[0] = base; 635 | base = m; 636 | 637 | size_t shift = 0; 638 | //int size_t = (WORDSIZE - ( (((size_t)m) + sizeof(void*)) & (WORDSIZE-1))) & (WORDSIZE-1); 639 | 640 | remaining = blocksize - sizeof(void*) - shift; 641 | loc = (static_cast(m) + sizeof(void*) + shift); 642 | } 643 | void* rloc = loc; 644 | loc = static_cast(loc) + size; 645 | remaining -= size; 646 | 647 | usedMemory += size; 648 | 649 | return rloc; 650 | } 651 | 652 | /** 653 | * Allocates (using this pool) a generic type T. 654 | * 655 | * Params: 656 | * count = number of instances to allocate. 657 | * Returns: pointer (of type T*) to memory buffer 658 | */ 659 | template 660 | T* allocate(const size_t count = 1) 661 | { 662 | T* mem = static_cast(this->malloc(sizeof(T)*count)); 663 | return mem; 664 | } 665 | 666 | }; 667 | /** @} */ 668 | 669 | /** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff 670 | * @{ */ 671 | 672 | // ---------------- CArray ------------------------- 673 | /** A STL container (as wrapper) for arrays of constant size defined at compile time (class imported from the MRPT project) 674 | * This code is an adapted version from Boost, modifed for its integration 675 | * within MRPT (JLBC, Dec/2009) (Renamed array -> CArray to avoid possible potential conflicts). 676 | * See 677 | * http://www.josuttis.com/cppcode 678 | * for details and the latest version. 679 | * See 680 | * http://www.boost.org/libs/array for Documentation. 681 | * for documentation. 682 | * 683 | * (C) Copyright Nicolai M. Josuttis 2001. 684 | * Permission to copy, use, modify, sell and distribute this software 685 | * is granted provided this copyright notice appears in all copies. 686 | * This software is provided "as is" without express or implied 687 | * warranty, and with no claim as to its suitability for any purpose. 688 | * 689 | * 29 Jan 2004 - minor fixes (Nico Josuttis) 690 | * 04 Dec 2003 - update to synch with library TR1 (Alisdair Meredith) 691 | * 23 Aug 2002 - fix for Non-MSVC compilers combined with MSVC libraries. 692 | * 05 Aug 2001 - minor update (Nico Josuttis) 693 | * 20 Jan 2001 - STLport fix (Beman Dawes) 694 | * 29 Sep 2000 - Initial Revision (Nico Josuttis) 695 | * 696 | * Jan 30, 2004 697 | */ 698 | template 699 | class CArray { 700 | public: 701 | T elems[N]; // fixed-size array of elements of type T 702 | 703 | public: 704 | // type definitions 705 | typedef T value_type; 706 | typedef T* iterator; 707 | typedef const T* const_iterator; 708 | typedef T& reference; 709 | typedef const T& const_reference; 710 | typedef std::size_t size_type; 711 | typedef std::ptrdiff_t difference_type; 712 | 713 | // iterator support 714 | inline iterator begin() { return elems; } 715 | inline const_iterator begin() const { return elems; } 716 | inline iterator end() { return elems+N; } 717 | inline const_iterator end() const { return elems+N; } 718 | 719 | // reverse iterator support 720 | #if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_MSVC_STD_ITERATOR) && !defined(BOOST_NO_STD_ITERATOR_TRAITS) 721 | typedef std::reverse_iterator reverse_iterator; 722 | typedef std::reverse_iterator const_reverse_iterator; 723 | #elif defined(_MSC_VER) && (_MSC_VER == 1300) && defined(BOOST_DINKUMWARE_STDLIB) && (BOOST_DINKUMWARE_STDLIB == 310) 724 | // workaround for broken reverse_iterator in VC7 725 | typedef std::reverse_iterator > reverse_iterator; 727 | typedef std::reverse_iterator > const_reverse_iterator; 729 | #else 730 | // workaround for broken reverse_iterator implementations 731 | typedef std::reverse_iterator reverse_iterator; 732 | typedef std::reverse_iterator const_reverse_iterator; 733 | #endif 734 | 735 | reverse_iterator rbegin() { return reverse_iterator(end()); } 736 | const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } 737 | reverse_iterator rend() { return reverse_iterator(begin()); } 738 | const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } 739 | // operator[] 740 | inline reference operator[](size_type i) { return elems[i]; } 741 | inline const_reference operator[](size_type i) const { return elems[i]; } 742 | // at() with range check 743 | reference at(size_type i) { rangecheck(i); return elems[i]; } 744 | const_reference at(size_type i) const { rangecheck(i); return elems[i]; } 745 | // front() and back() 746 | reference front() { return elems[0]; } 747 | const_reference front() const { return elems[0]; } 748 | reference back() { return elems[N-1]; } 749 | const_reference back() const { return elems[N-1]; } 750 | // size is constant 751 | static inline size_type size() { return N; } 752 | static bool empty() { return false; } 753 | static size_type max_size() { return N; } 754 | enum { static_size = N }; 755 | /** This method has no effects in this class, but raises an exception if the expected size does not match */ 756 | inline void resize(const size_t nElements) { if (nElements!=N) throw std::logic_error("Try to change the size of a CArray."); } 757 | // swap (note: linear complexity in N, constant for given instantiation) 758 | void swap (CArray& y) { std::swap_ranges(begin(),end(),y.begin()); } 759 | // direct access to data (read-only) 760 | const T* data() const { return elems; } 761 | // use array as C array (direct read/write access to data) 762 | T* data() { return elems; } 763 | // assignment with type conversion 764 | template CArray& operator= (const CArray& rhs) { 765 | std::copy(rhs.begin(),rhs.end(), begin()); 766 | return *this; 767 | } 768 | // assign one value to all elements 769 | inline void assign (const T& value) { for (size_t i=0;i= size()) { throw std::out_of_range("CArray<>: index out of range"); } } 775 | }; // end of CArray 776 | 777 | /** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors when DIM=-1. 778 | * Fixed size version for a generic DIM: 779 | */ 780 | template 781 | struct array_or_vector_selector 782 | { 783 | typedef CArray container_t; 784 | }; 785 | /** Dynamic size version */ 786 | template 787 | struct array_or_vector_selector<-1, T> { 788 | typedef std::vector container_t; 789 | }; 790 | 791 | /** @} */ 792 | 793 | /** kd-tree base-class 794 | * 795 | * Contains the member functions common to the classes KDTreeSingleIndexAdaptor and KDTreeSingleIndexDynamicAdaptor_. 796 | * 797 | * \tparam Derived The name of the class which inherits this class. 798 | * \tparam DatasetAdaptor The user-provided adaptor (see comments above). 799 | * \tparam Distance The distance metric to use, these are all classes derived from nanoflann::Metric 800 | * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) 801 | * \tparam IndexType Will be typically size_t or int 802 | */ 803 | 804 | template 805 | class KDTreeBaseClass 806 | { 807 | 808 | public: 809 | /** Frees the previously-built index. Automatically called within buildIndex(). */ 810 | void freeIndex(Derived &obj) 811 | { 812 | obj.pool.free_all(); 813 | obj.root_node = NULL; 814 | obj.m_size_at_index_build = 0; 815 | } 816 | 817 | typedef typename Distance::ElementType ElementType; 818 | typedef typename Distance::DistanceType DistanceType; 819 | 820 | /*--------------------- Internal Data Structures --------------------------*/ 821 | struct Node 822 | { 823 | /** Union used because a node can be either a LEAF node or a non-leaf node, so both data fields are never used simultaneously */ 824 | union { 825 | struct leaf 826 | { 827 | IndexType left, right; //!< Indices of points in leaf node 828 | } lr; 829 | struct nonleaf 830 | { 831 | int divfeat; //!< Dimension used for subdivision. 832 | DistanceType divlow, divhigh; //!< The values used for subdivision. 833 | } sub; 834 | } node_type; 835 | Node *child1, *child2; //!< Child nodes (both=NULL mean its a leaf node) 836 | }; 837 | 838 | typedef Node* NodePtr; 839 | 840 | struct Interval 841 | { 842 | ElementType low, high; 843 | }; 844 | 845 | /** 846 | * Array of indices to vectors in the dataset. 847 | */ 848 | std::vector vind; 849 | 850 | NodePtr root_node; 851 | 852 | size_t m_leaf_max_size; 853 | 854 | size_t m_size; //!< Number of current points in the dataset 855 | size_t m_size_at_index_build; //!< Number of points in the dataset when the index was built 856 | int dim; //!< Dimensionality of each data point 857 | 858 | /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ 859 | typedef typename array_or_vector_selector::container_t BoundingBox; 860 | 861 | /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ 862 | typedef typename array_or_vector_selector::container_t distance_vector_t; 863 | 864 | /** The KD-tree used to find neighbours */ 865 | 866 | BoundingBox root_bbox; 867 | 868 | /** 869 | * Pooled memory allocator. 870 | * 871 | * Using a pooled memory allocator is more efficient 872 | * than allocating memory directly when there is a large 873 | * number small of memory allocations. 874 | */ 875 | PooledAllocator pool; 876 | 877 | /** Returns number of points in dataset */ 878 | size_t size(const Derived &obj) const { return obj.m_size; } 879 | 880 | /** Returns the length of each point in the dataset */ 881 | size_t veclen(const Derived &obj) { 882 | return static_cast(DIM>0 ? DIM : obj.dim); 883 | } 884 | 885 | /// Helper accessor to the dataset points: 886 | inline ElementType dataset_get(const Derived &obj, size_t idx, int component) const{ 887 | return obj.dataset.kdtree_get_pt(idx, component); 888 | } 889 | 890 | /** 891 | * Computes the inde memory usage 892 | * Returns: memory used by the index 893 | */ 894 | size_t usedMemory(Derived &obj) 895 | { 896 | return obj.pool.usedMemory + obj.pool.wastedMemory + obj.dataset.kdtree_get_point_count() * sizeof(IndexType); // pool memory and vind array memory 897 | } 898 | 899 | void computeMinMax(const Derived &obj, IndexType* ind, IndexType count, int element, ElementType& min_elem, ElementType& max_elem) 900 | { 901 | min_elem = dataset_get(obj, ind[0],element); 902 | max_elem = dataset_get(obj, ind[0],element); 903 | for (IndexType i = 1; i < count; ++i) { 904 | ElementType val = dataset_get(obj, ind[i], element); 905 | if (val < min_elem) min_elem = val; 906 | if (val > max_elem) max_elem = val; 907 | } 908 | } 909 | 910 | /** 911 | * Create a tree node that subdivides the list of vecs from vind[first] 912 | * to vind[last]. The routine is called recursively on each sublist. 913 | * 914 | * @param left index of the first vector 915 | * @param right index of the last vector 916 | */ 917 | NodePtr divideTree(Derived &obj, const IndexType left, const IndexType right, BoundingBox& bbox) 918 | { 919 | NodePtr node = obj.pool.template allocate(); // allocate memory 920 | 921 | /* If too few exemplars remain, then make this a leaf node. */ 922 | if ( (right - left) <= static_cast(obj.m_leaf_max_size) ) { 923 | node->child1 = node->child2 = NULL; /* Mark as leaf node. */ 924 | node->node_type.lr.left = left; 925 | node->node_type.lr.right = right; 926 | 927 | // compute bounding-box of leaf points 928 | for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { 929 | bbox[i].low = dataset_get(obj, obj.vind[left], i); 930 | bbox[i].high = dataset_get(obj, obj.vind[left], i); 931 | } 932 | for (IndexType k = left + 1; k < right; ++k) { 933 | for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { 934 | if (bbox[i].low > dataset_get(obj, obj.vind[k], i)) bbox[i].low = dataset_get(obj, obj.vind[k], i); 935 | if (bbox[i].high < dataset_get(obj, obj.vind[k], i)) bbox[i].high = dataset_get(obj, obj.vind[k], i); 936 | } 937 | } 938 | } 939 | else { 940 | IndexType idx; 941 | int cutfeat; 942 | DistanceType cutval; 943 | middleSplit_(obj, &obj.vind[0] + left, right - left, idx, cutfeat, cutval, bbox); 944 | 945 | node->node_type.sub.divfeat = cutfeat; 946 | 947 | BoundingBox left_bbox(bbox); 948 | left_bbox[cutfeat].high = cutval; 949 | node->child1 = divideTree(obj, left, left + idx, left_bbox); 950 | 951 | BoundingBox right_bbox(bbox); 952 | right_bbox[cutfeat].low = cutval; 953 | node->child2 = divideTree(obj, left + idx, right, right_bbox); 954 | 955 | node->node_type.sub.divlow = left_bbox[cutfeat].high; 956 | node->node_type.sub.divhigh = right_bbox[cutfeat].low; 957 | 958 | for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { 959 | bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low); 960 | bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high); 961 | } 962 | } 963 | 964 | return node; 965 | } 966 | 967 | void middleSplit_(Derived &obj, IndexType* ind, IndexType count, IndexType& index, int& cutfeat, DistanceType& cutval, const BoundingBox& bbox) 968 | { 969 | const DistanceType EPS = static_cast(0.00001); 970 | ElementType max_span = bbox[0].high-bbox[0].low; 971 | for (int i = 1; i < (DIM > 0 ? DIM : obj.dim); ++i) { 972 | ElementType span = bbox[i].high - bbox[i].low; 973 | if (span > max_span) { 974 | max_span = span; 975 | } 976 | } 977 | ElementType max_spread = -1; 978 | cutfeat = 0; 979 | for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { 980 | ElementType span = bbox[i].high-bbox[i].low; 981 | if (span > (1 - EPS) * max_span) { 982 | ElementType min_elem, max_elem; 983 | computeMinMax(obj, ind, count, i, min_elem, max_elem); 984 | ElementType spread = max_elem - min_elem;; 985 | if (spread > max_spread) { 986 | cutfeat = i; 987 | max_spread = spread; 988 | } 989 | } 990 | } 991 | // split in the middle 992 | DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2; 993 | ElementType min_elem, max_elem; 994 | computeMinMax(obj, ind, count, cutfeat, min_elem, max_elem); 995 | 996 | if (split_val < min_elem) cutval = min_elem; 997 | else if (split_val > max_elem) cutval = max_elem; 998 | else cutval = split_val; 999 | 1000 | IndexType lim1, lim2; 1001 | planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2); 1002 | 1003 | if (lim1 > count / 2) index = lim1; 1004 | else if (lim2 < count / 2) index = lim2; 1005 | else index = count/2; 1006 | } 1007 | 1008 | /** 1009 | * Subdivide the list of points by a plane perpendicular on axe corresponding 1010 | * to the 'cutfeat' dimension at 'cutval' position. 1011 | * 1012 | * On return: 1013 | * dataset[ind[0..lim1-1]][cutfeat]cutval 1016 | */ 1017 | void planeSplit(Derived &obj, IndexType* ind, const IndexType count, int cutfeat, DistanceType &cutval, IndexType& lim1, IndexType& lim2) 1018 | { 1019 | /* Move vector indices for left subtree to front of list. */ 1020 | IndexType left = 0; 1021 | IndexType right = count-1; 1022 | for (;; ) { 1023 | while (left <= right && dataset_get(obj, ind[left], cutfeat) < cutval) ++left; 1024 | while (right && left <= right && dataset_get(obj, ind[right], cutfeat) >= cutval) --right; 1025 | if (left > right || !right) break; // "!right" was added to support unsigned Index types 1026 | std::swap(ind[left], ind[right]); 1027 | ++left; 1028 | --right; 1029 | } 1030 | /* If either list is empty, it means that all remaining features 1031 | * are identical. Split in the middle to maintain a balanced tree. 1032 | */ 1033 | lim1 = left; 1034 | right = count-1; 1035 | for (;; ) { 1036 | while (left <= right && dataset_get(obj, ind[left], cutfeat) <= cutval) ++left; 1037 | while (right && left <= right && dataset_get(obj, ind[right], cutfeat) > cutval) --right; 1038 | if (left > right || !right) break; // "!right" was added to support unsigned Index types 1039 | std::swap(ind[left], ind[right]); 1040 | ++left; 1041 | --right; 1042 | } 1043 | lim2 = left; 1044 | } 1045 | 1046 | DistanceType computeInitialDistances(const Derived &obj, const ElementType* vec, distance_vector_t& dists) const 1047 | { 1048 | assert(vec); 1049 | DistanceType distsq = DistanceType(); 1050 | 1051 | for (int i = 0; i < (DIM>0 ? DIM : obj.dim); ++i) { 1052 | if (vec[i] < obj.root_bbox[i].low) { 1053 | dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].low, i); 1054 | distsq += dists[i]; 1055 | } 1056 | if (vec[i] > obj.root_bbox[i].high) { 1057 | dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].high, i); 1058 | distsq += dists[i]; 1059 | } 1060 | } 1061 | return distsq; 1062 | } 1063 | 1064 | void save_tree(Derived &obj, FILE* stream, NodePtr tree) 1065 | { 1066 | save_value(stream, *tree); 1067 | if (tree->child1 != NULL) { 1068 | save_tree(obj, stream, tree->child1); 1069 | } 1070 | if (tree->child2 != NULL) { 1071 | save_tree(obj, stream, tree->child2); 1072 | } 1073 | } 1074 | 1075 | 1076 | void load_tree(Derived &obj, FILE* stream, NodePtr& tree) 1077 | { 1078 | tree = obj.pool.template allocate(); 1079 | load_value(stream, *tree); 1080 | if (tree->child1 != NULL) { 1081 | load_tree(obj, stream, tree->child1); 1082 | } 1083 | if (tree->child2 != NULL) { 1084 | load_tree(obj, stream, tree->child2); 1085 | } 1086 | } 1087 | 1088 | /** Stores the index in a binary file. 1089 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. 1090 | * See the example: examples/saveload_example.cpp 1091 | * \sa loadIndex */ 1092 | void saveIndex_(Derived &obj, FILE* stream) 1093 | { 1094 | save_value(stream, obj.m_size); 1095 | save_value(stream, obj.dim); 1096 | save_value(stream, obj.root_bbox); 1097 | save_value(stream, obj.m_leaf_max_size); 1098 | save_value(stream, obj.vind); 1099 | save_tree(obj, stream, obj.root_node); 1100 | } 1101 | 1102 | /** Loads a previous index from a binary file. 1103 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. 1104 | * See the example: examples/saveload_example.cpp 1105 | * \sa loadIndex */ 1106 | void loadIndex_(Derived &obj, FILE* stream) 1107 | { 1108 | load_value(stream, obj.m_size); 1109 | load_value(stream, obj.dim); 1110 | load_value(stream, obj.root_bbox); 1111 | load_value(stream, obj.m_leaf_max_size); 1112 | load_value(stream, obj.vind); 1113 | load_tree(obj, stream, obj.root_node); 1114 | } 1115 | 1116 | }; 1117 | 1118 | 1119 | /** @addtogroup kdtrees_grp KD-tree classes and adaptors 1120 | * @{ */ 1121 | 1122 | /** kd-tree static index 1123 | * 1124 | * Contains the k-d trees and other information for indexing a set of points 1125 | * for nearest-neighbor matching. 1126 | * 1127 | * The class "DatasetAdaptor" must provide the following interface (can be non-virtual, inlined methods): 1128 | * 1129 | * \code 1130 | * // Must return the number of data poins 1131 | * inline size_t kdtree_get_point_count() const { ... } 1132 | * 1133 | * 1134 | * // Must return the dim'th component of the idx'th point in the class: 1135 | * inline T kdtree_get_pt(const size_t idx, int dim) const { ... } 1136 | * 1137 | * // Optional bounding-box computation: return false to default to a standard bbox computation loop. 1138 | * // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. 1139 | * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) 1140 | * template 1141 | * bool kdtree_get_bbox(BBOX &bb) const 1142 | * { 1143 | * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits 1144 | * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits 1145 | * ... 1146 | * return true; 1147 | * } 1148 | * 1149 | * \endcode 1150 | * 1151 | * \tparam DatasetAdaptor The user-provided adaptor (see comments above). 1152 | * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. 1153 | * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) 1154 | * \tparam IndexType Will be typically size_t or int 1155 | */ 1156 | template 1157 | class KDTreeSingleIndexAdaptor : public KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> 1158 | { 1159 | private: 1160 | /** Hidden copy constructor, to disallow copying indices (Not implemented) */ 1161 | KDTreeSingleIndexAdaptor(const KDTreeSingleIndexAdaptor&); 1162 | public: 1163 | 1164 | /** 1165 | * The dataset used by this index 1166 | */ 1167 | const DatasetAdaptor &dataset; //!< The source of our data 1168 | 1169 | const KDTreeSingleIndexAdaptorParams index_params; 1170 | 1171 | Distance distance; 1172 | 1173 | typedef typename nanoflann::KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> BaseClassRef; 1174 | 1175 | typedef typename BaseClassRef::ElementType ElementType; 1176 | typedef typename BaseClassRef::DistanceType DistanceType; 1177 | 1178 | typedef typename BaseClassRef::Node Node; 1179 | typedef Node* NodePtr; 1180 | 1181 | typedef typename BaseClassRef::Interval Interval; 1182 | /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ 1183 | typedef typename BaseClassRef::BoundingBox BoundingBox; 1184 | 1185 | /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ 1186 | typedef typename BaseClassRef::distance_vector_t distance_vector_t; 1187 | 1188 | /** 1189 | * KDTree constructor 1190 | * 1191 | * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann 1192 | * 1193 | * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) 1194 | * is determined by means of: 1195 | * - The \a DIM template parameter if >0 (highest priority) 1196 | * - Otherwise, the \a dimensionality parameter of this constructor. 1197 | * 1198 | * @param inputData Dataset with the input features 1199 | * @param params Basically, the maximum leaf node size 1200 | */ 1201 | KDTreeSingleIndexAdaptor(const int dimensionality, const DatasetAdaptor& inputData, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams() ) : 1202 | dataset(inputData), index_params(params), distance(inputData) 1203 | { 1204 | BaseClassRef::root_node = NULL; 1205 | BaseClassRef::m_size = dataset.kdtree_get_point_count(); 1206 | BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; 1207 | BaseClassRef::dim = dimensionality; 1208 | if (DIM>0) BaseClassRef::dim = DIM; 1209 | BaseClassRef::m_leaf_max_size = params.leaf_max_size; 1210 | 1211 | // Create a permutable array of indices to the input vectors. 1212 | init_vind(); 1213 | } 1214 | 1215 | /** 1216 | * Builds the index 1217 | */ 1218 | void buildIndex() 1219 | { 1220 | BaseClassRef::m_size = dataset.kdtree_get_point_count(); 1221 | BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; 1222 | init_vind(); 1223 | this->freeIndex(*this); 1224 | BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; 1225 | if(BaseClassRef::m_size == 0) return; 1226 | computeBoundingBox(BaseClassRef::root_bbox); 1227 | BaseClassRef::root_node = this->divideTree(*this, 0, BaseClassRef::m_size, BaseClassRef::root_bbox ); // construct the tree 1228 | } 1229 | 1230 | /** \name Query methods 1231 | * @{ */ 1232 | 1233 | /** 1234 | * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside 1235 | * the result object. 1236 | * 1237 | * Params: 1238 | * result = the result object in which the indices of the nearest-neighbors are stored 1239 | * vec = the vector for which to search the nearest neighbors 1240 | * 1241 | * \tparam RESULTSET Should be any ResultSet 1242 | * \return True if the requested neighbors could be found. 1243 | * \sa knnSearch, radiusSearch 1244 | */ 1245 | template 1246 | bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const 1247 | { 1248 | assert(vec); 1249 | if (this->size(*this) == 0) 1250 | return false; 1251 | if (!BaseClassRef::root_node) 1252 | throw std::runtime_error("[nanoflann] findNeighbors() called before building the index."); 1253 | float epsError = 1 + searchParams.eps; 1254 | 1255 | distance_vector_t dists; // fixed or variable-sized container (depending on DIM) 1256 | dists.assign((DIM > 0 ? DIM : BaseClassRef::dim), 0); // Fill it with zeros. 1257 | DistanceType distsq = this->computeInitialDistances(*this, vec, dists); 1258 | searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, epsError); // "count_leaf" parameter removed since was neither used nor returned to the user. 1259 | return result.full(); 1260 | } 1261 | 1262 | /** 1263 | * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. Their indices are stored inside 1264 | * the result object. 1265 | * \sa radiusSearch, findNeighbors 1266 | * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. 1267 | * \return Number `N` of valid points in the result set. Only the first `N` entries in `out_indices` and `out_distances_sq` will be valid. 1268 | * Return may be less than `num_closest` only if the number of elements in the tree is less than `num_closest`. 1269 | */ 1270 | size_t knnSearch(const ElementType *query_point, const size_t num_closest, IndexType *out_indices, DistanceType *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const 1271 | { 1272 | nanoflann::KNNResultSet resultSet(num_closest); 1273 | resultSet.init(out_indices, out_distances_sq); 1274 | this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); 1275 | return resultSet.size(); 1276 | } 1277 | 1278 | /** 1279 | * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. 1280 | * The output is given as a vector of pairs, of which the first element is a point index and the second the corresponding distance. 1281 | * Previous contents of \a IndicesDists are cleared. 1282 | * 1283 | * If searchParams.sorted==true, the output list is sorted by ascending distances. 1284 | * 1285 | * For a better performance, it is advisable to do a .reserve() on the vector if you have any wild guess about the number of expected matches. 1286 | * 1287 | * \sa knnSearch, findNeighbors, radiusSearchCustomCallback 1288 | * \return The number of points within the given radius (i.e. indices.size() or dists.size() ) 1289 | */ 1290 | size_t radiusSearch(const ElementType *query_point, const DistanceType &radius, std::vector >& IndicesDists, const SearchParams& searchParams) const 1291 | { 1292 | RadiusResultSet resultSet(radius, IndicesDists); 1293 | const size_t nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); 1294 | if (searchParams.sorted) 1295 | std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter() ); 1296 | return nFound; 1297 | } 1298 | 1299 | /** 1300 | * Just like radiusSearch() but with a custom callback class for each point found in the radius of the query. 1301 | * See the source of RadiusResultSet<> as a start point for your own classes. 1302 | * \sa radiusSearch 1303 | */ 1304 | template 1305 | size_t radiusSearchCustomCallback(const ElementType *query_point, SEARCH_CALLBACK &resultSet, const SearchParams& searchParams = SearchParams() ) const 1306 | { 1307 | this->findNeighbors(resultSet, query_point, searchParams); 1308 | return resultSet.size(); 1309 | } 1310 | 1311 | /** @} */ 1312 | 1313 | public: 1314 | /** Make sure the auxiliary list \a vind has the same size than the current dataset, and re-generate if size has changed. */ 1315 | void init_vind() 1316 | { 1317 | // Create a permutable array of indices to the input vectors. 1318 | BaseClassRef::m_size = dataset.kdtree_get_point_count(); 1319 | if (BaseClassRef::vind.size() != BaseClassRef::m_size) BaseClassRef::vind.resize(BaseClassRef::m_size); 1320 | for (size_t i = 0; i < BaseClassRef::m_size; i++) BaseClassRef::vind[i] = i; 1321 | } 1322 | 1323 | void computeBoundingBox(BoundingBox& bbox) 1324 | { 1325 | bbox.resize((DIM > 0 ? DIM : BaseClassRef::dim)); 1326 | if (dataset.kdtree_get_bbox(bbox)) 1327 | { 1328 | // Done! It was implemented in derived class 1329 | } 1330 | else 1331 | { 1332 | const size_t N = dataset.kdtree_get_point_count(); 1333 | if (!N) throw std::runtime_error("[nanoflann] computeBoundingBox() called but no data points found."); 1334 | for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { 1335 | bbox[i].low = 1336 | bbox[i].high = this->dataset_get(*this, 0, i); 1337 | } 1338 | for (size_t k = 1; k < N; ++k) { 1339 | for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { 1340 | if (this->dataset_get(*this, k, i) < bbox[i].low) bbox[i].low = this->dataset_get(*this, k, i); 1341 | if (this->dataset_get(*this, k, i) > bbox[i].high) bbox[i].high = this->dataset_get(*this, k, i); 1342 | } 1343 | } 1344 | } 1345 | } 1346 | 1347 | /** 1348 | * Performs an exact search in the tree starting from a node. 1349 | * \tparam RESULTSET Should be any ResultSet 1350 | * \return true if the search should be continued, false if the results are sufficient 1351 | */ 1352 | template 1353 | bool searchLevel(RESULTSET& result_set, const ElementType* vec, const NodePtr node, DistanceType mindistsq, 1354 | distance_vector_t& dists, const float epsError) const 1355 | { 1356 | /* If this is a leaf node, then do check and return. */ 1357 | if ((node->child1 == NULL) && (node->child2 == NULL)) { 1358 | //count_leaf += (node->lr.right-node->lr.left); // Removed since was neither used nor returned to the user. 1359 | DistanceType worst_dist = result_set.worstDist(); 1360 | for (IndexType i = node->node_type.lr.left; inode_type.lr.right; ++i) { 1361 | const IndexType index = BaseClassRef::vind[i];// reorder... : i; 1362 | DistanceType dist = distance.evalMetric(vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); 1363 | if (dist < worst_dist) { 1364 | if(!result_set.addPoint(dist, BaseClassRef::vind[i])) { 1365 | // the resultset doesn't want to receive any more points, we're done searching! 1366 | return false; 1367 | } 1368 | } 1369 | } 1370 | return true; 1371 | } 1372 | 1373 | /* Which child branch should be taken first? */ 1374 | int idx = node->node_type.sub.divfeat; 1375 | ElementType val = vec[idx]; 1376 | DistanceType diff1 = val - node->node_type.sub.divlow; 1377 | DistanceType diff2 = val - node->node_type.sub.divhigh; 1378 | 1379 | NodePtr bestChild; 1380 | NodePtr otherChild; 1381 | DistanceType cut_dist; 1382 | if ((diff1 + diff2) < 0) { 1383 | bestChild = node->child1; 1384 | otherChild = node->child2; 1385 | cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); 1386 | } 1387 | else { 1388 | bestChild = node->child2; 1389 | otherChild = node->child1; 1390 | cut_dist = distance.accum_dist( val, node->node_type.sub.divlow, idx); 1391 | } 1392 | 1393 | /* Call recursively to search next level down. */ 1394 | if(!searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError)) { 1395 | // the resultset doesn't want to receive any more points, we're done searching! 1396 | return false; 1397 | } 1398 | 1399 | DistanceType dst = dists[idx]; 1400 | mindistsq = mindistsq + cut_dist - dst; 1401 | dists[idx] = cut_dist; 1402 | if (mindistsq*epsError <= result_set.worstDist()) { 1403 | if(!searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError)) { 1404 | // the resultset doesn't want to receive any more points, we're done 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 loading the index object it must be constructed associated to the same source of data points used while building it. 1415 | * See the example: examples/saveload_example.cpp 1416 | * \sa loadIndex */ 1417 | void saveIndex(FILE* stream) 1418 | { 1419 | this->saveIndex_(*this, stream); 1420 | } 1421 | 1422 | /** Loads a previous index from a binary file. 1423 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. 1424 | * See the example: examples/saveload_example.cpp 1425 | * \sa loadIndex */ 1426 | void loadIndex(FILE* stream) 1427 | { 1428 | this->loadIndex_(*this, stream); 1429 | } 1430 | 1431 | }; // class KDTree 1432 | 1433 | 1434 | /** kd-tree dynamic index 1435 | * 1436 | * Contains the k-d trees and other information for indexing a set of points 1437 | * for nearest-neighbor matching. 1438 | * 1439 | * The class "DatasetAdaptor" must provide the following interface (can be non-virtual, inlined methods): 1440 | * 1441 | * \code 1442 | * // Must return the number of data poins 1443 | * inline size_t kdtree_get_point_count() const { ... } 1444 | * 1445 | * // Must return the dim'th component of the idx'th point in the class: 1446 | * inline T kdtree_get_pt(const size_t idx, int dim) const { ... } 1447 | * 1448 | * // Optional bounding-box computation: return false to default to a standard bbox computation loop. 1449 | * // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. 1450 | * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) 1451 | * template 1452 | * bool kdtree_get_bbox(BBOX &bb) const 1453 | * { 1454 | * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits 1455 | * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits 1456 | * ... 1457 | * return true; 1458 | * } 1459 | * 1460 | * \endcode 1461 | * 1462 | * \tparam DatasetAdaptor The user-provided adaptor (see comments above). 1463 | * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. 1464 | * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) 1465 | * \tparam IndexType Will be typically size_t or int 1466 | */ 1467 | template 1468 | class KDTreeSingleIndexDynamicAdaptor_ : public KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> 1469 | { 1470 | public: 1471 | 1472 | /** 1473 | * The dataset used by this index 1474 | */ 1475 | 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, Distance, DatasetAdaptor, DIM, IndexType> BaseClassRef; 1484 | 1485 | typedef typename BaseClassRef::ElementType ElementType; 1486 | typedef typename BaseClassRef::DistanceType DistanceType; 1487 | 1488 | typedef typename BaseClassRef::Node Node; 1489 | typedef Node* NodePtr; 1490 | 1491 | typedef typename BaseClassRef::Interval Interval; 1492 | /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ 1493 | typedef typename BaseClassRef::BoundingBox BoundingBox; 1494 | 1495 | /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ 1496 | typedef typename BaseClassRef::distance_vector_t distance_vector_t; 1497 | 1498 | /** 1499 | * KDTree constructor 1500 | * 1501 | * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann 1502 | * 1503 | * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) 1504 | * is determined by means of: 1505 | * - The \a DIM template parameter if >0 (highest priority) 1506 | * - Otherwise, the \a dimensionality parameter of this constructor. 1507 | * 1508 | * @param inputData Dataset with the input features 1509 | * @param params Basically, the maximum leaf node size 1510 | */ 1511 | KDTreeSingleIndexDynamicAdaptor_(const int dimensionality, DatasetAdaptor& inputData, std::vector& treeIndex_, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams()) : 1512 | dataset(inputData), index_params(params), treeIndex(treeIndex_), distance(inputData) 1513 | { 1514 | BaseClassRef::root_node = NULL; 1515 | BaseClassRef::m_size = 0; 1516 | BaseClassRef::m_size_at_index_build = 0; 1517 | BaseClassRef::dim = dimensionality; 1518 | if (DIM>0) BaseClassRef::dim = DIM; 1519 | BaseClassRef::m_leaf_max_size = params.leaf_max_size; 1520 | } 1521 | 1522 | 1523 | /** Assignment operator definiton */ 1524 | KDTreeSingleIndexDynamicAdaptor_ operator=( const KDTreeSingleIndexDynamicAdaptor_& rhs ) { 1525 | KDTreeSingleIndexDynamicAdaptor_ tmp( rhs ); 1526 | std::swap( BaseClassRef::vind, tmp.BaseClassRef::vind ); 1527 | std::swap( BaseClassRef::m_leaf_max_size, tmp.BaseClassRef::m_leaf_max_size ); 1528 | std::swap( index_params, tmp.index_params ); 1529 | std::swap( treeIndex, tmp.treeIndex ); 1530 | std::swap( BaseClassRef::m_size, tmp.BaseClassRef::m_size ); 1531 | std::swap( BaseClassRef::m_size_at_index_build, tmp.BaseClassRef::m_size_at_index_build ); 1532 | std::swap( BaseClassRef::root_node, tmp.BaseClassRef::root_node ); 1533 | std::swap( BaseClassRef::root_bbox, tmp.BaseClassRef::root_bbox ); 1534 | std::swap( BaseClassRef::pool, tmp.BaseClassRef::pool ); 1535 | return *this; 1536 | } 1537 | 1538 | /** 1539 | * Builds the index 1540 | */ 1541 | void buildIndex() 1542 | { 1543 | BaseClassRef::m_size = BaseClassRef::vind.size(); 1544 | this->freeIndex(*this); 1545 | BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; 1546 | if(BaseClassRef::m_size == 0) return; 1547 | computeBoundingBox(BaseClassRef::root_bbox); 1548 | BaseClassRef::root_node = this->divideTree(*this, 0, BaseClassRef::m_size, BaseClassRef::root_bbox ); // construct the tree 1549 | } 1550 | 1551 | /** \name Query methods 1552 | * @{ */ 1553 | 1554 | /** 1555 | * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside 1556 | * the result object. 1557 | * 1558 | * Params: 1559 | * result = the result object in which the indices of the nearest-neighbors are stored 1560 | * vec = the vector for which to search the nearest neighbors 1561 | * 1562 | * \tparam RESULTSET Should be any ResultSet 1563 | * \return True if the requested neighbors could be found. 1564 | * \sa knnSearch, radiusSearch 1565 | */ 1566 | template 1567 | bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const 1568 | { 1569 | assert(vec); 1570 | if (this->size(*this) == 0) 1571 | return false; 1572 | if (!BaseClassRef::root_node) 1573 | return false; 1574 | float epsError = 1 + searchParams.eps; 1575 | 1576 | distance_vector_t dists; // fixed or variable-sized container (depending on DIM) 1577 | dists.assign((DIM > 0 ? DIM : BaseClassRef::dim) , 0); // Fill it with zeros. 1578 | DistanceType distsq = this->computeInitialDistances(*this, vec, dists); 1579 | searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, epsError); // "count_leaf" parameter removed since was neither used nor returned to the user. 1580 | return result.full(); 1581 | } 1582 | 1583 | /** 1584 | * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. Their indices are stored inside 1585 | * the result object. 1586 | * \sa radiusSearch, findNeighbors 1587 | * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. 1588 | * \return Number `N` of valid points in the result set. Only the first `N` entries in `out_indices` and `out_distances_sq` will be valid. 1589 | * Return may be less than `num_closest` only if the number of elements in the tree is less than `num_closest`. 1590 | */ 1591 | size_t knnSearch(const ElementType *query_point, const size_t num_closest, IndexType *out_indices, DistanceType *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const 1592 | { 1593 | nanoflann::KNNResultSet resultSet(num_closest); 1594 | resultSet.init(out_indices, out_distances_sq); 1595 | this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); 1596 | return resultSet.size(); 1597 | } 1598 | 1599 | /** 1600 | * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. 1601 | * The output is given as a vector of pairs, of which the first element is a point index and the second the corresponding distance. 1602 | * Previous contents of \a IndicesDists are cleared. 1603 | * 1604 | * If searchParams.sorted==true, the output list is sorted by ascending distances. 1605 | * 1606 | * For a better performance, it is advisable to do a .reserve() on the vector if you have any wild guess about the number of expected matches. 1607 | * 1608 | * \sa knnSearch, findNeighbors, radiusSearchCustomCallback 1609 | * \return The number of points within the given radius (i.e. indices.size() or dists.size() ) 1610 | */ 1611 | size_t radiusSearch(const ElementType *query_point, const DistanceType &radius, std::vector >& IndicesDists, const SearchParams& searchParams) const 1612 | { 1613 | RadiusResultSet resultSet(radius, IndicesDists); 1614 | const size_t nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); 1615 | if (searchParams.sorted) 1616 | std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter() ); 1617 | return nFound; 1618 | } 1619 | 1620 | /** 1621 | * Just like radiusSearch() but with a custom callback class for each point found in the radius of the query. 1622 | * See the source of RadiusResultSet<> as a start point for your own classes. 1623 | * \sa radiusSearch 1624 | */ 1625 | template 1626 | size_t radiusSearchCustomCallback(const ElementType *query_point, SEARCH_CALLBACK &resultSet, const SearchParams& searchParams = SearchParams() ) const 1627 | { 1628 | this->findNeighbors(resultSet, query_point, searchParams); 1629 | return resultSet.size(); 1630 | } 1631 | 1632 | /** @} */ 1633 | 1634 | public: 1635 | 1636 | 1637 | void computeBoundingBox(BoundingBox& bbox) 1638 | { 1639 | bbox.resize((DIM > 0 ? DIM : BaseClassRef::dim)); 1640 | if (dataset.kdtree_get_bbox(bbox)) 1641 | { 1642 | // Done! It was implemented in derived class 1643 | } 1644 | else 1645 | { 1646 | const size_t N = BaseClassRef::m_size; 1647 | if (!N) throw std::runtime_error("[nanoflann] computeBoundingBox() called but no data points found."); 1648 | for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { 1649 | bbox[i].low = 1650 | bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[0], i); 1651 | } 1652 | for (size_t k = 1; k < N; ++k) { 1653 | for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { 1654 | if (this->dataset_get(*this, BaseClassRef::vind[k], i) < bbox[i].low) bbox[i].low = this->dataset_get(*this, BaseClassRef::vind[k], i); 1655 | if (this->dataset_get(*this, BaseClassRef::vind[k], i) > bbox[i].high) bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[k], i); 1656 | } 1657 | } 1658 | } 1659 | } 1660 | 1661 | /** 1662 | * Performs an exact search in the tree starting from a node. 1663 | * \tparam RESULTSET Should be any ResultSet 1664 | */ 1665 | template 1666 | void searchLevel(RESULTSET& result_set, const ElementType* vec, const NodePtr node, DistanceType mindistsq, 1667 | distance_vector_t& dists, const float epsError) const 1668 | { 1669 | /* If this is a leaf node, then do check and return. */ 1670 | if ((node->child1 == NULL) && (node->child2 == NULL)) { 1671 | //count_leaf += (node->lr.right-node->lr.left); // Removed since was neither used nor returned to the user. 1672 | DistanceType worst_dist = result_set.worstDist(); 1673 | for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right; ++i) { 1674 | const IndexType index = BaseClassRef::vind[i];// reorder... : i; 1675 | if(treeIndex[index] == -1) 1676 | continue; 1677 | DistanceType dist = distance.evalMetric(vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); 1678 | if (distnode_type.sub.divfeat; 1687 | ElementType val = vec[idx]; 1688 | DistanceType diff1 = val - node->node_type.sub.divlow; 1689 | DistanceType diff2 = val - node->node_type.sub.divhigh; 1690 | 1691 | NodePtr bestChild; 1692 | NodePtr otherChild; 1693 | DistanceType cut_dist; 1694 | if ((diff1 + diff2) < 0) { 1695 | bestChild = node->child1; 1696 | otherChild = node->child2; 1697 | cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); 1698 | } 1699 | else { 1700 | bestChild = node->child2; 1701 | otherChild = node->child1; 1702 | cut_dist = distance.accum_dist( val, node->node_type.sub.divlow, idx); 1703 | } 1704 | 1705 | /* Call recursively to search next level down. */ 1706 | searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError); 1707 | 1708 | DistanceType dst = dists[idx]; 1709 | mindistsq = mindistsq + cut_dist - dst; 1710 | dists[idx] = cut_dist; 1711 | if (mindistsq*epsError <= result_set.worstDist()) { 1712 | searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError); 1713 | } 1714 | dists[idx] = dst; 1715 | } 1716 | 1717 | public: 1718 | /** Stores the index in a binary file. 1719 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. 1720 | * See the example: examples/saveload_example.cpp 1721 | * \sa loadIndex */ 1722 | void saveIndex(FILE* stream) 1723 | { 1724 | this->saveIndex_(*this, stream); 1725 | } 1726 | 1727 | /** Loads a previous index from a binary file. 1728 | * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. 1729 | * See the example: examples/saveload_example.cpp 1730 | * \sa loadIndex */ 1731 | void loadIndex(FILE* stream) 1732 | { 1733 | this->loadIndex_(*this, stream); 1734 | } 1735 | 1736 | }; 1737 | 1738 | 1739 | /** kd-tree dynaimic index 1740 | * 1741 | * class to create multiple static index and merge their results to behave as single dynamic index as proposed in Logarithmic Approach. 1742 | * 1743 | * Example of usage: 1744 | * examples/dynamic_pointcloud_example.cpp 1745 | * 1746 | * \tparam DatasetAdaptor The user-provided adaptor (see comments above). 1747 | * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. 1748 | * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) 1749 | * \tparam IndexType Will be typically size_t or int 1750 | */ 1751 | template 1752 | class KDTreeSingleIndexDynamicAdaptor 1753 | { 1754 | public: 1755 | typedef typename Distance::ElementType ElementType; 1756 | typedef typename Distance::DistanceType DistanceType; 1757 | protected: 1758 | 1759 | size_t m_leaf_max_size; 1760 | size_t treeCount; 1761 | size_t pointCount; 1762 | 1763 | /** 1764 | * The dataset used by this index 1765 | */ 1766 | DatasetAdaptor &dataset; //!< The source of our data 1767 | 1768 | std::vector treeIndex; //!< treeIndex[idx] is the index of tree in which point at idx is stored. treeIndex[idx]=-1 means that point has been removed. 1769 | 1770 | KDTreeSingleIndexAdaptorParams index_params; 1771 | 1772 | int dim; //!< Dimensionality of each data point 1773 | 1774 | typedef KDTreeSingleIndexDynamicAdaptor_ index_container_t; 1775 | std::vector index; 1776 | 1777 | public: 1778 | /** Get a const ref to the internal list of indices; the number of indices is adapted dynamically as 1779 | * the dataset grows in size. */ 1780 | const std::vector & getAllIndices() const { 1781 | return index; 1782 | } 1783 | 1784 | private: 1785 | /** finds position of least significant unset bit */ 1786 | int First0Bit(IndexType num) 1787 | { 1788 | int pos = 0; 1789 | while(num&1) 1790 | { 1791 | num = num>>1; 1792 | pos++; 1793 | } 1794 | return pos; 1795 | } 1796 | 1797 | /** Creates multiple empty trees to handle dynamic support */ 1798 | void init() 1799 | { 1800 | typedef KDTreeSingleIndexDynamicAdaptor_ my_kd_tree_t; 1801 | std::vector index_(treeCount, my_kd_tree_t(dim /*dim*/, dataset, treeIndex, index_params)); 1802 | index=index_; 1803 | } 1804 | 1805 | public: 1806 | 1807 | Distance distance; 1808 | 1809 | /** 1810 | * KDTree constructor 1811 | * 1812 | * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann 1813 | * 1814 | * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) 1815 | * is determined by means of: 1816 | * - The \a DIM template parameter if >0 (highest priority) 1817 | * - Otherwise, the \a dimensionality parameter of this constructor. 1818 | * 1819 | * @param inputData Dataset with the input features 1820 | * @param params Basically, the maximum leaf node size 1821 | */ 1822 | KDTreeSingleIndexDynamicAdaptor(const int dimensionality, DatasetAdaptor& inputData, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams() , const size_t maximumPointCount = 1000000000U) : 1823 | dataset(inputData), index_params(params), distance(inputData) 1824 | { 1825 | if (dataset.kdtree_get_point_count()) throw std::runtime_error("[nanoflann] cannot handle non empty point cloud."); 1826 | treeCount = log2(maximumPointCount); 1827 | pointCount = 0U; 1828 | dim = dimensionality; 1829 | treeIndex.clear(); 1830 | if (DIM > 0) dim = DIM; 1831 | m_leaf_max_size = params.leaf_max_size; 1832 | init(); 1833 | } 1834 | 1835 | 1836 | /** Add points to the set, Inserts all points from [start, end] */ 1837 | void addPoints(IndexType start, IndexType end) 1838 | { 1839 | int count = end - start + 1; 1840 | treeIndex.resize(treeIndex.size() + count); 1841 | for(IndexType idx = start; idx <= end; idx++) { 1842 | int pos = First0Bit(pointCount); 1843 | index[pos].vind.clear(); 1844 | treeIndex[pointCount]=pos; 1845 | for(int i = 0; i < pos; i++) { 1846 | for(int j = 0; j < static_cast(index[i].vind.size()); j++) { 1847 | index[pos].vind.push_back(index[i].vind[j]); 1848 | treeIndex[index[i].vind[j]] = pos; 1849 | } 1850 | index[i].vind.clear(); 1851 | index[i].freeIndex(index[i]); 1852 | } 1853 | index[pos].vind.push_back(idx); 1854 | index[pos].buildIndex(); 1855 | pointCount++; 1856 | } 1857 | } 1858 | 1859 | /** Remove a point from the set (Lazy Deletion) */ 1860 | void removePoint(size_t idx) 1861 | { 1862 | if(idx >= pointCount) 1863 | return; 1864 | treeIndex[idx] = -1; 1865 | } 1866 | 1867 | /** 1868 | * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside 1869 | * the result object. 1870 | * 1871 | * Params: 1872 | * result = the result object in which the indices of the nearest-neighbors are stored 1873 | * vec = the vector for which to search the nearest neighbors 1874 | * 1875 | * \tparam RESULTSET Should be any ResultSet 1876 | * \return True if the requested neighbors could be found. 1877 | * \sa knnSearch, radiusSearch 1878 | */ 1879 | template 1880 | bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const 1881 | { 1882 | for(size_t i = 0; i < treeCount; i++) 1883 | { 1884 | index[i].findNeighbors(result, &vec[0], searchParams); 1885 | } 1886 | return result.full(); 1887 | } 1888 | 1889 | }; 1890 | 1891 | /** An L2-metric KD-tree adaptor for working with data directly stored in an Eigen Matrix, without duplicating the data storage. 1892 | * Each row in the matrix represents a point in the state space. 1893 | * 1894 | * Example of usage: 1895 | * \code 1896 | * Eigen::Matrix mat; 1897 | * // Fill out "mat"... 1898 | * 1899 | * typedef KDTreeEigenMatrixAdaptor< Eigen::Matrix > my_kd_tree_t; 1900 | * const int max_leaf = 10; 1901 | * my_kd_tree_t mat_index(mat, max_leaf ); 1902 | * mat_index.index->buildIndex(); 1903 | * mat_index.index->... 1904 | * \endcode 1905 | * 1906 | * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality for the points in the data set, allowing more compiler optimizations. 1907 | * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. 1908 | */ 1909 | template 1910 | struct KDTreeEigenMatrixAdaptor 1911 | { 1912 | typedef KDTreeEigenMatrixAdaptor self_t; 1913 | typedef typename MatrixType::Scalar num_t; 1914 | typedef typename MatrixType::Index IndexType; 1915 | typedef typename Distance::template traits::distance_t metric_t; 1916 | typedef KDTreeSingleIndexAdaptor< metric_t,self_t, MatrixType::ColsAtCompileTime,IndexType> index_t; 1917 | 1918 | index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. 1919 | 1920 | /// Constructor: takes a const ref to the matrix object with the data points 1921 | KDTreeEigenMatrixAdaptor(const MatrixType &mat, const int leaf_max_size = 10) : m_data_matrix(mat) 1922 | { 1923 | const IndexType dims = mat.cols(); 1924 | index = new index_t( dims, *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); 1925 | index->buildIndex(); 1926 | } 1927 | private: 1928 | /** Hidden copy constructor, to disallow copying this class (Not implemented) */ 1929 | KDTreeEigenMatrixAdaptor(const self_t&); 1930 | public: 1931 | 1932 | ~KDTreeEigenMatrixAdaptor() { 1933 | delete index; 1934 | } 1935 | 1936 | const MatrixType &m_data_matrix; 1937 | 1938 | /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). 1939 | * Note that this is a short-cut method for index->findNeighbors(). 1940 | * The user can also call index->... methods as desired. 1941 | * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. 1942 | */ 1943 | 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 1944 | { 1945 | nanoflann::KNNResultSet resultSet(num_closest); 1946 | resultSet.init(out_indices, out_distances_sq); 1947 | index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); 1948 | } 1949 | 1950 | /** @name Interface expected by KDTreeSingleIndexAdaptor 1951 | * @{ */ 1952 | 1953 | const self_t & derived() const { 1954 | return *this; 1955 | } 1956 | self_t & derived() { 1957 | return *this; 1958 | } 1959 | 1960 | // Must return the number of data points 1961 | inline size_t kdtree_get_point_count() const { 1962 | return m_data_matrix.rows(); 1963 | } 1964 | 1965 | // Returns the dim'th component of the idx'th point in the class: 1966 | inline num_t kdtree_get_pt(const IndexType idx, int dim) const { 1967 | return m_data_matrix.coeff(idx, IndexType(dim)); 1968 | } 1969 | 1970 | // Optional bounding-box computation: return false to default to a standard bbox computation loop. 1971 | // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. 1972 | // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) 1973 | template 1974 | bool kdtree_get_bbox(BBOX& /*bb*/) const { 1975 | return false; 1976 | } 1977 | 1978 | /** @} */ 1979 | 1980 | }; // end of KDTreeEigenMatrixAdaptor 1981 | /** @} */ 1982 | 1983 | /** @} */ // end of grouping 1984 | } // end of NS 1985 | 1986 | 1987 | #endif /* NANOFLANN_HPP_ */ 1988 | -------------------------------------------------------------------------------- /include/point.h: -------------------------------------------------------------------------------- 1 | #ifndef POINT_H 2 | #define POINT_H 3 | 4 | #include 5 | 6 | class point_t { 7 | public: 8 | /* 9 | * constructors 10 | */ 11 | point_t(); 12 | point_t(float x, float y, float z); 13 | 14 | // unpack values into a float array 15 | void unpack(float * array); 16 | 17 | // get element of point by index. used for nanoflann kd tree 18 | float get_element(int index); 19 | 20 | // pretty print 21 | std::string to_string(); 22 | 23 | private: 24 | float elems[3]; 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /include/warp_field.h: -------------------------------------------------------------------------------- 1 | #ifndef WARP_FIELD_H 2 | #define WARP_FIELD_H 3 | 4 | #include 5 | #include "dual_quat.h" 6 | #include "deform_node.h" 7 | #include "frame.h" 8 | 9 | // includes for nanoflann, as well as a typedef for brevity 10 | #include "nanoflann.hpp" 11 | using namespace nanoflann; 12 | 13 | class warp_field_t; 14 | typedef KDTreeSingleIndexAdaptor< 15 | L2_Simple_Adaptor, 16 | warp_field_t, 17 | 3 18 | > kd_tree_t; 19 | 20 | class warp_field_t { 21 | public: 22 | // constructor 23 | warp_field_t(); 24 | 25 | // destructor 26 | ~warp_field_t(); 27 | 28 | /* 29 | * interface required for nanoflann implementation of kd-tree 30 | */ 31 | // size of the warp field 32 | size_t kdtree_get_point_count() const; 33 | 34 | // access individual elements of points in cloud 35 | float kdtree_get_pt(const size_t idx, int dim) const; 36 | 37 | // unused, optional interface method to determine bounding box 38 | template 39 | bool kdtree_get_bbox(BBOX& bb) const { 40 | return false; 41 | } 42 | 43 | // retrieve the N closest neighbouring nodes from a given point 44 | std::vector find_neighbours(point_t p); 45 | 46 | private: 47 | // constants 48 | const static int NUM_NEIGHBOURS = 8; 49 | 50 | // kd-tree for efficient closest neighbour search 51 | kd_tree_t * kd_tree; 52 | 53 | // vector of deformation nodes in the warp field defining the state of the warp field 54 | std::vector nodes; 55 | 56 | /* 57 | * private methods 58 | */ 59 | // calculate dual-quaternion blending for a given point 60 | dual_quat_t dual_quaternion_blending(point_t p); 61 | 62 | // updates the warp field 63 | void update_warp_field(); 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /installDependencies.sh: -------------------------------------------------------------------------------- 1 | # CMake 2 | sudo apt-get install cmake 3 | 4 | # 5 | # prerequisites for ceres 6 | # 7 | 8 | # google-glog + gflags 9 | sudo apt-get install libgoogle-glog-dev 10 | # BLAS & LAPACK 11 | sudo apt-get install libatlas-base-dev 12 | # Eigen3 13 | sudo apt-get install libeigen3-dev 14 | # SuiteSparse and CXSparse (optional) 15 | # - If you want to build Ceres as a *static* library (the default) 16 | # you can use the SuiteSparse package in the main Ubuntu package 17 | # repository: 18 | sudo apt-get install libsuitesparse-dev 19 | sudo apt-get update 20 | sudo apt-get install libsuitesparse-dev 21 | 22 | # 23 | # ceres solver 24 | # 25 | if cd build/ceres-solver; then 26 | git pull 27 | cd .. 28 | else 29 | cd build 30 | git clone https://github.com/ceres-solver/ceres-solver.git 31 | cd ceres-solver 32 | cd build 33 | cmake .. 34 | make -j3 35 | sudo make install 36 | cd ../.. 37 | fi 38 | cd .. 39 | 40 | -------------------------------------------------------------------------------- /projectBuild.sh: -------------------------------------------------------------------------------- 1 | cd build 2 | cmake CMakeLists.txt . 3 | make 4 | ./dynamicfusion 5 | -------------------------------------------------------------------------------- /report/build.sh: -------------------------------------------------------------------------------- 1 | rm report.pdf 2 | latex report 3 | bibtex report 4 | latex report 5 | latex report 6 | evince report.pdf 7 | -------------------------------------------------------------------------------- /report/report.bib: -------------------------------------------------------------------------------- 1 | @INPROCEEDINGS{kinectfusion, 2 | author={R. A. Newcombe and S. Izadi and O. Hilliges and D. Molyneaux and D. Kim and A. J. Davison and P. Kohi and J. Shotton and S. Hodges and A. Fitzgibbon}, 3 | booktitle={2011 10th IEEE International Symposium on Mixed and Augmented Reality}, 4 | title={KinectFusion: Real-time dense surface mapping and tracking}, 5 | year={2011}, 6 | volume={}, 7 | number={}, 8 | pages={127-136}, 9 | keywords={Cameras;Image reconstruction;Iterative closest point algorithm;Real time systems;Simultaneous localization and mapping;Surface reconstruction;Three dimensional displays;AR;Dense Reconstruction;Depth Cameras;GPU;Real-Time;SLAM;Tracking;Volumetric Representation}, 10 | doi={10.1109/ISMAR.2011.6092378}, 11 | ISSN={}, 12 | month={Oct},} 13 | volume deform 14 | killing fusion 15 | 16 | @INPROCEEDINGS{dynamicfusion, 17 | author={R. A. Newcombe and D. Fox and S. M. Seitz}, 18 | booktitle={2015 IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, 19 | title={DynamicFusion: Reconstruction and tracking of non-rigid scenes in real-time}, 20 | year={2015}, 21 | volume={}, 22 | number={}, 23 | pages={343-352}, 24 | keywords={geometry;image denoising;image reconstruction;motion estimation;object tracking;DynamicFusion approach;KinectFusion;RGBD scans;SLAM system;commodity sensors;denoised reconstructions;dense volumetric 6D motion field estimation;detailed reconstructions;live frame;nonrigid scenes tracking;nonrigidly deforming scenes reconstruction;scene geometry reconstruction;Cameras;Geometry;Real-time systems;Shape;Surface reconstruction;Tracking;Transforms}, 25 | doi={10.1109/CVPR.2015.7298631}, 26 | ISSN={1063-6919}, 27 | month={June} 28 | } 29 | 30 | @article{kdtree, 31 | author = {Bentley, Jon Louis}, 32 | title = {Multidimensional Binary Search Trees Used for Associative Searching}, 33 | journal = {Commun. ACM}, 34 | issue_date = {Sept. 1975}, 35 | volume = {18}, 36 | number = {9}, 37 | month = sep, 38 | year = {1975}, 39 | issn = {0001-0782}, 40 | pages = {509--517}, 41 | numpages = {9}, 42 | url = {http://doi.acm.org/10.1145/361002.361007}, 43 | doi = {10.1145/361002.361007}, 44 | acmid = {361007}, 45 | publisher = {ACM}, 46 | address = {New York, NY, USA}, 47 | keywords = {associative retrieval, attribute, binary search trees, binary tree insertion, information retrieval system, intersection queries, key, nearest neighbor queries, partial match queries}, 48 | } 49 | 50 | 51 | @article{icp, 52 | author = "Paul J. Besl and Neil D. McKay", 53 | title = "A Method for Registration of 3-D Shapes", 54 | journal = "IEEE Transactions on Pattern Analysis and Machine Intelligence", 55 | volume = "14", 56 | number = "2", 57 | year = "1992" 58 | } 59 | 60 | 61 | 62 | @article{killingfusion, 63 | author = {Slavcheva, Miroslava and Baust, Maximilian and Cremers, Daniel and Ilic, Slobodan}, 64 | year = {2017}, 65 | month = {07}, 66 | pages = {}, 67 | title = {KillingFusion: Non-rigid 3D Reconstruction without Correspondences} 68 | } 69 | 70 | @inproceedings{volumedeform, 71 | title={VolumeDeform: Real-Time Volumetric Non-rigid Reconstruction}, 72 | author={Matthias Innmann and Michael Zollhofer and Matthias Niessner and Christian Theobalt and Marc Stamminger}, 73 | booktitle={ECCV}, 74 | year={2016} 75 | } 76 | -------------------------------------------------------------------------------- /report/report.tex: -------------------------------------------------------------------------------- 1 | %&pdflatex 2 | \documentclass[a4paper]{article} 3 | 4 | %% Language and font encodings 5 | \usepackage[english]{babel} 6 | \usepackage[utf8x]{inputenc} 7 | \usepackage[T1]{fontenc} 8 | 9 | %% Sets page size and margins 10 | \usepackage[a4paper,top=3cm,bottom=2cm,left=3cm,right=3cm,marginparwidth=1.75cm]{geometry} 11 | 12 | %% Useful packages 13 | \usepackage{amsmath} 14 | \usepackage{graphicx} 15 | \usepackage{listings} 16 | \usepackage[colorinlistoftodos]{todonotes} 17 | \usepackage[colorlinks=true, allcolors=blue]{hyperref} 18 | 19 | \title{Real-time reconstruction of non-rigid objects from RGBD data: Interim Report} 20 | \author{Amelia Gordafarid Crowther} 21 | 22 | \begin{document} 23 | 24 | \maketitle 25 | \section{Introduction} 26 | 27 | \subsection{Motivation} 28 | 29 | The reconstruction of non-rigid objects is a popular area of research currently, with many useful applications in fields such as Robotics, Medicine and Augmented Reality. 30 | 31 | However, most existing systems are designed only to reconstruct static geometry. This leads to hard limitations in the adaptability of the system to a changing environment. Worse still, highly deformable or dynamic objects such as a moving person cannot be reconstructed at all by a static system. 32 | 33 | There has been a lot of interest in techniques for non-rigid reconstruction, but despite it being a popular research topic, few implementations of these proposed systems are widely available. 34 | 35 | The aim of this project is to provide such an implementation that can reconstruct a rigid object from a non-rigid scene in real-time. 36 | 37 | 38 | \section{Background} 39 | 40 | \subsection{Kinect Camera} 41 | 42 | The first and most fundamental element of the system is the sensor used to obtain information about the surface to be reconstructed. The most commonly used sensor for this purpose is the Kinect camera. Originally designed as a commodity level camera for home games consoles, it was available directly to consumers. This means that Kinect cameras are widely and cheaply available to use. 43 | 44 | However, they provide high quality information considering their price. This is provided to the system in the form of an ordinary RGB image, augmented with a fourth parameter indicating the perceived depth from the camera at that pixel. Using the available depth information, it is also possible to compute the normal to the surface at each pixel of the image. In some cases, depth information may be missing at certain pixels, producing so-called `holes' in the depth image. Errors such as these are not difficult to overcome but must be handled. 45 | 46 | 47 | The sensor is capable of producing an RGB-D image to the system in 640x480 resolution at up to 30Hz. This is important, as it determines the amount of information that can be added to the system at each update. Additionally, it places a hard limit on the update rate of the system, which can never exceed the sensor rate. 48 | 49 | \subsection{Point Cloud Correspondences} 50 | 51 | Once all noise has been removed from the RGB-D image, it can be converted into a point cloud, which forms the input to the system at each update. Every point in the cloud is considered to lie on the surface of the object that is being reconstructed. 52 | 53 | The main task of the system, which has been approached using multiple methods, is to find correspondences between the point clouds obtained at each update. These correspondences can be used to resolve all the data obtained into one model, which is the target surface for reconstruction. 54 | 55 | 56 | \subsection{KinectFusion} 57 | 58 | KinectFusion\cite{kinectfusion} provides one such method for determining correspondences between point clouds. It makes one very important assumption: that the surface it is reconstructing is rigid, and will not be deformed while it is being observed by the system. While this is a very restricted approach to the problem, this algorithm laid the groundwork for later non-rigid systems such as DynamicFusion. 59 | 60 | \subsubsection{Iterative Closest Point algorithm} 61 | Iterative Closest Point\cite{icp} (ICP) is a widely used algorithm in this field which has been optimised and adapted for many purposes. Its purpose is simply to take two point clouds and return the transformation which most closely aligns the two clouds. 62 | Psuedo-code for the ICP algorithm is shown below: 63 | 64 | \begin{lstlisting}[language=Python] 65 | def ICP(P, Q): 66 | transform = identity 67 | while not aligned(P, Q): 68 | correspondence = [] 69 | for p in P: 70 | p_t = apply(transform, p) 71 | q = closest point to p_t in Q 72 | correspondence += (p, q) 73 | transform = least_squares(correspondence) 74 | return transform 75 | \end{lstlisting} 76 | 77 | \subsubsection{Principles of operation} 78 | 79 | Initially, a pre-processing phase occurs, where depth and normal maps are constructed from the input data. The system then enters a continuously updating loop with three stages. 80 | 81 | \begin{enumerate} 82 | \item \textbf{Sensor pose estimation}: KinectFusion uses the ICP algorithm to align its prediction of the surface with the actual measurements of the surface it receives. The transform that is returned is rigid, which functions perfectly for this algorithm, but requires adaptation for a non-rigid case. 83 | 84 | \item \textbf{Update surface reconstruction}: Here, the system receives input from the sensor, which it uses to create a truncated signed distance function (TSDF) for that live frame. Together with the TSDFs from previous frames, a composite TSDF is created to represent the system's model of the scene. This composite TSDF is discretised into a 2-dimensional array and stored in global GPU memory. 85 | 86 | \item \textbf{Surface prediction}: At this stage KinectFusion will produce its own depth map by ray-casting against its own model of the scene. This produces a prediction of what it expects the scene to look like. 87 | \end{enumerate} 88 | 89 | 90 | \subsubsection{Limitations} 91 | 92 | The main limitation of the KinectFusion system is that it fails to handle deforming geometry. If a surface is not completely static then it will fail to reconstruct it. 93 | 94 | 95 | There have been several methods proposed for the reconstruction of non-rigid surfaces. DynamicFusion is the most 96 | 97 | \newpage 98 | \subsection{DynamicFusion} 99 | 100 | DynamicFusion\cite{dynamicfusion} was the first system to achieve dense, dynamic scene reconstruction in real-time. Both VolumeDeform and KillingFusion were heavily inspired by or based on DynamicFusion. 101 | 102 | \subsubsection{Warp field} 103 | 104 | The warp field is essentially a set of deformation nodes, each of which is denoted: 105 | 106 | $$\mathcal{N}^t_{\textbf{warp}}=\{ \textbf{dg}_v, \textbf{dg}_w, \textbf{dg}_{se3} \}$$ 107 | 108 | Where the parameters refer to: 109 | 110 | \begin{itemize} 111 | \item $\textbf{dg}_v$: The position vector of the deformation node 112 | 113 | \item $\textbf{dg}_w$: The radius of influence of the node, which determines the strength of the application of the transformation to the node's neighbours 114 | 115 | \item $\textbf{dg}_{se3}$: The transformation of the deformation node, represented using dual quaternions 116 | \end{itemize} 117 | 118 | 119 | \subsubsection{Dual quaternion blending} 120 | 121 | \textbf{Dual quaternions} 122 | 123 | Dual quaternions consist of eight elements providing six degrees of freedom. They are split across two quaternions, named the real and dual parts respectively. They can be used to represent any transformation consisting of a translation and a rotation around an axis.\\ 124 | 125 | \noindent\textbf{KD-Tree} 126 | 127 | 128 | KD-Trees\cite{kdtree} are data structures which are optimised to efficiently find the nearest neighbour to a given point, as well as all points within a range. They are very useful when performing dual quaternion blending, as they allow us to restrict the problem space to only the local deformation nodes on the surface.\\ 129 | 130 | Using these definitions, we are able to define dual quaternion blending. Intuitively, it is the average of the local dual quaternions of the deformation nodes, each weighted according to their radius of influence. Here is the definition from DynamicFusion: 131 | 132 | 133 | $$\textbf{DQB}(x_c) = \frac{\sum_{k \in N(x_c)} \textbf{w}_k(x_c)\hat{\textbf{q}}_{kc}}{\Vert \sum_{k \in N(x_c)} \textbf{w}_k(x_c)\hat{\textbf{q}}_{kc} \Vert}$$ 134 | 135 | In this context, $N(x_c)$ is the set of the $k$ nearest deformation nodes. These can be found very efficciently using a KD-Tree. 136 | 137 | $\textbf{w}_k(x_c)$ is the weight of the $k^{th}$ deformation node, which can be derived using the following formula: 138 | $$\textbf{w}_i(x_c) = \exp \Big(\frac{-\Vert \textbf{dg}^i_v - x_c \Vert ^2}{2(\textbf{dg}^i_w)^2}\Big)$$ 139 | 140 | \subsubsection{Warp function} 141 | 142 | Once dual quaternion blending has been applied, there is only one more step before we can produce the final warp function. The result must be converted from a quaternion back into a transformation matrix. 143 | 144 | Now we can define the warp function: 145 | 146 | $$\mathcal{W}(x_c) = SE3(\textbf{DQB}(x_c))$$ 147 | 148 | This function is capable of warping any deformation node in the canonical model into the live frame. 149 | 150 | \subsubsection{Principles of operation} 151 | 152 | 153 | There are three key steps that occur in each iteration of the DynamicFusion algorithm, and they each take place whenever a new live frame is produced: 154 | 155 | \begin{enumerate} 156 | \item \textbf{Warp field estimation}: The warp field is estimated by choosing parameters such that the error function below is minimised: 157 | 158 | $$E(\mathcal{W}_t, \mathcal{V}, D_t, \mathcal{E}) = \textbf{Data}(\mathcal{W}_t, \mathcal{V}_t, D_t) + \lambda\textbf{Reg}(\mathcal{W}_t, \mathcal{E})$$ 159 | 160 | This function consists of two terms: 161 | \begin{itemize} 162 | \item $\textbf{Data}(\mathcal{W}_t, \mathcal{V}_t, D_t) = \sum\limits_{u \in \Omega} \psi_{data}(\hat{\textbf{n}}_u^\top (\hat{\textbf{v}}_u - \textbf{vl}_u))$ 163 | 164 | This term represents the ICP cost between the model and the live frame. This step is similar to KinectFusion, although it uses a non-rigid version of ICP. 165 | 166 | \item $\textbf{Reg}(\mathcal{W}_t, \mathcal{E}) = \sum\limits_{i=0}^n\sum\limits_{j \in \mathcal{E}(i)}\alpha_{ij}\psi_{reg}(\textbf{T}_{ic}\textbf{dg}^j_v - \textbf{T}_{jc}\textbf{dg}^j_v)$ 167 | 168 | This term which regularises the warp field. This ensures that motion is as smooth as possible, and that deformation is as rigid as possible. 169 | \end{itemize} 170 | 171 | Additionally, the $\lambda$ parameter can be adjusted to determine the relative importance of each term to the error function as a whole. 172 | 173 | $E$ is minimised using Gauss-Newton non-linear optimisation. 174 | 175 | \item \textbf{Surface fusion}: Now that the warp function has been obtained, applying its inverse to the live frame will warp it into the canonical model. This means they occupy the same space relative to each other, so can be directly compared. 176 | 177 | \item \textbf{Updating the canonical model}: Now that the live frame has been warped into the canonical model, points can be added or removed from the canonical model as appropriate. 178 | \end{enumerate} 179 | 180 | \subsubsection{Limitations} 181 | 182 | DynamicFusion is quite limited by its handling of topology changes; the method is robust to a change from open to closed topology, but if the surface changes from a closed topology to an open one too quickly the system will fail to reconstruct the surface. 183 | 184 | It is also limited by unusual changes in motion. For example, it will struggle with large inter-frame motion, or the motion of occluded parts of the surface. 185 | 186 | 187 | \subsection{VolumeDeform} 188 | 189 | VolumeDeform\cite{volumedeform} is different to the other examined techniques in that it doesn't rely solely on depth information. It also uses the RGB data probided by the sensor to extract additional information about the live frame.\\ 190 | 191 | 192 | \subsubsection{Principles of operation} 193 | 194 | VolumeDeform operates similarly to the other methods outlined, in that it involves a loop that repeatedly acquires new knowledge and integrates it with an existing model. 195 | 196 | \begin{enumerate} 197 | \item \textbf{Mesh extraction}: The signed distance field representing the system's model of the scene is used to generate a deformed 3D mesh using Marching Cubes 198 | \item \textbf{Correspondence detection}: The mesh is rendered to produce a depth map which can be used to generate correspondences. 199 | 200 | Additionally, SIFT features are detected in the live frame and stored by the system. Since SIFT features are robust to changes in transformation, they can be recognised again in later frames. This provides extra information to inform the process of correspondence detection. 201 | 202 | \item \textbf{Deformation optimisation}: The deformation field is optimised so that it is consistent with the observed values of colour and depth. 203 | \end{enumerate} 204 | 205 | \subsubsection{Limitations} 206 | 207 | VolumeDeform comes with several limitations. Firstly, the feature tracking provided by the SIFT feature descriptor is not adequate when significant transformations are applied. SIFT feature descriptors are robust, but not robust enough given the computational resources available. 208 | 209 | Additionally, high levels of deformation are not handled well by the system, due to over-distribution of deformation. This is as a result of the regularizer used by the system, which does not account for local rigidity. 210 | 211 | 212 | \subsection{KillingFusion} 213 | 214 | KillingFusion\cite{killingfusion} operates very similarly to DynamicFusion, but introduces a much more sophisticated error function for estimating the warp field. 215 | 216 | \begin{align*} 217 | E_{non-rigid}(\Psi) &= E_{data}(\Psi) + \omega_kE_{Killing}(\Psi) + \omega_sE_{level-set}(\Psi)\\ 218 | E'_{data}(\Psi) &= (\phi_n(\Psi) - \phi_{global}) \nabla_{\phi_n}(\Psi)\\ 219 | E'_{Killing}(\Psi) &= 2H_{uvw}(vec(J^{\top}_{\Psi}) vec\big(J_{\Psi})\big)\begin{pmatrix}1\\\gamma \end{pmatrix}\\ 220 | E'_{level-set}(\Psi) &= \frac{|\nabla_{\phi_n}(\Psi)| - 1}{|\nabla_{\phi_n}(\Psi)|_\epsilon}H_{\phi_n}(\Psi)\nabla_{\phi_n}(\Psi) 221 | \end{align*} 222 | 223 | This error function imposes three requirements on a potential solution: 224 | \begin{itemize} 225 | \item \textbf{Data term}: Similar to DynamicFusion, contains a component for non-rigid transformation. 226 | \item \textbf{Killing condition}: This term requires the flow field to be a Killing vector field. If it satisifies this condition, then it will produce isometric motion Since a perfect isometric motion would impose only rigid transformations, this term only needs to be minimised so that the motion is approximately rigid. Additionally, the requirement has been weakened to make it computationally more efficient. 227 | \item \textbf{Level set property}: The minimisation of this term ensures that the gradient of the SDF function remains approximately 1 228 | \end{itemize} 229 | 230 | The additional parameters $\omega_k$ and $\omega_s$ are used to adjust the relative importance of the three terms when calculating $E_{non-rigid}$. 231 | 232 | This error function is also much more easily parallelised, leading to greatly increased performance.\\ 233 | 234 | 235 | \subsubsection{Limitations} 236 | 237 | KillingFusion has been shown to handle fast inter-frame motion and topological changes much better than alternatives such as DynamicFusion or VolumeDeform. This means that it is more desirable as a solution than the other methods outlined here. 238 | 239 | It does however still suffer from the same limitations as all of these methods in that it can only be applied to a small volume until further improvements to the method are made. 240 | 241 | \newpage 242 | \section{Project Plan} 243 | 244 | \subsection{Milestones} 245 | \begin{itemize} 246 | \item February: Fully understand codebase, start programming. 247 | \item March: Reliably estimate warp field. 248 | \item April: Produce correct behaviour for DynamicFusion, not necessarily real-time. 249 | \item May: Have code running on GPU using CUDA, produce real-time solution. 250 | \item 11th May: Start working on final report. 251 | \item June: All coding finished. 252 | \item 18th June: Final report submission. 253 | \end{itemize} 254 | 255 | \subsection{Minimum Viable Product} 256 | 257 | In a worst-case scenario, I may fail to meet the majority of the targets I set myself in terms of implementation as well as performance. In this case, I would be able to fall back on an offline implementation of DynamicFusion. This would mean I could demonstrate the functionality of the system, without needing it to operate in real-time. 258 | 259 | \subsection{Possible Extensions} 260 | 261 | As a possible extension to the project, a tool to visualise the canonical model could be produced. In particular, to see how the canonical model evolves in real-time would be very useful to the user. 262 | 263 | 264 | \section{Evaluation Plan} 265 | \subsection{Required Functionality} 266 | 267 | At a bare minimum, the system must be able to reconstruct a dynamic scene, not necessarily in real-time. 268 | 269 | Once the correct behaviour has been implemented, then the system can be optimised to work in real-time. This is the scenario I would expect to operate in. 270 | 271 | If the project progresses well, the system could be tested under unfavourable conditions, such as large inter-frame movement or rapid topology changes. It could then be improved to handle these cases more accurately, perhaps incorporating techniques from algorithms such as KillingFusion. 272 | 273 | If the project has progressed excellently, several extensions can be pursued as outlined in the last section. 274 | 275 | 276 | \subsection{Performance Metrics} 277 | 278 | It is very simple to evaluate the performance of the final system, as its efficiency is clearly observable in the average framerate it achieves. 279 | 280 | Ideally, the system will be able to run live and smoothly. This would mean achieving the same framerate as the Kinect sensor, which is 30Hz. Practically speaking, it is likely that the system will not achieve such high performance. Therefore, a reduced target framerate of approximately 10Hz or over can be considered more achievable, while still providing a live .... 281 | 282 | In the case that this target is not reached either, I will aim to produce an offline solution as a fall-back. Although the processing time will cease to be of such high importance in this scenario, it can still be used as a valid metric of the system's efficiency. 283 | 284 | 285 | \bibliographystyle{plain} 286 | \bibliography{report} 287 | 288 | \end{document} 289 | -------------------------------------------------------------------------------- /src/deform_node.cpp: -------------------------------------------------------------------------------- 1 | #include "deform_node.h" 2 | 3 | #include "warp_field.h" 4 | 5 | #include 6 | 7 | deform_node_t::deform_node_t(warp_field_t * wf, point_t p, dual_quat_t dq, float w){ 8 | warp_field = wf; 9 | position = p; 10 | transform = dq; 11 | radial_weight = w; 12 | } 13 | 14 | float 15 | deform_node_t::regularisation(float phi){ 16 | // TODO: this knn search will return this node as well. 17 | // the terms cancel so it will end up being zero, but may want to 18 | // remove for efficiency or check an extra one to compensate. 19 | auto neighbours = warp_field->find_neighbours(position); 20 | 21 | float result = 0.0f; 22 | for (deform_node_t * neighbour : neighbours){ 23 | float alpha = std::max(radial_weight, neighbour->radial_weight); 24 | } 25 | 26 | return result; 27 | } 28 | 29 | float 30 | deform_node_t::weight(point_t p){ 31 | //TODO 32 | return 0; 33 | } 34 | 35 | dual_quat_t 36 | deform_node_t::get_transform(){ 37 | return transform; 38 | } 39 | 40 | point_t 41 | deform_node_t::get_position(){ 42 | return position; 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/dual_quat.cpp: -------------------------------------------------------------------------------- 1 | #include "dual_quat.h" 2 | 3 | #include 4 | 5 | dual_quat_t::dual_quat_t(){ 6 | 7 | } 8 | 9 | dual_quat_t::dual_quat_t(quat_t real, quat_t dual){ 10 | this->real = real; 11 | this->dual = dual; 12 | } 13 | 14 | dual_quat_t 15 | dual_quat_t::operator*(float scale){ 16 | return dual_quat_t(real, dual * scale); 17 | } 18 | 19 | void 20 | dual_quat_t::operator+=(const dual_quat_t & other){ 21 | real += other.real; 22 | dual += other.dual; 23 | } 24 | 25 | void 26 | dual_quat_t::normalise(){ 27 | float dot = 28 | real.R_component_1() * real.R_component_1() + 29 | real.R_component_2() * real.R_component_2() + 30 | real.R_component_3() * real.R_component_3() + 31 | real.R_component_4() * real.R_component_4(); 32 | real /= sqrt(dot); 33 | } 34 | -------------------------------------------------------------------------------- /src/dynamic_fusion.cpp: -------------------------------------------------------------------------------- 1 | #include "dynamic_fusion.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace ceres; 7 | 8 | struct CostFunctor { 9 | template bool operator()(const T* const x, T* residual) const { 10 | residual[0] = 10.0 - x[0]; 11 | return true; 12 | } 13 | }; 14 | 15 | dynamic_fusion_t::dynamic_fusion_t(){ 16 | 17 | } 18 | 19 | dynamic_fusion_t::~dynamic_fusion_t(){ 20 | 21 | } 22 | 23 | void 24 | dynamic_fusion_t::execute(){ 25 | // The variable to solve for with its initial value. It will be 26 | // mutated in place by the solver. 27 | double x = 0.5; 28 | const double initial_x = x; 29 | 30 | // Build the problem. 31 | Problem problem; 32 | 33 | // Set up the only cost function (also known as residual). This uses 34 | // auto-differentiation to obtain the derivative (jacobian). 35 | CostFunction* cost_function = 36 | new AutoDiffCostFunction(new CostFunctor); 37 | problem.AddResidualBlock(cost_function, NULL, &x); 38 | 39 | // Run the solver! 40 | Solver::Options options; 41 | options.minimizer_progress_to_stdout = true; 42 | Solver::Summary summary; 43 | Solve(options, &problem, &summary); 44 | 45 | std::cout << summary.BriefReport() << "\n"; 46 | std::cout << "x : " << initial_x 47 | << " -> " << x << "\n"; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "dynamic_fusion.h" 2 | 3 | #include 4 | 5 | int main(){ 6 | dynamic_fusion_t d_fusion; 7 | d_fusion.execute(); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /src/point.cpp: -------------------------------------------------------------------------------- 1 | #include "point.h" 2 | 3 | point_t::point_t() : point_t(0, 0, 0){ 4 | 5 | } 6 | 7 | point_t::point_t(float x, float y, float z){ 8 | elems[0] = x; 9 | elems[1] = y; 10 | elems[2] = z; 11 | } 12 | 13 | void 14 | point_t::unpack(float * array){ 15 | if (array != nullptr){ 16 | for (int i = 0; i < 3; i++){ 17 | array[i] = elems[i]; 18 | } 19 | } 20 | } 21 | 22 | float 23 | point_t::get_element(int index){ 24 | if (index < 0 || index > 3){ 25 | // AAAAH 26 | return 0; 27 | } else { 28 | return elems[index]; 29 | } 30 | } 31 | 32 | std::string 33 | point_t::to_string(){ 34 | return "point(" + std::to_string(elems[0]) + ", " + std::to_string(elems[1]) + ", " + std::to_string(elems[2]) + ")"; 35 | } 36 | -------------------------------------------------------------------------------- /src/saxpy.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __global__ 4 | void saxpy(int n, float a, float *x, float *y) 5 | { 6 | int i = blockIdx.x*blockDim.x + threadIdx.x; 7 | if (i < n) y[i] = a*x[i] + y[i]; 8 | } 9 | 10 | int main(void) 11 | { 12 | int N = 1<<20; 13 | float *x, *y, *d_x, *d_y; 14 | x = (float*)malloc(N*sizeof(float)); 15 | y = (float*)malloc(N*sizeof(float)); 16 | 17 | cudaMalloc(&d_x, N*sizeof(float)); 18 | cudaMalloc(&d_y, N*sizeof(float)); 19 | 20 | for (int i = 0; i < N; i++) { 21 | x[i] = 1.0f; 22 | y[i] = 2.0f; 23 | } 24 | 25 | cudaMemcpy(d_x, x, N*sizeof(float), cudaMemcpyHostToDevice); 26 | cudaMemcpy(d_y, y, N*sizeof(float), cudaMemcpyHostToDevice); 27 | 28 | // Perform SAXPY on 1M elements 29 | saxpy<<<(N+255)/256, 256>>>(N, 2.0f, d_x, d_y); 30 | 31 | cudaMemcpy(y, d_y, N*sizeof(float), cudaMemcpyDeviceToHost); 32 | 33 | float maxError = 0.0f; 34 | for (int i = 0; i < N; i++) 35 | maxError = max(maxError, abs(y[i]-4.0f)); 36 | printf("Max error: %f\n", maxError); 37 | 38 | cudaFree(d_x); 39 | cudaFree(d_y); 40 | free(x); 41 | free(y); 42 | } 43 | -------------------------------------------------------------------------------- /src/warp_field.cpp: -------------------------------------------------------------------------------- 1 | #include "warp_field.h" 2 | 3 | #include 4 | 5 | warp_field_t::warp_field_t(){ 6 | kd_tree = new kd_tree_t(3, *this, KDTreeSingleIndexAdaptorParams(10)); 7 | } 8 | 9 | warp_field_t::~warp_field_t(){ 10 | delete kd_tree; 11 | } 12 | 13 | std::vector 14 | warp_field_t::find_neighbours(point_t p){ 15 | // output parameters from knnSearch, representing the indices of the closest points 16 | // and the distance to those points, which for now are discarded 17 | size_t neighbour_indices[NUM_NEIGHBOURS]; 18 | float neighbour_distances[NUM_NEIGHBOURS]; 19 | 20 | // unpack vector into float array 21 | float query[3]; 22 | p.unpack(query); 23 | 24 | // query KD-tree 25 | 26 | // TODO: this should be after every modification of the node vector 27 | kd_tree->buildIndex(); 28 | int number_found = kd_tree->knnSearch(query, NUM_NEIGHBOURS, neighbour_indices, neighbour_distances); 29 | 30 | // create map to return 31 | std::vector result; 32 | for (int i = 0; i < number_found; i++){ 33 | result.push_back(nodes[neighbour_indices[i]]); 34 | } 35 | 36 | return result; 37 | } 38 | 39 | dual_quat_t 40 | warp_field_t::dual_quaternion_blending(point_t p){ 41 | // find the N closest neighbours to the given point 42 | auto neighbours = find_neighbours(p); 43 | 44 | // sum over all neighbours transforms adjusted by their weight 45 | dual_quat_t q; 46 | for (auto neighbour : neighbours){ 47 | // NOTE: dynfu implementation has this as a product (*=) 48 | // but dynamicfusion paper specifies a summation? 49 | q += neighbour->get_transform() * neighbour->weight(p); 50 | } 51 | 52 | // normalize and return 53 | q.normalise(); 54 | return q; 55 | } 56 | 57 | size_t 58 | warp_field_t::kdtree_get_point_count() const { 59 | return nodes.size(); 60 | } 61 | 62 | float 63 | warp_field_t::kdtree_get_pt(const size_t idx, int dim) const { 64 | return nodes[idx]->get_position().get_element(dim); 65 | } 66 | 67 | void 68 | warp_field_t::update_warp_field(){ 69 | kd_tree->buildIndex(); 70 | } 71 | --------------------------------------------------------------------------------