1 | initial version |
What you want is an algorithm called adaptive non-maximal suppression. This will achieve an even distribution of those features which are locally "responsive". This is my implementation guided by the paper "Multi-Image Matching using Multi-Scale Oriented Patches" by Brown, Szeliski, and Winder.
void adaptiveNonMaximalSuppresion( std::vector<cv::KeyPoint>& keypoints,
const int numToKeep )
{
if( keypoints.size() < numToKeep ) { return; }
//
// Sort by response
//
std::sort( keypoints.begin(), keypoints.end(),
[&]( const cv::KeyPoint& lhs, const cv::KeyPoint& rhs )
{
return lhs.response > rhs.response;
} );
std::vector<cv::KeyPoint> anmsPts;
std::vector<double> radii;
radii.resize( keypoints.size() );
std::vector<double> radiiSorted;
radiiSorted.resize( keypoints.size() );
const float robustCoeff = 1.11; // see paper
for( int i = 0; i < keypoints.size(); ++i )
{
const float response = keypoints[i].response * robustCoeff;
double radius = std::numeric_limits<double>::max();
for( int j = 0; j < i && keypoints[j].response > response; ++j )
{
radius = std::min( radius, cv::norm( keypoints[i].pt - keypoints[j].pt ) );
}
radii[i] = radius;
radiiSorted[i] = radius;
}
std::sort( radiiSorted.begin(), radiiSorted.end(),
[&]( const double& lhs, const double& rhs )
{
return lhs > rhs;
} );
const double decisionRadius = radiiSorted[numToKeep];
for( int i = 0; i < radii.size(); ++i )
{
if( radii[i] >= decisionRadius )
{
anmsPts.push_back( keypoints[i] );
}
}
anmsPts.swap( keypoints );
}