Skip to content

Commit f9bbe70

Browse files
committed
Merge pull request #2383 from catree:feat_LOGOS_matching
2 parents 8a7a625 + a9c1cfc commit f9bbe70

File tree

12 files changed

+1050
-5
lines changed

12 files changed

+1050
-5
lines changed

modules/xfeatures2d/doc/xfeatures2d.bib

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ @article{Lowe04
7171
publisher = {Kluwer Academic Publishers}
7272
}
7373

74+
@article{Lowry2018LOGOSLG,
75+
title = {LOGOS: Local Geometric Support for High-Outlier Spatial Verification},
76+
author = {Stephanie Lowry and Henrik Andreasson},
77+
journal = {2018 IEEE International Conference on Robotics and Automation (ICRA)},
78+
year = {2018},
79+
pages = {7262-7269},
80+
doi = {10.1109/ICRA.2018.8460988},
81+
}
82+
7483
@article{Mikolajczyk2004,
7584
title = {Scale \& affine invariant interest point detectors},
7685
author = {Mikolajczyk, Krystian and Schmid, Cordelia},

modules/xfeatures2d/include/opencv2/xfeatures2d.hpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ known to be patented. You need to set the OPENCV_ENABLE_NONFREE option in cmake
5555
5656
@defgroup xfeatures2d_match Experimental 2D Features Matching Algorithm
5757
58-
This section describes the GMS (Grid-based Motion Statistics) matching strategy.
58+
This section describes the following matching strategies:
59+
- GMS: Grid-based Motion Statistics, @cite Bian2017gms
60+
- LOGOS: Local geometric support for high-outlier spatial verification, @cite Lowry2018LOGOSLG
5961
6062
@}
6163
*/
@@ -969,7 +971,7 @@ CV_EXPORTS void FASTForPointSet( InputArray image, CV_IN_OUT std::vector<KeyPoin
969971
//! @addtogroup xfeatures2d_match
970972
//! @{
971973

972-
/** @brief GMS (Grid-based Motion Statistics) feature matching strategy by @cite Bian2017gms .
974+
/** @brief GMS (Grid-based Motion Statistics) feature matching strategy described in @cite Bian2017gms .
973975
@param size1 Input size of image1.
974976
@param size2 Input size of image2.
975977
@param keypoints1 Input keypoints of image1.
@@ -984,10 +986,24 @@ CV_EXPORTS void FASTForPointSet( InputArray image, CV_IN_OUT std::vector<KeyPoin
984986
If matching results are not satisfying, please add more features. (We use 10000 for images with 640 X 480).
985987
If your images have big rotation and scale changes, please set withRotation or withScale to true.
986988
*/
989+
CV_EXPORTS_W void matchGMS(const Size& size1, const Size& size2, const std::vector<KeyPoint>& keypoints1, const std::vector<KeyPoint>& keypoints2,
990+
const std::vector<DMatch>& matches1to2, CV_OUT std::vector<DMatch>& matchesGMS, const bool withRotation = false,
991+
const bool withScale = false, const double thresholdFactor = 6.0);
987992

988-
CV_EXPORTS_W void matchGMS( const Size& size1, const Size& size2, const std::vector<KeyPoint>& keypoints1, const std::vector<KeyPoint>& keypoints2,
989-
const std::vector<DMatch>& matches1to2, CV_OUT std::vector<DMatch>& matchesGMS, const bool withRotation = false,
990-
const bool withScale = false, const double thresholdFactor = 6.0 );
993+
/** @brief LOGOS (Local geometric support for high-outlier spatial verification) feature matching strategy described in @cite Lowry2018LOGOSLG .
994+
@param keypoints1 Input keypoints of image1.
995+
@param keypoints2 Input keypoints of image2.
996+
@param nn1 Index to the closest BoW centroid for each descriptors of image1.
997+
@param nn2 Index to the closest BoW centroid for each descriptors of image2.
998+
@param matches1to2 Matches returned by the LOGOS matching strategy.
999+
@note
1000+
This matching strategy is suitable for features matching against large scale database.
1001+
First step consists in constructing the bag-of-words (BoW) from a representative image database.
1002+
Image descriptors are then represented by their closest codevector (nearest BoW centroid).
1003+
*/
1004+
CV_EXPORTS_W void matchLOGOS(const std::vector<KeyPoint>& keypoints1, const std::vector<KeyPoint>& keypoints2,
1005+
const std::vector<int>& nn1, const std::vector<int>& nn2,
1006+
std::vector<DMatch>& matches1to2);
9911007

9921008
//! @}
9931009

modules/xfeatures2d/src/logos.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// This file is part of OpenCV project.
2+
// It is subject to the license terms in the LICENSE file found in the top-level directory
3+
// of this distribution and at http://opencv.org/license.html
4+
#include "precomp.hpp"
5+
#include "logos/Logos.hpp"
6+
7+
namespace cv
8+
{
9+
namespace xfeatures2d
10+
{
11+
void matchLOGOS(const std::vector<KeyPoint>& keypoints1, const std::vector<KeyPoint>& keypoints2,
12+
const std::vector<int>& nn1, const std::vector<int>& nn2, std::vector<DMatch>& matches1to2)
13+
{
14+
CV_CheckEQ(keypoints1.size(), nn1.size(), "Number of keypoints1 must be equal to the number of nn1.");
15+
CV_CheckEQ(keypoints2.size(), nn2.size(), "Number of keypoints2 must be equal to the number of nn2.");
16+
if (keypoints1.empty() || keypoints2.empty())
17+
{
18+
return;
19+
}
20+
21+
std::vector<logos::Point*> vP1, vP2;
22+
vP1.reserve(keypoints1.size());
23+
vP2.reserve(keypoints2.size());
24+
25+
for (size_t i = 0; i < keypoints1.size(); i++)
26+
{
27+
logos::Point* pt1 = new logos::Point(keypoints1[i].pt.x, keypoints1[i].pt.y,
28+
static_cast<float>(keypoints1[i].angle*CV_PI/180),
29+
keypoints1[i].size, nn1[i]);
30+
vP1.push_back(pt1);
31+
}
32+
33+
for (size_t i = 0; i < keypoints2.size(); i++)
34+
{
35+
logos::Point* pt2 = new logos::Point(keypoints2[i].pt.x, keypoints2[i].pt.y,
36+
static_cast<float>(keypoints2[i].angle*CV_PI/180),
37+
keypoints2[i].size, nn2[i]);
38+
vP2.push_back(pt2);
39+
}
40+
41+
logos::Logos logos;
42+
std::vector<logos::PointPair*> globalMatches;
43+
logos.estimateMatches(vP1, vP2, globalMatches);
44+
45+
matches1to2.clear();
46+
matches1to2.reserve(globalMatches.size());
47+
for (size_t i = 0; i < globalMatches.size(); i++)
48+
{
49+
logos::PointPair* pp = globalMatches[i];
50+
matches1to2.push_back(DMatch(pp->getPos1(), pp->getPos2(), 0));
51+
}
52+
53+
for (size_t i = 0; i < globalMatches.size(); i++)
54+
{
55+
delete globalMatches[i];
56+
}
57+
58+
for (size_t i = 0; i < vP1.size(); i++)
59+
{
60+
delete vP1[i];
61+
}
62+
63+
for (size_t i = 0; i < vP2.size(); i++)
64+
{
65+
delete vP2[i];
66+
}
67+
}
68+
} //namespace xfeatures2d
69+
} //namespace cv
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// This file is part of OpenCV project.
2+
// It is subject to the license terms in the LICENSE file found in the top-level directory
3+
// of this distribution and at http://opencv.org/license.html
4+
/*
5+
* MIT License
6+
*
7+
* Copyright (c) 2018 Stephanie Lowry
8+
*
9+
* Permission is hereby granted, free of charge, to any person obtaining a copy
10+
* of this software and associated documentation files (the "Software"), to deal
11+
* in the Software without restriction, including without limitation the rights
12+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
* copies of the Software, and to permit persons to whom the Software is
14+
* furnished to do so, subject to the following conditions:
15+
*
16+
* The above copyright notice and this permission notice shall be included in all
17+
* copies or substantial portions of the Software.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25+
* SOFTWARE.
26+
*/
27+
#include <cmath>
28+
#include "Logos.hpp"
29+
#include <opencv2/core.hpp>
30+
31+
namespace logos
32+
{
33+
Logos::Logos()
34+
{
35+
LogosParameters defaultParams;
36+
init(defaultParams);
37+
}
38+
39+
Logos::Logos(const LogosParameters& p)
40+
{
41+
init(p);
42+
}
43+
44+
void Logos::init(const LogosParameters& p)
45+
{
46+
setParams(p);
47+
LB = static_cast<float>(-CV_PI);
48+
BINSIZE = logosParams.GLOBALORILIMIT/3;
49+
BINNUMBER = static_cast<unsigned int>(ceil(2*CV_PI/BINSIZE));
50+
bins.resize(BINNUMBER);
51+
std::fill(bins.begin(), bins.end(), 0);
52+
}
53+
54+
int Logos::estimateMatches(std::vector<Point*> vP1, std::vector<Point*> vP2, std::vector<PointPair*>& globalmatches)
55+
{
56+
matches.clear();
57+
58+
// for each point
59+
int count1 = 0;
60+
61+
for (std::vector<Point*>::iterator pit1 = vP1.begin(); pit1 != vP1.end(); ++pit1, count1++)
62+
{
63+
(*pit1)->nearestNeighbours(vP1, count1, getNum1());
64+
int count2 = 0;
65+
66+
// find possible matches
67+
for (std::vector<Point*>::iterator pit2 = vP2.begin(); pit2 != vP2.end(); ++pit2, count2++)
68+
{
69+
if ((*pit1)->getLabel() != (*pit2)->getLabel())
70+
{
71+
continue;
72+
}
73+
// this is a possible match in Image 2
74+
// get nearest neighbours
75+
(*pit2)->nearestNeighbours(vP2, count2, getNum2());
76+
77+
PointPair* ptpr = new PointPair(*pit1, *pit2);
78+
ptpr->addPositions(count1, count2);
79+
ptpr->computeLocalSupport(pp, getNum2());
80+
81+
// calc matches
82+
int support = 0;
83+
for (std::vector<PointPair*>::const_iterator it = pp.begin(); it < pp.end(); ++it)
84+
{
85+
Match m(ptpr, *it);
86+
if (evaluateMatch(m))
87+
{
88+
support++;
89+
}
90+
}
91+
for (size_t i = 0; i < pp.size(); i++)
92+
{
93+
delete pp[i];
94+
}
95+
pp.clear();
96+
if (support > 0)
97+
{
98+
ptpr->setSupport(support);
99+
matches.push_back(ptpr);
100+
updateBin(ptpr->getRelOri());
101+
}
102+
else
103+
{
104+
delete ptpr;
105+
ptpr = NULL;
106+
}
107+
}
108+
}
109+
110+
// do global orientation
111+
double maxang = calcGlobalOrientation();
112+
113+
// find which matches are within global orientation limit
114+
int numinliers = 0;
115+
globalmatches.clear();
116+
for (std::vector<PointPair*>::iterator it = matches.begin(); it != matches.end(); ++it)
117+
{
118+
if (std::fabs((*it)->getRelOri() - maxang) < logosParams.GLOBALORILIMIT)
119+
{
120+
numinliers++;
121+
globalmatches.push_back(*it);
122+
}
123+
else
124+
{
125+
delete *it;
126+
*it = NULL;
127+
}
128+
}
129+
130+
return numinliers;
131+
}
132+
133+
bool Logos::evaluateMatch(const Match& m) const
134+
{
135+
return ((m.getRelOrientation() < getIntraOriLimit()) &&
136+
(m.getRelScale() < getIntraScaleLimit()) &&
137+
(m.getInterOrientation() < getInterOriLimit()) &&
138+
(m.getInterScale() < getInterScaleLimit()));
139+
}
140+
141+
void Logos::updateBin(float input)
142+
{
143+
unsigned int binnumber = static_cast<unsigned int>(cvFloor((input-LB) / BINSIZE));
144+
// compare binnumber to BINNUMBER
145+
if (binnumber < BINNUMBER)
146+
{
147+
bins[binnumber]++;
148+
}
149+
else
150+
{
151+
bins[BINNUMBER-1]++;
152+
}
153+
}
154+
155+
float Logos::calcGlobalOrientation()
156+
{
157+
// find max bin
158+
// check BINNUMBER is big enough
159+
if (BINNUMBER < 3)
160+
{
161+
return 0;
162+
}
163+
164+
std::vector<int> bins2(BINNUMBER);
165+
int maxval = 0;
166+
unsigned int maxix = 0;
167+
bins2[0] = bins[0] + bins[1] + bins[BINNUMBER-1];
168+
maxval = bins2[0];
169+
for (unsigned int i = 1; i < BINNUMBER; i++)
170+
{
171+
if (i == BINNUMBER-1)
172+
{
173+
bins2[i] = bins[i]+bins[i-1]+bins[0];
174+
}
175+
else
176+
{
177+
bins2[i] = bins[i]+bins[i-1]+bins[i+1];
178+
}
179+
if (bins2[i] > maxval)
180+
{
181+
maxval = bins2[i];
182+
maxix = i;
183+
}
184+
}
185+
186+
// convert to an angle
187+
return LB + maxix*BINSIZE + BINSIZE/2;
188+
}
189+
}

0 commit comments

Comments
 (0)