Skip to content

Commit fe9ff64

Browse files
Merge pull request opencv#17643 from pemmanuelviel:pev--new-flann-demo
* Add a FLANN example showing how to search a query image in a dataset * Clean: remove warning * Replace dependency to boost::filesystem by calls to core/utils/filesystem * Wait for escape key to exit * Add an example of binary descriptors support * Add program options for saving and loading the flann structure * Fix warnings on Win64 * Fix warnings on 3.4 branch still relying on C++03 * Add ctor to img_info structure * Comments modification * * Demo file of FLANN moved and renamed * Fix distances type when using binary vectors in the FLANN example * Rename FLANN example file * Remove dependency of the flann example to opencv_contrib's SURF. * Remove mention of FLANN and other descriptors that aimed at giving hint on the other options * Cleaner program options management * Make waitKey usage minimal in FLANN example * Fix the conditions order * Use cv::Ptr
1 parent 3f65c12 commit fe9ff64

File tree

1 file changed

+250
-0
lines changed

1 file changed

+250
-0
lines changed

samples/cpp/flann_search_dataset.cpp

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
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

Comments
 (0)