|
| 1 | +// flann_search_dataset.cpp |
| 2 | +// Naive program to search a query picture in a dataset illustrating usage of FLANN |
| 3 | + |
| 4 | +#include <iostream> |
| 5 | +#include <vector> |
| 6 | +#include "opencv2/core.hpp" |
| 7 | +#include "opencv2/core/utils/filesystem.hpp" |
| 8 | +#include "opencv2/highgui.hpp" |
| 9 | +#include "opencv2/features2d.hpp" |
| 10 | +#include "opencv2/flann.hpp" |
| 11 | + |
| 12 | +using namespace cv; |
| 13 | +using std::cout; |
| 14 | +using std::endl; |
| 15 | + |
| 16 | +#define _ORB_ |
| 17 | + |
| 18 | +const char* keys = |
| 19 | + "{ help h | | Print help message. }" |
| 20 | + "{ dataset | | Path to the images folder used as dataset. }" |
| 21 | + "{ image | | Path to the image to search for in the dataset. }" |
| 22 | + "{ save | | Path and filename where to save the flann structure to. }" |
| 23 | + "{ load | | Path and filename where to load the flann structure from. }"; |
| 24 | + |
| 25 | +struct img_info { |
| 26 | + int img_index; |
| 27 | + unsigned int nbr_of_matches; |
| 28 | + |
| 29 | + img_info(int _img_index, unsigned int _nbr_of_matches) |
| 30 | + : img_index(_img_index) |
| 31 | + , nbr_of_matches(_nbr_of_matches) |
| 32 | + {} |
| 33 | +}; |
| 34 | + |
| 35 | + |
| 36 | +int main( int argc, char* argv[] ) |
| 37 | +{ |
| 38 | + //-- Test the program options |
| 39 | + CommandLineParser parser( argc, argv, keys ); |
| 40 | + if (parser.has("help")) |
| 41 | + { |
| 42 | + parser.printMessage(); |
| 43 | + return -1; |
| 44 | + } |
| 45 | + |
| 46 | + const cv::String img_path = parser.get<String>("image"); |
| 47 | + Mat img = imread( samples::findFile( img_path ), IMREAD_GRAYSCALE ); |
| 48 | + if (img.empty() ) |
| 49 | + { |
| 50 | + cout << "Could not open the image "<< img_path << endl; |
| 51 | + return -1; |
| 52 | + } |
| 53 | + |
| 54 | + const cv::String db_path = parser.get<String>("dataset"); |
| 55 | + if (!utils::fs::isDirectory(db_path)) |
| 56 | + { |
| 57 | + cout << "Dataset folder "<< db_path.c_str() <<" doesn't exist!" << endl; |
| 58 | + return -1; |
| 59 | + } |
| 60 | + |
| 61 | + const cv::String load_db_path = parser.get<String>("load"); |
| 62 | + if ((load_db_path != String()) && (!utils::fs::exists(load_db_path))) |
| 63 | + { |
| 64 | + cout << "File " << load_db_path.c_str() |
| 65 | + << " where to load the flann structure from doesn't exist!" << endl; |
| 66 | + return -1; |
| 67 | + } |
| 68 | + |
| 69 | + const cv::String save_db_path = parser.get<String>("save"); |
| 70 | + |
| 71 | + //-- Step 1: Detect the keypoints using a detector, compute the descriptors |
| 72 | + // in the folder containing the images of the dataset |
| 73 | +#ifdef _SIFT_ |
| 74 | + int minHessian = 400; |
| 75 | + Ptr<Feature2D> detector = SIFT::create( minHessian ); |
| 76 | +#elif defined(_ORB_) |
| 77 | + Ptr<Feature2D> detector = ORB::create(); |
| 78 | +#else |
| 79 | + cout << "Missing or unknown defined descriptor. " |
| 80 | + "Only SIFT and ORB are currently interfaced here" << endl; |
| 81 | + return -1; |
| 82 | +#endif |
| 83 | + |
| 84 | + std::vector<KeyPoint> db_keypoints; |
| 85 | + Mat db_descriptors; |
| 86 | + std::vector<unsigned int> db_images_indice_range; //store the range of indices per image |
| 87 | + std::vector<int> db_indice_2_image_lut; //match descriptor indice to its image |
| 88 | + |
| 89 | + db_images_indice_range.push_back(0); |
| 90 | + std::vector<cv::String> files; |
| 91 | + utils::fs::glob(db_path, cv::String(), files); |
| 92 | + for (std::vector<cv::String>::iterator itr = files.begin(); itr != files.end(); ++itr) |
| 93 | + { |
| 94 | + Mat tmp_img = imread( *itr, IMREAD_GRAYSCALE ); |
| 95 | + if (!tmp_img.empty()) |
| 96 | + { |
| 97 | + std::vector<KeyPoint> kpts; |
| 98 | + Mat descriptors; |
| 99 | + detector->detectAndCompute( tmp_img, noArray(), kpts, descriptors ); |
| 100 | + |
| 101 | + db_keypoints.insert( db_keypoints.end(), kpts.begin(), kpts.end() ); |
| 102 | + db_descriptors.push_back( descriptors ); |
| 103 | + db_images_indice_range.push_back( db_images_indice_range.back() |
| 104 | + + static_cast<unsigned int>(kpts.size()) ); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + //-- Set the LUT |
| 109 | + db_indice_2_image_lut.resize( db_images_indice_range.back() ); |
| 110 | + const int nbr_of_imgs = static_cast<int>( db_images_indice_range.size()-1 ); |
| 111 | + for (int i = 0; i < nbr_of_imgs; ++i) |
| 112 | + { |
| 113 | + const unsigned int first_indice = db_images_indice_range[i]; |
| 114 | + const unsigned int last_indice = db_images_indice_range[i+1]; |
| 115 | + std::fill( db_indice_2_image_lut.begin() + first_indice, |
| 116 | + db_indice_2_image_lut.begin() + last_indice, |
| 117 | + i ); |
| 118 | + } |
| 119 | + |
| 120 | + //-- Step 2: build the structure storing the descriptors |
| 121 | +#if defined(_SIFT_) |
| 122 | + cv::Ptr<flann::GenericIndex<cvflann::L2<float> > > index; |
| 123 | + if (load_db_path != String()) |
| 124 | + index = cv::makePtr<flann::GenericIndex<cvflann::L2<float> > >(db_descriptors, |
| 125 | + cvflann::SavedIndexParams(load_db_path)); |
| 126 | + else |
| 127 | + index = cv::makePtr<flann::GenericIndex<cvflann::L2<float> > >(db_descriptors, |
| 128 | + cvflann::KDTreeIndexParams(4)); |
| 129 | + |
| 130 | +#elif defined(_ORB_) |
| 131 | + cv::Ptr<flann::GenericIndex<cvflann::Hamming<unsigned char> > > index; |
| 132 | + if (load_db_path != String()) |
| 133 | + index = cv::makePtr<flann::GenericIndex<cvflann::Hamming<unsigned char> > > |
| 134 | + (db_descriptors, cvflann::SavedIndexParams(load_db_path)); |
| 135 | + else |
| 136 | + index = cv::makePtr<flann::GenericIndex<cvflann::Hamming<unsigned char> > > |
| 137 | + (db_descriptors, cvflann::LshIndexParams()); |
| 138 | +#else |
| 139 | + cout<< "Descriptor not listed. Set the proper FLANN distance for this descriptor" <<endl; |
| 140 | + return -1; |
| 141 | +#endif |
| 142 | + if (save_db_path != String()) |
| 143 | + index->save(save_db_path); |
| 144 | + |
| 145 | + |
| 146 | + // Return if no query image was set |
| 147 | + if (img_path == String()) |
| 148 | + return 0; |
| 149 | + |
| 150 | + //-- Detect the keypoints and compute the descriptors for the query image |
| 151 | + std::vector<KeyPoint> img_keypoints; |
| 152 | + Mat img_descriptors; |
| 153 | + detector->detectAndCompute( img, noArray(), img_keypoints, img_descriptors ); |
| 154 | + |
| 155 | + |
| 156 | + //-- Step 3: retrieve the descriptors in the dataset matching the ones of the query image |
| 157 | + // /!\ knnSearch doesn't follow OpenCV standards by not initialising empty Mat properties |
| 158 | + const int knn = 2; |
| 159 | + Mat indices(img_descriptors.rows, knn, CV_32S); |
| 160 | +#if defined(_SIFT_) |
| 161 | +#define DIST_TYPE float |
| 162 | + Mat dists(img_descriptors.rows, knn, CV_32F); |
| 163 | +#elif defined(_ORB_) |
| 164 | +#define DIST_TYPE int |
| 165 | + Mat dists(img_descriptors.rows, knn, CV_32S); |
| 166 | +#endif |
| 167 | + index->knnSearch( img_descriptors, indices, dists, knn, cvflann::SearchParams(32) ); |
| 168 | + |
| 169 | + //-- Filter matches using the Lowe's ratio test |
| 170 | + const float ratio_thresh = 0.7f; |
| 171 | + std::vector<DMatch> good_matches; //contains |
| 172 | + std::vector<unsigned int> matches_per_img_histogram( nbr_of_imgs, 0 ); |
| 173 | + for (int i = 0; i < dists.rows; ++i) |
| 174 | + { |
| 175 | + if (dists.at<DIST_TYPE>(i,0) < ratio_thresh * dists.at<DIST_TYPE>(i,1)) |
| 176 | + { |
| 177 | + const int indice_in_db = indices.at<int>(i,0); |
| 178 | + DMatch dmatch(i, indice_in_db, db_indice_2_image_lut[indice_in_db], |
| 179 | + static_cast<float>(dists.at<DIST_TYPE>(i,0))); |
| 180 | + good_matches.push_back( dmatch ); |
| 181 | + matches_per_img_histogram[ db_indice_2_image_lut[indice_in_db] ]++; |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + |
| 186 | + //-- Step 4: find the dataset image with the highest proportion of matches |
| 187 | + std::multimap<float, img_info> images_infos; |
| 188 | + for (int i = 0; i < nbr_of_imgs; ++i) |
| 189 | + { |
| 190 | + const unsigned int nbr_of_matches = matches_per_img_histogram[i]; |
| 191 | + if (nbr_of_matches < 4) //we need at leat 4 points for a homography |
| 192 | + continue; |
| 193 | + |
| 194 | + const unsigned int nbr_of_kpts = db_images_indice_range[i+1] - db_images_indice_range[i]; |
| 195 | + const float inverse_proportion_of_retrieved_kpts = |
| 196 | + static_cast<float>(nbr_of_kpts) / static_cast<float>(nbr_of_matches); |
| 197 | + |
| 198 | + img_info info(i, nbr_of_matches); |
| 199 | + images_infos.insert( std::pair<float,img_info>(inverse_proportion_of_retrieved_kpts, |
| 200 | + info) ); |
| 201 | + } |
| 202 | + |
| 203 | + if (images_infos.begin() == images_infos.end()) |
| 204 | + { |
| 205 | + cout<<"No good match could be found."<<endl; |
| 206 | + return 0; |
| 207 | + } |
| 208 | + |
| 209 | + //-- if there are several images with a similar proportion of matches, |
| 210 | + // select the one with the highest number of matches weighted by the |
| 211 | + // squared ratio of proportions |
| 212 | + const float best_matches_proportion = images_infos.begin()->first; |
| 213 | + float new_matches_proportion = best_matches_proportion; |
| 214 | + img_info best_img = images_infos.begin()->second; |
| 215 | + |
| 216 | + std::multimap<float, img_info>::iterator it = images_infos.begin(); |
| 217 | + ++it; |
| 218 | + while ((it!=images_infos.end()) && (it->first < 1.1*best_matches_proportion)) |
| 219 | + { |
| 220 | + const float ratio = new_matches_proportion / it->first; |
| 221 | + if( it->second.nbr_of_matches * (ratio * ratio) > best_img.nbr_of_matches) |
| 222 | + { |
| 223 | + new_matches_proportion = it->first; |
| 224 | + best_img = it->second; |
| 225 | + } |
| 226 | + ++it; |
| 227 | + } |
| 228 | + |
| 229 | + //-- Step 5: filter goodmatches that belong to the best image match of the dataset |
| 230 | + std::vector<DMatch> filtered_good_matches; |
| 231 | + for (std::vector<DMatch>::iterator itr(good_matches.begin()); itr != good_matches.end(); ++itr) |
| 232 | + { |
| 233 | + if (itr->imgIdx == best_img.img_index) |
| 234 | + filtered_good_matches.push_back(*itr); |
| 235 | + } |
| 236 | + |
| 237 | + //-- Retrieve the best image match from the dataset |
| 238 | + Mat db_img = imread( files[best_img.img_index], IMREAD_GRAYSCALE ); |
| 239 | + |
| 240 | + //-- Draw matches |
| 241 | + Mat img_matches; |
| 242 | + drawMatches( img, img_keypoints, db_img, db_keypoints, filtered_good_matches, img_matches, Scalar::all(-1), |
| 243 | + Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); |
| 244 | + |
| 245 | + //-- Show detected matches |
| 246 | + imshow("Good Matches", img_matches ); |
| 247 | + waitKey(); |
| 248 | + |
| 249 | + return 0; |
| 250 | +} |
0 commit comments