├── src
└── DLoopDetector.cmake.in
├── .travis.yml
├── include
└── DLoopDetector
│ ├── DLoopDetector.h
│ └── TemplatedLoopDetector.h
├── LICENSE.txt
├── README.md
├── demo
├── demo_brief.cpp
└── demoDetector.h
└── CMakeLists.txt
/src/DLoopDetector.cmake.in:
--------------------------------------------------------------------------------
1 | FIND_LIBRARY(DLoopDetector_LIBRARY DLoopDetector
2 | PATHS @CMAKE_INSTALL_PREFIX@/lib
3 | )
4 | FIND_PATH(DLoopDetector_INCLUDE_DIR DLoopDetectorConfig.cmake
5 | PATHS @CMAKE_INSTALL_PREFIX@/include/@PROJECT_NAME@
6 | )
7 | SET(DLoopDetector_LIBRARIES ${DLoopDetector_LIBRARY})
8 | SET(DLoopDetector_LIBS ${DLoopDetector_LIBRARY})
9 | SET(DLoopDetector_INCLUDE_DIRS ${DLoopDetector_INCLUDE_DIR})
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: cpp
2 | os:
3 | - linux
4 | - osx
5 | - windows
6 | dist:
7 | - bionic
8 | osx_image:
9 | - xcode11.3
10 | env:
11 | - OPENCV_BRANCH=master
12 | - OPENCV_BRANCH=3.4
13 | cache:
14 | directories:
15 | - ${TRAVIS_BUILD_DIR}/opencv
16 | before_script:
17 | - rmdir opencv || true
18 | - if [ ! -d opencv ]; then git clone --single-branch --branch ${OPENCV_BRANCH} https://github.com/opencv/opencv.git; fi
19 | - pushd opencv
20 | - mkdir -p build
21 | - cd build
22 | - cmake .. -DBUILD_LIST=core,imgproc,imgcodecs,calib3d,highgui,features2d,flann -DBUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=../install
23 | - cmake --build . --parallel 8 --target install --config Release
24 | - popd
25 | script:
26 | - mkdir -p build
27 | - cd build
28 | - export OPENCV_CONFIG=$(dirname $(find ${TRAVIS_BUILD_DIR}/opencv/install -name OpenCVConfig.cmake | head -n1))
29 | - cmake .. -DBUILD_DemoBRIEF=ON -DOpenCV_DIR=${OPENCV_CONFIG}
30 | - cmake --build . --config Release
31 |
--------------------------------------------------------------------------------
/include/DLoopDetector/DLoopDetector.h:
--------------------------------------------------------------------------------
1 | /*
2 | * File: DLoopDetector.h
3 | * Date: November 2011
4 | * Author: Dorian Galvez-Lopez
5 | * Description: Generic include file for the DLoopDetector classes and
6 | * the specialized loop detectors
7 | * License: see the LICENSE.txt file
8 | *
9 | */
10 |
11 | /*! \mainpage DLoopDetector Library
12 | *
13 | * DLoopDetector library for C++:
14 | * Loop detector for monocular image sequences based on bags of words
15 | *
16 | * Written by Dorian Galvez-Lopez,
17 | * University of Zaragoza
18 | *
19 | * Check my website to obtain updates: http://webdiis.unizar.es/~dorian
20 | *
21 | * \section requirements Requirements
22 | * This library requires the DUtils, DUtilsCV, DVision, DBoW2 and OpenCV libraries,
23 | * as well as the boost::dynamic_bitset class.
24 | *
25 | * \section citation Citation
26 | * If you use this software in academic works, please cite:
27 |
28 | @@ARTICLE{GalvezTRO12,
29 | author={Galvez-Lopez, Dorian and Tardos, J. D.},
30 | journal={IEEE Transactions on Robotics},
31 | title={Bags of Binary Words for Fast Place Recognition in Image Sequences},
32 | year={2012},
33 | month={October},
34 | volume={28},
35 | number={5},
36 | pages={1188--1197},
37 | doi={10.1109/TRO.2012.2197158},
38 | ISSN={1552-3098}
39 | }
40 |
41 | *
42 | */
43 |
44 |
45 | #ifndef __D_T_LOOP_DETECTOR__
46 | #define __D_T_LOOP_DETECTOR__
47 |
48 | /// Loop detector for sequences of monocular images
49 | namespace DLoopDetector
50 | {
51 | }
52 |
53 | #include "DBoW2.h"
54 | #include "TemplatedLoopDetector.h"
55 | #include "FORB.h"
56 | #include "FBrief.h"
57 |
58 | /// SURF64 Loop Detector
59 | typedef DLoopDetector::TemplatedLoopDetector
60 | OrbLoopDetector;
61 |
62 | /// BRIEF Loop Detector
63 | typedef DLoopDetector::TemplatedLoopDetector
64 | BriefLoopDetector;
65 |
66 | #endif
67 |
68 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | DLoopDetector: loop detector for monocular image sequences
2 |
3 | Copyright (c) 2015 Dorian Galvez-Lopez. http://doriangalvez.com
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions
8 | are met:
9 | 1. Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 | 2. Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in the
13 | documentation and/or other materials provided with the distribution.
14 | 3. The original author of the work must be notified of any
15 | redistribution of source code or in binary form.
16 | 4. Neither the name of copyright holders nor the names of its
17 | contributors may be used to endorse or promote products derived
18 | from this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 | ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
24 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 | POSSIBILITY OF SUCH DAMAGE.
31 |
32 | If you use it in an academic work, please cite:
33 |
34 | @ARTICLE{GalvezTRO12,
35 | author={G\'alvez-L\'opez, Dorian and Tard\'os, J. D.},
36 | journal={IEEE Transactions on Robotics},
37 | title={Bags of Binary Words for Fast Place Recognition in Image Sequences},
38 | year={2012},
39 | month={October},
40 | volume={28},
41 | number={5},
42 | pages={1188--1197},
43 | doi={10.1109/TRO.2012.2197158},
44 | ISSN={1552-3098}
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | DLoopDetector
2 | =============
3 |
4 | ## Overview
5 |
6 | DLoopDetector is an open source C++ library to detect loops in a sequence of images collected by a mobile robot. It implements the algorithm presented in GalvezTRO12, based on a bag-of-words database created from image local descriptors, and temporal and geometrical constraints. The current implementation includes versions to work with SURF64 and BRIEF descriptors. DLoopDetector is based on the DBoW2 library, so that it can work with any other type of descriptor with little effort.
7 |
8 | DLoopDetector requires OpenCV and the `boost::dynamic_bitset` class in order to use the BRIEF version.
9 |
10 | DLoopDetector has been tested on several real datasets, yielding an execution time of ~9 ms to detect a loop a in a sequence with more than 19000 images (without considering the feature extraction). When BRIEF descriptors were used, the feature extraction and the loop detection were performed in 16 ms on average.
11 |
12 | ## Citing
13 |
14 | If you use this software in an academic work, please cite:
15 |
16 | @ARTICLE{GalvezTRO12,
17 | author={G\'alvez-L\'opez, Dorian and Tard\'os, J. D.},
18 | journal={IEEE Transactions on Robotics},
19 | title={Bags of Binary Words for Fast Place Recognition in Image Sequences},
20 | year={2012},
21 | month={October},
22 | volume={28},
23 | number={5},
24 | pages={1188--1197},
25 | doi={10.1109/TRO.2012.2197158},
26 | ISSN={1552-3098}
27 | }
28 |
29 | ## Install and usage notes
30 |
31 | DLoopDetector requires [DLib](https://github.com/dorian3d/DLib) and [DBoW2](https://github.com/dorian3d/DBoW2), which are installed automatically.
32 |
33 | DLoopDetector requires OpenCV and the `boost::dynamic_bitset` class in order to use the BRIEF version. You can install Boost by typing:
34 |
35 | $ sudo apt-get install libboost-dev
36 |
37 |
38 | To check how to use DLoopDetector, compile the demo applications. This demo includes the DLoopDetector classes, bag-of-words vocabularies with 10^6 words to use with SURF64 or BRIEF features, a small collection of images of the Bicocca 2009-02-25b dataset of the Rawseeds FP-6 project and a demo application to find loops in these images.
39 |
40 | Note that the demo applications require some external resources that are downloaded automatically when they are activated in CMake. The resource file is 374MB so it may take a while to download.
41 |
42 | You can run either demo_surf or demo_brief. When the demo is running, you should see a window with the current image, a window with the trajectory of the robot and the status of the detection process in the console:
43 | 
44 |
45 | The main functionality of the demo is written in the `demoDetector.h` class. Check it to see how to change the parameters of the loop detector.
46 |
--------------------------------------------------------------------------------
/demo/demo_brief.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * File: demo_brief.cpp
3 | * Date: November 2011
4 | * Author: Dorian Galvez-Lopez
5 | * Description: demo application of DLoopDetector
6 | * License: see the LICENSE.txt file
7 | */
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | // DLoopDetector and DBoW2
14 | #include // defines BriefVocabulary
15 | #include "DLoopDetector.h" // defines BriefLoopDetector
16 | #include // Brief
17 |
18 | // OpenCV
19 | #include
20 | #include
21 |
22 | #include "demoDetector.h"
23 |
24 | using namespace DLoopDetector;
25 | using namespace DBoW2;
26 | using namespace DVision;
27 | using namespace std;
28 |
29 | // ----------------------------------------------------------------------------
30 |
31 | static const char *VOC_FILE = "./resources/brief_k10L6.voc.gz";
32 | static const char *IMAGE_DIR = "./resources/images";
33 | static const char *POSE_FILE = "./resources/pose.txt";
34 | static const int IMAGE_W = 640; // image size
35 | static const int IMAGE_H = 480;
36 | static const char *BRIEF_PATTERN_FILE = "./resources/brief_pattern.yml";
37 |
38 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
39 |
40 | /// This functor extracts BRIEF descriptors in the required format
41 | class BriefExtractor: public FeatureExtractor
42 | {
43 | public:
44 | /**
45 | * Extracts features from an image
46 | * @param im image
47 | * @param keys keypoints extracted
48 | * @param descriptors descriptors extracted
49 | */
50 | void operator()(const cv::Mat &im,
51 | vector &keys, vector &descriptors) const override;
52 |
53 | /**
54 | * Creates the brief extractor with the given pattern file
55 | * @param pattern_file
56 | */
57 | BriefExtractor(const std::string &pattern_file);
58 |
59 | private:
60 |
61 | /// BRIEF descriptor extractor
62 | BRIEF256 m_brief;
63 | };
64 |
65 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
66 |
67 | int main(int argc, char* argv[])
68 | {
69 | bool show = true;
70 | if (argc > 1 && std::string(argv[1]) == "-noshow")
71 | show = false;
72 |
73 | // prepares the demo
74 | demoDetector
75 | demo(VOC_FILE, IMAGE_DIR, POSE_FILE, IMAGE_W, IMAGE_H, show);
76 |
77 | try
78 | {
79 | // run the demo with the given functor to extract features
80 | BriefExtractor extractor(BRIEF_PATTERN_FILE);
81 | demo.run("BRIEF", extractor);
82 | }
83 | catch(const std::string &ex)
84 | {
85 | cout << "Error: " << ex << endl;
86 | }
87 |
88 | return 0;
89 | }
90 |
91 | // ----------------------------------------------------------------------------
92 |
93 | BriefExtractor::BriefExtractor(const std::string &pattern_file)
94 | {
95 | // The DVision::BRIEF extractor computes a random pattern by default when
96 | // the object is created.
97 | // We load the pattern that we used to build the vocabulary, to make
98 | // the descriptors compatible with the predefined vocabulary
99 |
100 | // loads the pattern
101 | cv::FileStorage fs(pattern_file.c_str(), cv::FileStorage::READ);
102 | if(!fs.isOpened()) throw string("Could not open file ") + pattern_file;
103 |
104 | vector x1, y1, x2, y2;
105 | fs["x1"] >> x1;
106 | fs["x2"] >> x2;
107 | fs["y1"] >> y1;
108 | fs["y2"] >> y2;
109 |
110 | m_brief.importPairs(x1, y1, x2, y2);
111 | }
112 |
113 | // ----------------------------------------------------------------------------
114 |
115 | void BriefExtractor::operator() (const cv::Mat &im,
116 | vector &keys, vector &descriptors) const
117 | {
118 | // extract FAST keypoints with opencv
119 | const int fast_th = 20; // corner detector response threshold
120 | cv::FAST(im, keys, fast_th, true);
121 |
122 | // compute their BRIEF descriptor
123 | m_brief.compute(im, keys, descriptors);
124 | }
125 |
126 | // ----------------------------------------------------------------------------
127 |
128 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8)
2 | project(DLoopDetector)
3 | include(ExternalProject)
4 |
5 | option(BUILD_DemoBRIEF "Build demo application with BRIEF features" OFF)
6 |
7 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
8 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
9 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
10 | "MinSizeRel" "RelWithDebInfo")
11 | endif()
12 |
13 | if(MSVC)
14 | add_compile_options(/W4)
15 | else()
16 | add_compile_options(-Wall -Wextra -Wpedantic)
17 | endif()
18 |
19 | set(HDRS
20 | include/DLoopDetector/DLoopDetector.h include/DLoopDetector/TemplatedLoopDetector.h)
21 |
22 | set(DEPENDENCY_DIR ${CMAKE_CURRENT_BINARY_DIR}/dependencies)
23 | set(DEPENDENCY_INSTALL_DIR ${DEPENDENCY_DIR}/install)
24 | set(DEPENDENCY_CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})
25 | if(DEFINED OpenCV_DIR)
26 | set(DEPENDENCY_CMAKE_ARGS ${DEPENDENCY_CMAKE_ARGS} -DOpenCV_DIR=${OpenCV_DIR})
27 | endif()
28 |
29 | find_package(OpenCV REQUIRED)
30 | include_directories(${OpenCV_INCLUDE_DIRS})
31 |
32 | macro(GetDependency name other_dependency)
33 | find_package(${name} QUIET
34 | PATHS ${DEPENDENCY_INSTALL_DIR})
35 | if(${${name}_FOUND})
36 | message("${name} library found, using it from the system")
37 | include_directories(${${name}_INCLUDE_DIRS})
38 | add_custom_target(${name}_dep)
39 | else(${${name}_FOUND})
40 | message("${name} library not found in the system, it will be downloaded on build")
41 | option(DOWNLOAD_${name}_dependency "Download ${name} dependency" ON)
42 | if(${DOWNLOAD_${name}_dependency})
43 | if(NOT ${other_dependency})
44 | set(dependency ${other_dependency}_dep)
45 | endif()
46 | ExternalProject_Add(${name}
47 | PREFIX ${DEPENDENCY_DIR}
48 | GIT_REPOSITORY http://github.com/dorian3d/${name}
49 | GIT_TAG master
50 | INSTALL_DIR ${DEPENDENCY_INSTALL_DIR}
51 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= ${DEPENDENCY_CMAKE_ARGS}
52 | DEPENDS ${dependency})
53 | add_custom_target(${name}_dep ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} DEPENDS ${name})
54 | else()
55 | message(SEND_ERROR "Please, activate DOWNLOAD_${name}_dependency option or download manually")
56 | endif(${DOWNLOAD_${name}_dependency})
57 | endif(${${name}_FOUND})
58 | endmacro(GetDependency)
59 |
60 | GetDependency(DLib "")
61 | GetDependency(DBoW2 DLib)
62 | add_custom_target(Dependencies ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} DEPENDS DBoW2_dep DLib_dep)
63 |
64 | include_directories(include/DLoopDetector/)
65 |
66 | if(BUILD_DemoBRIEF)
67 | add_executable(demo_brief demo/demo_brief.cpp)
68 | add_dependencies(demo_brief DLib_dep DBoW2_dep)
69 | target_link_libraries(demo_brief ${OpenCV_LIBS} ${DLib_LIBS} ${DBoW2_LIBS})
70 | target_include_directories(demo_brief PUBLIC ${DLib_INCLUDE_DIRS} ${DBoW2_INCLUDE_DIRS})
71 | set_target_properties(demo_brief PROPERTIES CXX_STANDARD 11)
72 | endif(BUILD_DemoBRIEF)
73 |
74 | #if(BUILD_DemoSURF)
75 | # add_executable(demo_surf demo/demo_surf.cpp)
76 | # target_link_libraries(demo_surf ${OpenCV_LIBS} ${DLib_LIBS} ${DBoW2_LIBS})
77 | #endif(BUILD_DemoSURF)
78 |
79 | if(BUILD_DemoBRIEF)
80 | set(RESOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/resources/)
81 | set(RESOURCE_FILE ${CMAKE_CURRENT_BINARY_DIR}/resources.tar.gz)
82 | if(NOT EXISTS ${RESOURCE_DIR})
83 | if(EXISTS ${RESOURCE_FILE})
84 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf ${RESOURCE_FILE})
85 | else()
86 | message("To run the demo application, please download \
87 | https://drive.google.com/uc?export=download&id=1MpZwPjXDAUxKfSTpeCjG0PAUpaeWuo7D \
88 | into ${RESOURCE_FILE} and run cmake again.")
89 | endif()
90 | endif(NOT EXISTS ${RESOURCE_DIR})
91 | endif(BUILD_DemoBRIEF)
92 |
93 | configure_file(src/DLoopDetector.cmake.in
94 | "${PROJECT_BINARY_DIR}/DLoopDetectorConfig.cmake" @ONLY)
95 |
96 | install(DIRECTORY include/DLoopDetector DESTINATION ${CMAKE_INSTALL_PREFIX}/include)
97 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/DLoopDetectorConfig.cmake"
98 | DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${PROJECT_NAME})
99 | install(FILES "${PROJECT_BINARY_DIR}/DLoopDetectorConfig.cmake"
100 | DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/DLoopDetector/)
101 | install(DIRECTORY ${DEPENDENCY_INSTALL_DIR}/ DESTINATION ${CMAKE_INSTALL_PREFIX} OPTIONAL)
102 |
--------------------------------------------------------------------------------
/demo/demoDetector.h:
--------------------------------------------------------------------------------
1 | /**
2 | * File: demoDetector.h
3 | * Date: November 2011
4 | * Author: Dorian Galvez-Lopez
5 | * Description: demo application of DLoopDetector
6 | * License: see the LICENSE.txt file
7 | */
8 |
9 | #ifndef __DEMO_DETECTOR__
10 | #define __DEMO_DETECTOR__
11 |
12 | #include
13 | #include
14 | #include
15 |
16 | // OpenCV
17 | #include
18 | #include
19 |
20 | // DLoopDetector and DBoW2
21 | #include
22 | #include "DLoopDetector.h"
23 | #include
24 | #include
25 | #include
26 |
27 | using namespace DLoopDetector;
28 | using namespace DBoW2;
29 | using namespace std;
30 |
31 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
32 |
33 | /// Generic class to create functors to extract features
34 | template
35 | class FeatureExtractor
36 | {
37 | public:
38 | /**
39 | * Extracts features
40 | * @param im image
41 | * @param keys keypoints extracted
42 | * @param descriptors descriptors extracted
43 | */
44 | virtual void operator()(const cv::Mat &im,
45 | vector &keys, vector &descriptors) const = 0;
46 | };
47 |
48 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
49 |
50 | /// @param TVocabulary vocabulary class (e.g: BriefVocabulary)
51 | /// @param TDetector detector class (e.g: BriefLoopDetector)
52 | /// @param TDescriptor descriptor class (e.g: bitset for Brief)
53 | template
54 | /// Class to run the demo
55 | class demoDetector
56 | {
57 | public:
58 |
59 | /**
60 | * @param vocfile vocabulary file to load
61 | * @param imagedir directory to read images from
62 | * @param posefile pose file
63 | * @param width image width
64 | * @param height image height
65 | */
66 | demoDetector(const std::string &vocfile, const std::string &imagedir,
67 | const std::string &posefile, int width, int height, bool show);
68 |
69 | ~demoDetector(){}
70 |
71 | /**
72 | * Runs the demo
73 | * @param name demo name
74 | * @param extractor functor to extract features
75 | */
76 | void run(const std::string &name,
77 | const FeatureExtractor &extractor);
78 |
79 | protected:
80 |
81 | /**
82 | * Reads the robot poses from a file
83 | * @param filename file
84 | * @param xs
85 | * @param ys
86 | */
87 | void readPoseFile(const char *filename, std::vector &xs,
88 | std::vector &ys) const;
89 |
90 | protected:
91 |
92 | std::string m_vocfile;
93 | std::string m_imagedir;
94 | std::string m_posefile;
95 | int m_width;
96 | int m_height;
97 | bool m_show;
98 | };
99 |
100 | // ---------------------------------------------------------------------------
101 |
102 | template
103 | demoDetector::demoDetector
104 | (const std::string &vocfile, const std::string &imagedir,
105 | const std::string &posefile, int width, int height, bool show)
106 | : m_vocfile(vocfile), m_imagedir(imagedir), m_posefile(posefile),
107 | m_width(width), m_height(height), m_show(show)
108 | {
109 | }
110 |
111 | // ---------------------------------------------------------------------------
112 |
113 | template
114 | void demoDetector::run
115 | (const std::string &name, const FeatureExtractor &extractor)
116 | {
117 | cout << "DLoopDetector Demo" << endl
118 | << "Dorian Galvez-Lopez" << endl
119 | << "http://doriangalvez.com" << endl << endl;
120 |
121 | // Set loop detector parameters
122 | typename TDetector::Parameters params(m_height, m_width);
123 |
124 | // Parameters given by default are:
125 | // use nss = true
126 | // alpha = 0.3
127 | // k = 3
128 | // geom checking = GEOM_DI
129 | // di levels = 0
130 |
131 | // We are going to change these values individually:
132 | params.use_nss = true; // use normalized similarity score instead of raw score
133 | params.alpha = 0.3; // nss threshold
134 | params.k = 1; // a loop must be consistent with 1 previous matches
135 | params.geom_check = GEOM_DI; // use direct index for geometrical checking
136 | params.di_levels = 2; // use two direct index levels
137 |
138 | // To verify loops you can select one of the next geometrical checkings:
139 | // GEOM_EXHAUSTIVE: correspondence points are computed by comparing all
140 | // the features between the two images.
141 | // GEOM_FLANN: as above, but the comparisons are done with a Flann structure,
142 | // which makes them faster. However, creating the flann structure may
143 | // be slow.
144 | // GEOM_DI: the direct index is used to select correspondence points between
145 | // those features whose vocabulary node at a certain level is the same.
146 | // The level at which the comparison is done is set by the parameter
147 | // di_levels:
148 | // di_levels = 0 -> features must belong to the same leaf (word).
149 | // This is the fastest configuration and the most restrictive one.
150 | // di_levels = l (l < L) -> node at level l starting from the leaves.
151 | // The higher l, the slower the geometrical checking, but higher
152 | // recall as well.
153 | // Here, L stands for the depth levels of the vocabulary tree.
154 | // di_levels = L -> the same as the exhaustive technique.
155 | // GEOM_NONE: no geometrical checking is done.
156 | //
157 | // In general, with a 10^6 vocabulary, GEOM_DI with 2 <= di_levels <= 4
158 | // yields the best results in recall/time.
159 | // Check the T-RO paper for more information.
160 | //
161 |
162 | // Load the vocabulary to use
163 | cout << "Loading " << name << " vocabulary..." << endl;
164 | TVocabulary voc(m_vocfile);
165 |
166 | // Initiate loop detector with the vocabulary
167 | cout << "Processing sequence..." << endl;
168 | TDetector detector(voc, params);
169 |
170 | // Process images
171 | vector keys;
172 | vector descriptors;
173 |
174 | // load image filenames
175 | vector filenames =
176 | DUtils::FileFunctions::Dir(m_imagedir.c_str(), ".png", true);
177 |
178 | // load robot poses
179 | vector xs, ys;
180 | readPoseFile(m_posefile.c_str(), xs, ys);
181 |
182 | // we can allocate memory for the expected number of images
183 | detector.allocate(filenames.size());
184 |
185 | // prepare visualization windows
186 | DUtilsCV::GUI::tWinHandler win = "Current image";
187 | DUtilsCV::GUI::tWinHandler winplot = "Trajectory";
188 |
189 | DUtilsCV::Drawing::Plot::Style normal_style(2); // thickness
190 | DUtilsCV::Drawing::Plot::Style loop_style('r', 2); // color, thickness
191 |
192 | DUtilsCV::Drawing::Plot implot(240, 320,
193 | - *std::max_element(xs.begin(), xs.end()),
194 | - *std::min_element(xs.begin(), xs.end()),
195 | *std::min_element(ys.begin(), ys.end()),
196 | *std::max_element(ys.begin(), ys.end()), 20);
197 |
198 | // prepare profiler to measure times
199 | DUtils::Profiler profiler;
200 |
201 | int count = 0;
202 |
203 | // go
204 | for(unsigned int i = 0; i < filenames.size(); ++i)
205 | {
206 | cout << "Adding image " << i << ": " << filenames[i] << "... " << endl;
207 |
208 | // get image
209 | cv::Mat im = cv::imread(filenames[i].c_str(), 0); // grey scale
210 |
211 | // show image
212 | if(m_show)
213 | DUtilsCV::GUI::showImage(im, true, &win, 10);
214 |
215 | // get features
216 | profiler.profile("features");
217 | extractor(im, keys, descriptors);
218 | profiler.stop();
219 |
220 | // add image to the collection and check if there is some loop
221 | DetectionResult result;
222 |
223 | profiler.profile("detection");
224 | detector.detectLoop(keys, descriptors, result);
225 | profiler.stop();
226 |
227 | if(result.detection())
228 | {
229 | cout << "- Loop found with image " << result.match << "!"
230 | << endl;
231 | ++count;
232 | }
233 | else
234 | {
235 | cout << "- No loop: ";
236 | switch(result.status)
237 | {
238 | case CLOSE_MATCHES_ONLY:
239 | cout << "All the images in the database are very recent" << endl;
240 | break;
241 |
242 | case NO_DB_RESULTS:
243 | cout << "There are no matches against the database (few features in"
244 | " the image?)" << endl;
245 | break;
246 |
247 | case LOW_NSS_FACTOR:
248 | cout << "Little overlap between this image and the previous one"
249 | << endl;
250 | break;
251 |
252 | case LOW_SCORES:
253 | cout << "No match reaches the score threshold (alpha: " <<
254 | params.alpha << ")" << endl;
255 | break;
256 |
257 | case NO_GROUPS:
258 | cout << "Not enough close matches to create groups. "
259 | << "Best candidate: " << result.match << endl;
260 | break;
261 |
262 | case NO_TEMPORAL_CONSISTENCY:
263 | cout << "No temporal consistency (k: " << params.k << "). "
264 | << "Best candidate: " << result.match << endl;
265 | break;
266 |
267 | case NO_GEOMETRICAL_CONSISTENCY:
268 | cout << "No geometrical consistency. Best candidate: "
269 | << result.match << endl;
270 | break;
271 |
272 | default:
273 | break;
274 | }
275 | }
276 |
277 | cout << endl;
278 |
279 | // show trajectory
280 | if(m_show && i > 0)
281 | {
282 | if(result.detection())
283 | implot.line(-xs[i-1], ys[i-1], -xs[i], ys[i], loop_style);
284 | else
285 | implot.line(-xs[i-1], ys[i-1], -xs[i], ys[i], normal_style);
286 |
287 | DUtilsCV::GUI::showImage(implot.getImage(), true, &winplot, 10);
288 | }
289 | }
290 |
291 | if(count == 0)
292 | {
293 | cout << "No loops found in this image sequence" << endl;
294 | }
295 | else
296 | {
297 | cout << count << " loops found in this image sequence!" << endl;
298 | }
299 |
300 | cout << endl << "Execution time:" << endl
301 | << " - Feature computation: " << profiler.getMeanTime("features") * 1e3
302 | << " ms/image" << endl
303 | << " - Loop detection: " << profiler.getMeanTime("detection") * 1e3
304 | << " ms/image" << endl;
305 |
306 | if(m_show) {
307 | cout << endl << "Press a key to finish..." << endl;
308 | DUtilsCV::GUI::showImage(implot.getImage(), true, &winplot, 0);
309 | }
310 | }
311 |
312 | // ---------------------------------------------------------------------------
313 |
314 | template
315 | void demoDetector::readPoseFile
316 | (const char *filename, std::vector &xs, std::vector &ys)
317 | const
318 | {
319 | xs.clear();
320 | ys.clear();
321 |
322 | fstream f(filename, ios::in);
323 |
324 | string s;
325 | double ts, x, y, t;
326 | while(!f.eof())
327 | {
328 | getline(f, s);
329 | if(!f.eof() && !s.empty())
330 | {
331 | sscanf(s.c_str(), "%lf, %lf, %lf, %lf", &ts, &x, &y, &t);
332 | xs.push_back(x);
333 | ys.push_back(y);
334 | }
335 | }
336 |
337 | f.close();
338 | }
339 |
340 | // ---------------------------------------------------------------------------
341 |
342 | #endif
343 |
344 |
--------------------------------------------------------------------------------
/include/DLoopDetector/TemplatedLoopDetector.h:
--------------------------------------------------------------------------------
1 | /**
2 | * File: TemplatedLoopDetector
3 | * Date: March 2011
4 | * Author: Dorian Galvez-Lopez
5 | * Description: templated loop detector
6 | * License: see the LICENSE.txt file
7 | *
8 | */
9 |
10 | #ifndef __D_T_TEMPLATED_LOOP_DETECTOR__
11 | #define __D_T_TEMPLATED_LOOP_DETECTOR__
12 |
13 | #include
14 | #include
15 | #include
16 | #include
17 |
18 | #include
19 | #include
20 |
21 | #include "TemplatedVocabulary.h"
22 | #include "TemplatedDatabase.h"
23 | #include "QueryResults.h"
24 | #include "BowVector.h"
25 |
26 | #include "DUtils.h"
27 | #include "DUtilsCV.h"
28 | #include "DVision.h"
29 |
30 | using namespace std;
31 | using namespace DUtils;
32 | using namespace DBoW2;
33 |
34 | namespace DLoopDetector {
35 |
36 |
37 | /// Geometrical checking methods
38 | enum GeometricalCheck
39 | {
40 | /// Exhaustive search
41 | GEOM_EXHAUSTIVE,
42 | /// Use direct index
43 | GEOM_DI,
44 | /// Use a Flann structure
45 | GEOM_FLANN,
46 | /// Do not perform geometrical checking
47 | GEOM_NONE
48 | };
49 |
50 | /// Reasons for dismissing loops
51 | enum DetectionStatus
52 | {
53 | /// Loop correctly detected
54 | LOOP_DETECTED,
55 | /// All the matches are very recent
56 | CLOSE_MATCHES_ONLY,
57 | /// No matches against the database
58 | NO_DB_RESULTS,
59 | /// Score of current image against previous one too low
60 | LOW_NSS_FACTOR,
61 | /// Scores (or NS Scores) were below the alpha threshold
62 | LOW_SCORES,
63 | /// Not enough matches to create groups
64 | NO_GROUPS,
65 | /// Not enough temporary consistent matches (k)
66 | NO_TEMPORAL_CONSISTENCY,
67 | /// The geometrical consistency failed
68 | NO_GEOMETRICAL_CONSISTENCY
69 | };
70 |
71 | /// Result of a detection
72 | struct DetectionResult
73 | {
74 | /// Detection status. LOOP_DETECTED iff loop detected
75 | DetectionStatus status;
76 | /// Query id
77 | EntryId query;
78 | /// Matched id if loop detected, otherwise, best candidate
79 | EntryId match;
80 |
81 | /**
82 | * Checks if the loop was detected
83 | * @return true iff a loop was detected
84 | */
85 | inline bool detection() const
86 | {
87 | return status == LOOP_DETECTED;
88 | }
89 | };
90 |
91 | /// TDescriptor: class of descriptor
92 | /// F: class of descriptor functions
93 | template
94 | /// Generic Loop detector
95 | class TemplatedLoopDetector
96 | {
97 | public:
98 |
99 | /// Parameters to create a loop detector
100 | struct Parameters
101 | {
102 | /// Height of expected images
103 | int image_rows;
104 | /// Width of expected images
105 | int image_cols;
106 |
107 | // Main loop detector parameters
108 |
109 | /// Use normalized similarity score?
110 | bool use_nss;
111 | /// Alpha threshold
112 | float alpha;
113 | /// Min consistent matches to pass the temporal check
114 | int k;
115 | /// Geometrical check
116 | GeometricalCheck geom_check;
117 | /// If using direct index for geometrical checking, direct index levels
118 | int di_levels;
119 |
120 | // These are less deciding parameters of the system
121 |
122 | /// Distance between entries to be consider a match
123 | int dislocal;
124 | /// Max number of results from db queries to consider
125 | int max_db_results;
126 | /// Min raw score between current entry and previous one to consider a match
127 | float min_nss_factor;
128 | /// Min number of close matches to consider some of them
129 | int min_matches_per_group;
130 | /// Max separation between matches to consider them of the same group
131 | int max_intragroup_gap;
132 | /// Max separation between groups of matches to consider them consistent
133 | int max_distance_between_groups;
134 | /// Max separation between two queries to consider them consistent
135 | int max_distance_between_queries;
136 |
137 | // These are for the RANSAC to compute the F
138 |
139 | /// Min number of inliers when computing a fundamental matrix
140 | int min_Fpoints;
141 | /// Max number of iterations of RANSAC
142 | int max_ransac_iterations;
143 | /// Success probability of RANSAC
144 | double ransac_probability;
145 | /// Max reprojection error of fundamental matrices
146 | double max_reprojection_error;
147 |
148 | // This is to compute correspondences
149 |
150 | /// Max value of the neighbour-ratio of accepted correspondences
151 | double max_neighbor_ratio;
152 |
153 | /**
154 | * Creates parameters by default
155 | */
156 | Parameters();
157 |
158 | /**
159 | * Creates parameters by default
160 | * @param height image height
161 | * @param width image width
162 | * @param frequency set the value of some parameters according to the
163 | * expected working frequency (Hz)
164 | * @param nss use normalized similarity score
165 | * @param _alpha alpha parameter
166 | * @param _k k parameter (number of temporary consistent matches)
167 | * @param geom type of geometrical check
168 | * @param dilevels direct index levels when geom == GEOM_DI
169 | */
170 | Parameters(int height, int width, float frequency = 1, bool nss = true,
171 | float _alpha = 0.3, int _k = 3,
172 | GeometricalCheck geom = GEOM_DI, int dilevels = 0);
173 |
174 | private:
175 | /**
176 | * Sets some parameters according to the frequency
177 | * @param frequency
178 | */
179 | void set(float frequency);
180 | };
181 |
182 | public:
183 |
184 | /**
185 | * Empty constructor
186 | */
187 | TemplatedLoopDetector(const Parameters ¶ms = Parameters());
188 |
189 | /**
190 | * Creates a loop detector with the given parameters and with a BoW2 database
191 | * with the given vocabulary
192 | * @param voc vocabulary
193 | * @param params loop detector parameters
194 | */
195 | TemplatedLoopDetector(const TemplatedVocabulary &voc,
196 | const Parameters ¶ms = Parameters());
197 |
198 | /**
199 | * Creates a loop detector with a copy of the given database, but clearing
200 | * its contents
201 | * @param db database to copy
202 | * @param params loop detector parameters
203 | */
204 | TemplatedLoopDetector(const TemplatedDatabase &db,
205 | const Parameters ¶ms = Parameters());
206 |
207 | /**
208 | * Creates a loop detector with a copy of the given database, but clearing
209 | * its contents
210 | * @param T class derived from TemplatedDatabase
211 | * @param db database to copy
212 | * @param params loop detector parameters
213 | */
214 | template
215 | TemplatedLoopDetector(const T &db, const Parameters ¶ms = Parameters());
216 |
217 | /**
218 | * Destructor
219 | */
220 | virtual ~TemplatedLoopDetector(void);
221 |
222 | /**
223 | * Retrieves a reference to the database used by the loop detector
224 | * @return const reference to database
225 | */
226 | inline const TemplatedDatabase& getDatabase() const;
227 |
228 | /**
229 | * Retrieves a reference to the vocabulary used by the loop detector
230 | * @return const reference to vocabulary
231 | */
232 | inline const TemplatedVocabulary& getVocabulary() const;
233 |
234 | /**
235 | * Sets the database to use. The contents of the database and the detector
236 | * entries are cleared
237 | * @param T class derived from TemplatedDatabase
238 | * @param db database to copy
239 | */
240 | template
241 | void setDatabase(const T &db);
242 |
243 | /**
244 | * Sets a new DBoW2 database created from the given vocabulary
245 | * @param voc vocabulary to copy
246 | */
247 | void setVocabulary(const TemplatedVocabulary& voc);
248 |
249 | /**
250 | * Allocates some memory for the first entries
251 | * @param nentries number of expected entries
252 | * @param nkeys number of keypoints per image expected
253 | */
254 | void allocate(int nentries, int nkeys = 0);
255 |
256 | /**
257 | * Adds the given tuple to the database
258 | * and returns the match if any
259 | * @param keys keypoints of the image
260 | * @param descriptors descriptors associated to the given keypoints
261 | * @param match (out) match or failing information
262 | * @return true iff there was match
263 | */
264 | bool detectLoop(const std::vector &keys,
265 | const std::vector &descriptors,
266 | DetectionResult &match);
267 |
268 | /**
269 | * Resets the detector and clears the database, such that the next entry
270 | * will be 0 again
271 | */
272 | inline void clear();
273 |
274 | protected:
275 |
276 | /// Matching island
277 | struct tIsland
278 | {
279 | /// Island starting entry
280 | EntryId first;
281 | /// Island ending entry
282 | EntryId last;
283 | /// Island score
284 | double score; // score of island
285 |
286 | /// Entry of the island with the highest score
287 | EntryId best_entry; // id and score of the entry with the highest score
288 | /// Highest single score in the island
289 | double best_score; // in the island
290 |
291 | /**
292 | * Creates an empty island
293 | */
294 | tIsland(){}
295 |
296 | /**
297 | * Creates an island
298 | * @param f first entry
299 | * @param l last entry
300 | */
301 | tIsland(EntryId f, EntryId l): first(f), last(l){}
302 |
303 | /**
304 | * Creates an island
305 | * @param f first entry
306 | * @param l last entry
307 | * @param s island score
308 | */
309 | tIsland(EntryId f, EntryId l, double s): first(f), last(l), score(s){}
310 |
311 | /**
312 | * Says whether this score is less than the score of another island
313 | * @param b
314 | * @return true iff this score < b.score
315 | */
316 | inline bool operator < (const tIsland &b) const
317 | {
318 | return this->score < b.score;
319 | }
320 |
321 | /**
322 | * Says whether this score is greater than the score of another island
323 | * @param b
324 | * @return true iff this score > b.score
325 | */
326 | inline bool operator > (const tIsland &b) const
327 | {
328 | return this->score > b.score;
329 | }
330 |
331 | /**
332 | * Returns true iff a > b
333 | * This function is used to sort in descending order
334 | * @param a
335 | * @param b
336 | * @return a > b
337 | */
338 | static inline bool gt(const tIsland &a, const tIsland &b)
339 | {
340 | return a.score > b.score;
341 | }
342 |
343 | /**
344 | * Returns true iff entry ids of a are less then those of b.
345 | * Assumes there is no overlap between the islands
346 | * @param a
347 | * @param b
348 | * @return a.first < b.first
349 | */
350 | static inline bool ltId(const tIsland &a, const tIsland &b)
351 | {
352 | return a.first < b.first;
353 | }
354 |
355 | /**
356 | * Returns the length of the island
357 | * @return length of island
358 | */
359 | inline int length() const { return last - first + 1; }
360 |
361 | /**
362 | * Returns a printable version of the island
363 | * @return printable island
364 | */
365 | std::string toString() const
366 | {
367 | stringstream ss;
368 | ss << "[" << first << "-" << last << ": " << score << " | best: <"
369 | << best_entry << ": " << best_score << "> ] ";
370 | return ss.str();
371 | }
372 | };
373 |
374 | /// Temporal consistency window
375 | struct tTemporalWindow
376 | {
377 | /// Island matched in the last query
378 | tIsland last_matched_island;
379 | /// Last query id
380 | EntryId last_query_id;
381 | /// Number of consistent entries in the window
382 | int nentries;
383 |
384 | /**
385 | * Creates an empty temporal window
386 | */
387 | tTemporalWindow(): nentries(0) {}
388 | };
389 |
390 |
391 | protected:
392 |
393 | /**
394 | * Removes from q those results whose score is lower than threshold
395 | * (that should be alpha * ns_factor)
396 | * @param q results from query
397 | * @param threshold min value of the resting results
398 | */
399 | void removeLowScores(QueryResults &q, double threshold) const;
400 |
401 | /**
402 | * Returns the islands of the given matches in ascending order of entry ids
403 | * @param q
404 | * @param islands (out) computed islands
405 | */
406 | void computeIslands(QueryResults &q, vector &islands) const;
407 |
408 | /**
409 | * Returns the score of the island composed of the entries of q whose indices
410 | * are in [i_first, i_last] (both included)
411 | * @param q
412 | * @param i_first first index of q of the island
413 | * @param i_last last index of q of the island
414 | * @return island score
415 | */
416 | double calculateIslandScore(const QueryResults &q, unsigned int i_first,
417 | unsigned int i_last) const;
418 |
419 | /**
420 | * Updates the temporal window by adding the given match , such
421 | * that the window will contain only those islands which are consistent
422 | * @param matched_island
423 | * @param entry_id
424 | */
425 | void updateTemporalWindow(const tIsland &matched_island, EntryId entry_id);
426 |
427 | /**
428 | * Returns the number of consistent islands in the temporal window
429 | * @return number of temporal consistent islands
430 | */
431 | inline int getConsistentEntries() const
432 | {
433 | return m_window.nentries;
434 | }
435 |
436 | /**
437 | * Check if an old entry is geometrically consistent (by calculating a
438 | * fundamental matrix) with the given set of keys and descriptors
439 | * @param old_entry entry id of the stored image to check
440 | * @param keys current keypoints
441 | * @param descriptors current descriptors associated to the given keypoints
442 | * @param curvec feature vector of the current entry
443 | */
444 | bool isGeometricallyConsistent_DI(EntryId old_entry,
445 | const std::vector &keys,
446 | const std::vector &descriptors,
447 | const FeatureVector &curvec) const;
448 |
449 | /**
450 | * Checks if an old entry is geometrically consistent (by using FLANN and
451 | * computing an essential matrix by using the neighbour ratio 0.6)
452 | * with the given set of keys and descriptors
453 | * @param old_entry entry id of the stored image to check
454 | * @param keys current keypoints
455 | * @param descriptors current descriptors
456 | * @param flann_structure flann structure with the descriptors of the current entry
457 | */
458 | bool isGeometricallyConsistent_Flann(EntryId old_entry,
459 | const std::vector &keys,
460 | const std::vector &descriptors,
461 | cv::FlannBasedMatcher &flann_structure) const;
462 |
463 | /**
464 | * Creates a flann structure from a set of descriptors to perform queries
465 | * @param descriptors
466 | * @param flann_structure (out) flann matcher
467 | */
468 | void getFlannStructure(const std::vector &descriptors,
469 | cv::FlannBasedMatcher &flann_structure) const;
470 |
471 | /**
472 | * Check if an old entry is geometrically consistent (by calculating a
473 | * fundamental matrix from left-right correspondences) with the given set
474 | * of keys and descriptors,
475 | * without using the direct index
476 | * @param old_keys keys of old entry
477 | * @param old_descriptors descriptors of old keys
478 | * @param cur_keys keys of current entry
479 | * @param cur_descriptors descriptors of cur keys
480 | */
481 | bool isGeometricallyConsistent_Exhaustive(
482 | const std::vector &old_keys,
483 | const std::vector &old_descriptors,
484 | const std::vector &cur_keys,
485 | const std::vector &cur_descriptors) const;
486 |
487 | /**
488 | * Calculate the matches between the descriptors A[i_A] and the descriptors
489 | * B[i_B]. Applies a left-right matching without neighbour ratio
490 | * @param A set A of descriptors
491 | * @param i_A only descriptors A[i_A] will be checked
492 | * @param B set B of descriptors
493 | * @param i_B only descriptors B[i_B] will be checked
494 | * @param i_match_A (out) indices of descriptors matched (s.t. A[i_match_A])
495 | * @param i_match_B (out) indices of descriptors matched (s.t. B[i_match_B])
496 | */
497 | void getMatches_neighratio(const std::vector &A,
498 | const vector &i_A, const vector &B,
499 | const vector &i_B,
500 | vector &i_match_A, vector &i_match_B) const;
501 |
502 | protected:
503 |
504 | /// Database
505 | // The loop detector stores its own copy of the database
506 | TemplatedDatabase *m_database;
507 |
508 | /// KeyPoints of images
509 | vector > m_image_keys;
510 |
511 | /// Descriptors of images
512 | vector > m_image_descriptors;
513 |
514 | /// Last bow vector added to database
515 | BowVector m_last_bowvec;
516 |
517 | /// Temporal consistency window
518 | tTemporalWindow m_window;
519 |
520 | /// Parameters of loop detector
521 | Parameters m_params;
522 |
523 | /// To compute the fundamental matrix
524 | DVision::FSolver m_fsolver;
525 |
526 | };
527 |
528 | // --------------------------------------------------------------------------
529 |
530 | template
531 | TemplatedLoopDetector::Parameters::Parameters():
532 | use_nss(true), alpha(0.3), k(4), geom_check(GEOM_DI), di_levels(0)
533 | {
534 | set(1);
535 | }
536 |
537 | // --------------------------------------------------------------------------
538 |
539 | template
540 | TemplatedLoopDetector::Parameters::Parameters
541 | (int height, int width, float frequency, bool nss, float _alpha,
542 | int _k, GeometricalCheck geom, int dilevels)
543 | : image_rows(height), image_cols(width), use_nss(nss), alpha(_alpha), k(_k),
544 | geom_check(geom), di_levels(dilevels)
545 | {
546 | set(frequency);
547 | }
548 |
549 | // --------------------------------------------------------------------------
550 |
551 | template
552 | void TemplatedLoopDetector::Parameters::set(float f)
553 | {
554 | dislocal = 20 * f;
555 | max_db_results = 50 * f;
556 | min_nss_factor = 0.005;
557 | min_matches_per_group = f;
558 | max_intragroup_gap = 3 * f;
559 | max_distance_between_groups = 3 * f;
560 | max_distance_between_queries = 2 * f;
561 |
562 | min_Fpoints = 12;
563 | max_ransac_iterations = 500;
564 | ransac_probability = 0.99;
565 | max_reprojection_error = 2.0;
566 |
567 | max_neighbor_ratio = 0.6;
568 | }
569 |
570 | // --------------------------------------------------------------------------
571 |
572 | template
573 | TemplatedLoopDetector::TemplatedLoopDetector
574 | (const Parameters ¶ms)
575 | : m_database(NULL), m_params(params)
576 | {
577 | }
578 |
579 | // --------------------------------------------------------------------------
580 |
581 | template
582 | TemplatedLoopDetector::TemplatedLoopDetector
583 | (const TemplatedVocabulary &voc, const Parameters ¶ms)
584 | : m_params(params)
585 | {
586 | m_database = new TemplatedDatabase(voc,
587 | params.geom_check == GEOM_DI, params.di_levels);
588 |
589 | m_fsolver.setImageSize(params.image_cols, params.image_rows);
590 | }
591 |
592 | // --------------------------------------------------------------------------
593 |
594 | template
595 | void TemplatedLoopDetector::setVocabulary
596 | (const TemplatedVocabulary& voc)
597 | {
598 | delete m_database;
599 | m_database = new TemplatedDatabase(voc,
600 | m_params.geom_check == GEOM_DI, m_params.di_levels);
601 | }
602 |
603 | // --------------------------------------------------------------------------
604 |
605 | template
606 | TemplatedLoopDetector::TemplatedLoopDetector
607 | (const TemplatedDatabase &db, const Parameters ¶ms)
608 | : m_params(params)
609 | {
610 | m_database = new TemplatedDatabase(db.getVocabulary(),
611 | params.geom_check == GEOM_DI, params.di_levels);
612 |
613 | m_fsolver.setImageSize(params.image_cols, params.image_rows);
614 | }
615 |
616 | // --------------------------------------------------------------------------
617 |
618 | template
619 | template
620 | TemplatedLoopDetector::TemplatedLoopDetector
621 | (const T &db, const Parameters ¶ms)
622 | : m_params(params)
623 | {
624 | m_database = new T(db);
625 | m_database->clear();
626 |
627 | m_fsolver.setImageSize(params.image_cols, params.image_rows);
628 | }
629 |
630 | // --------------------------------------------------------------------------
631 |
632 | template
633 | template
634 | void TemplatedLoopDetector::setDatabase(const T &db)
635 | {
636 | delete m_database;
637 | m_database = new T(db);
638 | clear();
639 | }
640 |
641 | // --------------------------------------------------------------------------
642 |
643 | template
644 | TemplatedLoopDetector::~TemplatedLoopDetector(void)
645 | {
646 | delete m_database;
647 | m_database = NULL;
648 | }
649 |
650 | // --------------------------------------------------------------------------
651 |
652 | template
653 | void TemplatedLoopDetector::allocate
654 | (int nentries, int nkeys)
655 | {
656 | const int sz = (const int)m_image_keys.size();
657 |
658 | if(sz < nentries)
659 | {
660 | m_image_keys.resize(nentries);
661 | m_image_descriptors.resize(nentries);
662 | }
663 |
664 | if(nkeys > 0)
665 | {
666 | for(int i = sz; i < nentries; ++i)
667 | {
668 | m_image_keys[i].reserve(nkeys);
669 | m_image_descriptors[i].reserve(nkeys);
670 | }
671 | }
672 |
673 | m_database->allocate(nentries, nkeys);
674 | }
675 |
676 | // --------------------------------------------------------------------------
677 |
678 | template
679 | inline const TemplatedDatabase&
680 | TemplatedLoopDetector::getDatabase() const
681 | {
682 | return *m_database;
683 | }
684 |
685 | // --------------------------------------------------------------------------
686 |
687 | template
688 | inline const TemplatedVocabulary&
689 | TemplatedLoopDetector::getVocabulary() const
690 | {
691 | return m_database->getVocabulary();
692 | }
693 |
694 | // --------------------------------------------------------------------------
695 |
696 | template
697 | bool TemplatedLoopDetector::detectLoop(
698 | const std::vector &keys,
699 | const std::vector &descriptors,
700 | DetectionResult &match)
701 | {
702 | EntryId entry_id = m_database->size();
703 | match.query = entry_id;
704 |
705 | BowVector bowvec;
706 | FeatureVector featvec;
707 |
708 | if(m_params.geom_check == GEOM_DI)
709 | m_database->getVocabulary()->transform(descriptors, bowvec, featvec,
710 | m_params.di_levels);
711 | else
712 | m_database->getVocabulary()->transform(descriptors, bowvec);
713 |
714 | if((int)entry_id <= m_params.dislocal)
715 | {
716 | // only add the entry to the database and finish
717 | m_database->add(bowvec, featvec);
718 | match.status = CLOSE_MATCHES_ONLY;
719 | }
720 | else
721 | {
722 | int max_id = (int)entry_id - m_params.dislocal;
723 |
724 | QueryResults qret;
725 | m_database->query(bowvec, qret, m_params.max_db_results, max_id);
726 |
727 | // update database
728 | m_database->add(bowvec, featvec); // returns entry_id
729 |
730 | if(!qret.empty())
731 | {
732 | // factor to compute normalized similarity score, if necessary
733 | double ns_factor = 1.0;
734 |
735 | if(m_params.use_nss)
736 | {
737 | ns_factor = m_database->getVocabulary()->score(bowvec, m_last_bowvec);
738 | }
739 |
740 | if(!m_params.use_nss || ns_factor >= m_params.min_nss_factor)
741 | {
742 | // scores in qret must be divided by ns_factor to obtain the
743 | // normalized similarity score, but we can
744 | // speed this up by moving ns_factor to alpha's
745 |
746 | // remove those scores whose nss is lower than alpha
747 | // (ret is sorted in descending score order now)
748 | removeLowScores(qret, m_params.alpha * ns_factor);
749 |
750 | if(!qret.empty())
751 | {
752 | // the best candidate is the one with highest score by now
753 | match.match = qret[0].Id;
754 |
755 | // compute islands
756 | vector islands;
757 | computeIslands(qret, islands);
758 | // this modifies qret and changes the score order
759 |
760 | // get best island
761 | if(!islands.empty())
762 | {
763 | const tIsland& island =
764 | *std::max_element(islands.begin(), islands.end());
765 |
766 | // check temporal consistency of this island
767 | updateTemporalWindow(island, entry_id);
768 |
769 | // get the best candidate (maybe match)
770 | match.match = island.best_entry;
771 |
772 | if(getConsistentEntries() > m_params.k)
773 | {
774 | // candidate loop detected
775 | // check geometry
776 | bool detection;
777 |
778 | if(m_params.geom_check == GEOM_DI)
779 | {
780 | // all the DI stuff is implicit in the database
781 | detection = isGeometricallyConsistent_DI(island.best_entry,
782 | keys, descriptors, featvec);
783 | }
784 | else if(m_params.geom_check == GEOM_FLANN)
785 | {
786 | cv::FlannBasedMatcher flann_structure;
787 | getFlannStructure(descriptors, flann_structure);
788 |
789 | detection = isGeometricallyConsistent_Flann(island.best_entry,
790 | keys, descriptors, flann_structure);
791 | }
792 | else if(m_params.geom_check == GEOM_EXHAUSTIVE)
793 | {
794 | detection = isGeometricallyConsistent_Exhaustive(
795 | m_image_keys[island.best_entry],
796 | m_image_descriptors[island.best_entry],
797 | keys, descriptors);
798 | }
799 | else // GEOM_NONE, accept the match
800 | {
801 | detection = true;
802 | }
803 |
804 | if(detection)
805 | {
806 | match.status = LOOP_DETECTED;
807 | }
808 | else
809 | {
810 | match.status = NO_GEOMETRICAL_CONSISTENCY;
811 | }
812 |
813 | } // if enough temporal matches
814 | else
815 | {
816 | match.status = NO_TEMPORAL_CONSISTENCY;
817 | }
818 |
819 | } // if there is some island
820 | else
821 | {
822 | match.status = NO_GROUPS;
823 | }
824 | } // if !qret empty after removing low scores
825 | else
826 | {
827 | match.status = LOW_SCORES;
828 | }
829 | } // if (ns_factor > min normal score)
830 | else
831 | {
832 | match.status = LOW_NSS_FACTOR;
833 | }
834 | } // if(!qret.empty())
835 | else
836 | {
837 | match.status = NO_DB_RESULTS;
838 | }
839 | }
840 |
841 | // update record
842 | // m_image_keys and m_image_descriptors have the same length
843 | if(m_image_keys.size() == entry_id)
844 | {
845 | m_image_keys.push_back(keys);
846 | m_image_descriptors.push_back(descriptors);
847 | }
848 | else
849 | {
850 | m_image_keys[entry_id] = keys;
851 | m_image_descriptors[entry_id] = descriptors;
852 | }
853 |
854 | // store this bowvec if we are going to use it in next iteratons
855 | if(m_params.use_nss && (int)entry_id + 1 > m_params.dislocal)
856 | {
857 | m_last_bowvec = bowvec;
858 | }
859 |
860 | return match.detection();
861 | }
862 |
863 | // --------------------------------------------------------------------------
864 |
865 | template
866 | inline void TemplatedLoopDetector::clear()
867 | {
868 | m_database->clear();
869 | m_window.nentries = 0;
870 | }
871 |
872 | // --------------------------------------------------------------------------
873 |
874 | template
875 | void TemplatedLoopDetector::computeIslands
876 | (QueryResults &q, vector &islands) const
877 | {
878 | islands.clear();
879 |
880 | if(q.size() == 1)
881 | {
882 | islands.push_back(tIsland(q[0].Id, q[0].Id, calculateIslandScore(q, 0, 0)));
883 | islands.back().best_entry = q[0].Id;
884 | islands.back().best_score = q[0].Score;
885 | }
886 | else if(!q.empty())
887 | {
888 | // sort query results in ascending order of ids
889 | std::sort(q.begin(), q.end(), Result::ltId);
890 |
891 | // create long enough islands
892 | QueryResults::const_iterator dit = q.begin();
893 | int first_island_entry = dit->Id;
894 | int last_island_entry = dit->Id;
895 |
896 | // these are indices of q
897 | unsigned int i_first = 0;
898 | unsigned int i_last = 0;
899 |
900 | double best_score = dit->Score;
901 | EntryId best_entry = dit->Id;
902 |
903 | ++dit;
904 | for(unsigned int idx = 1; dit != q.end(); ++dit, ++idx)
905 | {
906 | if((int)dit->Id - last_island_entry < m_params.max_intragroup_gap)
907 | {
908 | // go on until find the end of the island
909 | last_island_entry = dit->Id;
910 | i_last = idx;
911 | if(dit->Score > best_score)
912 | {
913 | best_score = dit->Score;
914 | best_entry = dit->Id;
915 | }
916 | }
917 | else
918 | {
919 | // end of island reached
920 | int length = last_island_entry - first_island_entry + 1;
921 | if(length >= m_params.min_matches_per_group)
922 | {
923 | islands.push_back( tIsland(first_island_entry, last_island_entry,
924 | calculateIslandScore(q, i_first, i_last)) );
925 |
926 | islands.back().best_score = best_score;
927 | islands.back().best_entry = best_entry;
928 | }
929 |
930 | // prepare next island
931 | first_island_entry = last_island_entry = dit->Id;
932 | i_first = i_last = idx;
933 | best_score = dit->Score;
934 | best_entry = dit->Id;
935 | }
936 | }
937 | // add last island
938 | if(last_island_entry - first_island_entry + 1 >=
939 | m_params.min_matches_per_group)
940 | {
941 | islands.push_back( tIsland(first_island_entry, last_island_entry,
942 | calculateIslandScore(q, i_first, i_last)) );
943 |
944 | islands.back().best_score = best_score;
945 | islands.back().best_entry = best_entry;
946 | }
947 | }
948 |
949 | }
950 |
951 | // --------------------------------------------------------------------------
952 |
953 | template
954 | double TemplatedLoopDetector::calculateIslandScore(
955 | const QueryResults &q, unsigned int i_first, unsigned int i_last) const
956 | {
957 | // get the sum of the scores
958 | double sum = 0;
959 | while(i_first <= i_last) sum += q[i_first++].Score;
960 | return sum;
961 | }
962 |
963 | // --------------------------------------------------------------------------
964 |
965 | template
966 | void TemplatedLoopDetector::updateTemporalWindow
967 | (const tIsland &matched_island, EntryId entry_id)
968 | {
969 | // if m_window.nentries > 0, island > m_window.last_matched_island and
970 | // entry_id > m_window.last_query_id hold
971 |
972 | if(m_window.nentries == 0 || int(entry_id - m_window.last_query_id)
973 | > m_params.max_distance_between_queries)
974 | {
975 | m_window.nentries = 1;
976 | }
977 | else
978 | {
979 | EntryId a1 = m_window.last_matched_island.first;
980 | EntryId a2 = m_window.last_matched_island.last;
981 | EntryId b1 = matched_island.first;
982 | EntryId b2 = matched_island.last;
983 |
984 | bool fit = (b1 <= a1 && a1 <= b2) || (a1 <= b1 && b1 <= a2);
985 |
986 | if(!fit)
987 | {
988 | int d1 = (int)a1 - (int)b2;
989 | int d2 = (int)b1 - (int)a2;
990 | int gap = (d1 > d2 ? d1 : d2);
991 |
992 | fit = (gap <= m_params.max_distance_between_groups);
993 | }
994 |
995 | if(fit) m_window.nentries++;
996 | else m_window.nentries = 1;
997 | }
998 |
999 | m_window.last_matched_island = matched_island;
1000 | m_window.last_query_id = entry_id;
1001 | }
1002 |
1003 | // --------------------------------------------------------------------------
1004 |
1005 | template
1006 | bool TemplatedLoopDetector::isGeometricallyConsistent_DI(
1007 | EntryId old_entry, const std::vector &keys,
1008 | const std::vector &descriptors,
1009 | const FeatureVector &bowvec) const
1010 | {
1011 | const FeatureVector &oldvec = m_database->retrieveFeatures(old_entry);
1012 |
1013 | // for each word in common, get the closest descriptors
1014 |
1015 | vector i_old, i_cur;
1016 |
1017 | FeatureVector::const_iterator old_it, cur_it;
1018 | const FeatureVector::const_iterator old_end = oldvec.end();
1019 | const FeatureVector::const_iterator cur_end = bowvec.end();
1020 |
1021 | old_it = oldvec.begin();
1022 | cur_it = bowvec.begin();
1023 |
1024 | while(old_it != old_end && cur_it != cur_end)
1025 | {
1026 | if(old_it->first == cur_it->first)
1027 | {
1028 | // compute matches between
1029 | // features old_it->second of m_image_keys[old_entry] and
1030 | // features cur_it->second of keys
1031 | vector i_old_now, i_cur_now;
1032 |
1033 | getMatches_neighratio(
1034 | m_image_descriptors[old_entry], old_it->second,
1035 | descriptors, cur_it->second,
1036 | i_old_now, i_cur_now);
1037 |
1038 | i_old.insert(i_old.end(), i_old_now.begin(), i_old_now.end());
1039 | i_cur.insert(i_cur.end(), i_cur_now.begin(), i_cur_now.end());
1040 |
1041 | // move old_it and cur_it forward
1042 | ++old_it;
1043 | ++cur_it;
1044 | }
1045 | else if(old_it->first < cur_it->first)
1046 | {
1047 | // move old_it forward
1048 | old_it = oldvec.lower_bound(cur_it->first);
1049 | // old_it = (first element >= cur_it.id)
1050 | }
1051 | else
1052 | {
1053 | // move cur_it forward
1054 | cur_it = bowvec.lower_bound(old_it->first);
1055 | // cur_it = (first element >= old_it.id)
1056 | }
1057 | }
1058 |
1059 | // calculate now the fundamental matrix
1060 | if((int)i_old.size() >= m_params.min_Fpoints)
1061 | {
1062 | vector old_points, cur_points;
1063 |
1064 | // add matches to the vectors to calculate the fundamental matrix
1065 | vector::const_iterator oit, cit;
1066 | oit = i_old.begin();
1067 | cit = i_cur.begin();
1068 |
1069 | for(; oit != i_old.end(); ++oit, ++cit)
1070 | {
1071 | const cv::KeyPoint &old_k = m_image_keys[old_entry][*oit];
1072 | const cv::KeyPoint &cur_k = keys[*cit];
1073 |
1074 | old_points.push_back(old_k.pt);
1075 | cur_points.push_back(cur_k.pt);
1076 | }
1077 |
1078 | cv::Mat oldMat(old_points.size(), 2, CV_32F, &old_points[0]);
1079 | cv::Mat curMat(cur_points.size(), 2, CV_32F, &cur_points[0]);
1080 |
1081 | return m_fsolver.checkFundamentalMat(oldMat, curMat,
1082 | m_params.max_reprojection_error, m_params.min_Fpoints,
1083 | m_params.ransac_probability, m_params.max_ransac_iterations);
1084 | }
1085 |
1086 | return false;
1087 | }
1088 |
1089 | // --------------------------------------------------------------------------
1090 |
1091 | template
1092 | bool TemplatedLoopDetector::
1093 | isGeometricallyConsistent_Exhaustive(
1094 | const std::vector &old_keys,
1095 | const std::vector &old_descriptors,
1096 | const std::vector &cur_keys,
1097 | const std::vector &cur_descriptors) const
1098 | {
1099 | vector i_old, i_cur;
1100 | vector i_all_old, i_all_cur;
1101 |
1102 | i_all_old.reserve(old_keys.size());
1103 | i_all_cur.reserve(cur_keys.size());
1104 |
1105 | for(unsigned int i = 0; i < old_keys.size(); ++i)
1106 | {
1107 | i_all_old.push_back(i);
1108 | }
1109 |
1110 | for(unsigned int i = 0; i < cur_keys.size(); ++i)
1111 | {
1112 | i_all_cur.push_back(i);
1113 | }
1114 |
1115 | getMatches_neighratio(old_descriptors, i_all_old,
1116 | cur_descriptors, i_all_cur, i_old, i_cur);
1117 |
1118 | if((int)i_old.size() >= m_params.min_Fpoints)
1119 | {
1120 | // add matches to the vectors to calculate the fundamental matrix
1121 | vector::const_iterator oit, cit;
1122 | oit = i_old.begin();
1123 | cit = i_cur.begin();
1124 |
1125 | vector old_points, cur_points;
1126 | old_points.reserve(i_old.size());
1127 | cur_points.reserve(i_cur.size());
1128 |
1129 | for(; oit != i_old.end(); ++oit, ++cit)
1130 | {
1131 | const cv::KeyPoint &old_k = old_keys[*oit];
1132 | const cv::KeyPoint &cur_k = cur_keys[*cit];
1133 |
1134 | old_points.push_back(old_k.pt);
1135 | cur_points.push_back(cur_k.pt);
1136 | }
1137 |
1138 | cv::Mat oldMat(old_points.size(), 2, CV_32F, &old_points[0]);
1139 | cv::Mat curMat(cur_points.size(), 2, CV_32F, &cur_points[0]);
1140 |
1141 | return m_fsolver.checkFundamentalMat(oldMat, curMat,
1142 | m_params.max_reprojection_error, m_params.min_Fpoints,
1143 | m_params.ransac_probability, m_params.max_ransac_iterations);
1144 | }
1145 |
1146 | return false;
1147 | }
1148 |
1149 | // --------------------------------------------------------------------------
1150 |
1151 | template
1152 | void TemplatedLoopDetector::getFlannStructure(
1153 | const std::vector &descriptors,
1154 | cv::FlannBasedMatcher &flann_structure) const
1155 | {
1156 | vector features(1);
1157 | F::toMat32F(descriptors, features[0]);
1158 |
1159 | flann_structure.clear();
1160 | flann_structure.add(features);
1161 | flann_structure.train();
1162 | }
1163 |
1164 | // --------------------------------------------------------------------------
1165 |
1166 | template
1167 | bool TemplatedLoopDetector::isGeometricallyConsistent_Flann
1168 | (EntryId old_entry,
1169 | const std::vector &keys,
1170 | const std::vector &,
1171 | cv::FlannBasedMatcher &flann_structure) const
1172 | {
1173 | vector i_old, i_cur; // indices of correspondences
1174 |
1175 | const vector& old_keys = m_image_keys[old_entry];
1176 | const vector& old_descs = m_image_descriptors[old_entry];
1177 | const vector& cur_keys = keys;
1178 |
1179 | vector queryDescs_v(1);
1180 | F::toMat32F(old_descs, queryDescs_v[0]);
1181 |
1182 | vector > matches;
1183 |
1184 | flann_structure.knnMatch(queryDescs_v[0], matches, 2);
1185 |
1186 | for(int old_idx = 0; old_idx < (int)matches.size(); ++old_idx)
1187 | {
1188 | if(!matches[old_idx].empty())
1189 | {
1190 | int cur_idx = matches[old_idx][0].trainIdx;
1191 | float dist = matches[old_idx][0].distance;
1192 |
1193 | bool ok = true;
1194 | if(matches[old_idx].size() >= 2)
1195 | {
1196 | float dist_ratio = dist / matches[old_idx][1].distance;
1197 | ok = dist_ratio <= m_params.max_neighbor_ratio;
1198 | }
1199 |
1200 | if(ok)
1201 | {
1202 | vector::iterator cit =
1203 | std::find(i_cur.begin(), i_cur.end(), cur_idx);
1204 |
1205 | if(cit == i_cur.end())
1206 | {
1207 | i_old.push_back(old_idx);
1208 | i_cur.push_back(cur_idx);
1209 | }
1210 | else
1211 | {
1212 | int idx = i_old[ cit - i_cur.begin() ];
1213 | if(dist < matches[idx][0].distance)
1214 | {
1215 | i_old[ cit - i_cur.begin() ] = old_idx;
1216 | }
1217 | }
1218 | }
1219 | }
1220 | }
1221 |
1222 | if((int)i_old.size() >= m_params.min_Fpoints)
1223 | {
1224 | // add matches to the vectors for calculating the fundamental matrix
1225 | vector::const_iterator oit, cit;
1226 | oit = i_old.begin();
1227 | cit = i_cur.begin();
1228 |
1229 | vector old_points, cur_points;
1230 | old_points.reserve(i_old.size());
1231 | cur_points.reserve(i_cur.size());
1232 |
1233 | for(; oit != i_old.end(); ++oit, ++cit)
1234 | {
1235 | const cv::KeyPoint &old_k = old_keys[*oit];
1236 | const cv::KeyPoint &cur_k = cur_keys[*cit];
1237 |
1238 | old_points.push_back(old_k.pt);
1239 | cur_points.push_back(cur_k.pt);
1240 | }
1241 |
1242 | cv::Mat oldMat(old_points.size(), 2, CV_32F, &old_points[0]);
1243 | cv::Mat curMat(cur_points.size(), 2, CV_32F, &cur_points[0]);
1244 |
1245 | return m_fsolver.checkFundamentalMat(oldMat, curMat,
1246 | m_params.max_reprojection_error, m_params.min_Fpoints,
1247 | m_params.ransac_probability, m_params.max_ransac_iterations);
1248 | }
1249 |
1250 | return false;
1251 | }
1252 |
1253 | // --------------------------------------------------------------------------
1254 |
1255 | template
1256 | void TemplatedLoopDetector::getMatches_neighratio(
1257 | const vector &A, const vector &i_A,
1258 | const vector &B, const vector &i_B,
1259 | vector &i_match_A, vector &i_match_B) const
1260 | {
1261 | i_match_A.resize(0);
1262 | i_match_B.resize(0);
1263 | i_match_A.reserve( min(i_A.size(), i_B.size()) );
1264 | i_match_B.reserve( min(i_A.size(), i_B.size()) );
1265 |
1266 | vector::const_iterator ait, bit;
1267 | unsigned int i, j;
1268 | i = 0;
1269 | for(ait = i_A.begin(); ait != i_A.end(); ++ait, ++i)
1270 | {
1271 | int best_j_now = -1;
1272 | double best_dist_1 = 1e9;
1273 | double best_dist_2 = 1e9;
1274 |
1275 | j = 0;
1276 | for(bit = i_B.begin(); bit != i_B.end(); ++bit, ++j)
1277 | {
1278 | double d = F::distance(A[*ait], B[*bit]);
1279 |
1280 | // in i
1281 | if(d < best_dist_1)
1282 | {
1283 | best_j_now = j;
1284 | best_dist_2 = best_dist_1;
1285 | best_dist_1 = d;
1286 | }
1287 | else if(d < best_dist_2)
1288 | {
1289 | best_dist_2 = d;
1290 | }
1291 | }
1292 |
1293 | if(best_dist_1 / best_dist_2 <= m_params.max_neighbor_ratio)
1294 | {
1295 | unsigned int idx_B = i_B[best_j_now];
1296 | bit = find(i_match_B.begin(), i_match_B.end(), idx_B);
1297 |
1298 | if(bit == i_match_B.end())
1299 | {
1300 | i_match_B.push_back(idx_B);
1301 | i_match_A.push_back(*ait);
1302 | }
1303 | else
1304 | {
1305 | unsigned int idx_A = i_match_A[ bit - i_match_B.begin() ];
1306 | double d = F::distance(A[idx_A], B[idx_B]);
1307 | if(best_dist_1 < d)
1308 | {
1309 | i_match_A[ bit - i_match_B.begin() ] = *ait;
1310 | }
1311 | }
1312 |
1313 | }
1314 | }
1315 | }
1316 |
1317 | // --------------------------------------------------------------------------
1318 |
1319 | template
1320 | void TemplatedLoopDetector::removeLowScores(QueryResults &q,
1321 | double threshold) const
1322 | {
1323 | // remember scores in q are in descending order now
1324 | //QueryResults::iterator qit =
1325 | // lower_bound(q.begin(), q.end(), threshold, Result::geqv);
1326 |
1327 | Result aux(0, threshold);
1328 | QueryResults::iterator qit =
1329 | lower_bound(q.begin(), q.end(), aux, Result::geq);
1330 |
1331 | // qit = first element < m_alpha_minus || end
1332 |
1333 | if(qit != q.end())
1334 | {
1335 | int valid_entries = qit - q.begin();
1336 | q.resize(valid_entries);
1337 | }
1338 | }
1339 |
1340 | // --------------------------------------------------------------------------
1341 |
1342 | } // namespace DLoopDetector
1343 |
1344 | #endif
1345 |
--------------------------------------------------------------------------------