├── .gitignore ├── package.xml ├── include └── libhaloc │ ├── state.h │ ├── publisher.h │ └── hash.h ├── LICENSE.md ├── CMakeLists.txt ├── src ├── publisher.cpp └── hash.cpp ├── README.md └── examples └── lip6indoor_dataset.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | launch/evaluation.launch 3 | *.jpg 4 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | libhaloc 4 | 0.0.1 5 | 6 | Hash-Based Loop Closing 7 | 8 | Pep Lluis Negre 9 | Pep Lluis Negre 10 | BSD 11 | https://github.com/srv/haloc 12 | https://github.com/srv/haloc/issues 13 | 14 | catkin 15 | 16 | roscpp 17 | roslib 18 | tf 19 | cv_bridge 20 | image_geometry 21 | std_msgs 22 | cmake_modules 23 | 24 | roscpp 25 | roslib 26 | tf 27 | cv_bridge 28 | image_geometry 29 | std_msgs 30 | 31 | 32 | -------------------------------------------------------------------------------- /include/libhaloc/state.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Universitat de les Illes Balears 2 | // This file is part of LIBHALOC. 3 | // 4 | // LIBHALOC is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // LIBHALOC is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with LIBHALOC. If not, see . 16 | 17 | #ifndef LIBHALOC_INCLUDE_LIBHALOC_STATE_H_ 18 | #define LIBHALOC_INCLUDE_LIBHALOC_STATE_H_ 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | namespace haloc { 29 | 30 | /** 31 | * @brief Struct to save operational variables after every hash 32 | * computation. 33 | */ 34 | struct State { 35 | /** 36 | * @brief Default constructor. 37 | */ 38 | State(); 39 | 40 | void Clear() { 41 | bucketed_kp.clear(); 42 | unbucketed_kp.clear(); 43 | num_kp_per_bucket.clear(); 44 | } 45 | 46 | // State variables 47 | std::vector bucketed_kp; //!> Stores the bucketed keypoints 48 | std::vector unbucketed_kp; //!> Stores the discarded keypoints when bucketing 49 | std::vector num_kp_per_bucket; //!> The number of keypoints per bucket 50 | }; 51 | 52 | } // namespace haloc 53 | 54 | #endif // LIBHALOC_INCLUDE_LIBHALOC_STATE_H_ 55 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014, 2 | Systems, Robotics and Vision Group (srv.uib.es) 3 | University of the Balearican Islands 4 | All rights reserved. 5 | 6 | BSD License 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | * All advertising materials mentioning features or use of this software 16 | must display the following acknowledgement: 17 | This product includes software developed by 18 | Systems, Robotics and Vision Group, Univ. of the Balearican Islands 19 | * Neither the name of Systems, Robotics and Vision Group, University of 20 | the Balearican Islands nor the names of its contributors may be used 21 | to endorse or promote products derived from this software without 22 | specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 28 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 33 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(libhaloc) 3 | 4 | # C++11 support 5 | include(CheckCXXCompilerFlag) 6 | CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) 7 | CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) 8 | if(COMPILER_SUPPORTS_CXX11) 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 10 | elseif(COMPILER_SUPPORTS_CXX0X) 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 12 | else() 13 | message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") 14 | endif() 15 | 16 | find_package(catkin REQUIRED COMPONENTS 17 | roscpp 18 | roslib 19 | tf 20 | cv_bridge 21 | image_geometry 22 | std_msgs 23 | cmake_modules) 24 | 25 | # Dependencies - Eigen: 26 | find_package(Eigen3 REQUIRED) 27 | include_directories(${EIGEN3_INCLUDE_DIRS}) 28 | 29 | # Dependencies - OpenCV: 30 | find_package(OpenCV REQUIRED) 31 | if (${OpenCV_VERSION} MATCHES "3.3.1") 32 | foreach(__cvcomponent ${OpenCV_LIB_COMPONENTS}) 33 | set (__original_cvcomponent ${__cvcomponent}) 34 | if(NOT __cvcomponent MATCHES "^opencv_") 35 | set(__cvcomponent opencv_${__cvcomponent}) 36 | endif() 37 | if (TARGET ${__cvcomponent}) 38 | set_target_properties(${__cvcomponent} PROPERTIES 39 | MAP_IMPORTED_CONFIG_DEBUG "" 40 | MAP_IMPORTED_CONFIG_RELEASE "" 41 | MAP_IMPORTED_CONFIG_RELWITHDEBINFO "" 42 | MAP_IMPORTED_CONFIG_MINSIZEREL "" 43 | ) 44 | endif() 45 | endforeach(__cvcomponent) 46 | endif() 47 | include_directories(${OpenCV_INCLUDE_DIRS}) 48 | link_directories(${OpenCV_LIBRARY_DIRS}) 49 | 50 | catkin_package(INCLUDE_DIRS include LIBRARIES haloc) 51 | 52 | # Add the Image Hashing library 53 | add_library(haloc 54 | src/hash.cpp 55 | src/publisher.cpp) 56 | target_link_libraries(haloc 57 | ${Boost_LIBRARIES} 58 | ${EIGEN3_LIBRARIES} 59 | ${OpenCV_LIBRARIES} 60 | ${catkin_LIBRARIES}) 61 | 62 | # Include directories 63 | include_directories(${catkin_INCLUDE_DIRS} include) 64 | 65 | # Add examples 66 | add_executable(lip6indoor_dataset 67 | examples/lip6indoor_dataset.cpp) 68 | target_link_libraries(lip6indoor_dataset 69 | ${catkin_LIBRARIES} 70 | ${OpenCV_LIBRARIES} 71 | haloc) 72 | 73 | -------------------------------------------------------------------------------- /include/libhaloc/publisher.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Universitat de les Illes Balears 2 | // This file is part of LIBHALOC. 3 | // 4 | // LIBHALOC is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // LIBHALOC is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with LIBHALOC. If not, see . 16 | 17 | #ifndef LIBHALOC_INCLUDE_LIBHALOC_PUBLISHER_H_ 18 | #define LIBHALOC_INCLUDE_LIBHALOC_PUBLISHER_H_ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "libhaloc/state.h" 28 | 29 | #include 30 | #include 31 | 32 | namespace haloc { 33 | 34 | class Publisher { 35 | public: 36 | /** 37 | * @brief Empty class constructor. 38 | */ 39 | Publisher(); 40 | 41 | /** 42 | * @brief Publishes the bucketed image. 43 | * 44 | * @param[in] state The state obtained after a hash computation. 45 | * @param[in] img The original image. 46 | * @param[in] bucket_rows The bucket rows 47 | * @param[in] bucket_cols The bucket cols 48 | */ 49 | void PublishBucketedImage(const State& state, const cv::Mat& img, 50 | const int& bucket_rows, const int& bucket_cols); 51 | 52 | /** 53 | * @brief Publishes the bucketed info 54 | * 55 | * @param[in] state The state obtained after a hash computation. 56 | * @param[in] max_feat The maximum number of features per bucket 57 | */ 58 | void PublishBucketedInfo(const State& state, const int& max_feat); 59 | 60 | protected: 61 | /** 62 | * @brief Returns a debug image with the bucketed keypoints. 63 | * 64 | * @param[in] state The state obtained after a hash computation. 65 | * @param[in] img The original image. 66 | * @param[in] bucket_rows The bucket rows 67 | * @param[in] bucket_cols The bucket cols 68 | * 69 | * @return The bucketed image. 70 | */ 71 | cv::Mat BuildBucketedImage(const State& state, const cv::Mat& img, 72 | const int& bucket_rows, const int& bucket_cols); 73 | 74 | private: 75 | 76 | // The ROS publishers 77 | ros::Publisher pub_bucketed_img_; 78 | ros::Publisher pub_bucketed_info_; 79 | 80 | }; 81 | 82 | } // namespace haloc 83 | 84 | #endif // LIBHALOC_INCLUDE_LIBHALOC_PUBLISHER_H_ 85 | -------------------------------------------------------------------------------- /src/publisher.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Universitat de les Illes Balears 2 | // This file is part of LIBHALOC. 3 | // 4 | // LIBHALOC is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // LIBHALOC is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with LIBHALOC. If not, see . 16 | 17 | #include 18 | 19 | #include "libhaloc/publisher.h" 20 | 21 | haloc::State::State() {} 22 | 23 | haloc::Publisher::Publisher() { 24 | ros::NodeHandle nhp("~"); 25 | pub_bucketed_img_ = nhp.advertise("bucketed_image", 2, true); 26 | pub_bucketed_info_ = nhp.advertise("bucketed_info", 2, true); 27 | } 28 | 29 | void haloc::Publisher::PublishBucketedImage(const State& state, 30 | const cv::Mat& img, const int& bucket_rows, const int& bucket_cols) { 31 | cv::Mat bucketed_img = BuildBucketedImage(state, img, bucket_rows, 32 | bucket_cols); 33 | cv_bridge::CvImage ros_image; 34 | ros_image.image = bucketed_img.clone(); 35 | ros_image.header.stamp = ros::Time::now(); 36 | ros_image.encoding = "bgr8"; 37 | pub_bucketed_img_.publish(ros_image.toImageMsg()); 38 | } 39 | 40 | void haloc::Publisher::PublishBucketedInfo(const State& state, 41 | const int& max_feat) { 42 | std::stringstream info; 43 | info << std::endl; 44 | for (uint i=0; i < state.num_kp_per_bucket.size(); ++i) { 45 | info << "Bucket " << i+1 << ": " << state.num_kp_per_bucket[i] << "/" << 46 | max_feat << std::endl; 47 | } 48 | std_msgs::String msg; 49 | msg.data = info.str(); 50 | pub_bucketed_info_.publish(msg); 51 | } 52 | 53 | cv::Mat haloc::Publisher::BuildBucketedImage(const State& state, 54 | const cv::Mat& img, const int& bucket_rows, const int& bucket_cols) { 55 | cv::Mat out_img = img; 56 | 57 | float bucket_width = img.cols / bucket_cols; 58 | float bucket_height = img.rows / bucket_rows; 59 | 60 | // Draw the bucket lines 61 | int h_point = static_cast(bucket_width); 62 | while (h_point < img.cols) { 63 | cv::line(out_img, cv::Point(h_point, 0), cv::Point(h_point, img.rows), cv::Scalar(47, 47, 47), 2, 8); 64 | h_point += static_cast(bucket_width); 65 | } 66 | int v_point = static_cast(bucket_height); 67 | while (v_point < img.rows) { 68 | cv::line(out_img, cv::Point(0, v_point), cv::Point(img.cols, v_point), cv::Scalar(47, 47, 47), 2, 8); 69 | v_point += static_cast(bucket_height); 70 | } 71 | 72 | // Draw bucket and unbucket keypoints 73 | cv::drawKeypoints(out_img, state.bucketed_kp, out_img, cv::Scalar(0, 255, 0), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); 74 | cv::drawKeypoints(out_img, state.unbucketed_kp, out_img, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); 75 | 76 | return out_img; 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | libhaloc 2 | ============= 3 | 4 | ROS library for HAsh-based LOop Closure detection. This library provides the tools for loop closing detection based on image hashing. Image hashing consists of representing every image with a small vector (hash). Then the hash of image A can be compared with the hash of image B in a super fast way in order to determine if images are similar. 5 | 6 | The image hashing implemented in this library is based on SIFT features so, since SIFT is invariant to scale and rotation, the image hashing will be also invariant to these properties. 7 | 8 | The library works for both mono and stereo cameras and provides a transformation (2d for mono and 3d for stereo) when loop closures are found. 9 | 10 | ## Related paper 11 | 12 | [Autonomous Robots][paper] 13 | 14 | CITATION: 15 | ```bash 16 | @Article{Negre Carrasco2015, 17 | author="Negre Carrasco, Pep Lluis 18 | and Bonin-Font, Francisco 19 | and Oliver-Codina, Gabriel", 20 | title="Global image signature for visual loop-closure detection", 21 | journal="Autonomous Robots", 22 | year="2015", 23 | pages="1--15", 24 | issn="1573-7527", 25 | doi="10.1007/s10514-015-9522-4", 26 | url="http://dx.doi.org/10.1007/s10514-015-9522-4" 27 | } 28 | ``` 29 | 30 | ## How to prepare your SLAM node 31 | 32 | 33 | Modify your CMakeLists.txt as follows: 34 | 35 | ```bash 36 | # Add this line before catkin_package() 37 | find_package(libhaloc REQUIRED) 38 | 39 | # Include the libhaloc directories 40 | include_directories( 41 | ${catkin_INCLUDE_DIRS} 42 | ${libhaloc_INCLUDE_DIRS} 43 | ... 44 | ) 45 | 46 | # Link your executable with libhaloc 47 | target_link_libraries(your_executable_here 48 | ${catkin_LIBRARIES} 49 | ${libhaloc_LIBRARIES} 50 | ... 51 | ) 52 | ``` 53 | 54 | Include the header in your .cpp file: 55 | ```bash 56 | #include 57 | ``` 58 | 59 | ## How to call the library 60 | 61 | 62 | You can use libhaloc in two different ways: 63 | - To generate the image hashes and then, use your own techniques to compare this hashes and retrieve the best candidates to close loop. 64 | - To use the full power of libhaloc to search loop closing candidates and compute the homogeneous transformation (if any). 65 | 66 | ### To use libhaloc as the first option: 67 | 68 | * Declare your haloc object: 69 | ```bash 70 | haloc::Hash haloc_; 71 | ``` 72 | 73 | * Init your haloc object (only once): 74 | ```bash 75 | if (!hash_.isInitialized()) 76 | hash_.init(c_cluster_.getSift()); 77 | ``` 78 | 79 | * Then, for every new image you process, extract its descriptor matrix (SIFT) 80 | ```bash 81 | cv::Mat sift; 82 | // Use opencv to extract the SIFT matrix and then: 83 | vector current_hash = hash_.getHash(sift); 84 | ``` 85 | 86 | * Now, you can store all the hashes into a table and compare it: 87 | ```bash 88 | std::vector matches; 89 | for (uint i=0; i12). 151 | * `min_inliers` - Minimum number of inliers to consider a matching as possible loop closure (>12). 152 | 153 | 154 | [stereo_slam]: https://github.com/srv/stereo_slam 155 | [paper]: http://link.springer.com/article/10.1007/s10514-015-9522-4 156 | -------------------------------------------------------------------------------- /examples/lip6indoor_dataset.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Universitat de les Illes Balears 2 | // This file is part of LIBHALOC. 3 | // 4 | // LIBHALOC is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // LIBHALOC is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with LIBHALOC. If not, see . 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | 23 | #include "libhaloc/hash.h" 24 | 25 | namespace fs = boost::filesystem; 26 | 27 | /** 28 | * @brief Shows the program usage. 29 | * 30 | * @param[in] name The program name 31 | */ 32 | static void ShowUsage(const std::string& name) { 33 | std::cerr << "Usage: " << name << " image_directory" << std::endl; 34 | } 35 | 36 | /** 37 | * @brief Main entry point 38 | * 39 | * @param[in] argc The argc 40 | * @param argv The argv 41 | * 42 | * @return 0 43 | */ 44 | int main(int argc, char** argv) { 45 | // Parse arguments 46 | std::string img_dir = ""; 47 | if (argc != 1 && argc != 2) { 48 | ShowUsage(argv[0]); 49 | return 0; 50 | } 51 | if (argc == 2) { 52 | img_dir = argv[1]; 53 | } else { 54 | ShowUsage(argv[0]); 55 | return 0; 56 | } 57 | 58 | // Init ROS 59 | ros::init(argc, argv, "lip6indoor_dataset"); 60 | 61 | // Init feature extractor 62 | cv::Ptr feat(new cv::Feature2D()); 63 | feat = cv::KAZE::create(); 64 | 65 | // Hash object 66 | haloc::Hash haloc; 67 | 68 | // Set params 69 | haloc::Hash::Params params; 70 | params.max_desc = 100; 71 | haloc.SetParams(params); 72 | 73 | // Sort directory of images 74 | typedef std::vector vec; 75 | vec v; 76 | copy(fs::directory_iterator(img_dir), fs::directory_iterator(), 77 | back_inserter(v)); 78 | sort(v.begin(), v.end()); 79 | vec::const_iterator it(v.begin()); 80 | 81 | ROS_INFO_STREAM("Processing directory: " << img_dir << " with " << 82 | v.size() << " images."); 83 | 84 | // Operational variables 85 | int img_idx = 0; 86 | int discard_window = 10; 87 | std::map > hash_table; 88 | 89 | // Loop over the images 90 | while (it != v.end()) { 91 | // Check if the directory entry is an directory. 92 | if (fs::is_directory(*it)) { 93 | it++; 94 | continue; 95 | } 96 | 97 | // Open the image 98 | std::string filename = it->filename().string(); 99 | std::string path = img_dir + "/" + filename; 100 | cv::Mat img = cv::imread(path, CV_LOAD_IMAGE_COLOR); 101 | ROS_INFO_STREAM("Reading image " << filename); 102 | 103 | // Extract keypoints and descriptors 104 | cv::Mat desc; 105 | std::vector kp; 106 | feat->detectAndCompute(img, cv::noArray(), kp, desc); 107 | ROS_INFO_STREAM("Number of features: " << kp.size()); 108 | 109 | // Compute the hash 110 | std::vector hash = haloc.GetHash(kp, desc, img.size()); 111 | hash_table.insert(std::pair >(img_idx, hash)); 112 | 113 | // Log 114 | haloc.PublishState(img); 115 | haloc::State state = haloc.GetState(); 116 | ROS_INFO_STREAM("Number of features after bucketing: " << 117 | state.bucketed_kp.size()); 118 | 119 | img_idx++; 120 | it++; 121 | std::cout << std::endl; 122 | } 123 | 124 | // Find loop closings 125 | ROS_INFO("Generating the output matrix..."); 126 | for (float eps=0.6; eps <= 1.0; eps=eps+0.02) { 127 | std::stringstream hist_file, output_file; 128 | hist_file << "/tmp/histogram" << eps << ".csv"; 129 | output_file << "/tmp/output" << eps << ".csv"; 130 | std::fstream hist(hist_file.str().c_str(), std::ios::out); 131 | std::fstream output(output_file.str().c_str(), std::ios::out); 132 | hist << std::fixed << std::setprecision(10); 133 | output << std::fixed << std::setprecision(10); 134 | 135 | ROS_INFO_STREAM("Processing eps: " << eps); 136 | 137 | int great_3 = 0; 138 | int great_4 = 0; 139 | int great_5 = 0; 140 | int great_6 = 0; 141 | 142 | for (uint i=0; i < hash_table.size(); ++i) { 143 | for (uint j=0; j < hash_table.size(); ++j) { 144 | int dist_original = 0; 145 | int dist = 0; 146 | int neighbourhood = abs(i-j); 147 | if (neighbourhood > 20 && j < i) { 148 | dist_original = haloc.CalcDist(hash_table[i], hash_table[j], eps); 149 | if (dist_original > 3) great_3++; 150 | if (dist_original > 4) great_4++; 151 | if (dist_original > 5) great_5++; 152 | if (dist_original > 6) great_6++; 153 | 154 | if (dist_original < 4) { 155 | dist = 0; 156 | } else { 157 | dist = 1; 158 | } 159 | } 160 | 161 | // Log 162 | hist << dist_original << ", "; 163 | 164 | // Log 165 | if (j != hash_table.size()-1) { 166 | output << dist << ", "; 167 | } else { 168 | output << dist << std::endl; 169 | } 170 | } 171 | } 172 | hist.close(); 173 | output.close(); 174 | 175 | ROS_INFO_STREAM(" >3: " << great_3); 176 | ROS_INFO_STREAM(" >4: " << great_4); 177 | ROS_INFO_STREAM(" >5: " << great_5); 178 | ROS_INFO_STREAM(" >6: " << great_6); 179 | 180 | } 181 | 182 | ROS_INFO_STREAM("Finished!"); 183 | 184 | ros::shutdown(); 185 | return 0; 186 | } 187 | -------------------------------------------------------------------------------- /include/libhaloc/hash.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Universitat de les Illes Balears 2 | // This file is part of LIBHALOC. 3 | // 4 | // LIBHALOC is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // LIBHALOC is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with LIBHALOC. If not, see . 16 | 17 | #ifndef LIBHALOC_INCLUDE_LIBHALOC_HASH_H_ 18 | #define LIBHALOC_INCLUDE_LIBHALOC_HASH_H_ 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "libhaloc/publisher.h" 28 | 29 | #include 30 | #include 31 | 32 | namespace haloc { 33 | 34 | class Hash { 35 | public: 36 | /** 37 | * @brief Struct for class parameters 38 | */ 39 | struct Params { 40 | /** 41 | * @brief Default constructor 42 | */ 43 | Params(); 44 | 45 | // Class parameters 46 | int bucket_rows; //!> Number of horizontal divisions for the descriptors bucketing 47 | int bucket_cols; //!> Number of vertical divisions for the descriptors bucketing 48 | int max_desc; //!> Maximum number of descriptors per image 49 | int num_proj; //!> Number of projections required 50 | 51 | // Default values 52 | static const int DEFAULT_BUCKET_ROWS = 3; 53 | static const int DEFAULT_BUCKET_COLS = 4; 54 | static const int DEFAULT_MAX_DESC = 100; 55 | static const int DEFAULT_NUM_PROJ = 2; 56 | }; 57 | 58 | /** 59 | * @brief Empty class constructor. 60 | */ 61 | Hash(); 62 | 63 | /** 64 | * @brief Sets the parameters. 65 | * 66 | * @param[in] params The parameters. 67 | */ 68 | inline void SetParams(const Params& params) { 69 | params_ = params; initialized_ = false;} 70 | 71 | /** 72 | * @brief Returns the parameters. 73 | * 74 | * @return The parameters. 75 | */ 76 | inline Params GetParams() const {return params_;} 77 | 78 | /** 79 | * @brief Determines if class is initialized. 80 | * 81 | * @return True if initialized, False otherwise. 82 | */ 83 | inline bool IsInitialized() const {return initialized_;} 84 | 85 | /** 86 | * @brief Gets the state. 87 | * 88 | * @return The state. 89 | */ 90 | inline State GetState() const {return state_;} 91 | 92 | /** 93 | * @brief Bucket the features and compute a hash for every bucket. 94 | * 95 | * @param[in] kp The keypoints vector. 96 | * @param[in] desc The descriptors. 97 | * @param[in] img_size The image size. 98 | * 99 | * @return The bucketed hash. 100 | */ 101 | std::vector GetHash(const std::vector& kp, 102 | const cv::Mat& desc, const cv::Size& img_size); 103 | 104 | /** 105 | * @brief Compute the distance between 2 hashes. 106 | * 107 | * @param[in] hash_1 The hash 1. 108 | * @param[in] hash_2 The hash 2. 109 | * 110 | * @return Distance: the number of buckets seeing the same view. 111 | */ 112 | int CalcDist(const std::vector& hash_1, 113 | const std::vector& hash_2, float eps); 114 | 115 | /** 116 | * @brief Publishes the state and debug variables. Must be called after a 117 | * hash computation. 118 | * 119 | * @param[in] img The original image 120 | */ 121 | void PublishState(const cv::Mat& img); 122 | 123 | protected: 124 | /** 125 | * @brief Init the class. 126 | * 127 | * @param[in] img_size The image size. 128 | * @param[in] num_feat The number of features for the input image. 129 | * @param[in] desc_length The descriptor length 130 | */ 131 | void Init(const cv::Size& img_size, const int& num_feat, 132 | const int& desc_length); 133 | 134 | /** 135 | * @brief Compute the combinations required for the match calculation 136 | */ 137 | void InitCombinations(); 138 | 139 | /** 140 | * @brief Initializes the random vectors for projections. 141 | * 142 | * @param[in] size The size. 143 | */ 144 | void InitProjections(const int& size); 145 | 146 | /** 147 | * @brief Calculates a random vector. 148 | * 149 | * @param[in] size The size. 150 | * @param[in] seed The seed. 151 | * 152 | * @return The random vector. 153 | */ 154 | std::vector ComputeRandomVector(const int& size, int seed); 155 | 156 | /** 157 | * @brief Makes a vector unitary. 158 | * 159 | * @param[in] x The input vector. 160 | * 161 | * @return The output unit vector. 162 | */ 163 | std::vector UnitVector(const std::vector& x); 164 | 165 | /** 166 | * @brief Bucket the descriptors. 167 | * 168 | * @param[in] kp The keypoint vector. 169 | * @param[in] desc The descriptors. 170 | * 171 | * @return The bucketed descriptors. 172 | */ 173 | std::vector BucketDescriptors(const std::vector& kp, 174 | const cv::Mat& desc); 175 | 176 | /** 177 | * @brief Compute the hash by projecting the descriptors. 178 | * 179 | * @param[in] desc The descriptors. 180 | * 181 | * @return The hash. 182 | */ 183 | std::vector ProjectDescriptors(const cv::Mat& desc); 184 | 185 | private: 186 | // Properties 187 | Params params_; //!> Stores parameters 188 | State state_; //!> Stores the state after every hash computation 189 | cv::Size img_size_; //!> Image size (only needed for bucketing) 190 | int desc_length_; //!> The length of the descriptors used 191 | std::vector< std::vector > r_; //!> Vector of random values 192 | bool initialized_; //!> True when class has been initialized 193 | std::vector< std::vector< std::pair > > comb_; //!> Combinations for the match 194 | Publisher pub_; //!> The publisher for debugging purposes 195 | }; 196 | 197 | } // namespace haloc 198 | 199 | #endif // LIBHALOC_INCLUDE_LIBHALOC_HASH_H_ 200 | -------------------------------------------------------------------------------- /src/hash.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Universitat de les Illes Balears 2 | // This file is part of LIBHALOC. 3 | // 4 | // LIBHALOC is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // LIBHALOC is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with LIBHALOC. If not, see . 16 | 17 | #include 18 | 19 | #include "libhaloc/hash.h" 20 | 21 | #include 22 | 23 | haloc::Hash::Params::Params() : 24 | bucket_rows(DEFAULT_BUCKET_ROWS), bucket_cols(DEFAULT_BUCKET_COLS), 25 | max_desc(DEFAULT_MAX_DESC), num_proj(DEFAULT_NUM_PROJ) 26 | {} 27 | 28 | haloc::Hash::Hash() : initialized_(false) {} 29 | 30 | std::vector haloc::Hash::GetHash( 31 | const std::vector& kp, const cv::Mat& desc, 32 | const cv::Size& img_size) { 33 | // Initialize first time 34 | if (!IsInitialized()) Init(img_size, kp.size(), desc.cols); 35 | state_.Clear(); 36 | 37 | // Initialize output 38 | std::vector hash; 39 | 40 | // The maximum number of features per bucket 41 | int max_features_x_bucket = static_cast( 42 | floor(params_.max_desc/(params_.bucket_cols*params_.bucket_rows))); 43 | 44 | // Bucket descriptors 45 | std::vector bucket_desc = BucketDescriptors(kp, desc); 46 | 47 | // Get a hash for every bucket 48 | const int min_feat = static_cast(0.7 * max_features_x_bucket); 49 | for (uint i=0; i < bucket_desc.size(); ++i) { 50 | std::vector bucketed_hash; 51 | if (bucket_desc[i].rows >= min_feat) { 52 | bucketed_hash = ProjectDescriptors(bucket_desc[i]); 53 | } else { 54 | for (uint i=0; i < desc.cols*params_.num_proj; ++i) 55 | bucketed_hash.push_back(0.0); 56 | } 57 | hash.insert(hash.end(), bucketed_hash.begin(), bucketed_hash.end()); 58 | } 59 | return hash; 60 | } 61 | 62 | int haloc::Hash::CalcDist(const std::vector& hash_a, 63 | const std::vector& hash_b, float eps) { 64 | // Init 65 | const int num_buckets = params_.bucket_cols*params_.bucket_rows; 66 | // const float eps = 0.6; 67 | int num_buckets_overlap = 0; 68 | 69 | // Compute the distance 70 | for (uint i=0; i < comb_.size(); ++i) { 71 | int comb_overlap = 0; 72 | for (uint j=0; j < num_buckets; ++j) { 73 | int idx_a = comb_[i][j].first * params_.num_proj * desc_length_; 74 | int idx_b = comb_[i][j].second * params_.num_proj * desc_length_; 75 | 76 | // Check if buckets are empty 77 | std::vector::const_iterator a_first = hash_a.begin() + idx_a; 78 | std::vector::const_iterator a_last = hash_a.begin() + idx_a + 79 | desc_length_*params_.num_proj; 80 | float sum_a = std::accumulate(a_first, a_last, 0.0); 81 | if (sum_a == 0.0) continue; 82 | 83 | std::vector::const_iterator b_first = hash_b.begin() + idx_b; 84 | std::vector::const_iterator b_last = hash_b.begin() + idx_b + 85 | desc_length_*params_.num_proj; 86 | float sum_b = std::accumulate(b_first, b_last, 0.0); 87 | if (sum_b == 0.0) continue; 88 | 89 | float proj_sum = 0.0; 90 | for (uint k=0; k < desc_length_*params_.num_proj; ++k) { 91 | proj_sum += fabs(hash_a[idx_a+k] - hash_b[idx_b+k]); 92 | } 93 | if (proj_sum <= eps) comb_overlap++; 94 | } 95 | if (comb_overlap > num_buckets_overlap) { 96 | num_buckets_overlap = comb_overlap; 97 | } 98 | } 99 | return num_buckets_overlap; 100 | } 101 | 102 | void haloc::Hash::PublishState(const cv::Mat& img) { 103 | // The bucketed image 104 | pub_.PublishBucketedImage(state_, img, params_.bucket_rows, 105 | params_.bucket_cols); 106 | 107 | // The bucketed info 108 | int max_features_x_bucket = static_cast( 109 | floor(params_.max_desc/(params_.bucket_cols*params_.bucket_rows))); 110 | pub_.PublishBucketedInfo(state_, max_features_x_bucket); 111 | } 112 | 113 | void haloc::Hash::Init(const cv::Size& img_size, const int& num_feat, 114 | const int& desc_length) { 115 | InitProjections(params_.max_desc); 116 | InitCombinations(); 117 | img_size_ = img_size; 118 | desc_length_ = desc_length; 119 | 120 | // Sanity check 121 | if (params_.max_desc < num_feat * 0.7) { 122 | ROS_WARN_STREAM("[Haloc:] WARNING -> Please setup the maximum number " << 123 | "descriptors correctly. The current image has " << num_feat << " and " << 124 | "the max_desc param is " << params_.max_desc << ". The parameter for " << 125 | "the maximum number of descriptors must be smaller than the number" << 126 | "of real features in the images."); 127 | } 128 | 129 | initialized_ = true; 130 | } 131 | 132 | void haloc::Hash::InitCombinations() { 133 | comb_.clear(); 134 | int num_buckets = params_.bucket_cols*params_.bucket_rows; 135 | int second_idx_shift = 0; 136 | for (uint i=0; i < num_buckets; ++i) { 137 | std::vector< std::pair > combinations_row; 138 | for (uint j=0; j < num_buckets; ++j) { 139 | int second_idx = j + second_idx_shift; 140 | if (second_idx > num_buckets-1) { 141 | second_idx = second_idx - num_buckets; 142 | } 143 | combinations_row.push_back(std::make_pair(j, second_idx)); 144 | } 145 | comb_.push_back(combinations_row); 146 | second_idx_shift++; 147 | } 148 | } 149 | 150 | void haloc::Hash::InitProjections(const int& desc_size) { 151 | // Initializations 152 | int seed = time(NULL); 153 | r_.clear(); 154 | 155 | // The maximum number of features per bucket 156 | int max_features_x_bucket = static_cast( 157 | floor(params_.max_desc/(params_.bucket_cols*params_.bucket_rows))); 158 | 159 | // The size of the descriptors may vary... 160 | // But, we limit the number of descriptors per bucket. 161 | int v_size = max_features_x_bucket; 162 | 163 | // We will generate N-orthogonal vectors creating a linear system of type Ax=b 164 | // Generate a first random vector 165 | std::vector r = ComputeRandomVector(v_size, seed); 166 | r_.push_back(UnitVector(r)); 167 | 168 | // Generate the set of orthogonal vectors 169 | for (uint i=1; i < params_.num_proj; i++) { 170 | // Generate a random vector of the correct size 171 | std::vector new_v = ComputeRandomVector(v_size - i, seed + i); 172 | 173 | // Get the right terms (b) 174 | Eigen::VectorXf b(r_.size()); 175 | for (uint n=0; n < r_.size(); n++) { 176 | std::vector cur_v = r_[n]; 177 | float sum = 0.0; 178 | for (uint m=0; m < new_v.size(); m++) 179 | sum += new_v[m]*cur_v[m]; 180 | b(n) = -sum; 181 | } 182 | 183 | // Get the matrix of equations (A) 184 | Eigen::MatrixXf A(i, i); 185 | for (uint n=0; n < r_.size(); n++) { 186 | uint k = 0; 187 | for (uint m=r_[n].size()-i; m < r_[n].size(); m++) { 188 | A(n, k) = r_[n][m]; 189 | k++; 190 | } 191 | } 192 | 193 | // Apply the solver 194 | Eigen::VectorXf x = A.colPivHouseholderQr().solve(b); 195 | 196 | // Add the solutions to the new vector 197 | for (uint n=0; n < r_.size(); n++) 198 | new_v.push_back(x(n)); 199 | new_v = UnitVector(new_v); 200 | 201 | // Push the new vector 202 | r_.push_back(new_v); 203 | } 204 | } 205 | 206 | std::vector haloc::Hash::ComputeRandomVector(const int& size, int seed) { 207 | std::vector h; 208 | for (int i=0; i < size; i++) 209 | h.push_back((float)rand()/RAND_MAX); 210 | return h; 211 | } 212 | 213 | std::vector haloc::Hash::UnitVector(const std::vector& x) { 214 | // Compute the norm 215 | float sum = 0.0; 216 | for (uint i=0; i < x.size(); i++) 217 | sum += pow(x[i], 2.0); 218 | float x_norm = sqrt(sum); 219 | 220 | // x^ = x/|x| 221 | std::vector out; 222 | for (uint i=0; i < x.size(); i++) 223 | out.push_back(x[i] / x_norm); 224 | 225 | return out; 226 | } 227 | 228 | std::vector haloc::Hash::BucketDescriptors( 229 | const std::vector& kp, const cv::Mat& desc) { 230 | // Find max values 231 | float u_max = 0; 232 | float v_max = 0; 233 | std::vector kp_in = kp; 234 | for (std::vector::iterator it = kp_in.begin(); 235 | it != kp_in.end(); it++) { 236 | if (it->pt.x > u_max) u_max = it->pt.x; 237 | if (it->pt.y > v_max) v_max = it->pt.y; 238 | } 239 | 240 | // Compute width and height of the buckets 241 | float bucket_width = img_size_.width / params_.bucket_cols; 242 | float bucket_height = img_size_.height / params_.bucket_rows; 243 | 244 | // Allocate number of buckets needed 245 | std::vector desc_buckets(params_.bucket_cols*params_.bucket_rows); 246 | std::vector< std::vector > kp_buckets( 247 | params_.bucket_cols*params_.bucket_rows); 248 | 249 | // Assign descriptors to their buckets 250 | for (uint i=0; i < kp_in.size(); ++i) { 251 | int u = static_cast(floor(kp_in[i].pt.x/bucket_width)); 252 | int v = static_cast(floor(kp_in[i].pt.y/bucket_height)); 253 | 254 | desc_buckets[v*params_.bucket_cols+u].push_back(desc.row(i)); 255 | kp_buckets[v*params_.bucket_cols+u].push_back(kp_in[i]); 256 | } 257 | 258 | // The maximum number of features per bucket 259 | int max_features_x_bucket = static_cast( 260 | floor(params_.max_desc/(params_.bucket_cols*params_.bucket_rows))); 261 | 262 | // Select the best keypoints for each bucket 263 | std::vector out_desc(params_.bucket_cols*params_.bucket_rows); 264 | for (int i=0; i < params_.bucket_cols*params_.bucket_rows; ++i) { 265 | // Sort keypoints by response 266 | std::vector cur_kps = kp_buckets[i]; 267 | std::vector index(cur_kps.size(), 0); 268 | for (uint j=0; j cur_kps[b].response); 273 | }); 274 | 275 | // Add up to max_features_x_bucket features from this bucket 276 | int k = 0; 277 | int num_kp = 0; 278 | for (uint j=0; j < cur_kps.size(); ++j) { 279 | if (k < max_features_x_bucket) { 280 | out_desc[i].push_back(desc_buckets[i].row(index[j])); 281 | state_.bucketed_kp.push_back(kp_buckets[i][index[j]]); 282 | num_kp++; 283 | } else { 284 | state_.unbucketed_kp.push_back(kp_buckets[i][index[j]]); 285 | } 286 | k++; 287 | } 288 | state_.num_kp_per_bucket.push_back(num_kp); 289 | } 290 | 291 | return out_desc; 292 | } 293 | 294 | std::vector haloc::Hash::ProjectDescriptors(const cv::Mat& desc) { 295 | // Initialize first time 296 | if (!IsInitialized()) Init(cv::Size(0, 0), desc.rows, desc.cols); 297 | 298 | // Initialize output 299 | std::vector hash; 300 | 301 | // Sanity checks 302 | if (desc.rows == 0) { 303 | ROS_ERROR("[Haloc:] ERROR -> Descriptor matrix is empty."); 304 | return hash; 305 | } 306 | 307 | if (desc.rows > r_[0].size()) { 308 | ROS_ERROR_STREAM("[Haloc:] ERROR -> The number of descriptors is " << 309 | "larger than the size of the projection vector. This should not happen."); 310 | return hash; 311 | } 312 | 313 | // Project the descriptors 314 | for (uint i=0; i < r_.size(); i++) { 315 | for (int n=0; n < desc.cols; n++) { 316 | float desc_sum = 0.0; 317 | for (uint m=0; m < desc.rows; m++) { 318 | float projected = r_[i][m]*desc.at(m, n); 319 | float projected_normalized = (projected + 1.0) / 2.0; 320 | desc_sum += projected_normalized; 321 | } 322 | hash.push_back(desc_sum / static_cast(desc.rows)); 323 | } 324 | } 325 | return hash; 326 | } 327 | --------------------------------------------------------------------------------