Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Tetragramm
GitHub Repository: Tetragramm/opencv
Path: blob/master/modules/features2d/src/evaluation.cpp
16337 views
1
//*M///////////////////////////////////////////////////////////////////////////////////////
2
//
3
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4
//
5
// By downloading, copying, installing or using the software you agree to this license.
6
// If you do not agree to this license, do not download, install,
7
// copy or use the software.
8
//
9
//
10
// License Agreement
11
// For Open Source Computer Vision Library
12
//
13
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
14
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
15
// Third party copyrights are property of their respective owners.
16
//
17
// Redistribution and use in source and binary forms, with or without modification,
18
// are permitted provided that the following conditions are met:
19
//
20
// * Redistribution's of source code must retain the above copyright notice,
21
// this list of conditions and the following disclaimer.
22
//
23
// * Redistribution's in binary form must reproduce the above copyright notice,
24
// this list of conditions and the following disclaimer in the documentation
25
// and/or other materials provided with the distribution.
26
//
27
// * The name of the copyright holders may not be used to endorse or promote products
28
// derived from this software without specific prior written permission.
29
//
30
// This software is provided by the copyright holders and contributors "as is" and
31
// any express or implied warranties, including, but not limited to, the implied
32
// warranties of merchantability and fitness for a particular purpose are disclaimed.
33
// In no event shall the Intel Corporation or contributors be liable for any direct,
34
// indirect, incidental, special, exemplary, or consequential damages
35
// (including, but not limited to, procurement of substitute goods or services;
36
// loss of use, data, or profits; or business interruption) however caused
37
// and on any theory of liability, whether in contract, strict liability,
38
// or tort (including negligence or otherwise) arising in any way out of
39
// the use of this software, even if advised of the possibility of such damage.
40
//
41
//M*/
42
43
#include "precomp.hpp"
44
#include <limits>
45
46
using namespace cv;
47
48
template<typename _Tp> static int solveQuadratic(_Tp a, _Tp b, _Tp c, _Tp& x1, _Tp& x2)
49
{
50
if( a == 0 )
51
{
52
if( b == 0 )
53
{
54
x1 = x2 = 0;
55
return c == 0;
56
}
57
x1 = x2 = -c/b;
58
return 1;
59
}
60
61
_Tp d = b*b - 4*a*c;
62
if( d < 0 )
63
{
64
x1 = x2 = 0;
65
return 0;
66
}
67
if( d > 0 )
68
{
69
d = std::sqrt(d);
70
double s = 1/(2*a);
71
x1 = (-b - d)*s;
72
x2 = (-b + d)*s;
73
if( x1 > x2 )
74
std::swap(x1, x2);
75
return 2;
76
}
77
x1 = x2 = -b/(2*a);
78
return 1;
79
}
80
81
//for android ndk
82
#undef _S
83
static inline Point2f applyHomography( const Mat_<double>& H, const Point2f& pt )
84
{
85
double z = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2);
86
if( z )
87
{
88
double w = 1./z;
89
return Point2f( (float)((H(0,0)*pt.x + H(0,1)*pt.y + H(0,2))*w), (float)((H(1,0)*pt.x + H(1,1)*pt.y + H(1,2))*w) );
90
}
91
return Point2f( std::numeric_limits<float>::max(), std::numeric_limits<float>::max() );
92
}
93
94
static inline void linearizeHomographyAt( const Mat_<double>& H, const Point2f& pt, Mat_<double>& A )
95
{
96
A.create(2,2);
97
double p1 = H(0,0)*pt.x + H(0,1)*pt.y + H(0,2),
98
p2 = H(1,0)*pt.x + H(1,1)*pt.y + H(1,2),
99
p3 = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2),
100
p3_2 = p3*p3;
101
if( p3 )
102
{
103
A(0,0) = H(0,0)/p3 - p1*H(2,0)/p3_2; // fxdx
104
A(0,1) = H(0,1)/p3 - p1*H(2,1)/p3_2; // fxdy
105
106
A(1,0) = H(1,0)/p3 - p2*H(2,0)/p3_2; // fydx
107
A(1,1) = H(1,1)/p3 - p2*H(2,1)/p3_2; // fydx
108
}
109
else
110
A.setTo(Scalar::all(std::numeric_limits<double>::max()));
111
}
112
113
class EllipticKeyPoint
114
{
115
public:
116
EllipticKeyPoint();
117
EllipticKeyPoint( const Point2f& _center, const Scalar& _ellipse );
118
119
static void convert( const std::vector<KeyPoint>& src, std::vector<EllipticKeyPoint>& dst );
120
static void convert( const std::vector<EllipticKeyPoint>& src, std::vector<KeyPoint>& dst );
121
122
static Mat_<double> getSecondMomentsMatrix( const Scalar& _ellipse );
123
Mat_<double> getSecondMomentsMatrix() const;
124
125
void calcProjection( const Mat_<double>& H, EllipticKeyPoint& projection ) const;
126
static void calcProjection( const std::vector<EllipticKeyPoint>& src, const Mat_<double>& H, std::vector<EllipticKeyPoint>& dst );
127
128
Point2f center;
129
Scalar ellipse; // 3 elements a, b, c: ax^2+2bxy+cy^2=1
130
Size_<float> axes; // half length of ellipse axes
131
Size_<float> boundingBox; // half sizes of bounding box which sides are parallel to the coordinate axes
132
};
133
134
EllipticKeyPoint::EllipticKeyPoint()
135
{
136
*this = EllipticKeyPoint(Point2f(0,0), Scalar(1, 0, 1) );
137
}
138
139
EllipticKeyPoint::EllipticKeyPoint( const Point2f& _center, const Scalar& _ellipse )
140
{
141
center = _center;
142
ellipse = _ellipse;
143
144
double a = ellipse[0], b = ellipse[1], c = ellipse[2];
145
double ac_b2 = a*c - b*b;
146
double x1, x2;
147
solveQuadratic(1., -(a+c), ac_b2, x1, x2);
148
axes.width = (float)(1/sqrt(x1));
149
axes.height = (float)(1/sqrt(x2));
150
151
boundingBox.width = (float)sqrt(ellipse[2]/ac_b2);
152
boundingBox.height = (float)sqrt(ellipse[0]/ac_b2);
153
}
154
155
Mat_<double> EllipticKeyPoint::getSecondMomentsMatrix( const Scalar& _ellipse )
156
{
157
Mat_<double> M(2, 2);
158
M(0,0) = _ellipse[0];
159
M(1,0) = M(0,1) = _ellipse[1];
160
M(1,1) = _ellipse[2];
161
return M;
162
}
163
164
Mat_<double> EllipticKeyPoint::getSecondMomentsMatrix() const
165
{
166
return getSecondMomentsMatrix(ellipse);
167
}
168
169
void EllipticKeyPoint::calcProjection( const Mat_<double>& H, EllipticKeyPoint& projection ) const
170
{
171
Point2f dstCenter = applyHomography(H, center);
172
173
Mat_<double> invM; invert(getSecondMomentsMatrix(), invM);
174
Mat_<double> Aff; linearizeHomographyAt(H, center, Aff);
175
Mat_<double> dstM; invert(Aff*invM*Aff.t(), dstM);
176
177
projection = EllipticKeyPoint( dstCenter, Scalar(dstM(0,0), dstM(0,1), dstM(1,1)) );
178
}
179
180
void EllipticKeyPoint::convert( const std::vector<KeyPoint>& src, std::vector<EllipticKeyPoint>& dst )
181
{
182
CV_INSTRUMENT_REGION();
183
184
if( !src.empty() )
185
{
186
dst.resize(src.size());
187
for( size_t i = 0; i < src.size(); i++ )
188
{
189
float rad = src[i].size/2;
190
CV_Assert( rad );
191
float fac = 1.f/(rad*rad);
192
dst[i] = EllipticKeyPoint( src[i].pt, Scalar(fac, 0, fac) );
193
}
194
}
195
}
196
197
void EllipticKeyPoint::convert( const std::vector<EllipticKeyPoint>& src, std::vector<KeyPoint>& dst )
198
{
199
CV_INSTRUMENT_REGION();
200
201
if( !src.empty() )
202
{
203
dst.resize(src.size());
204
for( size_t i = 0; i < src.size(); i++ )
205
{
206
Size_<float> axes = src[i].axes;
207
float rad = sqrt(axes.height*axes.width);
208
dst[i] = KeyPoint(src[i].center, 2*rad );
209
}
210
}
211
}
212
213
void EllipticKeyPoint::calcProjection( const std::vector<EllipticKeyPoint>& src, const Mat_<double>& H, std::vector<EllipticKeyPoint>& dst )
214
{
215
if( !src.empty() )
216
{
217
CV_Assert( !H.empty() && H.cols == 3 && H.rows == 3);
218
dst.resize(src.size());
219
std::vector<EllipticKeyPoint>::const_iterator srcIt = src.begin();
220
std::vector<EllipticKeyPoint>::iterator dstIt = dst.begin();
221
for( ; srcIt != src.end() && dstIt != dst.end(); ++srcIt, ++dstIt )
222
srcIt->calcProjection(H, *dstIt);
223
}
224
}
225
226
static void filterEllipticKeyPointsByImageSize( std::vector<EllipticKeyPoint>& keypoints, const Size& imgSize )
227
{
228
if( !keypoints.empty() )
229
{
230
std::vector<EllipticKeyPoint> filtered;
231
filtered.reserve(keypoints.size());
232
std::vector<EllipticKeyPoint>::const_iterator it = keypoints.begin();
233
for( int i = 0; it != keypoints.end(); ++it, i++ )
234
{
235
if( it->center.x + it->boundingBox.width < imgSize.width &&
236
it->center.x - it->boundingBox.width > 0 &&
237
it->center.y + it->boundingBox.height < imgSize.height &&
238
it->center.y - it->boundingBox.height > 0 )
239
filtered.push_back(*it);
240
}
241
keypoints.assign(filtered.begin(), filtered.end());
242
}
243
}
244
245
struct IntersectAreaCounter
246
{
247
IntersectAreaCounter( float _dr, int _minx,
248
int _miny, int _maxy,
249
const Point2f& _diff,
250
const Scalar& _ellipse1, const Scalar& _ellipse2 ) :
251
dr(_dr), bua(0), bna(0), minx(_minx), miny(_miny), maxy(_maxy),
252
diff(_diff), ellipse1(_ellipse1), ellipse2(_ellipse2) {}
253
IntersectAreaCounter( const IntersectAreaCounter& counter, Split )
254
{
255
*this = counter;
256
bua = 0;
257
bna = 0;
258
}
259
260
void operator()( const BlockedRange& range )
261
{
262
CV_Assert( miny < maxy );
263
CV_Assert( dr > FLT_EPSILON );
264
265
int temp_bua = bua, temp_bna = bna;
266
for( int i = range.begin(); i != range.end(); i++ )
267
{
268
float rx1 = minx + i*dr;
269
float rx2 = rx1 - diff.x;
270
for( float ry1 = (float)miny; ry1 <= (float)maxy; ry1 += dr )
271
{
272
float ry2 = ry1 - diff.y;
273
//compute the distance from the ellipse center
274
float e1 = (float)(ellipse1[0]*rx1*rx1 + 2*ellipse1[1]*rx1*ry1 + ellipse1[2]*ry1*ry1);
275
float e2 = (float)(ellipse2[0]*rx2*rx2 + 2*ellipse2[1]*rx2*ry2 + ellipse2[2]*ry2*ry2);
276
//compute the area
277
if( e1<1 && e2<1 ) temp_bna++;
278
if( e1<1 || e2<1 ) temp_bua++;
279
}
280
}
281
bua = temp_bua;
282
bna = temp_bna;
283
}
284
285
void join( IntersectAreaCounter& ac )
286
{
287
bua += ac.bua;
288
bna += ac.bna;
289
}
290
291
float dr;
292
int bua, bna;
293
294
int minx;
295
int miny, maxy;
296
297
Point2f diff;
298
Scalar ellipse1, ellipse2;
299
300
};
301
302
struct SIdx
303
{
304
SIdx() : S(-1), i1(-1), i2(-1) {}
305
SIdx(float _S, int _i1, int _i2) : S(_S), i1(_i1), i2(_i2) {}
306
float S;
307
int i1;
308
int i2;
309
310
bool operator<(const SIdx& v) const { return S > v.S; }
311
312
struct UsedFinder
313
{
314
UsedFinder(const SIdx& _used) : used(_used) {}
315
const SIdx& used;
316
bool operator()(const SIdx& v) const { return (v.i1 == used.i1 || v.i2 == used.i2); }
317
UsedFinder& operator=(const UsedFinder&);
318
};
319
};
320
321
static void computeOneToOneMatchedOverlaps( const std::vector<EllipticKeyPoint>& keypoints1, const std::vector<EllipticKeyPoint>& keypoints2t,
322
bool commonPart, std::vector<SIdx>& overlaps, float minOverlap )
323
{
324
CV_Assert( minOverlap >= 0.f );
325
overlaps.clear();
326
if( keypoints1.empty() || keypoints2t.empty() )
327
return;
328
329
overlaps.clear();
330
overlaps.reserve(cvRound(keypoints1.size() * keypoints2t.size() * 0.01));
331
332
for( size_t i1 = 0; i1 < keypoints1.size(); i1++ )
333
{
334
EllipticKeyPoint kp1 = keypoints1[i1];
335
float maxDist = sqrt(kp1.axes.width*kp1.axes.height),
336
fac = 30.f/maxDist;
337
if( !commonPart )
338
fac=3;
339
340
maxDist = maxDist*4;
341
fac = 1.f/(fac*fac);
342
343
EllipticKeyPoint keypoint1a = EllipticKeyPoint( kp1.center, Scalar(fac*kp1.ellipse[0], fac*kp1.ellipse[1], fac*kp1.ellipse[2]) );
344
345
for( size_t i2 = 0; i2 < keypoints2t.size(); i2++ )
346
{
347
EllipticKeyPoint kp2 = keypoints2t[i2];
348
Point2f diff = kp2.center - kp1.center;
349
350
if( norm(diff) < maxDist )
351
{
352
EllipticKeyPoint keypoint2a = EllipticKeyPoint( kp2.center, Scalar(fac*kp2.ellipse[0], fac*kp2.ellipse[1], fac*kp2.ellipse[2]) );
353
//find the largest eigenvalue
354
int maxx = (int)ceil(( keypoint1a.boundingBox.width > (diff.x+keypoint2a.boundingBox.width)) ?
355
keypoint1a.boundingBox.width : (diff.x+keypoint2a.boundingBox.width));
356
int minx = (int)floor((-keypoint1a.boundingBox.width < (diff.x-keypoint2a.boundingBox.width)) ?
357
-keypoint1a.boundingBox.width : (diff.x-keypoint2a.boundingBox.width));
358
359
int maxy = (int)ceil(( keypoint1a.boundingBox.height > (diff.y+keypoint2a.boundingBox.height)) ?
360
keypoint1a.boundingBox.height : (diff.y+keypoint2a.boundingBox.height));
361
int miny = (int)floor((-keypoint1a.boundingBox.height < (diff.y-keypoint2a.boundingBox.height)) ?
362
-keypoint1a.boundingBox.height : (diff.y-keypoint2a.boundingBox.height));
363
int mina = (maxx-minx) < (maxy-miny) ? (maxx-minx) : (maxy-miny) ;
364
365
//compute the area
366
float dr = (float)mina/50.f;
367
int N = (int)floor((float)(maxx - minx) / dr);
368
IntersectAreaCounter ac( dr, minx, miny, maxy, diff, keypoint1a.ellipse, keypoint2a.ellipse );
369
parallel_reduce( BlockedRange(0, N+1), ac );
370
if( ac.bna > 0 )
371
{
372
float ov = (float)ac.bna / (float)ac.bua;
373
if( ov >= minOverlap )
374
overlaps.push_back(SIdx(ov, (int)i1, (int)i2));
375
}
376
}
377
}
378
}
379
380
std::sort( overlaps.begin(), overlaps.end() );
381
382
typedef std::vector<SIdx>::iterator It;
383
384
It pos = overlaps.begin();
385
It end = overlaps.end();
386
387
while(pos != end)
388
{
389
It prev = pos++;
390
end = std::remove_if(pos, end, SIdx::UsedFinder(*prev));
391
}
392
overlaps.erase(pos, overlaps.end());
393
}
394
395
static void calculateRepeatability( const Mat& img1, const Mat& img2, const Mat& H1to2,
396
const std::vector<KeyPoint>& _keypoints1, const std::vector<KeyPoint>& _keypoints2,
397
float& repeatability, int& correspondencesCount,
398
Mat* thresholdedOverlapMask=0 )
399
{
400
std::vector<EllipticKeyPoint> keypoints1, keypoints2, keypoints1t, keypoints2t;
401
EllipticKeyPoint::convert( _keypoints1, keypoints1 );
402
EllipticKeyPoint::convert( _keypoints2, keypoints2 );
403
404
// calculate projections of key points
405
EllipticKeyPoint::calcProjection( keypoints1, H1to2, keypoints1t );
406
Mat H2to1; invert(H1to2, H2to1);
407
EllipticKeyPoint::calcProjection( keypoints2, H2to1, keypoints2t );
408
409
float overlapThreshold;
410
bool ifEvaluateDetectors = thresholdedOverlapMask == 0;
411
if( ifEvaluateDetectors )
412
{
413
overlapThreshold = 1.f - 0.4f;
414
415
// remove key points from outside of the common image part
416
Size sz1 = img1.size(), sz2 = img2.size();
417
filterEllipticKeyPointsByImageSize( keypoints1, sz1 );
418
filterEllipticKeyPointsByImageSize( keypoints1t, sz2 );
419
filterEllipticKeyPointsByImageSize( keypoints2, sz2 );
420
filterEllipticKeyPointsByImageSize( keypoints2t, sz1 );
421
}
422
else
423
{
424
overlapThreshold = 1.f - 0.5f;
425
426
thresholdedOverlapMask->create( (int)keypoints1.size(), (int)keypoints2t.size(), CV_8UC1 );
427
thresholdedOverlapMask->setTo( Scalar::all(0) );
428
}
429
size_t size1 = keypoints1.size(), size2 = keypoints2t.size();
430
size_t minCount = MIN( size1, size2 );
431
432
// calculate overlap errors
433
std::vector<SIdx> overlaps;
434
computeOneToOneMatchedOverlaps( keypoints1, keypoints2t, ifEvaluateDetectors, overlaps, overlapThreshold/*min overlap*/ );
435
436
correspondencesCount = -1;
437
repeatability = -1.f;
438
if( overlaps.empty() )
439
return;
440
441
if( ifEvaluateDetectors )
442
{
443
// regions one-to-one matching
444
correspondencesCount = (int)overlaps.size();
445
repeatability = minCount ? (float)correspondencesCount / minCount : -1;
446
}
447
else
448
{
449
for( size_t i = 0; i < overlaps.size(); i++ )
450
{
451
int y = overlaps[i].i1;
452
int x = overlaps[i].i2;
453
thresholdedOverlapMask->at<uchar>(y,x) = 1;
454
}
455
}
456
}
457
458
void cv::evaluateFeatureDetector( const Mat& img1, const Mat& img2, const Mat& H1to2,
459
std::vector<KeyPoint>* _keypoints1, std::vector<KeyPoint>* _keypoints2,
460
float& repeatability, int& correspCount,
461
const Ptr<FeatureDetector>& _fdetector )
462
{
463
CV_INSTRUMENT_REGION();
464
465
Ptr<FeatureDetector> fdetector(_fdetector);
466
std::vector<KeyPoint> *keypoints1, *keypoints2, buf1, buf2;
467
keypoints1 = _keypoints1 != 0 ? _keypoints1 : &buf1;
468
keypoints2 = _keypoints2 != 0 ? _keypoints2 : &buf2;
469
470
if( (keypoints1->empty() || keypoints2->empty()) && !fdetector )
471
CV_Error( Error::StsBadArg, "fdetector must not be empty when keypoints1 or keypoints2 is empty" );
472
473
if( keypoints1->empty() )
474
fdetector->detect( img1, *keypoints1 );
475
if( keypoints2->empty() )
476
fdetector->detect( img2, *keypoints2 );
477
478
calculateRepeatability( img1, img2, H1to2, *keypoints1, *keypoints2, repeatability, correspCount );
479
}
480
481
struct DMatchForEvaluation : public DMatch
482
{
483
uchar isCorrect;
484
DMatchForEvaluation( const DMatch &dm ) : DMatch( dm ), isCorrect(0) {}
485
};
486
487
static inline float recall( int correctMatchCount, int correspondenceCount )
488
{
489
return correspondenceCount ? (float)correctMatchCount / (float)correspondenceCount : -1;
490
}
491
492
static inline float precision( int correctMatchCount, int falseMatchCount )
493
{
494
return correctMatchCount + falseMatchCount ? (float)correctMatchCount / (float)(correctMatchCount + falseMatchCount) : -1;
495
}
496
497
void cv::computeRecallPrecisionCurve( const std::vector<std::vector<DMatch> >& matches1to2,
498
const std::vector<std::vector<uchar> >& correctMatches1to2Mask,
499
std::vector<Point2f>& recallPrecisionCurve )
500
{
501
CV_INSTRUMENT_REGION();
502
503
CV_Assert( matches1to2.size() == correctMatches1to2Mask.size() );
504
505
std::vector<DMatchForEvaluation> allMatches;
506
int correspondenceCount = 0;
507
for( size_t i = 0; i < matches1to2.size(); i++ )
508
{
509
for( size_t j = 0; j < matches1to2[i].size(); j++ )
510
{
511
DMatchForEvaluation match = matches1to2[i][j];
512
match.isCorrect = correctMatches1to2Mask[i][j] ;
513
allMatches.push_back( match );
514
correspondenceCount += match.isCorrect != 0 ? 1 : 0;
515
}
516
}
517
518
std::sort( allMatches.begin(), allMatches.end() );
519
520
int correctMatchCount = 0, falseMatchCount = 0;
521
recallPrecisionCurve.resize( allMatches.size() );
522
for( size_t i = 0; i < allMatches.size(); i++ )
523
{
524
if( allMatches[i].isCorrect )
525
correctMatchCount++;
526
else
527
falseMatchCount++;
528
529
float r = recall( correctMatchCount, correspondenceCount );
530
float p = precision( correctMatchCount, falseMatchCount );
531
recallPrecisionCurve[i] = Point2f(1-p, r);
532
}
533
}
534
535
float cv::getRecall( const std::vector<Point2f>& recallPrecisionCurve, float l_precision )
536
{
537
CV_INSTRUMENT_REGION();
538
539
int nearestPointIndex = getNearestPoint( recallPrecisionCurve, l_precision );
540
541
float recall = -1.f;
542
543
if( nearestPointIndex >= 0 )
544
recall = recallPrecisionCurve[nearestPointIndex].y;
545
546
return recall;
547
}
548
549
int cv::getNearestPoint( const std::vector<Point2f>& recallPrecisionCurve, float l_precision )
550
{
551
CV_INSTRUMENT_REGION();
552
553
int nearestPointIndex = -1;
554
555
if( l_precision >= 0 && l_precision <= 1 )
556
{
557
float minDiff = FLT_MAX;
558
for( size_t i = 0; i < recallPrecisionCurve.size(); i++ )
559
{
560
float curDiff = std::fabs(l_precision - recallPrecisionCurve[i].x);
561
if( curDiff <= minDiff )
562
{
563
nearestPointIndex = (int)i;
564
minDiff = curDiff;
565
}
566
}
567
}
568
569
return nearestPointIndex;
570
}
571
572