Commit 3166d0c6 authored by Jiri Horner's avatar Jiri Horner Committed by Alexander Alekhin

Merge pull request #9249 from hrnr:akaze_part3

[GSOC] Speeding-up AKAZE, part #3 (#9249)

* use finding of scale extremas from fast_akaze

* incorporade finding of extremas and subpixel refinement from Hideaki Suzuki's fast_akaze (https://github.com/h2suzuki/fast_akaze)
* use opencv parallel framework
* do not search for keypoints near the border, where we can't compute sensible descriptors (bugs fixed in ffd9ad99f4946e31508677dab09bddbecb82ae9f, 2c5389594bb560b62097de3602755ef97e60135f), but the descriptors were not 100% correct. this is a better solution

this version produces less keypoints with the same treshold. It is more effective in pruning similar keypoints (which do not bring any new information), so we have less keypoints, but with high quality. Accuracy is about the same.

* incorporate bugfix from upstream

* fix bug in subpixel refinement
* see commit db3dc22981e856ca8111f2f7fe57d9c2e0286efc in Pablo's repo

* rework finding of scale space extremas

* store just keypoints positions
* store positions in uchar mask for effective spatial search for neighbours
* construct keypoints structs at the very end

* lower inlier threshold in test

* win32 has lower accuracy
parent 0bd357e7
...@@ -49,6 +49,15 @@ void AKAZEFeatures::Allocate_Memory_Evolution(void) { ...@@ -49,6 +49,15 @@ void AKAZEFeatures::Allocate_Memory_Evolution(void) {
float rfactor = 0.0f; float rfactor = 0.0f;
int level_height = 0, level_width = 0; int level_height = 0, level_width = 0;
// maximum size of the area for the descriptor computation
float smax = 0.0;
if (options_.descriptor == AKAZE::DESCRIPTOR_MLDB_UPRIGHT || options_.descriptor == AKAZE::DESCRIPTOR_MLDB) {
smax = 10.0f*sqrtf(2.0f);
}
else if (options_.descriptor == AKAZE::DESCRIPTOR_KAZE_UPRIGHT || options_.descriptor == AKAZE::DESCRIPTOR_KAZE) {
smax = 12.0f*sqrtf(2.0f);
}
// Allocate the dimension of the matrices for the evolution // Allocate the dimension of the matrices for the evolution
for (int i = 0, power = 1; i <= options_.omax - 1; i++, power *= 2) { for (int i = 0, power = 1; i <= options_.omax - 1; i++, power *= 2) {
rfactor = 1.0f / power; rfactor = 1.0f / power;
...@@ -70,6 +79,8 @@ void AKAZEFeatures::Allocate_Memory_Evolution(void) { ...@@ -70,6 +79,8 @@ void AKAZEFeatures::Allocate_Memory_Evolution(void) {
step.octave = i; step.octave = i;
step.sublevel = j; step.sublevel = j;
step.octave_ratio = (float)power; step.octave_ratio = (float)power;
step.border = fRound(smax * step.sigma_size) + 1;
evolution_.push_back(step); evolution_.push_back(step);
} }
} }
...@@ -477,20 +488,6 @@ void AKAZEFeatures::Create_Nonlinear_Scale_Space(InputArray img) ...@@ -477,20 +488,6 @@ void AKAZEFeatures::Create_Nonlinear_Scale_Space(InputArray img)
return; return;
} }
/* ************************************************************************* */
/**
* @brief This method selects interesting keypoints through the nonlinear scale space
* @param kpts Vector of detected keypoints
*/
void AKAZEFeatures::Feature_Detection(std::vector<KeyPoint>& kpts)
{
CV_INSTRUMENT_REGION()
kpts.clear();
Find_Scale_Space_Extrema(kpts);
Do_Subpixel_Refinement(kpts);
}
/* ************************************************************************* */ /* ************************************************************************* */
#ifdef HAVE_OPENCL #ifdef HAVE_OPENCL
...@@ -608,212 +605,271 @@ void AKAZEFeatures::Compute_Determinant_Hessian_Response(void) { ...@@ -608,212 +605,271 @@ void AKAZEFeatures::Compute_Determinant_Hessian_Response(void) {
} }
/* ************************************************************************* */ /* ************************************************************************* */
/** /**
* @brief This method finds extrema in the nonlinear scale space * @brief This method selects interesting keypoints through the nonlinear scale space
* @param kpts Vector of detected keypoints * @param kpts Vector of detected keypoints
*/ */
void AKAZEFeatures::Find_Scale_Space_Extrema(std::vector<KeyPoint>& kpts) void AKAZEFeatures::Feature_Detection(std::vector<KeyPoint>& kpts)
{ {
CV_INSTRUMENT_REGION() CV_INSTRUMENT_REGION()
float value = 0.0; kpts.clear();
float dist = 0.0, ratio = 0.0, smax = 0.0; std::vector<Mat> keypoints_by_layers;
int npoints = 0, id_repeated = 0; Find_Scale_Space_Extrema(keypoints_by_layers);
int sigma_size_ = 0, left_x = 0, right_x = 0, up_y = 0, down_y = 0; Do_Subpixel_Refinement(keypoints_by_layers, kpts);
bool is_extremum = false, is_repeated = false, is_out = false; }
KeyPoint point;
vector<KeyPoint> kpts_aux;
// Set maximum size /**
if (options_.descriptor == AKAZE::DESCRIPTOR_MLDB_UPRIGHT || options_.descriptor == AKAZE::DESCRIPTOR_MLDB) { * @brief This method searches v for a neighbor point of the point candidate p
smax = 10.0f*sqrtf(2.0f); * @param x Coordinates of the keypoint candidate to search a neighbor
} * @param y Coordinates of the keypoint candidate to search a neighbor
else if (options_.descriptor == AKAZE::DESCRIPTOR_KAZE_UPRIGHT || options_.descriptor == AKAZE::DESCRIPTOR_KAZE) { * @param mask Matrix holding keypoints positions
smax = 12.0f*sqrtf(2.0f); * @param search_radius neighbour radius for searching keypoints
* @param idx The index to mask, pointing to keypoint found.
* @return true if a neighbor point is found; false otherwise
*/
static inline bool
find_neighbor_point(const int x, const int y, const Mat &mask, const int search_radius, int &idx)
{
// search neighborhood for keypoints
for (int i = y - search_radius; i < y + search_radius; ++i) {
const uchar *curr = mask.ptr<uchar>(i);
for (int j = x - search_radius; j < x + search_radius; ++j) {
if (curr[j] == 0) {
continue; // skip non-keypoint
}
// fine-compare with L2 metric (L2 is smaller than our search window)
int dx = j - x;
int dy = i - y;
if (dx * dx + dy * dy <= search_radius * search_radius) {
idx = i * mask.cols + j;
return true;
}
}
} }
for (size_t i = 0; i < evolution_.size(); i++) { return false;
Mat Ldet = evolution_[i].Mdet; }
const float* prev = Ldet.ptr<float>(0);
const float* curr = Ldet.ptr<float>(1); /**
for (int ix = 1; ix < Ldet.rows - 1; ix++) { * @brief Find keypoints in parallel for each pyramid layer
const float* next = Ldet.ptr<float>(ix + 1); */
class FindKeypointsSameScale : public ParallelLoopBody
for (int jx = 1; jx < Ldet.cols - 1; jx++) { {
is_extremum = false; public:
is_repeated = false; explicit FindKeypointsSameScale(const std::vector<Evolution>& ev,
is_out = false; std::vector<Mat>& kpts, float dthreshold)
value = *(Ldet.ptr<float>(ix)+jx); : evolution_(&ev), keypoints_by_layers_(&kpts), dthreshold_(dthreshold)
{}
// Filter the points with the detector threshold
if (value > options_.dthreshold && value >= options_.min_dthreshold && void operator()(const Range& range) const
value > curr[jx-1] && {
value > curr[jx+1] && for (int i = range.start; i < range.end; i++)
value > prev[jx-1] && {
value > prev[jx] && const Evolution &e = (*evolution_)[i];
value > prev[jx+1] && Mat &kpts = (*keypoints_by_layers_)[i];
value > next[jx-1] && // this mask will hold positions of keypoints in this level
value > next[jx] && kpts = Mat::zeros(e.Mdet.size(), CV_8UC1);
value > next[jx+1]) {
// if border is too big we shouldn't search any keypoints
is_extremum = true; if (e.border + 1 >= e.Ldet.rows)
point.response = fabs(value); continue;
point.size = evolution_[i].esigma*options_.derivative_factor;
point.octave = (int)evolution_[i].octave; const float * prev = e.Mdet.ptr<float>(e.border - 1);
point.class_id = (int)i; const float * curr = e.Mdet.ptr<float>(e.border );
ratio = (float)fastpow(2, point.octave); const float * next = e.Mdet.ptr<float>(e.border + 1);
sigma_size_ = fRound(point.size / ratio); const float * ldet = e.Mdet.ptr<float>();
point.pt.x = static_cast<float>(jx); uchar *mask = kpts.ptr<uchar>();
point.pt.y = static_cast<float>(ix); const int search_radius = e.sigma_size; // size of keypoint in this level
// Compare response with the same and lower scale for (int y = e.border; y < e.Ldet.rows - e.border; y++) {
for (size_t ik = 0; ik < kpts_aux.size(); ik++) { for (int x = e.border; x < e.Ldet.cols - e.border; x++) {
const float value = curr[x];
if ((point.class_id - 1) == kpts_aux[ik].class_id ||
point.class_id == kpts_aux[ik].class_id) { // Filter the points with the detector threshold
float distx = point.pt.x*ratio - kpts_aux[ik].pt.x; if (value <= dthreshold_)
float disty = point.pt.y*ratio - kpts_aux[ik].pt.y; continue;
dist = distx * distx + disty * disty; if (value <= curr[x-1] || value <= curr[x+1])
if (dist <= point.size * point.size) { continue;
if (point.response > kpts_aux[ik].response) { if (value <= prev[x-1] || value <= prev[x ] || value <= prev[x+1])
id_repeated = (int)ik; continue;
is_repeated = true; if (value <= next[x-1] || value <= next[x ] || value <= next[x+1])
} continue;
else {
is_extremum = false; int idx = 0;
} // Compare response with the same scale
break; if (find_neighbor_point(x, y, kpts, search_radius, idx)) {
} if (value > ldet[idx]) {
mask[idx] = 0; // clear old point - we have better candidate now
} else {
continue; // there already is a better keypoint
} }
} }
// Check out of bounds kpts.at<uchar>(y, x) = 1; // we have a new keypoint
if (is_extremum == true) { }
// Check that the point is under the image limits for the descriptor computation prev = curr;
left_x = fRound(point.pt.x - smax*sigma_size_) - 1; curr = next;
right_x = fRound(point.pt.x + smax*sigma_size_) + 1; next += e.Ldet.cols;
up_y = fRound(point.pt.y - smax*sigma_size_) - 1; }
down_y = fRound(point.pt.y + smax*sigma_size_) + 1; }
}
if (left_x < 0 || right_x >= Ldet.cols || private:
up_y < 0 || down_y >= Ldet.rows) { const std::vector<Evolution>* evolution_;
is_out = true; std::vector<Mat>* keypoints_by_layers_;
} float dthreshold_; ///< Detector response threshold to accept point
};
if (is_out == false) { /**
if (is_repeated == false) { * @brief This method finds extrema in the nonlinear scale space
point.pt.x = (float)(point.pt.x*ratio + .5*(ratio-1.0)); * @param keypoints_by_layers Output vectors of detected keypoints; one vector for each evolution level
point.pt.y = (float)(point.pt.y*ratio + .5*(ratio-1.0)); */
kpts_aux.push_back(point); void AKAZEFeatures::Find_Scale_Space_Extrema(std::vector<Mat>& keypoints_by_layers)
npoints++; {
} CV_INSTRUMENT_REGION()
else {
point.pt.x = (float)(point.pt.x*ratio + .5*(ratio-1.0)); keypoints_by_layers.resize(evolution_.size());
point.pt.y = (float)(point.pt.y*ratio + .5*(ratio-1.0));
kpts_aux[id_repeated] = point; // find points in the same level
} parallel_for_(Range(0, (int)evolution_.size()),
} // if is_out FindKeypointsSameScale(evolution_, keypoints_by_layers, options_.dthreshold));
} //if is_extremum
// Filter points with the lower scale level
for (size_t i = 1; i < keypoints_by_layers.size(); i++) {
// constants for this level
const Mat &keypoints = keypoints_by_layers[i];
const uchar *const kpts = keypoints_by_layers[i].ptr<uchar>();
uchar *const kpts_prev = keypoints_by_layers[i-1].ptr<uchar>();
const float *const ldet = evolution_[i].Mdet.ptr<float>();
const float *const ldet_prev = evolution_[i-1].Mdet.ptr<float>();
// ratios are just powers of 2
const int diff_ratio = (int)evolution_[i].octave_ratio / (int)evolution_[i-1].octave_ratio;
const int search_radius = evolution_[i].sigma_size * diff_ratio; // size of keypoint in this level
size_t j = 0;
for (int y = 0; y < keypoints.rows; y++) {
for (int x = 0; x < keypoints.cols; x++, j++) {
if (kpts[j] == 0) {
continue; // skip non-keypoints
} }
} // for jx int idx = 0;
prev = curr; // project point to lower scale layer
curr = next; const int p_x = x * diff_ratio;
} // for ix const int p_y = y * diff_ratio;
} // for i if (find_neighbor_point(p_x, p_y, keypoints_by_layers[i-1], search_radius, idx)) {
if (ldet[j] > ldet_prev[idx]) {
// Now filter points with the upper scale level kpts_prev[idx] = 0; // clear keypoint in lower layer
for (size_t i = 0; i < kpts_aux.size(); i++) {
is_repeated = false;
const KeyPoint& pt = kpts_aux[i];
for (size_t j = i + 1; j < kpts_aux.size(); j++) {
// Compare response with the upper scale
if ((pt.class_id + 1) == kpts_aux[j].class_id) {
float distx = pt.pt.x - kpts_aux[j].pt.x;
float disty = pt.pt.y - kpts_aux[j].pt.y;
dist = distx * distx + disty * disty;
if (dist <= pt.size * pt.size) {
if (pt.response < kpts_aux[j].response) {
is_repeated = true;
break;
} }
// else this pt may be pruned by the upper scale
} }
} }
} }
}
if (is_repeated == false) // Now filter points with the upper scale level (the other direction)
kpts.push_back(pt); for (int i = (int)keypoints_by_layers.size() - 2; i >= 0; i--) {
// constants for this level
const Mat &keypoints = keypoints_by_layers[i];
const uchar *const kpts = keypoints_by_layers[i].ptr<uchar>();
uchar *const kpts_next = keypoints_by_layers[i+1].ptr<uchar>();
const float *const ldet = evolution_[i].Mdet.ptr<float>();
const float *const ldet_next = evolution_[i+1].Mdet.ptr<float>();
// ratios are just powers of 2, i+1 ratio is always greater or equal to i
const int diff_ratio = (int)evolution_[i+1].octave_ratio / (int)evolution_[i].octave_ratio;
const int search_radius = evolution_[i+1].sigma_size; // size of keypoints in upper level
size_t j = 0;
for (int y = 0; y < keypoints.rows; y++) {
for (int x = 0; x < keypoints.cols; x++, j++) {
if (kpts[j] == 0) {
continue; // skip non-keypoints
}
int idx = 0;
// project point to upper scale layer
const int p_x = x / diff_ratio;
const int p_y = y / diff_ratio;
if (find_neighbor_point(p_x, p_y, keypoints_by_layers[i+1], search_radius, idx)) {
if (ldet[j] > ldet_next[idx]) {
kpts_next[idx] = 0; // clear keypoint in upper layer
}
}
}
}
} }
} }
/* ************************************************************************* */ /* ************************************************************************* */
/** /**
* @brief This method performs subpixel refinement of the detected keypoints * @brief This method performs subpixel refinement of the detected keypoints
* @param kpts Vector of detected keypoints * @param keypoints_by_layers Input vectors of detected keypoints, sorted by evolution levels
* @param kpts Output vector of the final refined keypoints
*/ */
void AKAZEFeatures::Do_Subpixel_Refinement(std::vector<KeyPoint>& kpts) void AKAZEFeatures::Do_Subpixel_Refinement(
std::vector<Mat>& keypoints_by_layers, std::vector<KeyPoint>& output_keypoints)
{ {
CV_INSTRUMENT_REGION() CV_INSTRUMENT_REGION()
float Dx = 0.0, Dy = 0.0, ratio = 0.0; for (size_t i = 0; i < keypoints_by_layers.size(); i++) {
float Dxx = 0.0, Dyy = 0.0, Dxy = 0.0; const Evolution &e = evolution_[i];
int x = 0, y = 0; const float * const ldet = e.Mdet.ptr<float>();
Matx22f A(0, 0, 0, 0); const float ratio = e.octave_ratio;
Vec2f b(0, 0); const int cols = e.Ldet.cols;
Vec2f dst(0, 0); const Mat& keypoints = keypoints_by_layers[i];
const uchar *const kpts = keypoints.ptr<uchar>();
for (size_t i = 0; i < kpts.size(); i++) {
ratio = (float)fastpow(2, kpts[i].octave); size_t j = 0;
x = fRound(kpts[i].pt.x / ratio); for (int y = 0; y < keypoints.rows; y++) {
y = fRound(kpts[i].pt.y / ratio); for (int x = 0; x < keypoints.cols; x++, j++) {
Mat Ldet = evolution_[kpts[i].class_id].Mdet; if (kpts[j] == 0) {
continue; // skip non-keypoints
// Compute the gradient }
Dx = (0.5f)*(*(Ldet.ptr<float>(y)+x + 1)
- *(Ldet.ptr<float>(y)+x - 1)); // create a new keypoint
Dy = (0.5f)*(*(Ldet.ptr<float>(y + 1) + x) KeyPoint kp;
- *(Ldet.ptr<float>(y - 1) + x)); kp.pt.x = x * e.octave_ratio;
kp.pt.y = y * e.octave_ratio;
// Compute the Hessian kp.size = e.esigma * options_.derivative_factor;
Dxx = (*(Ldet.ptr<float>(y)+x + 1) kp.angle = -1;
+ *(Ldet.ptr<float>(y)+x - 1) kp.response = ldet[j];
- 2.0f*(*(Ldet.ptr<float>(y)+x))); kp.octave = e.octave;
kp.class_id = static_cast<int>(i);
Dyy = (*(Ldet.ptr<float>(y + 1) + x)
+ *(Ldet.ptr<float>(y - 1) + x) // Compute the gradient
- 2.0f*(*(Ldet.ptr<float>(y)+x))); float Dx = 0.5f * (ldet[ y *cols + x + 1] - ldet[ y *cols + x - 1]);
float Dy = 0.5f * (ldet[(y + 1)*cols + x ] - ldet[(y - 1)*cols + x ]);
Dxy = (0.25f)*(*(Ldet.ptr<float>(y + 1) + x + 1)
+ (*(Ldet.ptr<float>(y - 1) + x - 1))) // Compute the Hessian
- (0.25f)*(*(Ldet.ptr<float>(y - 1) + x + 1) float Dxx = ldet[ y *cols + x + 1] + ldet[ y *cols + x - 1] - 2.0f * ldet[y*cols + x];
+ (*(Ldet.ptr<float>(y + 1) + x - 1))); float Dyy = ldet[(y + 1)*cols + x ] + ldet[(y - 1)*cols + x ] - 2.0f * ldet[y*cols + x];
float Dxy = 0.25f * (ldet[(y + 1)*cols + x + 1] + ldet[(y - 1)*cols + x - 1] -
// Solve the linear system ldet[(y - 1)*cols + x + 1] - ldet[(y + 1)*cols + x - 1]);
A(0, 0) = Dxx;
A(1, 1) = Dyy; // Solve the linear system
A(0, 1) = A(1, 0) = Dxy; Matx22f A( Dxx, Dxy,
b(0) = -Dx; Dxy, Dyy );
b(1) = -Dy; Vec2f b( -Dx, -Dy );
Vec2f dst( 0.0f, 0.0f );
solve(A, b, dst, DECOMP_LU); solve(A, b, dst, DECOMP_LU);
if (fabs(dst(0)) <= 1.0f && fabs(dst(1)) <= 1.0f) { float dx = dst(0);
kpts[i].pt.x = x + dst(0); float dy = dst(1);
kpts[i].pt.y = y + dst(1);
int power = fastpow(2, evolution_[kpts[i].class_id].octave); if (fabs(dx) > 1.0f || fabs(dy) > 1.0f)
kpts[i].pt.x = (float)(kpts[i].pt.x*power + .5*(power-1)); continue; // Ignore the point that is not stable
kpts[i].pt.y = (float)(kpts[i].pt.y*power + .5*(power-1));
kpts[i].angle = 0.0; // Refine the coordinates
kp.pt.x += dx * ratio + .5f*(ratio-1.f);
// In OpenCV the size of a keypoint its the diameter kp.pt.y += dy * ratio + .5f*(ratio-1.f);
kpts[i].size *= 2.0f;
} kp.angle = 0.0;
// Delete the point since its not stable kp.size *= 2.0f; // In OpenCV the size of a keypoint is the diameter
else {
kpts.erase(kpts.begin() + i); // Push the refined keypoint to the final storage
i--; output_keypoints.push_back(kp);
}
} }
} }
} }
......
...@@ -44,6 +44,7 @@ struct Evolution ...@@ -44,6 +44,7 @@ struct Evolution
int sublevel; ///< Image sublevel in each octave int sublevel; ///< Image sublevel in each octave
int sigma_size; ///< Integer esigma. For computing the feature detector responses int sigma_size; ///< Integer esigma. For computing the feature detector responses
float octave_ratio; ///< Scaling ratio of this octave. ratio = 2^octave float octave_ratio; ///< Scaling ratio of this octave. ratio = 2^octave
int border; ///< Width of border where descriptors cannot be computed
}; };
/* ************************************************************************* */ /* ************************************************************************* */
...@@ -76,8 +77,9 @@ public: ...@@ -76,8 +77,9 @@ public:
void Create_Nonlinear_Scale_Space(InputArray img); void Create_Nonlinear_Scale_Space(InputArray img);
void Feature_Detection(std::vector<cv::KeyPoint>& kpts); void Feature_Detection(std::vector<cv::KeyPoint>& kpts);
void Compute_Determinant_Hessian_Response(void); void Compute_Determinant_Hessian_Response(void);
void Find_Scale_Space_Extrema(std::vector<cv::KeyPoint>& kpts); void Find_Scale_Space_Extrema(std::vector<Mat>& keypoints_by_layers);
void Do_Subpixel_Refinement(std::vector<cv::KeyPoint>& kpts); void Do_Subpixel_Refinement(std::vector<Mat>& keypoints_by_layers,
std::vector<KeyPoint>& kpts);
/// Feature description methods /// Feature description methods
void Compute_Descriptors(std::vector<cv::KeyPoint>& kpts, OutputArray desc); void Compute_Descriptors(std::vector<cv::KeyPoint>& kpts, OutputArray desc);
......
...@@ -230,10 +230,10 @@ INSTANTIATE_TEST_CASE_P(ORB, DetectorRotationInvariance, ...@@ -230,10 +230,10 @@ INSTANTIATE_TEST_CASE_P(ORB, DetectorRotationInvariance,
Value(IMAGE_TSUKUBA, ORB::create(), 0.5f, 0.76f)); Value(IMAGE_TSUKUBA, ORB::create(), 0.5f, 0.76f));
INSTANTIATE_TEST_CASE_P(AKAZE, DetectorRotationInvariance, INSTANTIATE_TEST_CASE_P(AKAZE, DetectorRotationInvariance,
Value(IMAGE_TSUKUBA, AKAZE::create(), 0.5f, 0.76f)); Value(IMAGE_TSUKUBA, AKAZE::create(), 0.5f, 0.71f));
INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DetectorRotationInvariance, INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DetectorRotationInvariance,
Value(IMAGE_TSUKUBA, AKAZE::create(AKAZE::DESCRIPTOR_KAZE), 0.5f, 0.76f)); Value(IMAGE_TSUKUBA, AKAZE::create(AKAZE::DESCRIPTOR_KAZE), 0.5f, 0.71f));
/* /*
* Detector's scale invariance check * Detector's scale invariance check
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment