mirror of
https://github.com/opencv/opencv.git
synced 2024-11-27 20:50:25 +08:00
replaced detector/descriptor evaluation functions from test to features2d; modified VectorDescriptorMatch constructor; removed commented calonder descriptor implementation
This commit is contained in:
parent
20e407b235
commit
121e51d35b
@ -811,127 +811,6 @@ private:
|
||||
bool keep_floats_;
|
||||
};
|
||||
|
||||
#if 0
|
||||
class CV_EXPORTS CalonderClassifier
|
||||
{
|
||||
public:
|
||||
CalonderClassifier();
|
||||
CalonderClassifier( const vector<vector<Point2f> >& points, const vector<Mat>& refimgs,
|
||||
const vector<vector<int> >& labels=vector<vector<int> >(), int _numClasses=0,
|
||||
int _pathSize=DEFAULT_PATCH_SIZE,
|
||||
int _numTrees=DEFAULT_NUM_TREES,
|
||||
int _treeDepth=DEFAULT_TREE_DEPTH,
|
||||
int _numViews=DEFAULT_NUM_VIEWS,
|
||||
int _compressedDim=DEFAULT_COMPRESSED_DIM,
|
||||
int _compressType=DEFAULT_COMPRESS_TYPE,
|
||||
int _numQuantBits=DEFAULT_NUM_QUANT_BITS,
|
||||
const PatchGenerator& patchGenerator=PatchGenerator() );
|
||||
|
||||
virtual ~CalonderClassifier();
|
||||
virtual void clear();
|
||||
|
||||
void train( const vector<vector<Point2f> >& points, const vector<Mat>& refimgs,
|
||||
const vector<vector<int> >& labels=vector<vector<int> >(), int _nclasses=0,
|
||||
int _pathSize=DEFAULT_PATCH_SIZE,
|
||||
int _numTrees=DEFAULT_NUM_TREES,
|
||||
int _treeDepth=DEFAULT_TREE_DEPTH,
|
||||
int _numViews=DEFAULT_NUM_VIEWS,
|
||||
int _compressedDim=DEFAULT_COMPRESSED_DIM,
|
||||
int _compressType=DEFAULT_COMPRESS_TYPE,
|
||||
int _numQuantBits=DEFAULT_NUM_QUANT_BITS,
|
||||
const PatchGenerator& patchGenerator=PatchGenerator() );
|
||||
|
||||
virtual void operator()(const Mat& img, Point2f pt, vector<float>& signature, float thresh=0.f) const;
|
||||
virtual void operator()(const Mat& patch, vector<float>& signature, float thresh=-1.f) const;
|
||||
#define QUANTIZATION_AVAILABLE 1
|
||||
#if QUANTIZATION_AVAILABLE
|
||||
void quantizePosteriors( int _numQuantBits, bool isClearFloatPosteriors=false );
|
||||
void clearFloatPosteriors();
|
||||
virtual void operator()(const Mat& img, Point2f pt, vector<uchar>& signature, uchar thresh=-1.f) const;
|
||||
virtual void operator()(const Mat& patch, vector<uchar>& signature, uchar thresh=-1.f) const;
|
||||
#endif
|
||||
|
||||
void read( const FileNode& fn );
|
||||
void read( std::istream& is );
|
||||
void write( FileStorage& fs ) const;
|
||||
|
||||
bool empty() const;
|
||||
|
||||
void setVerbose( bool _verbose );
|
||||
|
||||
int getPatchSize() const;
|
||||
int getNumTrees() const;
|
||||
int getTreeDepth() const;
|
||||
int getNumViews() const;
|
||||
int getSignatureSize() const;
|
||||
int getCompressType() const;
|
||||
int getNumQuantBits() const;
|
||||
int getOrigNumClasses() const;
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
COMPRESS_NONE = -1,
|
||||
COMPRESS_DISTR_GAUSS = 0,
|
||||
COMPRESS_DISTR_BERNOULLI = 1,
|
||||
COMPRESS_DISTR_DBFRIENDLY = 2,
|
||||
};
|
||||
|
||||
static float GET_LOWER_QUANT_PERC() { return .03f; }
|
||||
static float GET_UPPER_QUANT_PERC() { return .92f; }
|
||||
|
||||
enum
|
||||
{
|
||||
MAX_NUM_QUANT_BITS = 8,
|
||||
DEFAULT_PATCH_SIZE = 32,
|
||||
DEFAULT_NUM_TREES = 48,
|
||||
DEFAULT_TREE_DEPTH = 9,
|
||||
DEFAULT_NUM_VIEWS = 500,
|
||||
DEFAULT_COMPRESSED_DIM = 176,
|
||||
DEFAULT_COMPRESS_TYPE = COMPRESS_DISTR_BERNOULLI,
|
||||
DEFAULT_NUM_QUANT_BITS = -1,
|
||||
};
|
||||
private:
|
||||
void prepare( int _patchSize, int _signatureSize, int _numTrees, int _treeDepth, int _numViews );
|
||||
|
||||
int getLeafIdx( int treeIdx, const Mat& patch ) const;
|
||||
void finalize( int _compressedDim, int _compressType, int _numQuantBits,
|
||||
const vector<int>& leafSampleCounters);
|
||||
|
||||
void compressLeaves( int _compressedDim, int _compressType );
|
||||
|
||||
bool verbose;
|
||||
|
||||
int patchSize;
|
||||
int signatureSize;
|
||||
int numTrees;
|
||||
int treeDepth;
|
||||
int numViews;
|
||||
|
||||
int origNumClasses;
|
||||
int compressType;
|
||||
int numQuantBits;
|
||||
|
||||
int numLeavesPerTree;
|
||||
int numNodesPerTree;
|
||||
|
||||
struct Node
|
||||
{
|
||||
uchar x1, y1, x2, y2;
|
||||
Node() : x1(0), y1(0), x2(0), y2(0) {}
|
||||
Node( uchar _x1, uchar _y1, uchar _x2, uchar _y2 ) : x1(_x1), y1(_y1), x2(_x2), y2(_y2)
|
||||
{}
|
||||
int operator() (const Mat_<uchar>& patch) const
|
||||
{ return patch(y1,x1) > patch(y2, x2) ? 1 : 0; }
|
||||
};
|
||||
vector<Node> nodes;
|
||||
vector<float> posteriors;
|
||||
#if QUANTIZATION_AVAILABLE
|
||||
vector<uchar> quantizedPosteriors;
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
/****************************************************************************************\
|
||||
* One-Way Descriptor *
|
||||
\****************************************************************************************/
|
||||
@ -1515,6 +1394,10 @@ protected:
|
||||
virtual void detectImpl( const Mat& image, const Mat& mask, vector<KeyPoint>& keypoints ) const;
|
||||
};
|
||||
|
||||
|
||||
CV_EXPORTS Mat windowedMatchingMask( const vector<KeyPoint>& keypoints1, const vector<KeyPoint>& keypoints2,
|
||||
float maxDeltaX, float maxDeltaY );
|
||||
|
||||
/****************************************************************************************\
|
||||
* DescriptorExtractor *
|
||||
\****************************************************************************************/
|
||||
@ -2274,8 +2157,8 @@ class CV_EXPORTS VectorDescriptorMatch : public GenericDescriptorMatch
|
||||
public:
|
||||
using GenericDescriptorMatch::add;
|
||||
|
||||
VectorDescriptorMatch( DescriptorExtractor *_extractor = 0, DescriptorMatcher * _matcher = 0 ) :
|
||||
extractor( _extractor ), matcher( _matcher ) {}
|
||||
VectorDescriptorMatch( const Ptr<DescriptorExtractor>& _extractor, const Ptr<DescriptorMatcher>& _matcher )
|
||||
: extractor( _extractor ), matcher( _matcher ) {}
|
||||
|
||||
~VectorDescriptorMatch() {}
|
||||
|
||||
@ -2303,10 +2186,9 @@ protected:
|
||||
//vector<int> classIds;
|
||||
};
|
||||
|
||||
CV_EXPORTS Mat windowedMatchingMask( const vector<KeyPoint>& keypoints1, const vector<KeyPoint>& keypoints2,
|
||||
float maxDeltaX, float maxDeltaY );
|
||||
|
||||
|
||||
/****************************************************************************************\
|
||||
* Drawing functions *
|
||||
\****************************************************************************************/
|
||||
struct CV_EXPORTS DrawMatchesFlags
|
||||
{
|
||||
enum{ DEFAULT = 0, // Output image matrix will be created (Mat::create),
|
||||
@ -2318,7 +2200,7 @@ struct CV_EXPORTS DrawMatchesFlags
|
||||
// Matches will be drawn on existing content of output image.
|
||||
NOT_DRAW_SINGLE_POINTS = 2, // Single keypoints will not be drawn.
|
||||
DRAW_RICH_KEYPOINTS = 4 // For each keypoint the circle around keypoint with keypoint size and
|
||||
// orientation will be drawn.
|
||||
// orientation will be drawn.
|
||||
};
|
||||
};
|
||||
|
||||
@ -2345,7 +2227,28 @@ CV_EXPORTS void drawMatches( const Mat& img1, const vector<KeyPoint>& keypoints1
|
||||
const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1),
|
||||
const vector<vector<char> >& matchesMask=vector<vector<char> >(), int flags=DrawMatchesFlags::DEFAULT );
|
||||
|
||||
}
|
||||
/****************************************************************************************\
|
||||
* Evaluation functions *
|
||||
\****************************************************************************************/
|
||||
|
||||
CV_EXPORTS void evaluateFeatureDetector( const Mat& img1, const Mat& img2, const Mat& H1to2,
|
||||
vector<KeyPoint>* keypoints1, vector<KeyPoint>* keypoints2,
|
||||
float& repeatability, int& correspCount,
|
||||
const Ptr<FeatureDetector>& fdetector=Ptr<FeatureDetector>() );
|
||||
|
||||
CV_EXPORTS void computeRecallPrecisionCurve( const vector<vector<DMatch> >& matches1to2,
|
||||
const vector<vector<uchar> >& correctMatches1to2Mask,
|
||||
vector<Point2f>& recallPrecisionCurve );
|
||||
CV_EXPORTS float getRecall( const vector<Point2f>& recallPrecisionCurve, float l_precision );
|
||||
|
||||
CV_EXPORTS void evaluateDescriptorMatch( const Mat& img1, const Mat& img2, const Mat& H1to2,
|
||||
vector<KeyPoint>& keypoints1, vector<KeyPoint>& keypoints2,
|
||||
vector<vector<DMatch> >* matches1to2, vector<vector<uchar> >* correctMatches1to2Mask,
|
||||
vector<Point2f>& recallPrecisionCurve,
|
||||
const Ptr<GenericDescriptorMatch>& dmatch=Ptr<GenericDescriptorMatch>() );
|
||||
|
||||
|
||||
} /* namespace cv */
|
||||
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
@ -991,596 +991,4 @@ void RTreeClassifier::discardFloatPosteriors()
|
||||
printf("[OK] RTC: discarded float posteriors of all trees\n");
|
||||
}
|
||||
|
||||
#if 0
|
||||
const int progressBarSize = 50;
|
||||
|
||||
CalonderClassifier::CalonderClassifier()
|
||||
{
|
||||
verbose = false;
|
||||
clear();
|
||||
}
|
||||
|
||||
CalonderClassifier::~CalonderClassifier()
|
||||
{}
|
||||
|
||||
CalonderClassifier::CalonderClassifier( const vector<vector<Point2f> >& points, const vector<Mat>& refimgs,
|
||||
const vector<vector<int> >& labels, int _numClasses,
|
||||
int _pathSize, int _numTrees, int _treeDepth,
|
||||
int _numViews, int _compressedDim, int _compressType, int _numQuantBits,
|
||||
const PatchGenerator &patchGenerator )
|
||||
{
|
||||
verbose = false;
|
||||
train( points, refimgs, labels, _numClasses, _pathSize, _numTrees, _treeDepth, _numViews,
|
||||
_compressedDim, _compressType, _numQuantBits, patchGenerator );
|
||||
}
|
||||
|
||||
int CalonderClassifier::getPatchSize() const
|
||||
{ return patchSize; }
|
||||
|
||||
int CalonderClassifier::getNumTrees() const
|
||||
{ return numTrees; }
|
||||
|
||||
int CalonderClassifier::getTreeDepth() const
|
||||
{ return treeDepth; }
|
||||
|
||||
int CalonderClassifier::getNumViews() const
|
||||
{ return numViews; }
|
||||
|
||||
int CalonderClassifier::getSignatureSize() const
|
||||
{ return signatureSize; }
|
||||
|
||||
int CalonderClassifier::getCompressType() const
|
||||
{ return compressType; }
|
||||
|
||||
int CalonderClassifier::getNumQuantBits() const
|
||||
{ return numQuantBits; }
|
||||
|
||||
int CalonderClassifier::getOrigNumClasses() const
|
||||
{ return origNumClasses; }
|
||||
|
||||
void CalonderClassifier::setVerbose( bool _verbose )
|
||||
{
|
||||
verbose = _verbose;
|
||||
}
|
||||
|
||||
void CalonderClassifier::clear()
|
||||
{
|
||||
patchSize = numTrees = origNumClasses = signatureSize = treeDepth = numViews = numQuantBits = 0;
|
||||
compressType = COMPRESS_NONE;
|
||||
|
||||
nodes.clear();
|
||||
posteriors.clear();
|
||||
#if QUANTIZATION_AVAILABLE
|
||||
quantizedPosteriors.clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CalonderClassifier::empty() const
|
||||
{
|
||||
return posteriors.empty() && quantizedPosteriors.empty();
|
||||
}
|
||||
|
||||
void CalonderClassifier::prepare( int _patchSize, int _signatureSize, int _numTrees, int _treeDepth, int _numViews )
|
||||
{
|
||||
clear();
|
||||
|
||||
patchSize = _patchSize;
|
||||
signatureSize = _signatureSize;
|
||||
numTrees = _numTrees;
|
||||
treeDepth = _treeDepth;
|
||||
numViews = _numViews;
|
||||
|
||||
numLeavesPerTree = 1 << treeDepth; // 2^d
|
||||
numNodesPerTree = numLeavesPerTree - 1; // 2^d - 1
|
||||
|
||||
nodes = vector<Node>( numTrees*numNodesPerTree );
|
||||
posteriors = vector<float>( numTrees*numLeavesPerTree*signatureSize, 0.f );
|
||||
}
|
||||
|
||||
static int calcNumPoints( const vector<vector<Point2f> >& points )
|
||||
{
|
||||
int count = 0;
|
||||
for( size_t i = 0; i < points.size(); i++ )
|
||||
count += points[i].size();
|
||||
return count;
|
||||
}
|
||||
|
||||
void CalonderClassifier::train( const vector<vector<Point2f> >& points, const vector<Mat>& refimgs,
|
||||
const vector<vector<int> >& labels, int _numClasses,
|
||||
int _patchSize, int _numTrees, int _treeDepth, int _numViews,
|
||||
int _compressedDim, int _compressType, int _numQuantBits,
|
||||
const PatchGenerator &patchGenerator )
|
||||
{
|
||||
if( points.empty() || refimgs.size() != points.size() )
|
||||
CV_Error( CV_StsBadSize, "points vector must be no empty and refimgs must have the same size as points" );
|
||||
if( _patchSize < 5 || _patchSize >= 256 )
|
||||
CV_Error( CV_StsBadArg, "patchSize must be in [5, 255]");
|
||||
if( _numTrees <= 0 || _treeDepth <= 0 )
|
||||
CV_Error( CV_StsBadArg, "numTrees, treeDepth, numViews must be positive");
|
||||
int numPoints = calcNumPoints( points );
|
||||
if( !labels.empty() && ( labels.size() != points.size() || _numClasses <=0 || _numClasses > numPoints ) )
|
||||
CV_Error( CV_StsBadArg, "labels has incorrect size or _numClasses is not in [1, numPoints]");
|
||||
_numViews = std::max( 1, _numViews );
|
||||
|
||||
int _origNumClasses = labels.empty() ? numPoints : _numClasses;
|
||||
|
||||
if( verbose )
|
||||
{
|
||||
cout << "Using train parameters:" << endl;
|
||||
cout << " patchSize=" << _patchSize << endl;
|
||||
cout << " numTrees=" << _numTrees << endl;
|
||||
cout << " treeDepth=" << _treeDepth << endl;
|
||||
cout << " numViews=" << _numViews << endl;
|
||||
cout << " compressedDim=" << _compressedDim << endl;
|
||||
cout << " compressType=" << _compressType << endl;
|
||||
cout << " numQuantBits=" << _numQuantBits << endl;
|
||||
cout << endl
|
||||
<< " numPoints=" << numPoints << endl;
|
||||
cout << " origNumClasses=" << _origNumClasses << endl;
|
||||
}
|
||||
|
||||
prepare( _patchSize, _origNumClasses, _numTrees, _treeDepth, _numViews );
|
||||
|
||||
origNumClasses = _origNumClasses;
|
||||
vector<int> leafSampleCounters = vector<int>( numTrees*numLeavesPerTree, 0 );
|
||||
// generate nodes
|
||||
RNG rng = theRNG();
|
||||
for( int i = 0; i < numTrees*numNodesPerTree; i++ )
|
||||
{
|
||||
uchar x1 = rng(_patchSize);
|
||||
uchar y1 = rng(_patchSize);
|
||||
uchar x2 = rng(_patchSize);
|
||||
uchar y2 = rng(_patchSize);
|
||||
nodes[i] = Node(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
Size size( patchSize, patchSize );
|
||||
Mat patch;
|
||||
if( verbose ) cout << "START training..." << endl;
|
||||
for( size_t treeIdx = 0; treeIdx < (size_t)numTrees; treeIdx++ )
|
||||
{
|
||||
if( verbose ) cout << "< tree " << treeIdx << endl;
|
||||
int globalPointIdx = 0;
|
||||
int* treeLeafSampleCounters = &leafSampleCounters[treeIdx*numLeavesPerTree];
|
||||
float* treePosteriors = &posteriors[treeIdx*numLeavesPerTree*signatureSize];
|
||||
for( size_t imgIdx = 0; imgIdx < points.size(); imgIdx++ )
|
||||
{
|
||||
const Point2f* imgPoints = &points[imgIdx][0];
|
||||
const int* imgLabels = labels.empty() ? 0 : &labels[imgIdx][0];
|
||||
int last = -1, cur;
|
||||
for( size_t pointIdx = 0; pointIdx < points[imgIdx].size(); pointIdx++, globalPointIdx++ )
|
||||
{
|
||||
int classID = imgLabels==0 ? globalPointIdx : imgLabels[pointIdx];
|
||||
Point2f pt = imgPoints[pointIdx];
|
||||
const Mat& src = refimgs[imgIdx];
|
||||
|
||||
if( verbose && (cur = (int)((float)globalPointIdx/numPoints*progressBarSize)) != last )
|
||||
{
|
||||
last = cur;
|
||||
cout << ".";
|
||||
cout.flush();
|
||||
}
|
||||
|
||||
CV_Assert( classID >= 0 && classID < signatureSize );
|
||||
for( int v = 0; v < numViews; v++ )
|
||||
{
|
||||
patchGenerator( src, pt, patch, size, rng );
|
||||
// add sample
|
||||
int leafIdx = getLeafIdx( treeIdx, patch );
|
||||
treeLeafSampleCounters[leafIdx]++;
|
||||
treePosteriors[leafIdx*signatureSize + classID]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( verbose ) cout << endl << ">" << endl;
|
||||
}
|
||||
|
||||
_compressedDim = std::max( 0, std::min(signatureSize, _compressedDim) );
|
||||
_numQuantBits = std::max( 0, std::min((int)MAX_NUM_QUANT_BITS, _numQuantBits) );
|
||||
finalize( _compressedDim, _compressType, _numQuantBits, leafSampleCounters );
|
||||
|
||||
if( verbose ) cout << "END training." << endl;
|
||||
}
|
||||
|
||||
int CalonderClassifier::getLeafIdx( int treeIdx, const Mat& patch ) const
|
||||
{
|
||||
const Node* treeNodes = &nodes[treeIdx*numNodesPerTree];
|
||||
int idx = 0;
|
||||
for( int d = 0; d < treeDepth-1; d++ )
|
||||
{
|
||||
int offset = treeNodes[idx](patch);
|
||||
idx = 2*idx + 1 + offset;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
void CalonderClassifier::finalize( int _compressedDim, int _compressType, int _numQuantBits,
|
||||
const vector<int>& leafSampleCounters )
|
||||
{
|
||||
for( int ti = 0; ti < numTrees; ti++ )
|
||||
{
|
||||
const int* treeLeafSampleCounters = &leafSampleCounters[ti*numLeavesPerTree];
|
||||
float* treePosteriors = &posteriors[ti*numLeavesPerTree*signatureSize];
|
||||
// Normalize by number of patches to reach each leaf
|
||||
for( int li = 0; li < numLeavesPerTree; li++ )
|
||||
{
|
||||
int sampleCount = treeLeafSampleCounters[li];
|
||||
if( sampleCount != 0 )
|
||||
{
|
||||
float normalizer = 1.0f / sampleCount;
|
||||
int leafPosteriorIdx = li*signatureSize;
|
||||
for( int ci = 0; ci < signatureSize; ci++ )
|
||||
treePosteriors[leafPosteriorIdx + ci] *= normalizer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply compressive sensing
|
||||
if( _compressedDim > 0 && _compressedDim < signatureSize )
|
||||
compressLeaves( _compressedDim, _compressType );
|
||||
else
|
||||
{
|
||||
if( verbose )
|
||||
cout << endl << "[WARNING] NO compression to leaves applied, because _compressedDim=" << _compressedDim << endl;
|
||||
}
|
||||
|
||||
// convert float-posteriors to uchar-posteriors (quantization step)
|
||||
#if QUANTIZATION_AVAILABLE
|
||||
if( _numQuantBits > 0 )
|
||||
quantizePosteriors( _numQuantBits );
|
||||
else
|
||||
{
|
||||
if( verbose )
|
||||
cout << endl << "[WARNING] NO quantization to posteriors, because _numQuantBits=" << _numQuantBits << endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Mat createCompressionMatrix( int rows, int cols, int distrType )
|
||||
{
|
||||
Mat mtr( rows, cols, CV_32FC1 );
|
||||
assert( rows <= cols );
|
||||
|
||||
RNG rng(23);
|
||||
|
||||
if( distrType == CalonderClassifier::COMPRESS_DISTR_GAUSS )
|
||||
{
|
||||
float sigma = 1./rows;
|
||||
for( int y = 0; y < rows; y++ )
|
||||
for( int x = 0; x < cols; x++ )
|
||||
mtr.at<float>(y,x) = rng.gaussian( sigma );
|
||||
}
|
||||
else if( distrType == CalonderClassifier::COMPRESS_DISTR_BERNOULLI )
|
||||
{
|
||||
float par = (float)(1./sqrt((float)rows));
|
||||
for( int y = 0; y < rows; y++ )
|
||||
for( int x = 0; x < cols; x++ )
|
||||
mtr.at<float>(y,x) = rng(2)==0 ? par : -par;
|
||||
}
|
||||
else if( distrType == CalonderClassifier::COMPRESS_DISTR_DBFRIENDLY )
|
||||
{
|
||||
float par = (float)sqrt(3./rows);
|
||||
for( int y = 0; y < rows; y++ )
|
||||
for( int x = 0; x < cols; x++ )
|
||||
{
|
||||
int rng6 = rng(6);
|
||||
mtr.at<float>(y,x) = rng6==0 ? par : (rng6==1 ? -par : 0.f);
|
||||
}
|
||||
}
|
||||
else
|
||||
CV_Assert( 0 );
|
||||
|
||||
return mtr;
|
||||
}
|
||||
|
||||
void CalonderClassifier::compressLeaves( int _compressedDim, int _compressType )
|
||||
{
|
||||
if( verbose )
|
||||
cout << endl << "[OK] compressing leaves with matrix " << _compressedDim << " x " << signatureSize << endl;
|
||||
|
||||
Mat compressionMtrT = (createCompressionMatrix( _compressedDim, signatureSize, _compressType )).t();
|
||||
|
||||
vector<float> comprPosteriors( numTrees*numLeavesPerTree*_compressedDim, 0);
|
||||
Mat( numTrees*numLeavesPerTree, _compressedDim, CV_32FC1, &comprPosteriors[0] ) =
|
||||
Mat( numTrees*numLeavesPerTree, signatureSize, CV_32FC1, &posteriors[0]) * compressionMtrT;
|
||||
|
||||
posteriors.resize( comprPosteriors.size() );
|
||||
copy( comprPosteriors.begin(), comprPosteriors.end(), posteriors.begin() );
|
||||
|
||||
signatureSize = _compressedDim;
|
||||
compressType = _compressType;
|
||||
}
|
||||
|
||||
#if QUANTIZATION_AVAILABLE
|
||||
static float percentile( const float* data, int n, float p )
|
||||
{
|
||||
assert( n>0 );
|
||||
assert( p>=0 && p<=1 );
|
||||
|
||||
vector<float> vec( data, data+n );
|
||||
std::sort(vec.begin(), vec.end());
|
||||
int ix = (int)(p*(n-1));
|
||||
return vec[ix];
|
||||
}
|
||||
|
||||
void quantizeVector( const float* src, int dim, float fbounds[2], uchar ubounds[2], uchar* dst )
|
||||
{
|
||||
assert( fbounds[0] < fbounds[1] );
|
||||
assert( ubounds[0] < ubounds[1] );
|
||||
|
||||
float normFactor = 1.f/(fbounds[1] - fbounds[0]);
|
||||
for( int i = 0; i < dim; i++ )
|
||||
{
|
||||
float part = (src[i] - fbounds[0]) * normFactor;
|
||||
assert( 0 <= part && part <= 1 ) ;
|
||||
uchar val = ubounds[0] + (uchar)( part*ubounds[1] );
|
||||
dst[i] = std::max( 0, (int)std::min(ubounds[1], val) );
|
||||
}
|
||||
}
|
||||
|
||||
void CalonderClassifier::quantizePosteriors( int _numQuantBits, bool isClearFloatPosteriors )
|
||||
{
|
||||
uchar ubounds[] = { 0, (uchar)((1<<_numQuantBits)-1) };
|
||||
float fbounds[] = { 0.f, 0.f };
|
||||
|
||||
int totalLeavesCount = numTrees*numLeavesPerTree;
|
||||
for( int li = 0; li < totalLeavesCount; li++ ) // TODO for some random choosen leaves !
|
||||
{
|
||||
fbounds[0] += percentile( &posteriors[li*signatureSize], signatureSize, GET_LOWER_QUANT_PERC() );
|
||||
fbounds[1] += percentile( &posteriors[li*signatureSize], signatureSize, GET_UPPER_QUANT_PERC() );
|
||||
}
|
||||
fbounds[0] /= totalLeavesCount;
|
||||
fbounds[1] /= totalLeavesCount;
|
||||
|
||||
quantizedPosteriors.resize( posteriors.size() );
|
||||
quantizeVector( &posteriors[0], posteriors.size(), fbounds, ubounds, &quantizedPosteriors[0] );
|
||||
|
||||
if( isClearFloatPosteriors )
|
||||
clearFloatPosteriors();
|
||||
}
|
||||
|
||||
void CalonderClassifier::clearFloatPosteriors()
|
||||
{
|
||||
quantizedPosteriors.clear();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void CalonderClassifier::operator()( const Mat& img, Point2f pt, vector<float>& signature, float thresh ) const
|
||||
{
|
||||
if( img.empty() || img.type() != CV_8UC1 )
|
||||
return;
|
||||
|
||||
Mat patch;
|
||||
getRectSubPix(img, Size(patchSize,patchSize), pt, patch, img.type());
|
||||
(*this)( patch, signature, thresh );
|
||||
}
|
||||
|
||||
void CalonderClassifier::operator()( const Mat& patch, vector<float>& signature, float thresh ) const
|
||||
{
|
||||
if( posteriors.empty() || patch.empty() || patch.type() != CV_8UC1 || patch.cols < patchSize || patch.rows < patchSize )
|
||||
return;
|
||||
|
||||
int treePostSize = numLeavesPerTree*signatureSize;
|
||||
|
||||
signature.resize( signatureSize, 0.f );
|
||||
float* sig = &signature[0];
|
||||
for( int ti = 0; ti < numTrees; ti++ )
|
||||
{
|
||||
int leafIdx = getLeafIdx( ti, patch );
|
||||
const float* post = &posteriors[ti*treePostSize + leafIdx*signatureSize];
|
||||
for( int ci = 0; ci < signatureSize; ci++ )
|
||||
sig[ci] += post[ci];
|
||||
}
|
||||
float coef = 1.f/numTrees;
|
||||
for( int ci = 0; ci < signatureSize; ci++ )
|
||||
{
|
||||
sig[ci] *= coef;
|
||||
if( sig[ci] < thresh )
|
||||
sig[ci] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if QUANTIZATION_AVAILABLE
|
||||
void CalonderClassifier::operator()( const Mat& img, Point2f pt, vector<uchar>& signature, uchar thresh ) const
|
||||
{
|
||||
if( img.empty() || img.type() != CV_8UC1 )
|
||||
return;
|
||||
|
||||
Mat patch;
|
||||
getRectSubPix(img, Size(patchSize,patchSize), pt, patch, img.type());
|
||||
(*this)(patch, signature, thresh );
|
||||
}
|
||||
|
||||
void CalonderClassifier::operator()( const Mat& patch, vector<uchar>& signature, uchar thresh ) const
|
||||
{
|
||||
if( quantizedPosteriors.empty() || patch.empty() || patch.type() != CV_8UC1 || patch.cols > patchSize || patch.rows > patchSize )
|
||||
return;
|
||||
|
||||
int treePostSize = numLeavesPerTree*signatureSize;
|
||||
|
||||
vector<float> sum( signatureSize, 0.f );
|
||||
for( int ti = 0; ti < numTrees; ti++ )
|
||||
{
|
||||
int leafIdx = getLeafIdx( ti, patch );
|
||||
const uchar* post = &quantizedPosteriors[ti*treePostSize + leafIdx*signatureSize];
|
||||
for( int ci = 0; ci < signatureSize; ci++ )
|
||||
sum[ci] += post[ci];
|
||||
}
|
||||
float coef = 1.f/numTrees;
|
||||
signature.resize( signatureSize );
|
||||
uchar* sig = &signature[0];
|
||||
for( int ci = 0; ci < signatureSize; ci++ )
|
||||
{
|
||||
sig[ci] = (uchar)(sum[ci]*coef);
|
||||
if( sig[ci] < thresh )
|
||||
sig[ci] = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void CalonderClassifier::read( const FileNode& fn )
|
||||
{
|
||||
prepare( fn["patchSize"], fn["signatureSize"], fn["numTrees"], fn["treeDepth"], fn["numViews"] );
|
||||
origNumClasses = fn["origNumClasses"];
|
||||
compressType = fn["compressType"];
|
||||
int _numQuantBits = fn["numQuantBits"];
|
||||
|
||||
for( int ti = 0; ti < numTrees; ti++ )
|
||||
{
|
||||
stringstream treeName;
|
||||
treeName << "tree" << ti;
|
||||
FileNode treeFN = fn["trees"][treeName.str()];
|
||||
|
||||
Node* treeNodes = &nodes[ti*numNodesPerTree];
|
||||
FileNodeIterator nodesFNIter = treeFN["nodes"].begin();
|
||||
for( int ni = 0; ni < numNodesPerTree; ni++ )
|
||||
{
|
||||
Node* node = treeNodes + ni;
|
||||
nodesFNIter >> node->x1 >> node->y1 >> node->x2 >> node->y2;
|
||||
}
|
||||
|
||||
FileNode posteriorsFN = treeFN["posteriors"];
|
||||
for( int li = 0; li < numLeavesPerTree; li++ )
|
||||
{
|
||||
stringstream leafName;
|
||||
leafName << "leaf" << li;
|
||||
float* post = &posteriors[ti*numLeavesPerTree*signatureSize + li*signatureSize];
|
||||
FileNodeIterator leafFNIter = posteriorsFN[leafName.str()].begin();
|
||||
for( int ci = 0; ci < signatureSize; ci++ )
|
||||
leafFNIter >> post[ci];
|
||||
}
|
||||
}
|
||||
#if QUANTIZATION_AVAILABLE
|
||||
if( _numQuantBits )
|
||||
quantizePosteriors(_numQuantBits);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CalonderClassifier::write( FileStorage& fs ) const
|
||||
{
|
||||
if( !fs.isOpened() )
|
||||
return;
|
||||
fs << "patchSize" << patchSize;
|
||||
fs << "numTrees" << numTrees;
|
||||
fs << "treeDepth" << treeDepth;
|
||||
fs << "numViews" << numViews;
|
||||
fs << "origNumClasses" << origNumClasses;
|
||||
fs << "signatureSize" << signatureSize;
|
||||
fs << "compressType" << compressType;
|
||||
fs << "numQuantBits" << numQuantBits;
|
||||
|
||||
fs << "trees" << "{";
|
||||
for( int ti = 0; ti < numTrees; ti++ )
|
||||
{
|
||||
stringstream treeName;
|
||||
treeName << "tree" << ti;
|
||||
fs << treeName.str() << "{";
|
||||
|
||||
fs << "nodes" << "[:";
|
||||
const Node* treeNodes = &nodes[ti*numNodesPerTree];
|
||||
for( int ni = 0; ni < numNodesPerTree; ni++ )
|
||||
{
|
||||
const Node* node = treeNodes + ni;
|
||||
fs << node->x1 << node->y1 << node->x2 << node->y2;
|
||||
}
|
||||
fs << "]"; // nodes
|
||||
|
||||
fs << "posteriors" << "{";
|
||||
for( int li = 0; li < numLeavesPerTree; li++ )
|
||||
{
|
||||
stringstream leafName;
|
||||
leafName << "leaf" << li;
|
||||
fs << leafName.str() << "[:";
|
||||
const float* post = &posteriors[ti*numLeavesPerTree*signatureSize + li*signatureSize];
|
||||
for( int ci = 0; ci < signatureSize; ci++ )
|
||||
{
|
||||
fs << post[ci];
|
||||
}
|
||||
fs << "]"; // leaf
|
||||
}
|
||||
fs << "}"; // posteriors
|
||||
fs << "}"; // tree
|
||||
}
|
||||
fs << "}"; // trees
|
||||
}
|
||||
|
||||
struct RTreeNode
|
||||
{
|
||||
short offset1, offset2;
|
||||
};
|
||||
|
||||
void CalonderClassifier::read( istream &is )
|
||||
{
|
||||
int _patchSize, _numTrees, _treeDepth, _numViews, _signatureSize, _origNumClasses, _numQuantBits, _compressType;
|
||||
|
||||
_patchSize = 32;
|
||||
_numViews = 0;
|
||||
_compressType = COMPRESS_DISTR_BERNOULLI;
|
||||
is.read((char*)(&_numTrees), sizeof(_numTrees));
|
||||
is.read((char*)(&_signatureSize), sizeof(_signatureSize));
|
||||
is.read((char*)(&_origNumClasses), sizeof(_origNumClasses));
|
||||
is.read((char*)(&_numQuantBits), sizeof(_numQuantBits));
|
||||
|
||||
// 1st tree
|
||||
int _classes;
|
||||
is.read((char*)(&_classes), sizeof(_classes));
|
||||
CV_Assert( _signatureSize == _classes );
|
||||
is.read((char*)(&_treeDepth), sizeof(_treeDepth));
|
||||
|
||||
prepare( _patchSize, _signatureSize, _numTrees, _treeDepth, _numViews );
|
||||
|
||||
origNumClasses = _origNumClasses;
|
||||
compressType = _compressType;
|
||||
|
||||
if( _numQuantBits>8 )
|
||||
{
|
||||
if( verbose )
|
||||
cout << "[WARNING] suspicious value numQuantBits=" << numQuantBits << " found; setting to " << DEFAULT_NUM_QUANT_BITS;
|
||||
_numQuantBits = DEFAULT_NUM_QUANT_BITS;
|
||||
}
|
||||
|
||||
// 1st tree
|
||||
vector<RTreeNode> rtreeNodes(numNodesPerTree);
|
||||
is.read((char*)(&rtreeNodes[0]), numNodesPerTree * sizeof(rtreeNodes[0]));
|
||||
for( int ni = 0; ni < numNodesPerTree; ni ++ )
|
||||
{
|
||||
short offset1 = rtreeNodes[ni].offset1,
|
||||
offset2 = rtreeNodes[ni].offset2;
|
||||
nodes[ni] = Node(offset1 % _patchSize, offset1 / _patchSize, offset2 % _patchSize, offset2 / _patchSize );
|
||||
}
|
||||
for( int li = 0; li < numLeavesPerTree; li++ )
|
||||
is.read((char*)&posteriors[li*signatureSize], signatureSize * sizeof(float));
|
||||
|
||||
// other trees
|
||||
for( int treeIdx = 1; treeIdx < numTrees; treeIdx++ )
|
||||
{
|
||||
is.read((char*)(&_classes), sizeof(_classes));
|
||||
CV_Assert( _classes == signatureSize );
|
||||
is.read((char*)(&_treeDepth), sizeof(_treeDepth));
|
||||
CV_Assert( _treeDepth == treeDepth );
|
||||
|
||||
is.read((char*)(&rtreeNodes[0]), numNodesPerTree * sizeof(rtreeNodes[0]));
|
||||
|
||||
Node* treeNodes = &nodes[treeIdx*numNodesPerTree];
|
||||
for( int ni = 0; ni < numNodesPerTree; ni ++ )
|
||||
{
|
||||
short offset1 = rtreeNodes[ni].offset1,
|
||||
offset2 = rtreeNodes[ni].offset2;
|
||||
treeNodes[ni] = Node(offset1 % _patchSize, offset1 / _patchSize, offset2 % _patchSize, offset2 / _patchSize );
|
||||
}
|
||||
float* treePosteriors = &posteriors[treeIdx*numLeavesPerTree*signatureSize];
|
||||
for( int li = 0; li < numLeavesPerTree; li++ )
|
||||
is.read((char*)&treePosteriors[li*signatureSize], signatureSize * sizeof(float));
|
||||
|
||||
}
|
||||
|
||||
#if QUANTIZATION_AVAILABLE
|
||||
if( _numQuantBits )
|
||||
quantizePosteriors(_numQuantBits);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
502
modules/features2d/src/evaluation.cpp
Normal file
502
modules/features2d/src/evaluation.cpp
Normal file
@ -0,0 +1,502 @@
|
||||
//*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include <limits>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
inline Point2f applyHomography( const Mat_<double>& H, const Point2f& pt )
|
||||
{
|
||||
double z = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2);
|
||||
if( z )
|
||||
{
|
||||
double w = 1./z;
|
||||
return Point2f( (H(0,0)*pt.x + H(0,1)*pt.y + H(0,2))*w, (H(1,0)*pt.x + H(1,1)*pt.y + H(1,2))*w );
|
||||
}
|
||||
return Point2f( numeric_limits<double>::max(), numeric_limits<double>::max() );
|
||||
}
|
||||
|
||||
inline void linearizeHomographyAt( const Mat_<double>& H, const Point2f& pt, Mat_<double>& A )
|
||||
{
|
||||
A.create(2,2);
|
||||
double p1 = H(0,0)*pt.x + H(0,1)*pt.y + H(0,2),
|
||||
p2 = H(1,0)*pt.x + H(1,1)*pt.y + H(1,2),
|
||||
p3 = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2),
|
||||
p3_2 = p3*p3;
|
||||
if( p3 )
|
||||
{
|
||||
A(0,0) = H(0,0)/p3 - p1*H(2,0)/p3_2; // fxdx
|
||||
A(0,1) = H(0,1)/p3 - p1*H(2,1)/p3_2; // fxdy
|
||||
|
||||
A(1,0) = H(1,0)/p3 - p2*H(2,0)/p3_2; // fydx
|
||||
A(1,1) = H(1,1)/p3 - p2*H(2,1)/p3_2; // fydx
|
||||
}
|
||||
else
|
||||
A.setTo(Scalar::all(numeric_limits<double>::max()));
|
||||
}
|
||||
|
||||
class EllipticKeyPoint
|
||||
{
|
||||
public:
|
||||
EllipticKeyPoint();
|
||||
EllipticKeyPoint( const Point2f& _center, const Scalar& _ellipse );
|
||||
|
||||
static void convert( const vector<KeyPoint>& src, vector<EllipticKeyPoint>& dst );
|
||||
static void convert( const vector<EllipticKeyPoint>& src, vector<KeyPoint>& dst );
|
||||
|
||||
static Mat_<double> getSecondMomentsMatrix( const Scalar& _ellipse );
|
||||
Mat_<double> getSecondMomentsMatrix() const;
|
||||
|
||||
void calcProjection( const Mat_<double>& H, EllipticKeyPoint& projection ) const;
|
||||
static void calcProjection( const vector<EllipticKeyPoint>& src, const Mat_<double>& H, vector<EllipticKeyPoint>& dst );
|
||||
|
||||
Point2f center;
|
||||
Scalar ellipse; // 3 elements a, b, c: ax^2+2bxy+cy^2=1
|
||||
Size_<float> axes; // half lenght of elipse axes
|
||||
Size_<float> boundingBox; // half sizes of bounding box which sides are parallel to the coordinate axes
|
||||
};
|
||||
|
||||
EllipticKeyPoint::EllipticKeyPoint()
|
||||
{
|
||||
*this = EllipticKeyPoint(Point2f(0,0), Scalar(1, 0, 1) );
|
||||
}
|
||||
|
||||
EllipticKeyPoint::EllipticKeyPoint( const Point2f& _center, const Scalar& _ellipse )
|
||||
{
|
||||
center = _center;
|
||||
ellipse = _ellipse;
|
||||
|
||||
Mat_<double> M = getSecondMomentsMatrix(_ellipse), eval;
|
||||
eigen( M, eval );
|
||||
assert( eval.rows == 2 && eval.cols == 1 );
|
||||
axes.width = 1.f / sqrt(eval(0,0));
|
||||
axes.height = 1.f / sqrt(eval(1,0));
|
||||
|
||||
float ac_b2 = ellipse[0]*ellipse[2] - ellipse[1]*ellipse[1];
|
||||
boundingBox.width = sqrt(ellipse[2]/ac_b2);
|
||||
boundingBox.height = sqrt(ellipse[0]/ac_b2);
|
||||
}
|
||||
|
||||
Mat_<double> EllipticKeyPoint::getSecondMomentsMatrix( const Scalar& _ellipse )
|
||||
{
|
||||
Mat_<double> M(2, 2);
|
||||
M(0,0) = _ellipse[0];
|
||||
M(1,0) = M(0,1) = _ellipse[1];
|
||||
M(1,1) = _ellipse[2];
|
||||
return M;
|
||||
}
|
||||
|
||||
Mat_<double> EllipticKeyPoint::getSecondMomentsMatrix() const
|
||||
{
|
||||
return getSecondMomentsMatrix(ellipse);
|
||||
}
|
||||
|
||||
void EllipticKeyPoint::calcProjection( const Mat_<double>& H, EllipticKeyPoint& projection ) const
|
||||
{
|
||||
Point2f dstCenter = applyHomography(H, center);
|
||||
|
||||
Mat_<double> invM; invert(getSecondMomentsMatrix(), invM);
|
||||
Mat_<double> Aff; linearizeHomographyAt(H, center, Aff);
|
||||
Mat_<double> dstM; invert(Aff*invM*Aff.t(), dstM);
|
||||
|
||||
projection = EllipticKeyPoint( dstCenter, Scalar(dstM(0,0), dstM(0,1), dstM(1,1)) );
|
||||
}
|
||||
|
||||
void EllipticKeyPoint::convert( const vector<KeyPoint>& src, vector<EllipticKeyPoint>& dst )
|
||||
{
|
||||
if( !src.empty() )
|
||||
{
|
||||
dst.resize(src.size());
|
||||
for( size_t i = 0; i < src.size(); i++ )
|
||||
{
|
||||
float rad = src[i].size/2;
|
||||
assert( rad );
|
||||
float fac = 1.f/(rad*rad);
|
||||
dst[i] = EllipticKeyPoint( src[i].pt, Scalar(fac, 0, fac) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EllipticKeyPoint::convert( const vector<EllipticKeyPoint>& src, vector<KeyPoint>& dst )
|
||||
{
|
||||
if( !src.empty() )
|
||||
{
|
||||
dst.resize(src.size());
|
||||
for( size_t i = 0; i < src.size(); i++ )
|
||||
{
|
||||
Size_<float> axes = src[i].axes;
|
||||
float rad = sqrt(axes.height*axes.width);
|
||||
dst[i] = KeyPoint(src[i].center, 2*rad );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EllipticKeyPoint::calcProjection( const vector<EllipticKeyPoint>& src, const Mat_<double>& H, vector<EllipticKeyPoint>& dst )
|
||||
{
|
||||
if( !src.empty() )
|
||||
{
|
||||
assert( !H.empty() && H.cols == 3 && H.rows == 3);
|
||||
dst.resize(src.size());
|
||||
vector<EllipticKeyPoint>::const_iterator srcIt = src.begin();
|
||||
vector<EllipticKeyPoint>::iterator dstIt = dst.begin();
|
||||
for( ; srcIt != src.end(); ++srcIt, ++dstIt )
|
||||
srcIt->calcProjection(H, *dstIt);
|
||||
}
|
||||
}
|
||||
|
||||
static void filterEllipticKeyPointsByImageSize( vector<EllipticKeyPoint>& keypoints, const Size& imgSize )
|
||||
{
|
||||
if( !keypoints.empty() )
|
||||
{
|
||||
vector<EllipticKeyPoint> filtered;
|
||||
filtered.reserve(keypoints.size());
|
||||
vector<EllipticKeyPoint>::const_iterator it = keypoints.begin();
|
||||
for( int i = 0; it != keypoints.end(); ++it, i++ )
|
||||
{
|
||||
if( it->center.x + it->boundingBox.width < imgSize.width &&
|
||||
it->center.x - it->boundingBox.width > 0 &&
|
||||
it->center.y + it->boundingBox.height < imgSize.height &&
|
||||
it->center.y - it->boundingBox.height > 0 )
|
||||
filtered.push_back(*it);
|
||||
}
|
||||
keypoints.assign(filtered.begin(), filtered.end());
|
||||
}
|
||||
}
|
||||
|
||||
static void overlap( const vector<EllipticKeyPoint>& keypoints1, const vector<EllipticKeyPoint>& keypoints2t, bool commonPart,
|
||||
SparseMat_<float>& overlaps )
|
||||
{
|
||||
overlaps.clear();
|
||||
if( keypoints1.empty() || keypoints2t.empty() )
|
||||
return;
|
||||
|
||||
int size[] = { keypoints1.size(), keypoints2t.size() };
|
||||
overlaps.create( 2, size );
|
||||
|
||||
for( size_t i1 = 0; i1 < keypoints1.size(); i1++ )
|
||||
{
|
||||
EllipticKeyPoint kp1 = keypoints1[i1];
|
||||
float maxDist = sqrt(kp1.axes.width*kp1.axes.height),
|
||||
fac = 30.f/maxDist;
|
||||
if( !commonPart )
|
||||
fac=3;
|
||||
|
||||
maxDist = maxDist*4;
|
||||
fac = 1.0/(fac*fac);
|
||||
|
||||
EllipticKeyPoint keypoint1a = EllipticKeyPoint( kp1.center, Scalar(fac*kp1.ellipse[0], fac*kp1.ellipse[1], fac*kp1.ellipse[2]) );
|
||||
|
||||
for( size_t i2 = 0; i2 < keypoints2t.size(); i2++ )
|
||||
{
|
||||
EllipticKeyPoint kp2 = keypoints2t[i2];
|
||||
Point2f diff = kp2.center - kp1.center;
|
||||
|
||||
if( norm(diff) < maxDist )
|
||||
{
|
||||
EllipticKeyPoint keypoint2a = EllipticKeyPoint( kp2.center, Scalar(fac*kp2.ellipse[0], fac*kp2.ellipse[1], fac*kp2.ellipse[2]) );
|
||||
//find the largest eigenvalue
|
||||
float maxx = ceil(( keypoint1a.boundingBox.width > (diff.x+keypoint2a.boundingBox.width)) ?
|
||||
keypoint1a.boundingBox.width : (diff.x+keypoint2a.boundingBox.width));
|
||||
float minx = floor((-keypoint1a.boundingBox.width < (diff.x-keypoint2a.boundingBox.width)) ?
|
||||
-keypoint1a.boundingBox.width : (diff.x-keypoint2a.boundingBox.width));
|
||||
|
||||
float maxy = ceil(( keypoint1a.boundingBox.height > (diff.y+keypoint2a.boundingBox.height)) ?
|
||||
keypoint1a.boundingBox.height : (diff.y+keypoint2a.boundingBox.height));
|
||||
float miny = floor((-keypoint1a.boundingBox.height < (diff.y-keypoint2a.boundingBox.height)) ?
|
||||
-keypoint1a.boundingBox.height : (diff.y-keypoint2a.boundingBox.height));
|
||||
float mina = (maxx-minx) < (maxy-miny) ? (maxx-minx) : (maxy-miny) ;
|
||||
float dr = mina/50.0;
|
||||
float bua = 0, bna = 0;
|
||||
//compute the area
|
||||
for( float rx1 = minx; rx1 <= maxx; rx1+=dr )
|
||||
{
|
||||
float rx2 = rx1-diff.x;
|
||||
for( float ry1=miny; ry1<=maxy; ry1+=dr )
|
||||
{
|
||||
float ry2=ry1-diff.y;
|
||||
//compute the distance from the ellipse center
|
||||
float e1 = keypoint1a.ellipse[0]*rx1*rx1+2*keypoint1a.ellipse[1]*rx1*ry1+keypoint1a.ellipse[2]*ry1*ry1;
|
||||
float e2 = keypoint2a.ellipse[0]*rx2*rx2+2*keypoint2a.ellipse[1]*rx2*ry2+keypoint2a.ellipse[2]*ry2*ry2;
|
||||
//compute the area
|
||||
if( e1<1 && e2<1 ) bna++;
|
||||
if( e1<1 || e2<1 ) bua++;
|
||||
}
|
||||
}
|
||||
if( bna > 0)
|
||||
overlaps.ref(i1,i2) = bna/bua;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void calculateRepeatability( const Mat& img1, const Mat& img2, const Mat& H1to2,
|
||||
const vector<KeyPoint>& _keypoints1, const vector<KeyPoint>& _keypoints2,
|
||||
float& repeatability, int& correspondencesCount,
|
||||
SparseMat_<uchar>* thresholdedOverlapMask=0 )
|
||||
{
|
||||
vector<EllipticKeyPoint> keypoints1, keypoints2, keypoints1t, keypoints2t;
|
||||
EllipticKeyPoint::convert( _keypoints1, keypoints1 );
|
||||
EllipticKeyPoint::convert( _keypoints2, keypoints2 );
|
||||
|
||||
// calculate projections of key points
|
||||
EllipticKeyPoint::calcProjection( keypoints1, H1to2, keypoints1t );
|
||||
Mat H2to1; invert(H1to2, H2to1);
|
||||
EllipticKeyPoint::calcProjection( keypoints2, H2to1, keypoints2t );
|
||||
|
||||
bool ifEvaluateDetectors = !thresholdedOverlapMask; // == commonPart
|
||||
float overlapThreshold;
|
||||
if( ifEvaluateDetectors )
|
||||
{
|
||||
overlapThreshold = 1.f - 0.4f;
|
||||
|
||||
// remove key points from outside of the common image part
|
||||
Size sz1 = img1.size(), sz2 = img2.size();
|
||||
filterEllipticKeyPointsByImageSize( keypoints1, sz1 );
|
||||
filterEllipticKeyPointsByImageSize( keypoints1t, sz2 );
|
||||
filterEllipticKeyPointsByImageSize( keypoints2, sz2 );
|
||||
filterEllipticKeyPointsByImageSize( keypoints2t, sz1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
overlapThreshold = 1.f - 0.5f;
|
||||
}
|
||||
int minCount = min( keypoints1.size(), keypoints2t.size() );
|
||||
|
||||
// calculate overlap errors
|
||||
SparseMat_<float> overlaps;
|
||||
overlap( keypoints1, keypoints2t, ifEvaluateDetectors, overlaps );
|
||||
|
||||
correspondencesCount = -1;
|
||||
repeatability = -1.f;
|
||||
const int* size = overlaps.size();
|
||||
if( !size || overlaps.nzcount() == 0 )
|
||||
return;
|
||||
|
||||
if( ifEvaluateDetectors )
|
||||
{
|
||||
// threshold the overlaps
|
||||
for( int y = 0; y < size[0]; y++ )
|
||||
{
|
||||
for( int x = 0; x < size[1]; x++ )
|
||||
{
|
||||
if ( overlaps(y,x) < overlapThreshold )
|
||||
overlaps.erase(y,x);
|
||||
}
|
||||
}
|
||||
|
||||
// regions one-to-one matching
|
||||
correspondencesCount = 0;
|
||||
while( overlaps.nzcount() > 0 )
|
||||
{
|
||||
double maxOverlap = 0;
|
||||
int maxIdx[2];
|
||||
minMaxLoc( overlaps, 0, &maxOverlap, 0, maxIdx );
|
||||
for( size_t i1 = 0; i1 < keypoints1.size(); i1++ )
|
||||
overlaps.erase(i1, maxIdx[1]);
|
||||
for( size_t i2 = 0; i2 < keypoints2t.size(); i2++ )
|
||||
overlaps.erase(maxIdx[0], i2);
|
||||
correspondencesCount++;
|
||||
}
|
||||
repeatability = minCount ? (float)correspondencesCount/minCount : -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
thresholdedOverlapMask->create( 2, size );
|
||||
for( int y = 0; y < size[0]; y++ )
|
||||
{
|
||||
for( int x = 0; x < size[1]; x++ )
|
||||
{
|
||||
float val = overlaps(y,x);
|
||||
if ( val >= overlapThreshold )
|
||||
thresholdedOverlapMask->ref(y,x) = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cv::evaluateFeatureDetector( const Mat& img1, const Mat& img2, const Mat& H1to2,
|
||||
vector<KeyPoint>* _keypoints1, vector<KeyPoint>* _keypoints2,
|
||||
float& repeatability, int& correspCount,
|
||||
const Ptr<FeatureDetector>& _fdetector )
|
||||
{
|
||||
Ptr<FeatureDetector> fdetector(_fdetector);
|
||||
vector<KeyPoint> *keypoints1, *keypoints2, buf1, buf2;
|
||||
keypoints1 = _keypoints1 != 0 ? _keypoints1 : &buf1;
|
||||
keypoints2 = _keypoints2 != 0 ? _keypoints2 : &buf2;
|
||||
|
||||
if( (keypoints1->empty() || keypoints2->empty()) && fdetector.empty() )
|
||||
CV_Error( CV_StsBadArg, "fdetector must be no empty when keypoints1 or keypoints2 is empty" );
|
||||
|
||||
if( keypoints1->empty() )
|
||||
fdetector->detect( img1, *keypoints1 );
|
||||
if( keypoints2->empty() )
|
||||
fdetector->detect( img1, *keypoints2 );
|
||||
|
||||
calculateRepeatability( img1, img2, H1to2, *keypoints1, *keypoints2, repeatability, correspCount );
|
||||
}
|
||||
|
||||
struct DMatchForEvaluation : public DMatch
|
||||
{
|
||||
uchar isCorrect;
|
||||
DMatchForEvaluation( const DMatch &dm ) : DMatch( dm ) {}
|
||||
};
|
||||
|
||||
static inline float recall( int correctMatchCount, int correspondenceCount )
|
||||
{
|
||||
return correspondenceCount ? (float)correctMatchCount / (float)correspondenceCount : -1;
|
||||
}
|
||||
|
||||
static inline float precision( int correctMatchCount, int falseMatchCount )
|
||||
{
|
||||
return correctMatchCount + falseMatchCount ? (float)correctMatchCount / (float)(correctMatchCount + falseMatchCount) : -1;
|
||||
}
|
||||
|
||||
void cv::computeRecallPrecisionCurve( const vector<vector<DMatch> >& matches1to2,
|
||||
const vector<vector<uchar> >& correctMatches1to2Mask,
|
||||
vector<Point2f>& recallPrecisionCurve )
|
||||
{
|
||||
CV_Assert( matches1to2.size() == correctMatches1to2Mask.size() );
|
||||
|
||||
vector<DMatchForEvaluation> allMatches;
|
||||
int correspondenceCount = 0;
|
||||
for( size_t i = 0; i < matches1to2.size(); i++ )
|
||||
{
|
||||
for( size_t j = 0; j < matches1to2[i].size(); j++ )
|
||||
{
|
||||
DMatchForEvaluation match = matches1to2[i][j];
|
||||
match.isCorrect = correctMatches1to2Mask[i][j] ;
|
||||
allMatches.push_back( match );
|
||||
correspondenceCount += match.isCorrect != 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::sort( allMatches.begin(), allMatches.end() );
|
||||
|
||||
int correctMatchCount = 0, falseMatchCount = 0;
|
||||
recallPrecisionCurve.resize( allMatches.size() );
|
||||
for( size_t i = 0; i < allMatches.size(); i++ )
|
||||
{
|
||||
if( allMatches[i].isCorrect )
|
||||
correctMatchCount++;
|
||||
else
|
||||
falseMatchCount++;
|
||||
|
||||
float r = recall( correctMatchCount, correspondenceCount );
|
||||
float p = precision( correctMatchCount, falseMatchCount );
|
||||
recallPrecisionCurve[i] = Point2f(1-p, r);
|
||||
}
|
||||
}
|
||||
|
||||
float cv::getRecall( const vector<Point2f>& recallPrecisionCurve, float l_precision )
|
||||
{
|
||||
float recall = -1;
|
||||
|
||||
if( l_precision >= 0 && l_precision <= 1 )
|
||||
{
|
||||
int bestIdx = -1;
|
||||
float minDiff = FLT_MAX;
|
||||
for( size_t i = 0; i < recallPrecisionCurve.size(); i++ )
|
||||
{
|
||||
float curDiff = std::fabs(l_precision - recallPrecisionCurve[i].x);
|
||||
if( curDiff <= minDiff )
|
||||
{
|
||||
bestIdx = i;
|
||||
minDiff = curDiff;
|
||||
}
|
||||
}
|
||||
|
||||
recall = recallPrecisionCurve[bestIdx].y;
|
||||
}
|
||||
|
||||
return recall;
|
||||
}
|
||||
|
||||
void cv::evaluateDescriptorMatch( const Mat& img1, const Mat& img2, const Mat& H1to2,
|
||||
vector<KeyPoint>& keypoints1, vector<KeyPoint>& keypoints2,
|
||||
vector<vector<DMatch> >* _matches1to2, vector<vector<uchar> >* _correctMatches1to2Mask,
|
||||
vector<Point2f>& recallPrecisionCurve,
|
||||
const Ptr<GenericDescriptorMatch>& _dmatch )
|
||||
{
|
||||
Ptr<GenericDescriptorMatch> dmatch = _dmatch;
|
||||
dmatch->clear();
|
||||
|
||||
vector<vector<DMatch> > *matches1to2, buf1;
|
||||
vector<vector<uchar> > *correctMatches1to2Mask, buf2;
|
||||
matches1to2 = _matches1to2 != 0 ? _matches1to2 : &buf1;
|
||||
correctMatches1to2Mask = _correctMatches1to2Mask != 0 ? _correctMatches1to2Mask : &buf2;
|
||||
|
||||
if( keypoints1.empty() || keypoints2.empty() )
|
||||
CV_Error( CV_StsBadArg, "keypoints1 and keypoints2 must be no empty" );
|
||||
if( matches1to2->empty() && dmatch.empty() )
|
||||
CV_Error( CV_StsBadArg, "dmatch must be no empty when matches1to2 is empty" );
|
||||
if( matches1to2->empty() )
|
||||
{
|
||||
dmatch->add( img2, keypoints2 );
|
||||
//TODO: use more sophisticated strategy to choose threshold
|
||||
dmatch->match( img1, keypoints1, *matches1to2, std::numeric_limits<float>::max() );
|
||||
}
|
||||
float repeatability;
|
||||
int correspCount;
|
||||
SparseMat_<uchar> thresholdedOverlapMask; // thresholded allOverlapErrors
|
||||
calculateRepeatability( img1, img2, H1to2,
|
||||
keypoints1, keypoints2,
|
||||
repeatability, correspCount,
|
||||
&thresholdedOverlapMask );
|
||||
|
||||
correctMatches1to2Mask->resize(matches1to2->size());
|
||||
int ddd = 0;
|
||||
for( size_t i = 0; i < matches1to2->size(); i++ )
|
||||
{
|
||||
(*correctMatches1to2Mask)[i].resize((*matches1to2)[i].size());
|
||||
for( size_t j = 0;j < (*matches1to2)[i].size(); j++ )
|
||||
{
|
||||
int indexQuery = (*matches1to2)[i][j].indexQuery;
|
||||
int indexTrain = (*matches1to2)[i][j].indexTrain;
|
||||
(*correctMatches1to2Mask)[i][j] = thresholdedOverlapMask( indexQuery, indexTrain );
|
||||
ddd += thresholdedOverlapMask( indexQuery, indexTrain ) != 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
computeRecallPrecisionCurve( *matches1to2, *correctMatches1to2Mask, recallPrecisionCurve );
|
||||
}
|
@ -51,143 +51,9 @@ using namespace cv;
|
||||
/****************************************************************************************\
|
||||
* Functions to evaluate affine covariant detectors and descriptors. *
|
||||
\****************************************************************************************/
|
||||
inline Point2f applyHomography( const Mat_<double>& H, const Point2f& pt )
|
||||
{
|
||||
double z = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2);
|
||||
if( z )
|
||||
{
|
||||
double w = 1./z;
|
||||
return Point2f( (H(0,0)*pt.x + H(0,1)*pt.y + H(0,2))*w, (H(1,0)*pt.x + H(1,1)*pt.y + H(1,2))*w );
|
||||
}
|
||||
return Point2f( numeric_limits<double>::max(), numeric_limits<double>::max() );
|
||||
}
|
||||
|
||||
inline void linearizeHomographyAt( const Mat_<double>& H, const Point2f& pt, Mat_<double>& A )
|
||||
{
|
||||
A.create(2,2);
|
||||
double p1 = H(0,0)*pt.x + H(0,1)*pt.y + H(0,2),
|
||||
p2 = H(1,0)*pt.x + H(1,1)*pt.y + H(1,2),
|
||||
p3 = H(2,0)*pt.x + H(2,1)*pt.y + H(2,2),
|
||||
p3_2 = p3*p3;
|
||||
if( p3 )
|
||||
{
|
||||
A(0,0) = H(0,0)/p3 - p1*H(2,0)/p3_2; // fxdx
|
||||
A(0,1) = H(0,1)/p3 - p1*H(2,1)/p3_2; // fxdy
|
||||
|
||||
A(1,0) = H(1,0)/p3 - p2*H(2,0)/p3_2; // fydx
|
||||
A(1,1) = H(1,1)/p3 - p2*H(2,1)/p3_2; // fydx
|
||||
}
|
||||
else
|
||||
A.setTo(Scalar::all(numeric_limits<double>::max()));
|
||||
}
|
||||
|
||||
class EllipticKeyPoint
|
||||
{
|
||||
public:
|
||||
EllipticKeyPoint();
|
||||
EllipticKeyPoint( const Point2f& _center, const Scalar& _ellipse );
|
||||
|
||||
static void convert( const vector<KeyPoint>& src, vector<EllipticKeyPoint>& dst );
|
||||
static void convert( const vector<EllipticKeyPoint>& src, vector<KeyPoint>& dst );
|
||||
|
||||
static Mat_<double> getSecondMomentsMatrix( const Scalar& _ellipse );
|
||||
Mat_<double> getSecondMomentsMatrix() const;
|
||||
|
||||
void calcProjection( const Mat_<double>& H, EllipticKeyPoint& projection ) const;
|
||||
|
||||
Point2f center;
|
||||
Scalar ellipse; // 3 elements a, b, c: ax^2+2bxy+cy^2=1
|
||||
Size_<float> axes; // half lenght of elipse axes
|
||||
Size_<float> boundingBox; // half sizes of bounding box which sides are parallel to the coordinate axes
|
||||
};
|
||||
|
||||
EllipticKeyPoint::EllipticKeyPoint()
|
||||
{
|
||||
*this = EllipticKeyPoint(Point2f(0,0), Scalar(1, 0, 1) );
|
||||
}
|
||||
|
||||
EllipticKeyPoint::EllipticKeyPoint( const Point2f& _center, const Scalar& _ellipse )
|
||||
{
|
||||
center = _center;
|
||||
ellipse = _ellipse;
|
||||
|
||||
Mat_<double> M = getSecondMomentsMatrix(_ellipse), eval;
|
||||
eigen( M, eval );
|
||||
assert( eval.rows == 2 && eval.cols == 1 );
|
||||
axes.width = 1.f / sqrt(eval(0,0));
|
||||
axes.height = 1.f / sqrt(eval(1,0));
|
||||
|
||||
float ac_b2 = ellipse[0]*ellipse[2] - ellipse[1]*ellipse[1];
|
||||
boundingBox.width = sqrt(ellipse[2]/ac_b2);
|
||||
boundingBox.height = sqrt(ellipse[0]/ac_b2);
|
||||
}
|
||||
|
||||
Mat_<double> EllipticKeyPoint::getSecondMomentsMatrix( const Scalar& _ellipse )
|
||||
{
|
||||
Mat_<double> M(2, 2);
|
||||
M(0,0) = _ellipse[0];
|
||||
M(1,0) = M(0,1) = _ellipse[1];
|
||||
M(1,1) = _ellipse[2];
|
||||
return M;
|
||||
}
|
||||
|
||||
Mat_<double> EllipticKeyPoint::getSecondMomentsMatrix() const
|
||||
{
|
||||
return getSecondMomentsMatrix(ellipse);
|
||||
}
|
||||
|
||||
void EllipticKeyPoint::calcProjection( const Mat_<double>& H, EllipticKeyPoint& projection ) const
|
||||
{
|
||||
Point2f dstCenter = applyHomography(H, center);
|
||||
|
||||
Mat_<double> invM; invert(getSecondMomentsMatrix(), invM);
|
||||
Mat_<double> Aff; linearizeHomographyAt(H, center, Aff);
|
||||
Mat_<double> dstM; invert(Aff*invM*Aff.t(), dstM);
|
||||
|
||||
projection = EllipticKeyPoint( dstCenter, Scalar(dstM(0,0), dstM(0,1), dstM(1,1)) );
|
||||
}
|
||||
|
||||
void EllipticKeyPoint::convert( const vector<KeyPoint>& src, vector<EllipticKeyPoint>& dst )
|
||||
{
|
||||
if( !src.empty() )
|
||||
{
|
||||
dst.resize(src.size());
|
||||
for( size_t i = 0; i < src.size(); i++ )
|
||||
{
|
||||
float rad = src[i].size/2;
|
||||
assert( rad );
|
||||
float fac = 1.f/(rad*rad);
|
||||
dst[i] = EllipticKeyPoint( src[i].pt, Scalar(fac, 0, fac) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EllipticKeyPoint::convert( const vector<EllipticKeyPoint>& src, vector<KeyPoint>& dst )
|
||||
{
|
||||
if( !src.empty() )
|
||||
{
|
||||
dst.resize(src.size());
|
||||
for( size_t i = 0; i < src.size(); i++ )
|
||||
{
|
||||
Size_<float> axes = src[i].axes;
|
||||
float rad = sqrt(axes.height*axes.width);
|
||||
dst[i] = KeyPoint(src[i].center, 2*rad );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void calcEllipticKeyPointProjections( const vector<EllipticKeyPoint>& src, const Mat_<double>& H, vector<EllipticKeyPoint>& dst )
|
||||
{
|
||||
if( !src.empty() )
|
||||
{
|
||||
assert( !H.empty() && H.cols == 3 && H.rows == 3);
|
||||
dst.resize(src.size());
|
||||
vector<EllipticKeyPoint>::const_iterator srcIt = src.begin();
|
||||
vector<EllipticKeyPoint>::iterator dstIt = dst.begin();
|
||||
for( ; srcIt != src.end(); ++srcIt, ++dstIt )
|
||||
srcIt->calcProjection(H, *dstIt);
|
||||
}
|
||||
}
|
||||
Point2f applyHomography( const Mat_<double>& H, const Point2f& pt );
|
||||
void linearizeHomographyAt( const Mat_<double>& H, const Point2f& pt, Mat_<double>& A );
|
||||
|
||||
void calcKeyPointProjections( const vector<KeyPoint>& src, const Mat_<double>& H, vector<KeyPoint>& dst )
|
||||
{
|
||||
@ -202,7 +68,10 @@ void calcKeyPointProjections( const vector<KeyPoint>& src, const Mat_<double>& H
|
||||
Point2f dstPt = applyHomography(H, srcIt->pt);
|
||||
|
||||
float srcSize2 = srcIt->size * srcIt->size;
|
||||
Mat_<double> invM; invert(EllipticKeyPoint::getSecondMomentsMatrix( Scalar(1./srcSize2, 0., 1./srcSize2)), invM);
|
||||
Mat_<double> M(2, 2);
|
||||
M(0,0) = M(1,1) = 1./srcSize2;
|
||||
M(1,0) = M(0,1) = 0;
|
||||
Mat_<double> invM; invert(M, invM);
|
||||
Mat_<double> Aff; linearizeHomographyAt(H, srcIt->pt, Aff);
|
||||
Mat_<double> dstM; invert(Aff*invM*Aff.t(), dstM);
|
||||
Mat_<double> eval; eigen( dstM, eval );
|
||||
@ -236,261 +105,6 @@ void filterKeyPointsByImageSize( vector<KeyPoint>& keypoints, const Size& imgSiz
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* calulate ovelap errors
|
||||
*/
|
||||
void overlap( const vector<EllipticKeyPoint>& keypoints1, const vector<EllipticKeyPoint>& keypoints2t, bool commonPart,
|
||||
SparseMat_<float>& overlaps )
|
||||
{
|
||||
overlaps.clear();
|
||||
if( keypoints1.empty() || keypoints2t.empty() )
|
||||
return;
|
||||
|
||||
int size[] = { keypoints1.size(), keypoints2t.size() };
|
||||
overlaps.create( 2, size );
|
||||
|
||||
for( size_t i1 = 0; i1 < keypoints1.size(); i1++ )
|
||||
{
|
||||
EllipticKeyPoint kp1 = keypoints1[i1];
|
||||
float maxDist = sqrt(kp1.axes.width*kp1.axes.height),
|
||||
fac = 30.f/maxDist;
|
||||
if( !commonPart )
|
||||
fac=3;
|
||||
|
||||
maxDist = maxDist*4;
|
||||
fac = 1.0/(fac*fac);
|
||||
|
||||
EllipticKeyPoint keypoint1a = EllipticKeyPoint( kp1.center, Scalar(fac*kp1.ellipse[0], fac*kp1.ellipse[1], fac*kp1.ellipse[2]) );
|
||||
|
||||
for( size_t i2 = 0; i2 < keypoints2t.size(); i2++ )
|
||||
{
|
||||
EllipticKeyPoint kp2 = keypoints2t[i2];
|
||||
Point2f diff = kp2.center - kp1.center;
|
||||
|
||||
if( norm(diff) < maxDist )
|
||||
{
|
||||
EllipticKeyPoint keypoint2a = EllipticKeyPoint( kp2.center, Scalar(fac*kp2.ellipse[0], fac*kp2.ellipse[1], fac*kp2.ellipse[2]) );
|
||||
//find the largest eigenvalue
|
||||
float maxx = ceil(( keypoint1a.boundingBox.width > (diff.x+keypoint2a.boundingBox.width)) ?
|
||||
keypoint1a.boundingBox.width : (diff.x+keypoint2a.boundingBox.width));
|
||||
float minx = floor((-keypoint1a.boundingBox.width < (diff.x-keypoint2a.boundingBox.width)) ?
|
||||
-keypoint1a.boundingBox.width : (diff.x-keypoint2a.boundingBox.width));
|
||||
|
||||
float maxy = ceil(( keypoint1a.boundingBox.height > (diff.y+keypoint2a.boundingBox.height)) ?
|
||||
keypoint1a.boundingBox.height : (diff.y+keypoint2a.boundingBox.height));
|
||||
float miny = floor((-keypoint1a.boundingBox.height < (diff.y-keypoint2a.boundingBox.height)) ?
|
||||
-keypoint1a.boundingBox.height : (diff.y-keypoint2a.boundingBox.height));
|
||||
float mina = (maxx-minx) < (maxy-miny) ? (maxx-minx) : (maxy-miny) ;
|
||||
float dr = mina/50.0;
|
||||
float bua = 0, bna = 0;
|
||||
//compute the area
|
||||
for( float rx1 = minx; rx1 <= maxx; rx1+=dr )
|
||||
{
|
||||
float rx2 = rx1-diff.x;
|
||||
for( float ry1=miny; ry1<=maxy; ry1+=dr )
|
||||
{
|
||||
float ry2=ry1-diff.y;
|
||||
//compute the distance from the ellipse center
|
||||
float e1 = keypoint1a.ellipse[0]*rx1*rx1+2*keypoint1a.ellipse[1]*rx1*ry1+keypoint1a.ellipse[2]*ry1*ry1;
|
||||
float e2 = keypoint2a.ellipse[0]*rx2*rx2+2*keypoint2a.ellipse[1]*rx2*ry2+keypoint2a.ellipse[2]*ry2*ry2;
|
||||
//compute the area
|
||||
if( e1<1 && e2<1 ) bna++;
|
||||
if( e1<1 || e2<1 ) bua++;
|
||||
}
|
||||
}
|
||||
if( bna > 0)
|
||||
overlaps.ref(i1,i2) = 100.0*bna/bua;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void filterEllipticKeyPointsByImageSize( vector<EllipticKeyPoint>& keypoints, const Size& imgSize )
|
||||
{
|
||||
if( !keypoints.empty() )
|
||||
{
|
||||
vector<EllipticKeyPoint> filtered;
|
||||
filtered.reserve(keypoints.size());
|
||||
vector<EllipticKeyPoint>::const_iterator it = keypoints.begin();
|
||||
for( int i = 0; it != keypoints.end(); ++it, i++ )
|
||||
{
|
||||
if( it->center.x + it->boundingBox.width < imgSize.width &&
|
||||
it->center.x - it->boundingBox.width > 0 &&
|
||||
it->center.y + it->boundingBox.height < imgSize.height &&
|
||||
it->center.y - it->boundingBox.height > 0 )
|
||||
filtered.push_back(*it);
|
||||
}
|
||||
keypoints.assign(filtered.begin(), filtered.end());
|
||||
}
|
||||
}
|
||||
|
||||
void getEllipticKeyPointsInCommonPart( vector<EllipticKeyPoint>& keypoints1, vector<EllipticKeyPoint>& keypoints2,
|
||||
vector<EllipticKeyPoint>& keypoints1t, vector<EllipticKeyPoint>& keypoints2t,
|
||||
Size& imgSize1, const Size& imgSize2 )
|
||||
{
|
||||
filterEllipticKeyPointsByImageSize( keypoints1, imgSize1 );
|
||||
filterEllipticKeyPointsByImageSize( keypoints1t, imgSize2 );
|
||||
filterEllipticKeyPointsByImageSize( keypoints2, imgSize2 );
|
||||
filterEllipticKeyPointsByImageSize( keypoints2t, imgSize1 );
|
||||
}
|
||||
|
||||
void calculateRepeatability( const vector<EllipticKeyPoint>& _keypoints1, const vector<EllipticKeyPoint>& _keypoints2,
|
||||
const Mat& img1, const Mat& img2, const Mat& H1to2,
|
||||
float& repeatability, int& correspondencesCount,
|
||||
SparseMat_<uchar>* thresholdedOverlapMask=0 )
|
||||
{
|
||||
vector<EllipticKeyPoint> keypoints1( _keypoints1.begin(), _keypoints1.end() ),
|
||||
keypoints2( _keypoints2.begin(), _keypoints2.end() ),
|
||||
keypoints1t( keypoints1.size() ),
|
||||
keypoints2t( keypoints2.size() );
|
||||
|
||||
// calculate projections of key points
|
||||
calcEllipticKeyPointProjections( keypoints1, H1to2, keypoints1t );
|
||||
Mat H2to1; invert(H1to2, H2to1);
|
||||
calcEllipticKeyPointProjections( keypoints2, H2to1, keypoints2t );
|
||||
|
||||
bool ifEvaluateDetectors = !thresholdedOverlapMask; // == commonPart
|
||||
float overlapThreshold;
|
||||
if( ifEvaluateDetectors )
|
||||
{
|
||||
overlapThreshold = 100.f - 40.f;
|
||||
|
||||
// remove key points from outside of the common image part
|
||||
Size sz1 = img1.size(), sz2 = img2.size();
|
||||
getEllipticKeyPointsInCommonPart( keypoints1, keypoints2, keypoints1t, keypoints2t, sz1, sz2 );
|
||||
}
|
||||
else
|
||||
{
|
||||
overlapThreshold = 100.f - 50.f;
|
||||
}
|
||||
int minCount = min( keypoints1.size(), keypoints2t.size() );
|
||||
|
||||
// calculate overlap errors
|
||||
SparseMat_<float> overlaps;
|
||||
overlap( keypoints1, keypoints2t, ifEvaluateDetectors, overlaps );
|
||||
|
||||
correspondencesCount = -1;
|
||||
repeatability = -1.f;
|
||||
const int* size = overlaps.size();
|
||||
if( !size || overlaps.nzcount() == 0 )
|
||||
return;
|
||||
|
||||
if( ifEvaluateDetectors )
|
||||
{
|
||||
// threshold the overlaps
|
||||
for( int y = 0; y < size[0]; y++ )
|
||||
{
|
||||
for( int x = 0; x < size[1]; x++ )
|
||||
{
|
||||
if ( overlaps(y,x) < overlapThreshold )
|
||||
overlaps.erase(y,x);
|
||||
}
|
||||
}
|
||||
|
||||
// regions one-to-one matching
|
||||
correspondencesCount = 0;
|
||||
while( overlaps.nzcount() > 0 )
|
||||
{
|
||||
double maxOverlap = 0;
|
||||
int maxIdx[2];
|
||||
minMaxLoc( overlaps, 0, &maxOverlap, 0, maxIdx );
|
||||
for( size_t i1 = 0; i1 < keypoints1.size(); i1++ )
|
||||
overlaps.erase(i1, maxIdx[1]);
|
||||
for( size_t i2 = 0; i2 < keypoints2t.size(); i2++ )
|
||||
overlaps.erase(maxIdx[0], i2);
|
||||
correspondencesCount++;
|
||||
}
|
||||
repeatability = minCount ? (float)(correspondencesCount*100)/minCount : -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
thresholdedOverlapMask->create( 2, size );
|
||||
for( int y = 0; y < size[0]; y++ )
|
||||
{
|
||||
for( int x = 0; x < size[1]; x++ )
|
||||
{
|
||||
float val = overlaps(y,x);
|
||||
if ( val >= overlapThreshold )
|
||||
thresholdedOverlapMask->ref(y,x) = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void evaluateDetectors( const vector<EllipticKeyPoint>& keypoints1, const vector<EllipticKeyPoint>& keypoints2,
|
||||
const Mat& img1, const Mat& img2, const Mat& H1to2,
|
||||
float& repeatability, int& correspCount )
|
||||
{
|
||||
calculateRepeatability( keypoints1, keypoints2,
|
||||
img1, img2, H1to2,
|
||||
repeatability, correspCount );
|
||||
}
|
||||
|
||||
inline float recall( int correctMatchCount, int correspondenceCount )
|
||||
{
|
||||
return correspondenceCount ? (float)correctMatchCount / (float)correspondenceCount : -1;
|
||||
}
|
||||
|
||||
inline float precision( int correctMatchCount, int falseMatchCount )
|
||||
{
|
||||
return correctMatchCount + falseMatchCount ? (float)correctMatchCount / (float)(correctMatchCount + falseMatchCount) : -1;
|
||||
}
|
||||
|
||||
|
||||
struct DMatchForEvaluation : public DMatch
|
||||
{
|
||||
int isCorrect;
|
||||
|
||||
DMatchForEvaluation( const DMatch &dm )
|
||||
: DMatch( dm )
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void evaluateDescriptors( const vector<EllipticKeyPoint>& keypoints1, const vector<EllipticKeyPoint>& keypoints2,
|
||||
const vector<vector<DMatch> >& matches1to2, vector<DMatchForEvaluation> &allMatches,
|
||||
const Mat& img1, const Mat& img2, const Mat& H1to2,
|
||||
int &correctMatchCount, int &falseMatchCount, int& correspondenceCount )
|
||||
{
|
||||
assert( !keypoints1.empty() && !keypoints2.empty() && !matches1to2.empty() );
|
||||
assert( keypoints1.size() == matches1to2.size() );
|
||||
|
||||
float repeatability;
|
||||
int correspCount;
|
||||
SparseMat_<uchar> thresholdedOverlapMask; // thresholded allOverlapErrors
|
||||
calculateRepeatability( keypoints1, keypoints2,
|
||||
img1, img2, H1to2,
|
||||
repeatability, correspCount,
|
||||
&thresholdedOverlapMask );
|
||||
correspondenceCount = thresholdedOverlapMask.nzcount();
|
||||
|
||||
correctMatchCount = 0;
|
||||
falseMatchCount = 0;
|
||||
|
||||
for( size_t i = 0; i < matches1to2.size(); i++ )
|
||||
{
|
||||
for( size_t j = 0;j < matches1to2[i].size(); j++ )
|
||||
{
|
||||
//if( matches1to2[i].match.indexTrain > 0 )
|
||||
//{
|
||||
DMatchForEvaluation match = matches1to2[i][j];
|
||||
match.isCorrect = thresholdedOverlapMask( match.indexQuery, match.indexTrain);
|
||||
if( match.isCorrect )
|
||||
correctMatchCount++;
|
||||
else
|
||||
falseMatchCount++;
|
||||
allMatches.push_back( match );
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// matches1to2[i].isCorrect = -1;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************\
|
||||
* Detectors evaluation *
|
||||
\****************************************************************************************/
|
||||
@ -1063,22 +677,18 @@ void DetectorQualityTest::runDatasetTest (const vector<Mat> &imgs, const vector<
|
||||
|
||||
calcQuality[di].resize(TEST_CASE_COUNT);
|
||||
|
||||
vector<KeyPoint> keypoints1; vector<EllipticKeyPoint> ekeypoints1;
|
||||
|
||||
vector<KeyPoint> keypoints1;
|
||||
detector->detect( imgs[0], keypoints1 );
|
||||
writeKeypoints( keypontsFS, keypoints1, 0);
|
||||
EllipticKeyPoint::convert( keypoints1, ekeypoints1 );
|
||||
int progressCount = DATASETS_COUNT*TEST_CASE_COUNT;
|
||||
for( int ci = 0; ci < TEST_CASE_COUNT; ci++ )
|
||||
{
|
||||
progress = update_progress( progress, di*TEST_CASE_COUNT + ci, progressCount, 0 );
|
||||
vector<KeyPoint> keypoints2;
|
||||
detector->detect( imgs[ci+1], keypoints2 );
|
||||
evaluateFeatureDetector( imgs[0], imgs[ci+1], Hs[ci], &keypoints1, &keypoints2,
|
||||
calcQuality[di][ci].repeatability, calcQuality[di][ci].correspondenceCount,
|
||||
detector );
|
||||
writeKeypoints( keypontsFS, keypoints2, ci+1);
|
||||
vector<EllipticKeyPoint> ekeypoints2;
|
||||
EllipticKeyPoint::convert( keypoints2, ekeypoints2 );
|
||||
evaluateDetectors( ekeypoints1, ekeypoints2, imgs[0], imgs[ci], Hs[ci],
|
||||
calcQuality[di][ci].repeatability, calcQuality[di][ci].correspondenceCount );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1185,7 +795,7 @@ protected:
|
||||
virtual int processResults( int datasetIdx, int caseIdx );
|
||||
|
||||
virtual void writePlotData( int di ) const;
|
||||
void calculatePlotData( vector<DMatchForEvaluation> &allMatches, int allCorrespCount, int di );
|
||||
void calculatePlotData( vector<vector<DMatch> > &allMatches, vector<vector<uchar> > &allCorrectMatchesMask, int di );
|
||||
|
||||
struct Quality
|
||||
{
|
||||
@ -1333,8 +943,8 @@ void DescriptorQualityTest::readAlgorithm( )
|
||||
|
||||
if( defaultDescMatch == 0 )
|
||||
{
|
||||
DescriptorExtractor *extractor = createDescriptorExtractor( algName );
|
||||
DescriptorMatcher *matcher = createDescriptorMatcher( matcherName );
|
||||
Ptr<DescriptorExtractor> extractor = createDescriptorExtractor( algName );
|
||||
Ptr<DescriptorMatcher> matcher = createDescriptorMatcher( matcherName );
|
||||
defaultDescMatch = new VectorDescriptorMatch( extractor, matcher );
|
||||
specificDescMatch = new VectorDescriptorMatch( extractor, matcher );
|
||||
|
||||
@ -1346,10 +956,15 @@ void DescriptorQualityTest::readAlgorithm( )
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorQualityTest::calculatePlotData( vector<DMatchForEvaluation> &allMatches, int allCorrespCount, int di )
|
||||
void DescriptorQualityTest::calculatePlotData( vector<vector<DMatch> > &allMatches, vector<vector<uchar> > &allCorrectMatchesMask, int di )
|
||||
{
|
||||
std::sort( allMatches.begin(), allMatches.end() );
|
||||
vector<Point2f> recallPrecisionCurve;
|
||||
computeRecallPrecisionCurve( allMatches, allCorrectMatchesMask, recallPrecisionCurve );
|
||||
// you have recallPrecisionCurve for all images from dataset
|
||||
// size of recallPrecisionCurve == total matches count
|
||||
#if 0
|
||||
|
||||
std::sort( allMatches.begin(), allMatches.end() );
|
||||
//calcDatasetQuality[di].resize( allMatches.size() );
|
||||
calcDatasetQuality[di].clear();
|
||||
int correctMatchCount = 0, falseMatchCount = 0;
|
||||
@ -1358,6 +973,7 @@ void DescriptorQualityTest::calculatePlotData( vector<DMatchForEvaluation> &allM
|
||||
int step = 1 + allMatches.size() / npoints;
|
||||
const float resultPrecision = 0.5;
|
||||
bool isResultCalculated = false;
|
||||
|
||||
for( size_t i=0;i<allMatches.size();i++)
|
||||
{
|
||||
if( allMatches[i].isCorrect )
|
||||
@ -1373,7 +989,7 @@ void DescriptorQualityTest::calculatePlotData( vector<DMatchForEvaluation> &allM
|
||||
|
||||
calcDatasetQuality[di].push_back( quality );
|
||||
|
||||
if( !isResultCalculated && quality.precision < resultPrecision)
|
||||
if( !isResultCalculated && quality.precision < resultPrecision )
|
||||
{
|
||||
for(int ci=0;ci<TEST_CASE_COUNT;ci++)
|
||||
{
|
||||
@ -1390,6 +1006,7 @@ void DescriptorQualityTest::calculatePlotData( vector<DMatchForEvaluation> &allM
|
||||
quality.precision = precision( correctMatchCount, falseMatchCount );
|
||||
|
||||
calcDatasetQuality[di].push_back( quality );
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@ -1407,20 +1024,18 @@ void DescriptorQualityTest::runDatasetTest (const vector<Mat> &imgs, const vecto
|
||||
Ptr<GenericDescriptorMatch> descMatch = commRunParams[di].isActiveParams ? specificDescMatch : defaultDescMatch;
|
||||
calcQuality[di].resize(TEST_CASE_COUNT);
|
||||
|
||||
vector<KeyPoint> keypoints1; vector<EllipticKeyPoint> ekeypoints1;
|
||||
vector<KeyPoint> keypoints1;
|
||||
readKeypoints( keypontsFS, keypoints1, 0);
|
||||
EllipticKeyPoint::convert( keypoints1, ekeypoints1 );
|
||||
|
||||
int progressCount = DATASETS_COUNT*TEST_CASE_COUNT;
|
||||
vector<DMatchForEvaluation> allMatches;
|
||||
|
||||
int allCorrespCount = 0;
|
||||
vector<vector<DMatch> > allMatches1to2;
|
||||
vector<vector<uchar> > allCorrectMatchesMask;
|
||||
for( int ci = 0; ci < TEST_CASE_COUNT; ci++ )
|
||||
{
|
||||
progress = update_progress( progress, di*TEST_CASE_COUNT + ci, progressCount, 0 );
|
||||
|
||||
vector<KeyPoint> keypoints2;
|
||||
vector<EllipticKeyPoint> ekeypoints2;
|
||||
if( commRunParams[di].projectKeypointsFrom1Image )
|
||||
{
|
||||
// TODO need to test function calcKeyPointProjections
|
||||
@ -1429,24 +1044,20 @@ void DescriptorQualityTest::runDatasetTest (const vector<Mat> &imgs, const vecto
|
||||
}
|
||||
else
|
||||
readKeypoints( keypontsFS, keypoints2, ci+1 );
|
||||
EllipticKeyPoint::convert( keypoints2, ekeypoints2 );
|
||||
descMatch->add( imgs[ci+1], keypoints2 );
|
||||
vector<vector<DMatch> > matches1to2;
|
||||
//TODO: use more sophisticated strategy to choose threshold
|
||||
descMatch->match( imgs[0], keypoints1, matches1to2, std::numeric_limits<float>::max() );
|
||||
|
||||
// TODO if( commRunParams[di].matchFilter )
|
||||
int correspCount;
|
||||
int correctMatchCount = 0, falseMatchCount = 0;
|
||||
evaluateDescriptors( ekeypoints1, ekeypoints2, matches1to2, allMatches, imgs[0], imgs[ci+1], Hs[ci],
|
||||
correctMatchCount, falseMatchCount, correspCount );
|
||||
|
||||
allCorrespCount += correspCount;
|
||||
|
||||
descMatch->clear ();
|
||||
vector<vector<DMatch> > matches1to2;
|
||||
vector<vector<uchar> > correctMatchesMask;
|
||||
vector<Point2f> recallPrecisionCurve; // not used because we need recallPrecisionCurve for
|
||||
// all images in dataset
|
||||
evaluateDescriptorMatch( imgs[0], imgs[ci+1], Hs[ci], keypoints1, keypoints2,
|
||||
&matches1to2, &correctMatchesMask, recallPrecisionCurve,
|
||||
descMatch );
|
||||
allMatches1to2.insert( allMatches1to2.end(), matches1to2.begin(), matches1to2.end() );
|
||||
allCorrectMatchesMask.insert( allCorrectMatchesMask.end(), correctMatchesMask.begin(), correctMatchesMask.end() );
|
||||
}
|
||||
|
||||
calculatePlotData( allMatches, allCorrespCount, di );
|
||||
calculatePlotData( allMatches1to2, allCorrectMatchesMask, di );
|
||||
}
|
||||
|
||||
int DescriptorQualityTest::processResults( int datasetIdx, int caseIdx )
|
||||
|
Loading…
Reference in New Issue
Block a user