├── utils
├── orb_track.gif
├── sp_track_origin_size.gif
├── sp_track_resized_1_3rd.gif
└── pytorch_model_convert.py
├── package.xml
├── LICENSE
├── .gitignore
├── README.md
├── include
├── FeatureTrackerBase.h
├── FeatureDetectorBase.h
├── ORBFeatureMatcher.h
├── SuperPointFeatureMatcher.h
├── ORBFeatureDetector.h
├── SuperPointFeatureTracker.h
├── SuperPointFeatureDetector.h
├── FeatureDescriptor.h
└── ORBFeatureTracker.h
├── CMakeLists.txt
└── src
├── ORBFeatureMatcher.cc
├── main_track.cc
├── SuperPointFeatureMatcher.cc
├── SuperPointFeatureTracker.cc
├── ORBFeatureTracker.cc
├── ORBFeatureDetector.cc
└── SuperPointFeatureDetector.cc
/utils/orb_track.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/b51/FeatureTracker/HEAD/utils/orb_track.gif
--------------------------------------------------------------------------------
/utils/sp_track_origin_size.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/b51/FeatureTracker/HEAD/utils/sp_track_origin_size.gif
--------------------------------------------------------------------------------
/utils/sp_track_resized_1_3rd.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/b51/FeatureTracker/HEAD/utils/sp_track_resized_1_3rd.gif
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | FeatureTracker
4 | 0.0.0
5 | The FeatureTracker package
6 |
7 | b51
8 |
9 | MIT
10 |
11 | catkin
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 b51, mail: b51live@gmail.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ####################### MacOS gitignore ######################
2 | # General
3 | .DS_Store
4 | .AppleDouble
5 | .LSOverride
6 |
7 | # Icon must end with two \r
8 | Icon
9 |
10 | # Thumbnails
11 | ._*
12 |
13 | # Files that might appear in the root of a volume
14 | .DocumentRevisions-V100
15 | .fseventsd
16 | .Spotlight-V100
17 | .TemporaryItems
18 | .Trashes
19 | .VolumeIcon.icns
20 | .com.apple.timemachine.donotpresent
21 |
22 | # Directories potentially created on remote AFP share
23 | .AppleDB
24 | .AppleDesktop
25 | Network Trash Folder
26 | Temporary Items
27 | .apdisk
28 |
29 | ####################### C++ gitignore ########################
30 | # Prerequisites
31 | *.d
32 |
33 | # Compiled Object files
34 | *.slo
35 | *.lo
36 | *.o
37 | *.obj
38 |
39 | # Precompiled Headers
40 | *.gch
41 | *.pch
42 |
43 | # Compiled Dynamic libraries
44 | *.so
45 | *.dylib
46 | *.dll
47 |
48 | # Fortran module files
49 | *.mod
50 | *.smod
51 |
52 | # Compiled Static libraries
53 | *.lai
54 | *.la
55 | *.a
56 | *.lib
57 |
58 | # Executables
59 | *.exe
60 | *.out
61 | *.app
62 |
63 | ####################### Python gitignore #####################
64 | # Byte-compiled / optimized / DLL files
65 | __pycache__/
66 | *.py[cod]
67 | *$py.class
68 |
69 | # C extensions
70 | #*.so
71 |
72 | # Distribution / packaging
73 | .Python
74 | build/
75 | develop-eggs/
76 | eggs/
77 | .eggs/
78 | lib/
79 | lib64/
80 | *.egg-info/
81 | .installed.cfg
82 | *.egg
83 |
84 | *.swp
85 | *.pt
86 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## FeatureTracker
2 |
3 | ### What this repository for
4 | Detect, match and track different type features
5 |
6 | **Current type of feture points**
7 | - [X] ORB feature points
8 | - [X] SuperPoint feature points
9 |
10 | ### Usage
11 | **PreInstall**
12 |
13 | install libpytorch: [Install from source with Anaconda](https://pytorch.org/get-started/locally/)
14 | Set libpytorch cmake path at CMakeLists.txt
15 |
16 | **Dependence Info**
17 |
18 | pytorch: 1.1.0
19 |
20 | cuda/cudatoolkit: 9.0
21 |
22 | **Build**
23 | ```bash
24 | $ cd FeatureTracker
25 | $ mkdir build
26 | $ cd build && cmake ..
27 | $ make -j4
28 | ```
29 |
30 | **Run**
31 | ***ORB feature points***
32 | ```bash
33 | $ ./FeatureTracker --image_dir /path/to/images_dir --type orb [--image_suffix .jpg]
34 | ```
35 | ***SuperPoint feature points***
36 | ```bash
37 | Download superpoint model
38 | $ cd utils
39 | $ wget https://github.com/MagicLeapResearch/SuperPointPretrainedNetwork/raw/master/superpoint_v1.pth
40 | $ python pytorch_model_convert.py superpoint_v1.pth
41 | $ cd ../build
42 | $ ./FeatureTracker --image_dir /path/to/image_dir --model_path ../utils/super_point_scripted_model.pt --type sp [--image_suffix .jpg --H 480 --W 640]
43 | ```
44 |
45 | Kitti:
46 |
47 | **ORB Tracker**
48 |
49 |
50 |
51 |
52 | **SuperPoint Tracker**
53 |
54 | Resized image to 414, 125 (width/3, height/3)
55 |
56 |
57 | Original size image
58 |
59 |
--------------------------------------------------------------------------------
/include/FeatureTrackerBase.h:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: FeatureTrackerBase.h
6 | *
7 | * Created On: Sat 12 Oct 2019 08:23:14 AM UTC
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #ifndef FEATURE_TRACKER_FEATURE_TRACKER_BASE_H_
13 | #define FEATURE_TRACKER_FEATURE_TRACKER_BASE_H_
14 |
15 | #include
16 | #include
17 | #include
18 |
19 | class FeatureTrackerBase {
20 | public:
21 | FeatureTrackerBase(){};
22 |
23 | virtual ~FeatureTrackerBase(){};
24 |
25 | virtual void Init(int width, int height, int max_number_of_features) = 0;
26 |
27 | virtual void Track(const cv::Mat& image,
28 | Eigen::Matrix2Xf* current_measurements,
29 | Eigen::Matrix2Xf* previous_measurements,
30 | std::vector* feature_ids) = 0;
31 |
32 | virtual bool IsInitialized() const = 0;
33 |
34 | inline virtual int GetWidth() const = 0;
35 |
36 | inline virtual int GetHeight() const = 0;
37 |
38 | virtual void GetFeatureLocations(
39 | std::vector* points) const = 0;
40 |
41 | virtual const std::vector& GetNewTrackIds() const = 0;
42 |
43 | virtual int GetNumberOfFeaturesMatched() const = 0;
44 |
45 | virtual void Display() = 0;
46 |
47 | private:
48 | FeatureTrackerBase(const FeatureTrackerBase& ft) = delete;
49 | FeatureTrackerBase& operator=(const FeatureTrackerBase& ft) = delete;
50 | };
51 | #endif
52 |
--------------------------------------------------------------------------------
/include/FeatureDetectorBase.h:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: FeatureDetectorBase.h
6 | *
7 | * Created On: Thu 27 Jun 2019 03:29:38 PM CST
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #ifndef FEATURE_TRACKER_FEATURE_DETECTOR_BASE_H_
13 | #define FEATURE_TRACKER_FEATURE_DETECTOR_BASE_H_
14 |
15 | #include
16 | #include
17 |
18 | #include "FeatureDescriptor.h"
19 |
20 | class FeatureDetectorBase {
21 | public:
22 | FeatureDetectorBase(){};
23 | virtual ~FeatureDetectorBase(){};
24 |
25 | virtual void Init(int width, int height, int max_number_of_features) = 0;
26 |
27 | virtual void Detect(const cv::Mat& image,
28 | Eigen::Matrix2Xd* current_measurements,
29 | std::vector* current_feature_orientations,
30 | std::vector* current_feature_scales,
31 | FeatureDescriptoru* current_feature_descriptors) {}
32 |
33 | virtual void Detect(const cv::Mat& image,
34 | Eigen::Matrix2Xf* current_measurements,
35 | FeatureDescriptorf* current_feature_descriptors) {}
36 |
37 | virtual bool IsInitialized() const = 0;
38 | virtual int GetHeight() const = 0;
39 | virtual int GetWidth() const = 0;
40 | virtual int GetMaxNumberOfFeatures() const = 0;
41 |
42 | private:
43 | FeatureDetectorBase(const FeatureDetectorBase& fd) = delete;
44 | FeatureDetectorBase& operator=(const FeatureDetectorBase& fd) = delete;
45 | };
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/include/ORBFeatureMatcher.h:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: ORBFeatureMatcher.h
6 | *
7 | * Created On: Thu 26 Sep 2019 03:29:02 PM CST
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #ifndef FEATURE_TRACKER_ORB_FEATURE_MATCHER_H_
13 | #define FEATURE_TRACKER_ORB_FEATURE_MATCHER_H_
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | class ORBFeatureMatcher {
23 | public:
24 | typedef cv::NormTypes DistType;
25 |
26 | ORBFeatureMatcher();
27 |
28 | ~ORBFeatureMatcher() = default;
29 |
30 | void Init();
31 |
32 | void Init(DistType dist_type, float refine_dist);
33 |
34 | bool IsInitialized() const { return is_initialized_; }
35 |
36 | ORBFeatureMatcher(const ORBFeatureMatcher& d);
37 |
38 | ORBFeatureMatcher& operator=(const ORBFeatureMatcher& d);
39 |
40 | int Match(const cv::Mat& descriptors_img1, const cv::Mat& descriptors_img2,
41 | std::vector* output_matches);
42 |
43 | inline DistType GetDistType() const { return dist_type_; }
44 |
45 | inline float GetRefineDist() const { return refine_dist_; }
46 |
47 | void SetDistType(DistType dist_type);
48 |
49 | inline void SetRefineDist(float refine_dist) { refine_dist_ = refine_dist; }
50 |
51 | std::vector& GetMatches() { return matches_; }
52 |
53 | std::vector& GetRefinedMatches() { return refined_matches_; }
54 |
55 | private:
56 | cv::Ptr matcher_;
57 | DistType dist_type_;
58 | float refine_dist_;
59 | bool is_initialized_;
60 | std::vector matches_;
61 | std::vector refined_matches_;
62 | };
63 |
64 | #endif
65 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.3)
2 | project(FeatureTracker)
3 |
4 | ## Compile as C++11, supported in ROS Kinetic and newer
5 | add_definitions(-std=c++11)
6 |
7 | #########################################################################
8 | if (NOT CMAKE_BUILD_TYPE)
9 | set(CMAKE_BUILD_TYPE "Release")
10 | message(STATUS "No build type selected, default to ${CMAKE_BUILD_TYPE}")
11 | endif()
12 |
13 | if(CMAKE_BUILD_TYPE STREQUAL "Release")
14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O3 -fPIC -march=native")
15 | else(CMAKE_BUILD_TYPE STREQUAL "Debug")
16 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fPIC")
17 | endif(CMAKE_BUILD_TYPE STREQUAL "Release")
18 | #########################################################################
19 |
20 | #find_package(catkin REQUIRED)
21 | find_package(OpenCV REQUIRED)
22 | find_package(Eigen3 REQUIRED)
23 | find_package(OpenMP REQUIRED)
24 |
25 | find_package(PythonLibs 3.7 REQUIRED)
26 | execute_process(COMMAND
27 | python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
28 | OUTPUT_VARIABLE
29 | PYTHON_SITE_PACKAGES
30 | OUTPUT_STRIP_TRAILING_WHITESPACE
31 | )
32 | list(APPEND CMAKE_PREFIX_PATH ${PYTHON_SITE_PACKAGES}/torch/share/cmake)
33 | #list(APPEND CMAKE_PREFIX_PATH "~/.local/lib/python3.6/site-packages/torch/share/cmake")
34 | find_package(Torch REQUIRED)
35 |
36 | #catkin_package(
37 | # INCLUDE_DIRS include
38 | # #CATKIN_DEPENDS
39 | #)
40 |
41 | include_directories(
42 | include
43 | ${OPENCV_INCLUDE_DIRS}
44 | ${EIGEN3_INCLUDE_DIRS}
45 | )
46 |
47 | ## Declare a C++ executable
48 | add_executable(${PROJECT_NAME}
49 | src/main_track.cc
50 | src/ORBFeatureDetector.cc
51 | src/ORBFeatureMatcher.cc
52 | src/ORBFeatureTracker.cc
53 | src/SuperPointFeatureDetector.cc
54 | src/SuperPointFeatureMatcher.cc
55 | src/SuperPointFeatureTracker.cc
56 | )
57 |
58 | ## Specify libraries to link a library or executable target against
59 | target_link_libraries(${PROJECT_NAME} PUBLIC
60 | OpenMP::OpenMP_CXX
61 | ${TORCH_LIBRARIES}
62 | ${OpenCV_LIBS}
63 | glog
64 | gflags
65 | )
66 |
--------------------------------------------------------------------------------
/include/SuperPointFeatureMatcher.h:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: SuperPointFeatureMatcher.h
6 | *
7 | * Created On: Thu 10 Oct 2019 07:48:30 AM UTC
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #ifndef FEATURE_TRACKER_SUPER_POINT_FEATURE_MATCHER_H_
13 | #define FEATURE_TRACKER_SUPER_POINT_FEATURE_MATCHER_H_
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #include "FeatureDescriptor.h"
23 |
24 | class SuperPointFeatureMatcher {
25 | public:
26 | /* Feature matched indices. */
27 | struct FeatureMatch {
28 | int index1;
29 | int index2;
30 | float distance;
31 | FeatureMatch(int idx1, int idx2, float dist)
32 | : index1(idx1), index2(idx2), distance(dist){};
33 | FeatureMatch()
34 | : index1(-1), index2(-1), distance(std::numeric_limits::max()){};
35 | };
36 |
37 | SuperPointFeatureMatcher();
38 |
39 | ~SuperPointFeatureMatcher() = default;
40 |
41 | void Init();
42 |
43 | void Init(float nn_thresh);
44 |
45 | bool IsInitialized() const { return is_initialized_; }
46 |
47 | SuperPointFeatureMatcher(const SuperPointFeatureMatcher& d);
48 |
49 | SuperPointFeatureMatcher& operator=(const SuperPointFeatureMatcher& d);
50 |
51 | int Match(const FeatureDescriptorf& descriptors_img1,
52 | const FeatureDescriptorf& descriptors_img2,
53 | Eigen::Matrix3Xf* output_matches);
54 |
55 | inline float GetRefineDist() const { return refine_dist_; }
56 |
57 | std::vector& GetMatches() { return matches_; }
58 |
59 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW
60 |
61 | private:
62 | int Match(const FeatureDescriptorf& descriptors_img1,
63 | const FeatureDescriptorf& descriptors_img2);
64 | float refine_dist_;
65 | bool is_initialized_;
66 | std::vector matches_;
67 | };
68 |
69 | #endif
70 |
--------------------------------------------------------------------------------
/include/ORBFeatureDetector.h:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: ORBFeatureDetector.h
6 | *
7 | * Created On: Wed 25 Sep 2019 04:26:40 PM CST
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #ifndef FEATURE_TRACKER_ORB_FEATURE_DETECTOR_H_
13 | #define FEATURE_TRACKER_ORB_FEATURE_DETECTOR_H_
14 |
15 | #include "FeatureDetectorBase.h"
16 |
17 | #include
18 | #include
19 |
20 | class ORBFeatureDetector : public FeatureDetectorBase {
21 | public:
22 | ORBFeatureDetector();
23 | ~ORBFeatureDetector() = default;
24 |
25 | virtual void Init(int width, int height, int max_number_of_features);
26 |
27 | void Init(int width, int height, int max_number_of_features,
28 | float scale_factor, int number_of_levels, int edge_dist,
29 | int patch_size, bool refine_corners);
30 |
31 | virtual void Detect(const cv::Mat& image,
32 | Eigen::Matrix2Xf* current_measurements,
33 | std::vector* current_feature_orientations,
34 | std::vector* current_feature_scales,
35 | FeatureDescriptoru* current_feature_descriptors);
36 |
37 | int Detect(const cv::Mat& image, std::vector* feature_locations,
38 | cv::Mat* descriptors);
39 |
40 | inline virtual bool IsInitialized() const { return is_initialized_; }
41 |
42 | inline virtual int GetWidth() const { return width_; }
43 |
44 | inline virtual int GetHeight() const { return height_; }
45 |
46 | inline virtual int GetMaxNumberOfFeatures() const {
47 | return max_number_of_features_;
48 | }
49 |
50 | inline float GetScaleFactor() const { return scale_factor_; }
51 |
52 | inline int GetNumberOfLevels() const { return number_of_levels_; }
53 |
54 | inline int GetEdgeDist() const { return edge_dist_; }
55 |
56 | inline int GetPatchSize() const { return patch_size_; }
57 |
58 | inline bool GetRefineCorners() const { return refine_corners_; }
59 |
60 | void SetMask(const cv::Mat& mask);
61 |
62 | void SetNumberOfLevels(int number_of_levels);
63 |
64 | const cv::Mat& GetCvImage() const { return image_; }
65 |
66 | ORBFeatureDetector(const ORBFeatureDetector& d);
67 |
68 | ORBFeatureDetector& operator=(const ORBFeatureDetector& d);
69 |
70 | void Refine(const cv::Mat& image,
71 | std::vector* feature_locations);
72 |
73 | private:
74 | int width_;
75 | int height_;
76 |
77 | int max_number_of_features_;
78 |
79 | float scale_factor_;
80 | int number_of_levels_;
81 | int edge_dist_;
82 | int patch_size_;
83 | bool refine_corners_;
84 | float downsample_scale_;
85 | bool is_initialized_;
86 |
87 | cv::Mat image_;
88 | cv::Mat mask_;
89 |
90 | cv::Ptr detector_;
91 | };
92 |
93 | #endif
94 |
--------------------------------------------------------------------------------
/include/SuperPointFeatureTracker.h:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: SuperPointFeatureTracker.h
6 | *
7 | * Created On: Fri 11 Oct 2019 07:52:55 AM UTC
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #ifndef FEATURE_TRACKER_SUPER_POINT_FEATURE_TRACKER_H_
13 | #define FEATURE_TRACKER_SUPER_POINT_FEATURE_TRACKER_H_
14 |
15 | #include "FeatureTrackerBase.h"
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | #include "FeatureDescriptor.h"
24 | #include "FeatureDetectorBase.h"
25 | #include "SuperPointFeatureDetector.h"
26 | #include "SuperPointFeatureMatcher.h"
27 |
28 | class SuperPointFeatureTracker : public FeatureTrackerBase {
29 | public:
30 | SuperPointFeatureTracker();
31 |
32 | ~SuperPointFeatureTracker() = default;
33 |
34 | virtual void Init(int width, int height, int max_number_of_features);
35 |
36 | virtual void Track(const cv::Mat& image,
37 | Eigen::Matrix2Xf* current_measurements,
38 | Eigen::Matrix2Xf* previous_measurements,
39 | std::vector* feature_ids);
40 |
41 | void Init(int width, int height, int max_number_of_features,
42 | std::string model_path, float conf_thresh, int nms_dist,
43 | float nn_thresh);
44 |
45 |
46 | virtual bool IsInitialized() const { return is_initialized_; }
47 |
48 | virtual int GetWidth() const { return width_; }
49 |
50 | virtual int GetHeight() const { return height_; }
51 |
52 | virtual void GetFeatureLocations(std::vector* points) const;
53 |
54 | virtual const std::vector& GetNewTrackIds() const {
55 | return previous_track_ids_;
56 | }
57 |
58 | virtual int GetNumberOfFeaturesMatched() const { return matches_.cols(); }
59 |
60 | void GetFeatureDescriptor(FeatureDescriptorf* descriptors) {
61 | descriptors->Copy(current_descriptors_);
62 | }
63 |
64 | virtual void Display();
65 |
66 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW
67 |
68 | private:
69 | void LocalInit(int width, int height);
70 |
71 | std::shared_ptr detector_;
72 | std::shared_ptr matcher_;
73 |
74 | int width_;
75 | int height_;
76 | int current_track_id_;
77 | bool is_initialized_;
78 |
79 | Eigen::Matrix2Xf previous_feature_locations_;
80 | Eigen::Matrix2Xf current_feature_locations_;
81 | /**
82 | * matches_[0]: current_image matched keypoints index
83 | * [1]: previous_image matched keypoints index
84 | * [2]: distance
85 | */
86 | Eigen::Matrix3Xf matches_;
87 |
88 | std::vector previous_track_ids_;
89 | std::vector current_track_ids_;
90 |
91 | FeatureDescriptorf previous_descriptors_;
92 | FeatureDescriptorf current_descriptors_;
93 |
94 | cv::Mat previous_image_;
95 | cv::Mat current_image_;
96 | };
97 |
98 | #endif
99 |
--------------------------------------------------------------------------------
/src/ORBFeatureMatcher.cc:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: ORBFeatureMatcher.cc
6 | *
7 | * Created On: Thu 26 Sep 2019 03:27:02 PM CST
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #include "ORBFeatureMatcher.h"
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | DEFINE_int32(dist_type, 4, "1=NORM_L1, 2=NORM_L2, 4=NORM_HAMMING.");
20 | DEFINE_double(refine_dist, 64.0, "Reject distance > refine_dist matches.");
21 |
22 | ORBFeatureMatcher::ORBFeatureMatcher()
23 | : dist_type_(DistType::NORM_HAMMING),
24 | refine_dist_(0),
25 | is_initialized_(false) {}
26 |
27 | void ORBFeatureMatcher::Init() {
28 | Init(static_cast(FLAGS_dist_type), FLAGS_refine_dist);
29 | }
30 |
31 | void ORBFeatureMatcher::Init(DistType dist_type, float refine_dist) {
32 | CHECK_GT(refine_dist, 0.);
33 | dist_type_ = dist_type;
34 | refine_dist_ = refine_dist;
35 | matches_.reserve(2000);
36 | refined_matches_.reserve(2000);
37 |
38 | matcher_ = cv::BFMatcher::create(dist_type_);
39 |
40 | is_initialized_ = true;
41 | }
42 |
43 | ORBFeatureMatcher::ORBFeatureMatcher(const ORBFeatureMatcher& d) {
44 | if (d.IsInitialized()) {
45 | Init(d.GetDistType(), d.GetRefineDist());
46 | }
47 | }
48 |
49 | ORBFeatureMatcher& ORBFeatureMatcher::operator=(const ORBFeatureMatcher& d) {
50 | if (this == &d) {
51 | return *this;
52 | }
53 | if (d.IsInitialized()) {
54 | Init(d.GetDistType(), d.GetRefineDist());
55 | }
56 | return *this;
57 | }
58 |
59 | int ORBFeatureMatcher::Match(const cv::Mat& descriptors_img1,
60 | const cv::Mat& descriptors_img2,
61 | std::vector* output_matches) {
62 | if (descriptors_img1.empty() or descriptors_img2.empty()) return -1;
63 |
64 | matches_.resize(0);
65 | matcher_->match(descriptors_img1, descriptors_img2, matches_);
66 |
67 | std::unordered_set query_indicies;
68 | std::unordered_set train_indicies;
69 |
70 | VLOG(4) << "Number of features in image 1: " << descriptors_img1.rows
71 | << ", in image 2: " << descriptors_img2.rows;
72 |
73 | std::unordered_map train_index_to_index_map;
74 | refined_matches_.resize(0);
75 | for (size_t i = 0; i < matches_.size(); i++) {
76 | if (matches_[i].distance < refine_dist_) {
77 | const int& key = matches_[i].trainIdx;
78 | if (train_index_to_index_map.count(key) == 0) {
79 | train_index_to_index_map[key] = refined_matches_.size();
80 | refined_matches_.push_back(matches_[i]);
81 | } else if (refined_matches_[train_index_to_index_map[key]].distance <
82 | matches_[i].distance) {
83 | refined_matches_[train_index_to_index_map[key]] = matches_[i];
84 | }
85 | }
86 | }
87 | VLOG(4) << "Number of refined matches: " << refined_matches_.size();
88 | if (refined_matches_.size() == 0)
89 | LOG(WARNING) << "Couldn't find refined matches";
90 |
91 | *output_matches = refined_matches_;
92 | return refined_matches_.size();
93 | }
94 |
--------------------------------------------------------------------------------
/utils/pytorch_model_convert.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #########################################################################
3 | #
4 | # Author: b51
5 | # Mail: b51live@gmail.com
6 | # FileName: pytorch_model_convert.py
7 | #
8 | # Created On: Mon 30 Sep 2019 09:32:30 AM UTC
9 | # Licensed under The MIT License [see LICENSE for details]
10 | #
11 | #########################################################################
12 |
13 | import torch
14 | import sys
15 |
16 |
17 | class SuperPointNet(torch.nn.Module):
18 | """ Pytorch definition of SuperPoint Network. """
19 |
20 | def __init__(self):
21 | super(SuperPointNet, self).__init__()
22 | self.relu = torch.nn.ReLU(inplace=True)
23 | self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
24 | c1, c2, c3, c4, c5, d1 = 64, 64, 128, 128, 256, 256
25 | # Shared Encoder.
26 | self.conv1a = torch.nn.Conv2d(1, c1, kernel_size=3, stride=1, padding=1)
27 | self.conv1b = torch.nn.Conv2d(c1, c1, kernel_size=3, stride=1, padding=1)
28 | self.conv2a = torch.nn.Conv2d(c1, c2, kernel_size=3, stride=1, padding=1)
29 | self.conv2b = torch.nn.Conv2d(c2, c2, kernel_size=3, stride=1, padding=1)
30 | self.conv3a = torch.nn.Conv2d(c2, c3, kernel_size=3, stride=1, padding=1)
31 | self.conv3b = torch.nn.Conv2d(c3, c3, kernel_size=3, stride=1, padding=1)
32 | self.conv4a = torch.nn.Conv2d(c3, c4, kernel_size=3, stride=1, padding=1)
33 | self.conv4b = torch.nn.Conv2d(c4, c4, kernel_size=3, stride=1, padding=1)
34 | # Detector Head.
35 | self.convPa = torch.nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1)
36 | self.convPb = torch.nn.Conv2d(c5, 65, kernel_size=1, stride=1, padding=0)
37 | # Descriptor Head.
38 | self.convDa = torch.nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1)
39 | self.convDb = torch.nn.Conv2d(c5, d1, kernel_size=1, stride=1, padding=0)
40 |
41 | def forward(self, x):
42 | """ Forward pass that jointly computes unprocessed point and descriptor
43 | tensors.
44 | Input
45 | x: Image pytorch tensor shaped N x 1 x H x W.
46 | Output
47 | semi: Output point pytorch tensor shaped N x 65 x H/8 x W/8.
48 | desc: Output descriptor pytorch tensor shaped N x 256 x H/8 x W/8.
49 | """
50 | # Shared Encoder.
51 | x = self.relu(self.conv1a(x))
52 | x = self.relu(self.conv1b(x))
53 | x = self.pool(x)
54 | x = self.relu(self.conv2a(x))
55 | x = self.relu(self.conv2b(x))
56 | x = self.pool(x)
57 | x = self.relu(self.conv3a(x))
58 | x = self.relu(self.conv3b(x))
59 | x = self.pool(x)
60 | x = self.relu(self.conv4a(x))
61 | x = self.relu(self.conv4b(x))
62 | # Detector Head.
63 | cPa = self.relu(self.convPa(x))
64 | semi = self.convPb(cPa)
65 | # Descriptor Head.
66 | cDa = self.relu(self.convDa(x))
67 | desc = self.convDb(cDa)
68 | dn = torch.norm(desc, p=2, dim=1) # Compute the norm.
69 | desc = desc.div(torch.unsqueeze(dn, 1)) # Divide by norm to normalize.
70 | return semi, desc
71 |
72 |
73 | def main(argv):
74 | super_point_net = SuperPointNet()
75 | super_point_net.load_state_dict(torch.load(
76 | argv[1], map_location=lambda storage, loc: storage))
77 | #super_point_net.eval()
78 | #script_module = torch.jit.script(super_point_net)
79 | #script_module.save("super_point_scripted_model.pt")
80 | example = torch.rand(1, 1, 120, 160)
81 | traced_script_module = torch.jit.trace(super_point_net, example)
82 | traced_script_module.save("super_point_scripted_model.pt")
83 |
84 |
85 | if __name__ == "__main__":
86 | main(sys.argv)
87 |
--------------------------------------------------------------------------------
/include/SuperPointFeatureDetector.h:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: SuperPointFeatureDetector.h
6 | *
7 | * Created On: Thu 26 Sep 2019 04:07:29 AM UTC
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #ifndef FEATURE_TRACKER_SUPER_POINT_FEATURE_DETECTOR_H_
13 | #define FEATURE_TRACKER_SUPER_POINT_FEATURE_DETECTOR_H_
14 |
15 | #include "FeatureDetectorBase.h"
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | class SuperPointFeatureDetector : public FeatureDetectorBase {
24 | public:
25 | SuperPointFeatureDetector();
26 | ~SuperPointFeatureDetector() = default;
27 |
28 | virtual void Init(int width, int height, int max_number_of_features);
29 |
30 | void Init(int width, int height, int max_number_of_features,
31 | std::string model_path, float conf_thresh, int nms_dist);
32 |
33 | virtual void Detect(const cv::Mat& image,
34 | Eigen::Matrix2Xf* current_measurements,
35 | FeatureDescriptorf* current_feature_descriptors);
36 |
37 | inline virtual bool IsInitialized() const { return is_initialized_; }
38 |
39 | inline virtual int GetWidth() const { return width_; }
40 |
41 | inline virtual int GetHeight() const { return height_; }
42 |
43 | inline virtual int GetMaxNumberOfFeatures() const {
44 | return max_number_of_features_;
45 | }
46 |
47 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW
48 |
49 | private:
50 | /**
51 | * Detect input
52 | * image: gray image normed to [0.0, 1.0]
53 | * return:
54 | * feature_locations: 3 X N feature points, [x, y, confidence]^T X N
55 | * descriptors: normed 256 X N descriptors
56 | */
57 | void Detect(const cv::Mat& image, Eigen::Matrix3Xf& filtered_pts,
58 | torch::Tensor& descriptors);
59 |
60 | /**
61 | * Run a faster approximate Non-Max-Suppression on Eigen corners shaped:
62 | * 3xN [x_i,y_i,conf_i]^T
63 | *
64 | * Algo summary: Create a grid sized height x width. Assign each corner
65 | * location a 1, rest are zeros. Iterate through all the 1's and convert
66 | * them either to -1 or 0. Suppress points by setting nearby values to 0.
67 | *
68 | * Grid Value Legend:
69 | * -1 : Kept.
70 | * 0 : Empty or suppressed.
71 | * 1 : To be processed (converted to either kept or supressed).
72 | *
73 | * NOTE: The NMS first rounds points to integers, so NMS distance might not
74 | * be exactly dist_thresh. It also assumes points are within image boundaries.
75 | *
76 | * Inputs
77 | * in_corners - 3 x N Eigen Matrix with corners [x_i, y_i, confidence_i]^T.
78 | * width - Image width.
79 | * height - Image height.
80 | * dist_thresh - Distance to suppress, measured as an infinty norm distance.
81 | * Returns
82 | * filtered_pts - 3 x N Eigen Matrix with surviving corners.
83 | */
84 | void NMSFast(const Eigen::Matrix3Xf& pts, int width, int height,
85 | int dist_thresh, Eigen::Matrix3Xf& filtered_pts);
86 |
87 | int width_;
88 | int height_;
89 | int max_number_of_features_;
90 |
91 | std::string model_path_;
92 | float conf_thresh_;
93 | int nms_dist_;
94 | bool is_initialized_;
95 |
96 | std::shared_ptr module_;
97 | cv::Mat image_;
98 | };
99 |
100 | #endif
101 |
--------------------------------------------------------------------------------
/include/FeatureDescriptor.h:
--------------------------------------------------------------------------------
1 | #ifndef BINARY_FEATURE_STORE_H_
2 | #define BINARY_FEATURE_STORE_H_
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | static const int kDefaultReservedSpace = 2000;
11 |
12 | template
13 | class FeatureDescriptor {
14 | public:
15 | FeatureDescriptor() : num_features_(0), descriptor_size_(0) {}
16 |
17 | FeatureDescriptor(int descriptor_size,
18 | int reserved_space = kDefaultReservedSpace)
19 | : num_features_(0), descriptor_size_(descriptor_size) {
20 | descriptor_data_.reserve(reserved_space * descriptor_size_);
21 | }
22 |
23 | // Set the number of bytes per descriptor. Any existing data is invalidated.
24 | // The number of features is always zero following this call; the
25 | // reserved_space argument only reserves space so that future resize() calls
26 | // avoid reallocation.
27 | void Configure(int descriptor_size,
28 | int reserved_space = kDefaultReservedSpace) {
29 | descriptor_size_ = descriptor_size;
30 | Resize(0);
31 | descriptor_data_.reserve(reserved_space * descriptor_size_);
32 | }
33 |
34 | // Change the number of features. If n > num_features_ then this function
35 | // may trigger a re-allocation. If n < num_features_ then this function
36 | // never re-allocates. In either case the previous data is always retained.
37 | void Resize(int n) {
38 | num_features_ = n;
39 | descriptor_data_.resize(n * descriptor_size_);
40 | }
41 |
42 | void Clear() {
43 | num_features_ = 0;
44 | descriptor_data_.clear();
45 | }
46 |
47 | inline uint32_t Size() const { return num_features_; }
48 |
49 | inline bool Empty() const { return num_features_ == 0; }
50 |
51 | inline uint32_t DescriptorSize() const { return descriptor_size_; }
52 |
53 | inline ScalarType* descriptor(uint32_t index) {
54 | return &descriptor_data_[index * descriptor_size_];
55 | }
56 |
57 | inline const ScalarType* descriptor(uint32_t index) const {
58 | return &descriptor_data_[index * descriptor_size_];
59 | }
60 |
61 | inline void SetDescriptorToZero(uint32_t index) {
62 | memset(descriptor(index), 0, descriptor_size_);
63 | }
64 |
65 | inline void SetDescriptorRandom(uint32_t index, int seed) {
66 | std::mt19937 generator(seed);
67 | ScalarType* data = descriptor(index);
68 | for (size_t i = 0; i < descriptor_size_; ++i) {
69 | data[i] = generator() % 256;
70 | }
71 | }
72 |
73 | inline ScalarType* descriptor_data() { return descriptor_data_.data(); }
74 | inline const ScalarType* descriptor_data() const {
75 | return descriptor_data_.data();
76 | }
77 |
78 | void Swap(FeatureDescriptor* other) {
79 | CHECK_NE(other, this);
80 | CHECK_NOTNULL(other);
81 | std::swap(num_features_, other->num_features_);
82 | std::swap(descriptor_size_, other->descriptor_size_);
83 | std::swap(descriptor_data_, other->descriptor_data_);
84 | }
85 |
86 | // Copy the feature store from another. The preferred method of moving data is
87 | // with swap.
88 | void Copy(const FeatureDescriptor& other) {
89 | num_features_ = other.num_features_;
90 | descriptor_size_ = other.descriptor_size_;
91 | descriptor_data_ = other.descriptor_data_;
92 | }
93 |
94 | private:
95 | // Prevent accidental use of the copy constructor since it is expensive.
96 | // The preferred way to move feature stores around is with swap().
97 | FeatureDescriptor(const FeatureDescriptor&) = delete;
98 | FeatureDescriptor& operator=(const FeatureDescriptor&) = delete;
99 |
100 | // The number of features in the store.
101 | uint32_t num_features_;
102 |
103 | // Number of bytes per descriptor.
104 | uint32_t descriptor_size_;
105 |
106 | std::vector descriptor_data_;
107 | };
108 |
109 | using FeatureDescriptoru = FeatureDescriptor;
110 | using FeatureDescriptorf = FeatureDescriptor;
111 |
112 | #endif
113 |
--------------------------------------------------------------------------------
/src/main_track.cc:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: main_track.cc
6 | *
7 | * Created On: Sun 13 Oct 2019 11:00:50 AM CST
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | #include "FeatureTrackerBase.h"
20 | #include "ORBFeatureTracker.h"
21 | #include "SuperPointFeatureTracker.h"
22 |
23 | DEFINE_string(
24 | image_dir,
25 | "/home/b51live/Sources/SuperPointPretrainedNetwork/assets",
26 | "Where to find images");
27 | DEFINE_string(image_suffix, ".jpg", "Suffix of images, default: .jpg");
28 | DEFINE_int32(max_number_of_features, 500, "max number of detected features");
29 |
30 | DEFINE_string(type, "sp", "Feature detector type[orb, sp], default: orb");
31 |
32 | void LoadImages(const std::string& dir_name,
33 | std::vector& image_filenames) {
34 | std::vector files;
35 | cv::glob(dir_name, files);
36 | for (auto file : files) {
37 | std::string image_file(file);
38 | size_t index = image_file.rfind(".");
39 | if (index != std::string::npos) {
40 | std::string suffix = image_file.substr(index);
41 | if (suffix.compare(FLAGS_image_suffix) == 0) {
42 | image_filenames.emplace_back(image_file);
43 | }
44 | }
45 | } // end of for loop
46 | }
47 |
48 | int main(int argc, char* argv[]) {
49 | google::InitGoogleLogging(argv[0]);
50 | google::ParseCommandLineFlags(&argc, &argv, true);
51 | FLAGS_logtostderr = true;
52 | FLAGS_colorlogtostderr = true;
53 |
54 | std::vector image_filenames;
55 | std::string string_file = FLAGS_image_dir; // + "/rgb.txt";
56 | LoadImages(string_file, image_filenames);
57 |
58 | std::cout << "Usage: " << argv[0]
59 | << " --image_dir path/to/images --image_suffix .jpg/.png --model"
60 | " path/to/model --type orb/sp [--model path/to/sp_model --W"
61 | " sp_input_W --H sp_input_H --CUDA device_num]"
62 | << std::endl;
63 |
64 | cv::Mat image = cv::imread(image_filenames[0], CV_LOAD_IMAGE_UNCHANGED);
65 |
66 | LOG(INFO) << "type: " << FLAGS_type;
67 | std::shared_ptr tracker;
68 | if (FLAGS_type.compare("orb") == 0) {
69 | tracker = std::make_shared();
70 | } else if (FLAGS_type.compare("sp") == 0 or
71 | FLAGS_type.compare("superpoint") == 0) {
72 | tracker = std::make_shared();
73 | } else {
74 | LOG(FATAL) << "Feature type is not supported, only support orb/sp";
75 | }
76 |
77 | tracker->Init(image.cols, image.rows, FLAGS_max_number_of_features);
78 |
79 | // coordinates in pixel plane
80 | Eigen::Matrix2Xf current_measurements;
81 | Eigen::Matrix2Xf previous_measurements;
82 | std::vector feature_ids;
83 |
84 | for (auto image_filename : image_filenames) {
85 | image = cv::imread(image_filename, CV_LOAD_IMAGE_UNCHANGED);
86 | if (image.channels() > 1) {
87 | cv::cvtColor(image, image, CV_BGR2GRAY);
88 | }
89 | if (image.empty()) {
90 | LOG(FATAL) << "Failed to load image at: " << FLAGS_image_dir << "/"
91 | << image_filename;
92 | }
93 | tracker->Track(image, ¤t_measurements, &previous_measurements,
94 | &feature_ids);
95 | tracker->Display();
96 | VLOG(4) << "Feature size: " << current_measurements.size();
97 | if (FLAGS_type.compare("orb") == 0) {
98 | FeatureDescriptoru descriptors;
99 | std::dynamic_pointer_cast(tracker)
100 | ->GetFeatureDescriptor(&descriptors);
101 | } else if (FLAGS_type.compare("sp") == 0 or
102 | FLAGS_type.compare("superpoint") == 0) {
103 | FeatureDescriptorf descriptors;
104 | std::dynamic_pointer_cast(tracker)
105 | ->GetFeatureDescriptor(&descriptors);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/include/ORBFeatureTracker.h:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: ORBFeatureTracker.h
6 | *
7 | * Created On: Thu 26 Sep 2019 04:07:53 PM CST
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #ifndef FEATURE_TRACKER_ORB_FEATURE_TRACKER_H_
13 | #define FEATURE_TRACKER_ORB_FEATURE_TRACKER_H_
14 |
15 | #include "FeatureTrackerBase.h"
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #include "FeatureDescriptor.h"
23 | #include "FeatureDetectorBase.h"
24 | #include "ORBFeatureDetector.h"
25 | #include "ORBFeatureMatcher.h"
26 |
27 | class ORBFeatureTracker : public FeatureTrackerBase {
28 | public:
29 | typedef cv::NormTypes DistType;
30 |
31 | ORBFeatureTracker();
32 |
33 | ~ORBFeatureTracker() = default;
34 |
35 | virtual void Init(int width, int height, int max_number_of_features);
36 |
37 | void Init(int width, int height, int max_number_of_features,
38 | float scale_factor, int number_of_levels, int edge_dist,
39 | int patch_size, bool refine_corners, DistType dist_type,
40 | float refine_dist);
41 |
42 | virtual void Track(const cv::Mat& image,
43 | Eigen::Matrix2Xf* current_measurements,
44 | Eigen::Matrix2Xf* previous_measurements,
45 | std::vector* feature_ids);
46 |
47 | virtual void Display();
48 |
49 | inline virtual int GetWidth() const { return width_; }
50 |
51 | inline virtual int GetHeight() const { return height_; }
52 |
53 | inline virtual bool IsInitialized() const { return is_initialized_; }
54 |
55 | inline float GetScaleFactor() const { return detector_->GetScaleFactor(); }
56 |
57 | inline int GetEdgeDist() const { return detector_->GetEdgeDist(); }
58 |
59 | inline int GetPatchSize() const { return detector_->GetPatchSize(); }
60 |
61 | inline bool GetRefineCorners() const { return detector_->GetRefineCorners(); }
62 |
63 | inline DistType GetDistType() const { return matcher_->GetDistType(); }
64 |
65 | inline float GetRefineDist() const { return matcher_->GetRefineDist(); }
66 |
67 | inline int GetMaxNumberOfFeatures() const {
68 | return detector_->GetMaxNumberOfFeatures();
69 | }
70 |
71 | inline int GetNumberOfLevels() const {
72 | return detector_->GetNumberOfLevels();
73 | }
74 |
75 | void SetDetectorMask(const cv::Mat& mask);
76 |
77 | const std::vector& GetMatches() const { return matches_; }
78 |
79 | void GetFeatureDescriptor(FeatureDescriptoru* descriptors);
80 |
81 | void GetFeatureDescriptor(cv::Mat* descriptors) const {
82 | current_descriptors_.copyTo(*descriptors);
83 | }
84 |
85 | virtual void GetFeatureLocations(std::vector* points) const {
86 | CHECK_NOTNULL(points);
87 | points->resize(current_feature_locations_.size());
88 | for (size_t i = 0; i < current_feature_locations_.size(); i++) {
89 | (*points)[i] << current_feature_locations_[i].pt.x,
90 | current_feature_locations_[i].pt.y;
91 | }
92 | }
93 |
94 | virtual const std::vector& GetNewTrackIds() const { return previous_track_ids_; }
95 |
96 | virtual int GetNumberOfFeaturesDetected() const {
97 | return current_feature_locations_.size();
98 | }
99 |
100 | int GetNumberOfFeaturesMatched() const { return matches_.size(); }
101 |
102 | private:
103 | void LocalInit(int width, int height);
104 |
105 | std::unique_ptr detector_;
106 | std::unique_ptr matcher_;
107 | int width_;
108 | int height_;
109 | int current_track_id_;
110 | bool is_initialized_;
111 |
112 | std::vector previous_feature_locations_;
113 | std::vector current_feature_locations_;
114 | std::vector matches_;
115 | std::vector previous_track_ids_;
116 | std::vector current_track_ids_;
117 |
118 | cv::Mat previous_descriptors_;
119 | cv::Mat current_descriptors_;
120 |
121 | // For debugging we keep the images here
122 | cv::Mat previous_image_;
123 | cv::Mat current_image_;
124 | };
125 |
126 | #endif
127 |
--------------------------------------------------------------------------------
/src/SuperPointFeatureMatcher.cc:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: SuperPointFeatureMatcher.cc
6 | *
7 | * Created On: Thu 10 Oct 2019 07:47:43 AM UTC
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #include "SuperPointFeatureMatcher.h"
13 |
14 | #include
15 | #include
16 |
17 | DEFINE_double(
18 | nn_thresh, 0.7,
19 | "Optional descriptor distance below which is a good match (default: 0.7)");
20 |
21 | SuperPointFeatureMatcher::SuperPointFeatureMatcher()
22 | : is_initialized_(false) {}
23 |
24 | void SuperPointFeatureMatcher::Init() {
25 | Init(FLAGS_nn_thresh);
26 | }
27 |
28 | void SuperPointFeatureMatcher::Init(float nn_thresh) {
29 | CHECK_GT(nn_thresh, 0.0);
30 | refine_dist_ = nn_thresh;
31 | matches_.reserve(2000);
32 | is_initialized_ = true;
33 | }
34 |
35 | SuperPointFeatureMatcher::SuperPointFeatureMatcher(
36 | const SuperPointFeatureMatcher& d) {
37 | if (d.IsInitialized()) {
38 | Init(d.GetRefineDist());
39 | }
40 | }
41 |
42 | SuperPointFeatureMatcher& SuperPointFeatureMatcher::operator=(
43 | const SuperPointFeatureMatcher& d) {
44 | if (this == &d) {
45 | return *this;
46 | }
47 | if (d.IsInitialized()) {
48 | Init(d.GetRefineDist());
49 | }
50 | return *this;
51 | }
52 |
53 | int SuperPointFeatureMatcher::Match(const FeatureDescriptorf& descriptors_img1,
54 | const FeatureDescriptorf& descriptors_img2,
55 | Eigen::Matrix3Xf* output_matches) {
56 | if (descriptors_img1.Empty() or descriptors_img2.Empty()) return -1;
57 |
58 | int number_of_matches = Match(descriptors_img1, descriptors_img2);
59 |
60 | output_matches->resize(3, number_of_matches);
61 | #pragma omp parallel for
62 | for (int i = 0; i < number_of_matches; i++) {
63 | output_matches->col(i)[0] = matches_[i].index1;
64 | output_matches->col(i)[1] = matches_[i].index2;
65 | output_matches->col(i)[2] = matches_[i].distance;
66 | }
67 | VLOG(4) << " number_of_matches " << output_matches->cols();
68 | return number_of_matches;
69 | }
70 |
71 | int SuperPointFeatureMatcher::Match(
72 | const FeatureDescriptorf& descriptors_img1,
73 | const FeatureDescriptorf& descriptors_img2) {
74 | matches_.resize(0);
75 | // descriptors_img1 size = N1 X 256
76 | // descriptors_img2 size = N2 X 256
77 | int number_of_features_1 = descriptors_img1.Size();
78 | int number_of_features_2 = descriptors_img2.Size();
79 | int descriptor_size = descriptors_img1.DescriptorSize();
80 |
81 | Eigen::MatrixXf eig_descriptors_1, eig_descriptors_2;
82 | eig_descriptors_1.resize(number_of_features_1, descriptor_size);
83 | eig_descriptors_2.resize(number_of_features_2, descriptor_size);
84 |
85 | for (int i = 0; i < number_of_features_1; i++) {
86 | eig_descriptors_1.row(i) = Eigen::Map(
87 | descriptors_img1.descriptor(i), 1, descriptor_size);
88 | }
89 | for (int i = 0; i < number_of_features_2; i++) {
90 | eig_descriptors_2.row(i) = Eigen::Map(
91 | descriptors_img2.descriptor(i), 1, descriptor_size);
92 | }
93 |
94 | // nn_matches size = N1 X N2
95 | Eigen::MatrixXf nn_matches =
96 | eig_descriptors_1 * eig_descriptors_2.transpose();
97 | // clip scores to range [-1.0, 1.0]
98 | nn_matches = 2.0 * nn_matches.array().max(-1.0).min(1.0);
99 | // make sure scores positive
100 | nn_matches = 2.0 - nn_matches.array();
101 | // get sqrt root of scores
102 | nn_matches = nn_matches.array().sqrt();
103 |
104 | // get min scores of each row
105 | Eigen::VectorXf scores(nn_matches.rows(), 1);
106 | scores = nn_matches.rowwise().minCoeff();
107 |
108 | // get each min scores index of rows
109 | Eigen::VectorXf indices_1(nn_matches.rows(), 1);
110 | for (int i = 0; i < nn_matches.rows(); i++)
111 | nn_matches.row(i).minCoeff(&indices_1(i));
112 | // get each min scores index of cols
113 | Eigen::VectorXf indices_2(nn_matches.cols(), 1);
114 | for (int i = 0; i < nn_matches.cols(); i++)
115 | nn_matches.col(i).minCoeff(&indices_2(i));
116 |
117 | // Threshold the NN matches, meet condition true, else false
118 | Eigen::Matrix keep(scores.rows(), 1);
119 | keep = (scores.array() < refine_dist_);
120 |
121 | // Check if nearest neighbor goes both directions and keep those.
122 | #pragma omp parallel for
123 | for (int i = 0; i < indices_1.rows(); i++) {
124 | keep[i] = (indices_2[indices_1[i]] == i) && keep[i];
125 | if (keep[i]) {
126 | matches_.emplace_back(FeatureMatch(i, indices_1[i], scores[i]));
127 | }
128 | }
129 | return matches_.size();
130 | }
131 |
--------------------------------------------------------------------------------
/src/SuperPointFeatureTracker.cc:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: SuperPointFeatureTracker.cc
6 | *
7 | * Created On: Fri 11 Oct 2019 07:52:29 AM UTC
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #include "SuperPointFeatureTracker.h"
13 |
14 | SuperPointFeatureTracker::SuperPointFeatureTracker()
15 | : detector_(nullptr),
16 | matcher_(nullptr),
17 | width_(0),
18 | height_(0),
19 | current_track_id_(-1),
20 | is_initialized_(false) {}
21 |
22 | void SuperPointFeatureTracker::Init(int width, int height,
23 | int max_number_of_features) {
24 | LocalInit(width, height);
25 | detector_->Init(width, height, max_number_of_features);
26 | matcher_->Init();
27 | is_initialized_ = true;
28 | }
29 |
30 | void SuperPointFeatureTracker::Init(int width, int height,
31 | int max_number_of_features,
32 | std::string model_path, float conf_thresh,
33 | int nms_dist, float nn_thresh) {
34 | LocalInit(width, height);
35 | detector_->Init(width, height, max_number_of_features, model_path,
36 | conf_thresh, nms_dist);
37 | matcher_->Init(nn_thresh);
38 | is_initialized_ = true;
39 | }
40 |
41 | void SuperPointFeatureTracker::LocalInit(int width, int height) {
42 | width_ = width;
43 | height_ = height;
44 |
45 | previous_track_ids_.reserve(1000);
46 | current_track_ids_.reserve(1000);
47 |
48 | detector_ = std::make_shared();
49 | matcher_ = std::make_shared();
50 | }
51 |
52 | void SuperPointFeatureTracker::GetFeatureLocations(
53 | std::vector* points) const {
54 | CHECK_NOTNULL(points);
55 | points->resize(current_feature_locations_.cols());
56 | for (int i = 0; i < current_feature_locations_.cols(); i++) {
57 | (*points)[i] << current_feature_locations_.col(i)[0],
58 | current_feature_locations_.col(i)[1];
59 | }
60 | }
61 |
62 | void SuperPointFeatureTracker::Track(const cv::Mat& image,
63 | Eigen::Matrix2Xf* current_measurements,
64 | Eigen::Matrix2Xf* previous_measurements,
65 | std::vector* feature_ids) {
66 | previous_feature_locations_ = current_feature_locations_;
67 | image.copyTo(current_image_);
68 |
69 | std::swap(previous_track_ids_, current_track_ids_);
70 | current_descriptors_.Swap(&previous_descriptors_);
71 |
72 | detector_->Detect(image, ¤t_feature_locations_, ¤t_descriptors_);
73 | int number_of_features = current_feature_locations_.cols();
74 | if (previous_feature_locations_.cols() > 0 and number_of_features > 0) {
75 | matcher_->Match(current_descriptors_, previous_descriptors_, &matches_);
76 | }
77 | int number_of_matches = matches_.cols();
78 | current_track_ids_.clear();
79 | current_track_ids_.resize(number_of_features, -1);
80 | for (int i = 0; i < number_of_matches; i++) {
81 | current_track_ids_[matches_.col(i)[0]] = previous_track_ids_[matches_.col(i)[1]];
82 | }
83 | for (auto& id : current_track_ids_) {
84 | if (id == -1) {
85 | id = ++current_track_id_;
86 | }
87 | }
88 |
89 | current_measurements->resize(2, number_of_matches);
90 | previous_measurements->resize(2, number_of_matches);
91 | feature_ids->clear();
92 |
93 | for (int i = 0; i < number_of_matches; i++) {
94 | current_measurements->col(i) = current_feature_locations_.col(matches_.col(i)[0]);
95 | previous_measurements->col(i) = previous_feature_locations_.col(matches_.col(i)[1]);
96 | feature_ids->push_back(current_track_ids_[matches_.col(i)[0]]);
97 | }
98 | }
99 |
100 | void SuperPointFeatureTracker::Display() {
101 | cv::Mat image_matches;
102 | std::vector previous_cv_keypoints;
103 | std::vector current_cv_keypoints;
104 | std::vector cv_matches;
105 | int number_of_matches = matches_.cols();
106 | if (number_of_matches > 0) {
107 | for (int i = 0; i < number_of_matches; i++) {
108 | int x = std::round(current_feature_locations_.col(matches_.col(i)[0])[0]);
109 | int y = std::round(current_feature_locations_.col(matches_.col(i)[0])[1]);
110 | int px = std::round(previous_feature_locations_.col(matches_.col(i)[0])[0]);
111 | int py = std::round(previous_feature_locations_.col(matches_.col(i)[1])[1]);
112 | // int id = current_track_ids_[matches_.col(i)[0]];
113 |
114 | previous_cv_keypoints.emplace_back(cv::KeyPoint(px, py, 1.));
115 | current_cv_keypoints.emplace_back(cv::KeyPoint(x, y, 1.));
116 | cv_matches.emplace_back(i, i, matches_.col(i)[2]);
117 | // cv::Scalar(0, 255, 255));
118 | // cv::putText(current_image_, std::to_string(id), cv::Point(px, py), 1, 1.0,
119 | // cv::Scalar(0, 0, 0), 1, 1, false);
120 | }
121 | cv::drawMatches(current_image_, current_cv_keypoints, previous_image_,
122 | previous_cv_keypoints, cv_matches, image_matches,
123 | cv::Scalar(255, 0, 0), cv::Scalar::all(-1),
124 | std::vector(), 0);
125 | cv::imshow("tracker", image_matches);
126 | cv::waitKey(1);
127 | /* save images
128 | static int iter = 0;
129 | std::stringstream ss;
130 | ss << std::setfill('0') << std::setw(6) << iter++;
131 | std::string img_name = "image_" + ss.str() + ".jpg";
132 | cv::imwrite(img_name, image_matches);
133 | */
134 | }
135 | current_image_.copyTo(previous_image_);
136 | }
137 |
--------------------------------------------------------------------------------
/src/ORBFeatureTracker.cc:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: ORBFeatureTracker.cc
6 | *
7 | * Created On: Thu 26 Sep 2019 04:06:52 PM CST
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #include "ORBFeatureTracker.h"
13 |
14 | ORBFeatureTracker::ORBFeatureTracker()
15 | : detector_(nullptr),
16 | matcher_(nullptr),
17 | width_(0),
18 | height_(0),
19 | current_track_id_(-1),
20 | is_initialized_(false) {}
21 |
22 | void ORBFeatureTracker::Init(int width, int height,
23 | int max_number_of_features) {
24 | LocalInit(width, height);
25 | detector_->Init(width, height, max_number_of_features);
26 | matcher_->Init();
27 | is_initialized_ = true;
28 | }
29 |
30 | void ORBFeatureTracker::Init(int width, int height, int max_number_of_features,
31 | float scale_factor, int number_of_levels,
32 | int edge_dist, int patch_size, bool refine_corners,
33 | DistType dist_type, float refine_dist) {
34 | LocalInit(width, height);
35 | detector_->Init(width, height, max_number_of_features, scale_factor,
36 | number_of_levels, edge_dist, patch_size, refine_corners);
37 | matcher_->Init(dist_type, refine_dist);
38 | is_initialized_ = true;
39 | }
40 |
41 | void ORBFeatureTracker::LocalInit(int width, int height) {
42 | width_ = width;
43 | height_ = height;
44 |
45 | previous_feature_locations_.reserve(1000);
46 | current_feature_locations_.reserve(1000);
47 | matches_.reserve(1000);
48 | previous_track_ids_.reserve(1000);
49 | current_track_ids_.reserve(1000);
50 |
51 | detector_.reset(new ORBFeatureDetector);
52 | matcher_.reset(new ORBFeatureMatcher);
53 | }
54 |
55 | void ORBFeatureTracker::Track(const cv::Mat& image,
56 | Eigen::Matrix2Xf* current_measurements,
57 | Eigen::Matrix2Xf* previous_measurements,
58 | std::vector* feature_ids) {
59 | std::swap(previous_feature_locations_, current_feature_locations_);
60 | current_feature_locations_.resize(0);
61 |
62 | std::swap(previous_track_ids_, current_track_ids_);
63 | cv::swap(current_descriptors_, previous_descriptors_);
64 |
65 | int number_of_features =
66 | detector_->Detect(image, ¤t_feature_locations_, ¤t_descriptors_);
67 | current_image_ = detector_->GetCvImage();
68 |
69 | previous_image_ = detector_->GetCvImage();
70 |
71 | if (previous_feature_locations_.size() > 0 and number_of_features > 0) {
72 | matcher_->Match(current_descriptors_, previous_descriptors_, &matches_);
73 | }
74 | current_track_ids_.clear();
75 | current_track_ids_.resize(current_feature_locations_.size(), -1);
76 |
77 | std::vector checked_matches;
78 | cv::Point2f diff(0., 0.);
79 | checked_matches.reserve(matches_.size());
80 | VLOG(4) << "original matches size: " << matches_.size();
81 | for (const auto& match : matches_) {
82 | diff = previous_feature_locations_[match.trainIdx].pt -
83 | current_feature_locations_[match.queryIdx].pt;
84 | if (std::fabs(diff.x) < width_ and std::fabs(diff.y) < height_) {
85 | checked_matches.push_back(match);
86 | }
87 | }
88 | VLOG(4) << "checked matches size: " << checked_matches.size();
89 | std::swap(matches_, checked_matches);
90 |
91 | for (const auto& match : matches_) {
92 | current_track_ids_[match.queryIdx] = previous_track_ids_[match.trainIdx];
93 | }
94 |
95 | for (auto& id : current_track_ids_) {
96 | if (id == -1) {
97 | id = ++current_track_id_;
98 | }
99 | }
100 |
101 | current_measurements->resize(2, matches_.size());
102 | previous_measurements->resize(2, matches_.size());
103 |
104 | feature_ids->clear();
105 | for (size_t i = 0; i < matches_.size(); i++) {
106 | current_measurements->operator()(0, i) =
107 | current_feature_locations_[matches_[i].queryIdx].pt.x;
108 | current_measurements->operator()(1, i) =
109 | current_feature_locations_[matches_[i].queryIdx].pt.y;
110 |
111 | previous_measurements->operator()(0, i) =
112 | previous_feature_locations_[matches_[i].trainIdx].pt.x;
113 | previous_measurements->operator()(1, i) =
114 | previous_feature_locations_[matches_[i].trainIdx].pt.y;
115 |
116 | feature_ids->push_back(current_track_ids_[matches_[i].queryIdx]);
117 | }
118 | }
119 |
120 | void ORBFeatureTracker::GetFeatureDescriptor(FeatureDescriptoru* descriptors) {
121 | int descriptor_size = current_descriptors_.cols;
122 | int number_of_features = current_descriptors_.rows;
123 | descriptors->Configure(descriptor_size, number_of_features);
124 | descriptors->Resize(number_of_features);
125 | if (current_descriptors_.isContinuous()) {
126 | memcpy(descriptors->descriptor(0), current_descriptors_.data,
127 | descriptor_size * number_of_features);
128 | } else {
129 | for (int i = 0; i < number_of_features; i++) {
130 | memcpy(descriptors->descriptor(i),
131 | ¤t_descriptors_.at(i, 0), descriptor_size);
132 | }
133 | }
134 | }
135 |
136 | void ORBFeatureTracker::Display() {
137 | static int iter = 0;
138 | cv::Mat image_matches;
139 | if (iter > 0) {
140 | cv::drawMatches(current_image_, current_feature_locations_, previous_image_,
141 | previous_feature_locations_, matches_, image_matches,
142 | cv::Scalar(255, 0, 0), cv::Scalar::all(-1),
143 | std::vector(), 0);
144 | cv::imshow("debug_image", image_matches);
145 | cv::waitKey(1);
146 | /* save images
147 | std::stringstream ss;
148 | ss << std::setfill('0') << std::setw(6) << iter;
149 | std::string img_name = "image_" + ss.str() + ".jpg";
150 | cv::imwrite(img_name, image_matches);
151 | */
152 | }
153 | iter++;
154 | current_image_.copyTo(previous_image_);
155 | }
156 |
--------------------------------------------------------------------------------
/src/ORBFeatureDetector.cc:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: ORBFeatureDetector.cc
6 | *
7 | * Created On: Thu 27 Jun 2019 03:26:58 PM CST
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #include "ORBFeatureDetector.h"
13 |
14 | #include
15 | #include
16 |
17 | DEFINE_double(scale_factor, 1.1f, "Scale increment");
18 | DEFINE_int32(number_of_levels, 8, "Number of scales.");
19 | DEFINE_int32(edge_dist, 5, "Distance to edge not considered.");
20 | DEFINE_int32(patch_size, 31, "Patch size.");
21 | DEFINE_bool(refine_corners, false, "Whether we want to refine corners");
22 |
23 | const static unsigned int kDescriptorLengthInBytes = 32;
24 |
25 | ORBFeatureDetector::ORBFeatureDetector()
26 | : width_(0),
27 | height_(0),
28 | max_number_of_features_(0),
29 | scale_factor_(0.),
30 | number_of_levels_(0),
31 | edge_dist_(0),
32 | patch_size_(0),
33 | refine_corners_(false),
34 | is_initialized_(false) {}
35 |
36 | void ORBFeatureDetector::Init(int width, int height,
37 | int max_number_of_features) {
38 | CHECK_GT(width, 0);
39 | CHECK_GT(height, 0);
40 | CHECK_GT(max_number_of_features, 0);
41 | Init(width, height, max_number_of_features, FLAGS_scale_factor,
42 | FLAGS_number_of_levels, FLAGS_edge_dist, FLAGS_patch_size,
43 | FLAGS_refine_corners);
44 | }
45 |
46 | void ORBFeatureDetector::Init(int width, int height, int max_number_of_features,
47 | float scale_factor, int number_of_levels,
48 | int edge_dist, int patch_size,
49 | bool refine_corners) {
50 | CHECK_GT(width, 0);
51 | CHECK_GT(height, 0);
52 | CHECK_GT(max_number_of_features, 0);
53 | CHECK_GT(scale_factor, 0);
54 | CHECK_GT(number_of_levels, 0);
55 | CHECK_GT(edge_dist, 0);
56 | CHECK_GT(patch_size, 0);
57 |
58 | width_ = width;
59 | height_ = height;
60 | max_number_of_features_ = max_number_of_features;
61 | scale_factor_ = scale_factor;
62 | number_of_levels_ = number_of_levels;
63 | edge_dist_ = edge_dist;
64 | patch_size_ = patch_size;
65 | refine_corners_ = refine_corners;
66 |
67 | image_.create(height_, width_, CV_8UC1);
68 | mask_ = cv::Mat(cv::Size(width_, height_), CV_8UC1, 0xFF);
69 | VLOG(3) << "ORB detector parameters :";
70 | VLOG(3) << " max_number_of_features: " << max_number_of_features_;
71 | VLOG(3) << " scale_factor: " << scale_factor_;
72 | VLOG(3) << " number_of_levels: " << number_of_levels_;
73 | VLOG(3) << " edge_dist: " << edge_dist_;
74 | detector_ =
75 | cv::ORB::create(max_number_of_features_, scale_factor_, number_of_levels_,
76 | edge_dist_, 0, 2, cv::ORB::HARRIS_SCORE, patch_size_);
77 | is_initialized_ = true;
78 | }
79 |
80 | ORBFeatureDetector::ORBFeatureDetector(const ORBFeatureDetector& d)
81 | : FeatureDetectorBase() {
82 | if (d.IsInitialized()) {
83 | Init(d.GetWidth(), d.GetHeight(), d.GetMaxNumberOfFeatures(),
84 | d.GetScaleFactor(), d.GetNumberOfLevels(), d.GetEdgeDist(),
85 | d.GetPatchSize(), d.GetRefineCorners());
86 | }
87 | }
88 |
89 | ORBFeatureDetector& ORBFeatureDetector::operator=(const ORBFeatureDetector& d) {
90 | if (this == &d) {
91 | return *this;
92 | }
93 | if (d.IsInitialized()) {
94 | Init(d.GetWidth(), d.GetHeight(), d.GetMaxNumberOfFeatures(),
95 | d.GetScaleFactor(), d.GetNumberOfLevels(), d.GetEdgeDist(),
96 | d.GetPatchSize(), d.GetRefineCorners());
97 | }
98 | return *this;
99 | }
100 |
101 | void ORBFeatureDetector::SetMask(const cv::Mat& mat) {
102 | mask_ = mat;
103 | }
104 |
105 | void ORBFeatureDetector::SetNumberOfLevels(int number_of_levels) {
106 | if (number_of_levels != number_of_levels_) {
107 | number_of_levels_ = number_of_levels;
108 | detector_ = cv::ORB::create(max_number_of_features_, scale_factor_,
109 | number_of_levels_, edge_dist_, 0, 2,
110 | cv::ORB::HARRIS_SCORE, patch_size_);
111 | }
112 | }
113 |
114 | void ORBFeatureDetector::Refine(const cv::Mat& image,
115 | std::vector* feature_locations) {
116 | std::vector locs;
117 | locs.resize(feature_locations->size());
118 | for (size_t i = 0; i < feature_locations->size(); i++) {
119 | locs[i] = (*feature_locations)[i].pt;
120 | }
121 | cv::TermCriteria criteria =
122 | cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 40, 0.001);
123 | cv::cornerSubPix(image, locs, cv::Size(5, 5), cv::Size(-1, -1), criteria);
124 | for (size_t i = 0; i < feature_locations->size(); i++) {
125 | (*feature_locations)[i].pt = locs[i];
126 | }
127 | }
128 |
129 | int ORBFeatureDetector::Detect(const cv::Mat& image,
130 | std::vector* feature_locations,
131 | cv::Mat* descriptors) {
132 | image.copyTo(image_);
133 |
134 | int number_of_features = -1;
135 |
136 | feature_locations->resize(0);
137 | detector_->detectAndCompute(image, mask_, *feature_locations, *descriptors);
138 | number_of_features = feature_locations->size();
139 | VLOG(3) << "number_of_features: " << number_of_features;
140 |
141 | if (refine_corners_) {
142 | Refine(image, feature_locations);
143 | }
144 | return number_of_features;
145 | }
146 |
147 | void ORBFeatureDetector::Detect(
148 | const cv::Mat& image, Eigen::Matrix2Xf* current_measurements,
149 | std::vector* current_feature_orientations,
150 | std::vector* current_feature_scales,
151 | FeatureDescriptoru* current_feature_descriptors) {
152 | std::vector feature_locations;
153 | cv::Mat descriptors;
154 | int number_of_features = Detect(image, &feature_locations, &descriptors);
155 |
156 | // Prepare output
157 | current_measurements->resize(2, number_of_features);
158 | for (int i = 0; i < number_of_features; ++i) {
159 | current_measurements->operator()(0, i) = feature_locations[i].pt.x;
160 | current_measurements->operator()(1, i) = feature_locations[i].pt.y;
161 | }
162 |
163 | current_feature_orientations->clear();
164 | current_feature_orientations->reserve(number_of_features);
165 | current_feature_scales->clear();
166 |
167 | std::vector all_scales;
168 | for (int i = 0; i < number_of_levels_; ++i) {
169 | all_scales.push_back(std::pow(scale_factor_, i));
170 | }
171 | current_feature_scales->resize(number_of_features, -1.);
172 | current_feature_descriptors->Configure(kDescriptorLengthInBytes,
173 | number_of_features);
174 | current_feature_descriptors->Resize(number_of_features);
175 |
176 | // Assign orientation and scale
177 | for (int i = 0; i < number_of_features; ++i) {
178 | (*current_feature_orientations)[i] = feature_locations[i].angle;
179 | (*current_feature_scales)[i] = all_scales[feature_locations[i].octave];
180 | }
181 |
182 | if (descriptors.isContinuous()) {
183 | // if continuous and row major
184 | memcpy(current_feature_descriptors->descriptor(0), descriptors.data,
185 | kDescriptorLengthInBytes * number_of_features);
186 | } else {
187 | for (int i = 0; i < number_of_features; ++i) {
188 | memcpy(current_feature_descriptors->descriptor(i),
189 | &descriptors.at(i, 0), kDescriptorLengthInBytes);
190 | }
191 | }
192 | // for (int i = 0; i < number_of_features; i++) {
193 | // cv::circle(image,
194 | // cv::Point(current_measurements->operator()(0, i),
195 | // current_measurements->operator()(1, i)),
196 | // 5, cv::Scalar(0, 255, 255));
197 | // }
198 | // cv::imshow("features", image);
199 | // cv::waitKey(10);
200 | }
201 |
--------------------------------------------------------------------------------
/src/SuperPointFeatureDetector.cc:
--------------------------------------------------------------------------------
1 | /*************************************************************************
2 | *
3 | * Author: b51
4 | * Mail: b51live@gmail.com
5 | * FileName: SuperPointFeatureDetector.cc
6 | *
7 | * Created On: Fri 27 Sep 2019 08:30:41 AM UTC
8 | * Licensed under The MIT License [see LICENSE for details]
9 | *
10 | ************************************************************************/
11 |
12 | #include "SuperPointFeatureDetector.h"
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | DEFINE_string(model, "/home/ubuntu/Models",
20 | "Path to pytorch model");
21 | DEFINE_double(conf_thresh, 0.015,
22 | "Detector confidence threshold (default: 0.015)");
23 | DEFINE_int32(H, 120, "Net input image height");
24 | DEFINE_int32(W, 160, "Net input image width");
25 | DEFINE_int32(nms_dist, 4, "Non Maximum Suppression (NMS) distance (default: 4).");
26 | DEFINE_int32(CUDA, 0, "Default GPU device");
27 |
28 | //Size of each output cell, Keep this fixed
29 | const int CELL = 8;
30 |
31 | SuperPointFeatureDetector::SuperPointFeatureDetector()
32 | : width_(0),
33 | height_(0),
34 | max_number_of_features_(0),
35 | conf_thresh_(0.),
36 | nms_dist_(0),
37 | is_initialized_(false),
38 | module_(nullptr) {}
39 |
40 | void SuperPointFeatureDetector::Init(int width, int height,
41 | int max_number_of_features) {
42 | CHECK_GT(width, 0);
43 | CHECK_GT(height, 0);
44 | CHECK_GT(max_number_of_features, 0);
45 | Init(width, height, max_number_of_features, FLAGS_model,
46 | FLAGS_conf_thresh, FLAGS_nms_dist);
47 | }
48 |
49 | void SuperPointFeatureDetector::Init(int width, int height,
50 | int max_number_of_features,
51 | std::string model_path, float conf_thresh,
52 | int nms_dist) {
53 | CHECK_GT(width, 0);
54 | CHECK_GT(height, 0);
55 | CHECK_GT(max_number_of_features, 0);
56 | width_ = width;
57 | height_ = height;
58 | max_number_of_features_ = max_number_of_features;
59 | model_path_ = model_path;
60 | conf_thresh_ = conf_thresh;
61 | nms_dist_ = nms_dist;
62 |
63 | image_.create(height_, width_, CV_8UC1);
64 |
65 | try {
66 | // TODO(b51): Make shared_ptr normal useable
67 | module_ = torch::jit::load(model_path.c_str());
68 | // Load module on tx2
69 | // module_ = std::make_shared(
70 | // torch::jit::load(model_path.c_str()));
71 | module_->to(torch::Device(torch::kCUDA, FLAGS_CUDA));
72 | } catch (const c10::Error& e) {
73 | LOG(FATAL) << "Error loading the model from " << model_path;
74 | }
75 | VLOG(3) << "SuperPoint detector parameters:";
76 | VLOG(3) << " max number of features: " << max_number_of_features_;
77 | VLOG(3) << " model loaded: " << model_path;
78 | is_initialized_ = true;
79 | }
80 |
81 | void SuperPointFeatureDetector::NMSFast(
82 | const Eigen::Matrix3Xf& pts, int width, int height, int dist_thresh,
83 | Eigen::Matrix3Xf& filtered_pts) {
84 | Eigen::MatrixXf sorted_descend_pts;
85 | sorted_descend_pts.resize(pts.rows(), pts.cols());
86 |
87 | // Sort with heatmap value in descending order
88 | std::vector vec;
89 | for (int i = 0; i < pts.cols(); i++)
90 | vec.push_back(pts.col(i));
91 | std::sort(vec.begin(), vec.end(),
92 | [](Eigen::Vector3f const& v1, Eigen::Vector3f const& v2) {
93 | return v1[2] > v2[2];
94 | });
95 | for (int i = 0; i < pts.cols(); i++)
96 | sorted_descend_pts.col(i) = vec[i];
97 |
98 | // Pad the border of the grid, so that we can NMS points near the border.
99 | int pad = dist_thresh;
100 | int paded_width = width + 2 * pad;
101 | int paded_height = height + 2 * pad;
102 | Eigen::MatrixXf grid = Eigen::MatrixXf::Zero(paded_width, paded_height);
103 | #pragma omp parallel for
104 | for (int i = 0; i < sorted_descend_pts.cols(); i++) {
105 | grid(std::round(sorted_descend_pts.col(i)[0] + pad),
106 | std::round(sorted_descend_pts.col(i)[1] + pad)) = 1;
107 | }
108 |
109 | // Iterate through points, highest to lowest conf, suppress neighborhood.
110 | int count = 0;
111 | int pad_2 = 2 * pad;
112 | filtered_pts = Eigen::Matrix3Xf::Zero(3, sorted_descend_pts.cols());
113 | for (int i = 0; i < sorted_descend_pts.cols(); i++) {
114 | // Account for top and left padding.
115 | int x = std::round(sorted_descend_pts.col(i)[0] + pad);
116 | int y = std::round(sorted_descend_pts.col(i)[1] + pad);
117 | if (grid(x, y) == 1) {
118 | // Block of size(p, q), starting at (i, j)
119 | // dynamic-size block expression: matrix.block(i, j, p, q)
120 | // https://eigen.tuxfamily.org/dox/group__TutorialBlockOperations.html
121 | grid.block(x - pad, y - pad, pad_2 + 1, pad_2 + 1) =
122 | Eigen::MatrixXf::Zero(pad_2 + 1, pad_2 + 1);
123 | grid(x, y) = -1;
124 | // Ignore points along border
125 | if ((x - pad) < pad or (x - pad >= width - pad) or
126 | (y - pad) < pad or (y - pad >= height - pad))
127 | continue;
128 | filtered_pts.col(count++) =
129 | Eigen::Vector3f(x - pad, y - pad, sorted_descend_pts.col(i)[2]);
130 | }
131 | }
132 | // resize the matrix to rows x cols while leaving old values untouched
133 | filtered_pts.conservativeResize(Eigen::NoChange, count);
134 | }
135 |
136 | void SuperPointFeatureDetector::Detect(const cv::Mat& image,
137 | Eigen::Matrix3Xf& filtered_pts,
138 | torch::Tensor& descriptors) {
139 | int H = image.rows;
140 | int W = image.cols;
141 | // convert image to float in range [0.0, 1.0]
142 | std::vector inputs;
143 |
144 | // convert cv mat to tensor and transport to cuda
145 | at::Tensor image_tensor =
146 | torch::from_blob(image.data, {1, 1, H, W}, at::kFloat);
147 | inputs.emplace_back(image_tensor.to(torch::Device(torch::kCUDA, FLAGS_CUDA)));
148 |
149 | // inference
150 | auto outputs = module_->forward(inputs).toTuple();
151 | torch::Tensor semi = outputs->elements()[0].toTensor();
152 | torch::Tensor coarse_desc = outputs->elements()[1].toTensor();
153 |
154 | // Deal with feature points locations
155 | semi = semi.squeeze();
156 | torch::Tensor dense = semi.exp();
157 | dense = dense / (dense.sum(0) + 0.00001);
158 | torch::Tensor nodust = dense.slice(0, 0, dense.size(0) - 1);
159 | int Hc = H / CELL;
160 | int Wc = W / CELL;
161 | nodust = nodust.transpose(0, 1).transpose(1, 2);
162 | torch::Tensor heatmap = nodust.reshape({Hc, Wc, CELL, CELL});
163 | heatmap = heatmap.transpose(1, 2);
164 | heatmap = heatmap.reshape({Hc * CELL, Wc * CELL});
165 | torch::Tensor heatmap_cpu = heatmap.to(torch::kCPU, /*non_blocking=*/true);
166 |
167 | // Eigen::Map eig_heatmap(heatmap_cpu.data(),
168 | // heatmap.size(0), heatmap.size(1));
169 | cv::Mat mat_heatmap(heatmap_cpu.size(0), heatmap_cpu.size(1), CV_32F,
170 | heatmap_cpu.data());
171 | /**
172 | * Filtered to binary mat first and than get non zero value coordinates
173 | * cv::threshold(src, dst, threshold, max_binary_value, threshold_type)
174 | * threshold_type: 0, Binary
175 | * 1, Binary Inverted
176 | * 2, Threshold Truncated
177 | * 3, Threshold to Zero
178 | * 4, Threshold to Zero Inverted
179 | */
180 | cv::Mat bin_mat;
181 | cv::threshold(mat_heatmap, bin_mat, conf_thresh_, 1, 0);
182 | bin_mat.convertTo(bin_mat, CV_8UC1);
183 | cv::Mat coordinates;
184 | cv::findNonZero(bin_mat, coordinates);
185 |
186 | Eigen::Matrix3Xf pts;
187 | pts.resize(3, coordinates.total());
188 | #pragma omp parallel for
189 | for (size_t i = 0; i < coordinates.total(); i++) {
190 | pts.block<3, 1>(0, i) = Eigen::Vector3f(
191 | coordinates.at(i).x, coordinates.at(i).y,
192 | mat_heatmap.at(coordinates.at(i)));
193 | }
194 | // nms fast
195 | NMSFast(pts, W, H, nms_dist_, filtered_pts);
196 |
197 | VLOG(4) << "filtered points size: " << filtered_pts.cols();
198 | int number_of_features = filtered_pts.cols();
199 |
200 | // Process descriptor
201 | // Interpolate into descriptor map using 2D point locations.
202 | cv::Mat mat_measurements(2, number_of_features, CV_32F);
203 | for (int i = 0; i < number_of_features; i++) {
204 | // grid_sampler needs grid range between [-1., 1.]
205 | mat_measurements.at(0, i) =
206 | (filtered_pts.col(i)[0] / (float(W) / 2.)) - 1.;
207 | mat_measurements.at(1, i) =
208 | (filtered_pts.col(i)[1] / (float(H) / 2.)) - 1.;
209 | /* For debug display
210 | Eigen::Vector3f p = filtered_pts.col(i);
211 | LOG(INFO) << "x: " << p[0] << " y: " << p[1] << " value: " << p[2];
212 | cv::circle(image, cv::Point(p[0], p[1]), 2, cv::Scalar(0, 255, 255));
213 |
214 | cv::imwrite("key_points.jpg", image);
215 | exit(0);
216 | */
217 | }
218 | torch::Tensor sample_pts =
219 | torch::from_blob(mat_measurements.data, {2, number_of_features},
220 | at::kFloat)
221 | .to(torch::Device(torch::kCUDA, FLAGS_CUDA));
222 | sample_pts = sample_pts.transpose(0, 1);
223 | sample_pts = sample_pts.view({1, 1, -1, 2});
224 |
225 | /**
226 | * grid_sampler(at::Tensor input, at::Tensor grid, interpolation_mode,
227 | * padding_mode)
228 | * grid: range needs to be [-1., 1]
229 | * interpolation_mode:
230 | * at::native::detail
231 | * enum class GridSamplerInterpolation {Bilinear, Nearest};
232 | * padding_mode
233 | * at::native::detail
234 | * enum class GridSamplerPadding {Zeros, Border, Reflection}
235 | */
236 | descriptors = at::grid_sampler(
237 | coarse_desc, sample_pts.to(torch::Device(torch::kCUDA, FLAGS_CUDA)), 0,
238 | 0);
239 | descriptors = descriptors.reshape({coarse_desc.size(1), -1});
240 | descriptors = descriptors / (descriptors.norm(c10::nullopt, 0).view({1, -1}));
241 | descriptors = descriptors.to(torch::kCPU, true);
242 | }
243 |
244 | void SuperPointFeatureDetector::Detect(
245 | const cv::Mat& image, Eigen::Matrix2Xf* current_measurements,
246 | FeatureDescriptorf* current_feature_descriptors) {
247 | image.copyTo(image_);
248 | cv::Mat input_image;
249 | double H_scale = 1.;
250 | double W_scale = 1.;
251 | // resize to net input
252 |
253 | cv::resize(image, input_image, cv::Size(FLAGS_W, FLAGS_H), 0, 0,
254 | cv::INTER_AREA);
255 | H_scale = image.rows / FLAGS_H;
256 | W_scale = image.cols / FLAGS_W;
257 |
258 | // Superpoint net only accept gray image
259 | if (input_image.channels() > 1) {
260 | cv::cvtColor(input_image, input_image, CV_BGR2GRAY);
261 | }
262 | // normalize to [0.0, 1.0]
263 | cv::Mat normed_image;
264 | input_image.convertTo(normed_image, CV_32F, 1.0 / 255, 0);
265 |
266 | Eigen::Matrix3Xf filtered_pts;
267 | torch::Tensor descriptors;
268 | Detect(normed_image, filtered_pts, descriptors);
269 | // LOG(INFO) << " descriptor 0: " << descriptors.slice(1, 0, 1);
270 |
271 | int number_of_features = filtered_pts.cols();
272 | int descriptor_size = descriptors.size(0);
273 | current_measurements->resize(Eigen::NoChange, number_of_features);
274 | current_measurements->block(0, 0, 2, number_of_features) =
275 | filtered_pts.block(0, 0, 2, number_of_features);
276 |
277 | // restor to original size
278 | current_measurements->row(0) = current_measurements->row(0) * W_scale;
279 | current_measurements->row(1) = current_measurements->row(1) * H_scale;
280 |
281 | CHECK_EQ(number_of_features, descriptors.size(1));
282 |
283 | cv::Mat mat_descriptors(descriptors.size(0), descriptors.size(1), CV_32F,
284 | descriptors.data());
285 | // Make mat_descriptors width = descriptor_size (256 here)
286 | // height = number_features
287 | mat_descriptors = mat_descriptors.t();
288 |
289 | current_feature_descriptors->Configure(descriptor_size,
290 | number_of_features);
291 | current_feature_descriptors->Resize(number_of_features);
292 | if (mat_descriptors.isContinuous()) {
293 | // if continuous
294 | memcpy(current_feature_descriptors->descriptor(0), mat_descriptors.data,
295 | descriptor_size * number_of_features * sizeof(float));
296 | } else {
297 | for (int i = 0; i < number_of_features; ++i) {
298 | memcpy(current_feature_descriptors->descriptor(i),
299 | &mat_descriptors.at(i, 0), descriptor_size * sizeof(float));
300 | }
301 | }
302 | // LOG(INFO) << current_feature_descriptors->Size();
303 | // LOG(INFO) << current_feature_descriptors->DescriptorSize();
304 | // Eigen::Map > for_display(
305 | // current_feature_descriptors->descriptor(0), 1, 256);
306 | // LOG(INFO) << for_display;
307 | }
308 |
--------------------------------------------------------------------------------