//============================================================================= // // KAZE.cpp // Author: Pablo F. Alcantarilla // Institution: University d'Auvergne // Address: Clermont Ferrand, France // Date: 21/01/2012 // Email: pablofdezalc@gmail.com // // KAZE Features Copyright 2012, Pablo F. Alcantarilla // All Rights Reserved // See LICENSE for the license information //============================================================================= /** * @file KAZEFeatures.cpp * @brief Main class for detecting and describing features in a nonlinear * scale space * @date Jan 21, 2012 * @author Pablo F. Alcantarilla */ #include "../precomp.hpp" #include "KAZEFeatures.h" #include "utils.h" namespace cv { // Namespaces using namespace std; /* ************************************************************************* */ /** * @brief KAZE constructor with input options * @param options KAZE configuration options * @note The constructor allocates memory for the nonlinear scale space */ KAZEFeatures::KAZEFeatures(KAZEOptions& options) : options_(options) { ncycles_ = 0; reordering_ = true; // Now allocate memory for the evolution Allocate_Memory_Evolution(); } /* ************************************************************************* */ /** * @brief This method allocates the memory for the nonlinear diffusion evolution */ void KAZEFeatures::Allocate_Memory_Evolution(void) { // Allocate the dimension of the matrices for the evolution for (int i = 0; i <= options_.omax - 1; i++) { for (int j = 0; j <= options_.nsublevels - 1; j++) { TEvolution aux; aux.Lx = Mat::zeros(options_.img_height, options_.img_width, CV_32F); aux.Ly = Mat::zeros(options_.img_height, options_.img_width, CV_32F); aux.Lxx = Mat::zeros(options_.img_height, options_.img_width, CV_32F); aux.Lxy = Mat::zeros(options_.img_height, options_.img_width, CV_32F); aux.Lyy = Mat::zeros(options_.img_height, options_.img_width, CV_32F); aux.Lt = Mat::zeros(options_.img_height, options_.img_width, CV_32F); aux.Lsmooth = Mat::zeros(options_.img_height, options_.img_width, CV_32F); aux.Ldet = Mat::zeros(options_.img_height, options_.img_width, CV_32F); aux.esigma = options_.soffset*pow((float)2.0f, (float)(j) / (float)(options_.nsublevels)+i); aux.etime = 0.5f*(aux.esigma*aux.esigma); aux.sigma_size = fRound(aux.esigma); aux.octave = i; aux.sublevel = j; evolution_.push_back(aux); } } // Allocate memory for the FED number of cycles and time steps for (size_t i = 1; i < evolution_.size(); i++) { int naux = 0; vector tau; float ttime = 0.0; ttime = evolution_[i].etime - evolution_[i - 1].etime; naux = fed_tau_by_process_time(ttime, 1, 0.25f, reordering_, tau); nsteps_.push_back(naux); tsteps_.push_back(tau); ncycles_++; } } /* ************************************************************************* */ /** * @brief This method creates the nonlinear scale space for a given image * @param img Input image for which the nonlinear scale space needs to be created * @return 0 if the nonlinear scale space was created successfully. -1 otherwise */ int KAZEFeatures::Create_Nonlinear_Scale_Space(const Mat &img) { CV_Assert(evolution_.size() > 0); // Copy the original image to the first level of the evolution img.copyTo(evolution_[0].Lt); gaussian_2D_convolution(evolution_[0].Lt, evolution_[0].Lt, 0, 0, options_.soffset); gaussian_2D_convolution(evolution_[0].Lt, evolution_[0].Lsmooth, 0, 0, options_.sderivatives); // Firstly compute the kcontrast factor Compute_KContrast(evolution_[0].Lt, options_.kcontrast_percentille); // Allocate memory for the flow and step images Mat Lflow = Mat::zeros(evolution_[0].Lt.rows, evolution_[0].Lt.cols, CV_32F); Mat Lstep = Mat::zeros(evolution_[0].Lt.rows, evolution_[0].Lt.cols, CV_32F); // Now generate the rest of evolution levels for (size_t i = 1; i < evolution_.size(); i++) { evolution_[i - 1].Lt.copyTo(evolution_[i].Lt); gaussian_2D_convolution(evolution_[i - 1].Lt, evolution_[i].Lsmooth, 0, 0, options_.sderivatives); // Compute the Gaussian derivatives Lx and Ly Scharr(evolution_[i].Lsmooth, evolution_[i].Lx, CV_32F, 1, 0, 1, 0, BORDER_DEFAULT); Scharr(evolution_[i].Lsmooth, evolution_[i].Ly, CV_32F, 0, 1, 1, 0, BORDER_DEFAULT); // Compute the conductivity equation if (options_.diffusivity == KAZE::DIFF_PM_G1) pm_g1(evolution_[i].Lx, evolution_[i].Ly, Lflow, options_.kcontrast); else if (options_.diffusivity == KAZE::DIFF_PM_G2) pm_g2(evolution_[i].Lx, evolution_[i].Ly, Lflow, options_.kcontrast); else if (options_.diffusivity == KAZE::DIFF_WEICKERT) weickert_diffusivity(evolution_[i].Lx, evolution_[i].Ly, Lflow, options_.kcontrast); // Perform FED n inner steps for (int j = 0; j < nsteps_[i - 1]; j++) nld_step_scalar(evolution_[i].Lt, Lflow, Lstep, tsteps_[i - 1][j]); } return 0; } /* ************************************************************************* */ /** * @brief This method computes the k contrast factor * @param img Input image * @param kpercentile Percentile of the gradient histogram */ void KAZEFeatures::Compute_KContrast(const Mat &img, const float &kpercentile) { options_.kcontrast = compute_k_percentile(img, kpercentile, options_.sderivatives, options_.kcontrast_bins, 0, 0); } /* ************************************************************************* */ /** * @brief This method computes the feature detector response for the nonlinear scale space * @note We use the Hessian determinant as feature detector */ void KAZEFeatures::Compute_Detector_Response(void) { float lxx = 0.0, lxy = 0.0, lyy = 0.0; // Firstly compute the multiscale derivatives Compute_Multiscale_Derivatives(); for (size_t i = 0; i < evolution_.size(); i++) { for (int ix = 0; ix < options_.img_height; ix++) { for (int jx = 0; jx < options_.img_width; jx++) { lxx = *(evolution_[i].Lxx.ptr(ix)+jx); lxy = *(evolution_[i].Lxy.ptr(ix)+jx); lyy = *(evolution_[i].Lyy.ptr(ix)+jx); *(evolution_[i].Ldet.ptr(ix)+jx) = (lxx*lyy - lxy*lxy); } } } } /* ************************************************************************* */ /** * @brief This method selects interesting keypoints through the nonlinear scale space * @param kpts Vector of keypoints */ void KAZEFeatures::Feature_Detection(std::vector& kpts) { kpts.clear(); Compute_Detector_Response(); Determinant_Hessian(kpts); Do_Subpixel_Refinement(kpts); } /* ************************************************************************* */ class MultiscaleDerivativesKAZEInvoker : public ParallelLoopBody { public: explicit MultiscaleDerivativesKAZEInvoker(std::vector& ev) : evolution_(&ev) { } void operator()(const Range& range) const { std::vector& evolution = *evolution_; for (int i = range.start; i < range.end; i++) { compute_scharr_derivatives(evolution[i].Lsmooth, evolution[i].Lx, 1, 0, evolution[i].sigma_size); compute_scharr_derivatives(evolution[i].Lsmooth, evolution[i].Ly, 0, 1, evolution[i].sigma_size); compute_scharr_derivatives(evolution[i].Lx, evolution[i].Lxx, 1, 0, evolution[i].sigma_size); compute_scharr_derivatives(evolution[i].Ly, evolution[i].Lyy, 0, 1, evolution[i].sigma_size); compute_scharr_derivatives(evolution[i].Lx, evolution[i].Lxy, 0, 1, evolution[i].sigma_size); evolution[i].Lx = evolution[i].Lx*((evolution[i].sigma_size)); evolution[i].Ly = evolution[i].Ly*((evolution[i].sigma_size)); evolution[i].Lxx = evolution[i].Lxx*((evolution[i].sigma_size)*(evolution[i].sigma_size)); evolution[i].Lxy = evolution[i].Lxy*((evolution[i].sigma_size)*(evolution[i].sigma_size)); evolution[i].Lyy = evolution[i].Lyy*((evolution[i].sigma_size)*(evolution[i].sigma_size)); } } private: std::vector* evolution_; }; /* ************************************************************************* */ /** * @brief This method computes the multiscale derivatives for the nonlinear scale space */ void KAZEFeatures::Compute_Multiscale_Derivatives(void) { parallel_for_(Range(0, (int)evolution_.size()), MultiscaleDerivativesKAZEInvoker(evolution_)); } /* ************************************************************************* */ class FindExtremumKAZEInvoker : public ParallelLoopBody { public: explicit FindExtremumKAZEInvoker(std::vector& ev, std::vector >& kpts_par, const KAZEOptions& options) : evolution_(&ev), kpts_par_(&kpts_par), options_(options) { } void operator()(const Range& range) const { std::vector& evolution = *evolution_; std::vector >& kpts_par = *kpts_par_; for (int i = range.start; i < range.end; i++) { float value = 0.0; bool is_extremum = false; for (int ix = 1; ix < options_.img_height - 1; ix++) { for (int jx = 1; jx < options_.img_width - 1; jx++) { is_extremum = false; value = *(evolution[i].Ldet.ptr(ix)+jx); // Filter the points with the detector threshold if (value > options_.dthreshold) { if (value >= *(evolution[i].Ldet.ptr(ix)+jx - 1)) { // First check on the same scale if (check_maximum_neighbourhood(evolution[i].Ldet, 1, value, ix, jx, 1)) { // Now check on the lower scale if (check_maximum_neighbourhood(evolution[i - 1].Ldet, 1, value, ix, jx, 0)) { // Now check on the upper scale if (check_maximum_neighbourhood(evolution[i + 1].Ldet, 1, value, ix, jx, 0)) is_extremum = true; } } } } // Add the point of interest!! if (is_extremum) { KeyPoint point; point.pt.x = (float)jx; point.pt.y = (float)ix; point.response = fabs(value); point.size = evolution[i].esigma; point.octave = (int)evolution[i].octave; point.class_id = i; // We use the angle field for the sublevel value // Then, we will replace this angle field with the main orientation point.angle = static_cast(evolution[i].sublevel); kpts_par[i - 1].push_back(point); } } } } } private: std::vector* evolution_; std::vector >* kpts_par_; KAZEOptions options_; }; /* ************************************************************************* */ /** * @brief This method performs the detection of keypoints by using the normalized * score of the Hessian determinant through the nonlinear scale space * @param kpts Vector of keypoints * @note We compute features for each of the nonlinear scale space level in a different processing thread */ void KAZEFeatures::Determinant_Hessian(std::vector& kpts) { int level = 0; float dist = 0.0, smax = 3.0; int npoints = 0, id_repeated = 0; int left_x = 0, right_x = 0, up_y = 0, down_y = 0; bool is_extremum = false, is_repeated = false, is_out = false; // Delete the memory of the vector of keypoints vectors // In case we use the same kaze object for multiple images for (size_t i = 0; i < kpts_par_.size(); i++) { vector().swap(kpts_par_[i]); } kpts_par_.clear(); vector aux; // Allocate memory for the vector of vectors for (size_t i = 1; i < evolution_.size() - 1; i++) { kpts_par_.push_back(aux); } parallel_for_(Range(1, (int)evolution_.size()-1), FindExtremumKAZEInvoker(evolution_, kpts_par_, options_)); // Now fill the vector of keypoints!!! for (int i = 0; i < (int)kpts_par_.size(); i++) { for (int j = 0; j < (int)kpts_par_[i].size(); j++) { level = i + 1; is_extremum = true; is_repeated = false; is_out = false; // Check in case we have the same point as maxima in previous evolution levels for (int ik = 0; ik < (int)kpts.size(); ik++) { if (kpts[ik].class_id == level || kpts[ik].class_id == level + 1 || kpts[ik].class_id == level - 1) { dist = pow(kpts_par_[i][j].pt.x - kpts[ik].pt.x, 2) + pow(kpts_par_[i][j].pt.y - kpts[ik].pt.y, 2); if (dist < evolution_[level].sigma_size*evolution_[level].sigma_size) { if (kpts_par_[i][j].response > kpts[ik].response) { id_repeated = ik; is_repeated = true; } else { is_extremum = false; } break; } } } if (is_extremum == true) { // Check that the point is under the image limits for the descriptor computation left_x = fRound(kpts_par_[i][j].pt.x - smax*kpts_par_[i][j].size); right_x = fRound(kpts_par_[i][j].pt.x + smax*kpts_par_[i][j].size); up_y = fRound(kpts_par_[i][j].pt.y - smax*kpts_par_[i][j].size); down_y = fRound(kpts_par_[i][j].pt.y + smax*kpts_par_[i][j].size); if (left_x < 0 || right_x >= evolution_[level].Ldet.cols || up_y < 0 || down_y >= evolution_[level].Ldet.rows) { is_out = true; } is_out = false; if (is_out == false) { if (is_repeated == false) { kpts.push_back(kpts_par_[i][j]); npoints++; } else { kpts[id_repeated] = kpts_par_[i][j]; } } } } } } /* ************************************************************************* */ /** * @brief This method performs subpixel refinement of the detected keypoints * @param kpts Vector of detected keypoints */ void KAZEFeatures::Do_Subpixel_Refinement(std::vector &kpts) { int step = 1; int x = 0, y = 0; float Dx = 0.0, Dy = 0.0, Ds = 0.0, dsc = 0.0; float Dxx = 0.0, Dyy = 0.0, Dss = 0.0, Dxy = 0.0, Dxs = 0.0, Dys = 0.0; Mat A = Mat::zeros(3, 3, CV_32F); Mat b = Mat::zeros(3, 1, CV_32F); Mat dst = Mat::zeros(3, 1, CV_32F); vector kpts_(kpts); for (size_t i = 0; i < kpts_.size(); i++) { x = static_cast(kpts_[i].pt.x); y = static_cast(kpts_[i].pt.y); // Compute the gradient Dx = (1.0f / (2.0f*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr(y)+x + step) - *(evolution_[kpts_[i].class_id].Ldet.ptr(y)+x - step)); Dy = (1.0f / (2.0f*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr(y + step) + x) - *(evolution_[kpts_[i].class_id].Ldet.ptr(y - step) + x)); Ds = 0.5f*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr(y)+x) - *(evolution_[kpts_[i].class_id - 1].Ldet.ptr(y)+x)); // Compute the Hessian Dxx = (1.0f / (step*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr(y)+x + step) + *(evolution_[kpts_[i].class_id].Ldet.ptr(y)+x - step) - 2.0f*(*(evolution_[kpts_[i].class_id].Ldet.ptr(y)+x))); Dyy = (1.0f / (step*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr(y + step) + x) + *(evolution_[kpts_[i].class_id].Ldet.ptr(y - step) + x) - 2.0f*(*(evolution_[kpts_[i].class_id].Ldet.ptr(y)+x))); Dss = *(evolution_[kpts_[i].class_id + 1].Ldet.ptr(y)+x) + *(evolution_[kpts_[i].class_id - 1].Ldet.ptr(y)+x) - 2.0f*(*(evolution_[kpts_[i].class_id].Ldet.ptr(y)+x)); Dxy = (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr(y + step) + x + step) + (*(evolution_[kpts_[i].class_id].Ldet.ptr(y - step) + x - step))) - (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr(y - step) + x + step) + (*(evolution_[kpts_[i].class_id].Ldet.ptr(y + step) + x - step))); Dxs = (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr(y)+x + step) + (*(evolution_[kpts_[i].class_id - 1].Ldet.ptr(y)+x - step))) - (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr(y)+x - step) + (*(evolution_[kpts_[i].class_id - 1].Ldet.ptr(y)+x + step))); Dys = (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr(y + step) + x) + (*(evolution_[kpts_[i].class_id - 1].Ldet.ptr(y - step) + x))) - (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr(y - step) + x) + (*(evolution_[kpts_[i].class_id - 1].Ldet.ptr(y + step) + x))); // Solve the linear system *(A.ptr(0)) = Dxx; *(A.ptr(1) + 1) = Dyy; *(A.ptr(2) + 2) = Dss; *(A.ptr(0) + 1) = *(A.ptr(1)) = Dxy; *(A.ptr(0) + 2) = *(A.ptr(2)) = Dxs; *(A.ptr(1) + 2) = *(A.ptr(2) + 1) = Dys; *(b.ptr(0)) = -Dx; *(b.ptr(1)) = -Dy; *(b.ptr(2)) = -Ds; solve(A, b, dst, DECOMP_LU); if (fabs(*(dst.ptr(0))) <= 1.0f && fabs(*(dst.ptr(1))) <= 1.0f && fabs(*(dst.ptr(2))) <= 1.0f) { kpts_[i].pt.x += *(dst.ptr(0)); kpts_[i].pt.y += *(dst.ptr(1)); dsc = kpts_[i].octave + (kpts_[i].angle + *(dst.ptr(2))) / ((float)(options_.nsublevels)); // In OpenCV the size of a keypoint is the diameter!! kpts_[i].size = 2.0f*options_.soffset*pow((float)2.0f, dsc); kpts_[i].angle = 0.0; } // Set the points to be deleted after the for loop else { kpts_[i].response = -1; } } // Clear the vector of keypoints kpts.clear(); for (size_t i = 0; i < kpts_.size(); i++) { if (kpts_[i].response != -1) { kpts.push_back(kpts_[i]); } } } /* ************************************************************************* */ class KAZE_Descriptor_Invoker : public ParallelLoopBody { public: KAZE_Descriptor_Invoker(std::vector &kpts, Mat &desc, std::vector& evolution, const KAZEOptions& options) : kpts_(&kpts) , desc_(&desc) , evolution_(&evolution) , options_(options) { } virtual ~KAZE_Descriptor_Invoker() { } void operator() (const Range& range) const { std::vector &kpts = *kpts_; Mat &desc = *desc_; std::vector &evolution = *evolution_; for (int i = range.start; i < range.end; i++) { kpts[i].angle = 0.0; if (options_.upright) { kpts[i].angle = 0.0; if (options_.extended) Get_KAZE_Upright_Descriptor_128(kpts[i], desc.ptr((int)i)); else Get_KAZE_Upright_Descriptor_64(kpts[i], desc.ptr((int)i)); } else { KAZEFeatures::Compute_Main_Orientation(kpts[i], evolution, options_); if (options_.extended) Get_KAZE_Descriptor_128(kpts[i], desc.ptr((int)i)); else Get_KAZE_Descriptor_64(kpts[i], desc.ptr((int)i)); } } } private: void Get_KAZE_Upright_Descriptor_64(const KeyPoint& kpt, float* desc) const; void Get_KAZE_Descriptor_64(const KeyPoint& kpt, float* desc) const; void Get_KAZE_Upright_Descriptor_128(const KeyPoint& kpt, float* desc) const; void Get_KAZE_Descriptor_128(const KeyPoint& kpt, float *desc) const; std::vector * kpts_; Mat * desc_; std::vector * evolution_; KAZEOptions options_; }; /* ************************************************************************* */ /** * @brief This method computes the set of descriptors through the nonlinear scale space * @param kpts Vector of keypoints * @param desc Matrix with the feature descriptors */ void KAZEFeatures::Feature_Description(std::vector &kpts, Mat &desc) { for(size_t i = 0; i < kpts.size(); i++) { CV_Assert(0 <= kpts[i].class_id && kpts[i].class_id < static_cast(evolution_.size())); } // Allocate memory for the matrix of descriptors if (options_.extended == true) { desc = Mat::zeros((int)kpts.size(), 128, CV_32FC1); } else { desc = Mat::zeros((int)kpts.size(), 64, CV_32FC1); } parallel_for_(Range(0, (int)kpts.size()), KAZE_Descriptor_Invoker(kpts, desc, evolution_, options_)); } /* ************************************************************************* */ /** * @brief This method computes the main orientation for a given keypoint * @param kpt Input keypoint * @note The orientation is computed using a similar approach as described in the * original SURF method. See Bay et al., Speeded Up Robust Features, ECCV 2006 */ void KAZEFeatures::Compute_Main_Orientation(KeyPoint &kpt, const std::vector& evolution_, const KAZEOptions& options) { int ix = 0, iy = 0, idx = 0, s = 0, level = 0; float xf = 0.0, yf = 0.0, gweight = 0.0; vector resX(109), resY(109), Ang(109); // Variables for computing the dominant direction float sumX = 0.0, sumY = 0.0, max = 0.0, ang1 = 0.0, ang2 = 0.0; // Get the information from the keypoint xf = kpt.pt.x; yf = kpt.pt.y; level = kpt.class_id; s = fRound(kpt.size / 2.0f); // Calculate derivatives responses for points within radius of 6*scale for (int i = -6; i <= 6; ++i) { for (int j = -6; j <= 6; ++j) { if (i*i + j*j < 36) { iy = fRound(yf + j*s); ix = fRound(xf + i*s); if (iy >= 0 && iy < options.img_height && ix >= 0 && ix < options.img_width) { gweight = gaussian(iy - yf, ix - xf, 2.5f*s); resX[idx] = gweight*(*(evolution_[level].Lx.ptr(iy)+ix)); resY[idx] = gweight*(*(evolution_[level].Ly.ptr(iy)+ix)); } else { resX[idx] = 0.0; resY[idx] = 0.0; } Ang[idx] = getAngle(resX[idx], resY[idx]); ++idx; } } } // Loop slides pi/3 window around feature point for (ang1 = 0; ang1 < 2.0f*CV_PI; ang1 += 0.15f) { ang2 = (ang1 + (float)(CV_PI / 3.0) > (float)(2.0*CV_PI) ? ang1 - (float)(5.0*CV_PI / 3.0) : ang1 + (float)(CV_PI / 3.0)); sumX = sumY = 0.f; for (size_t k = 0; k < Ang.size(); ++k) { // Get angle from the x-axis of the sample point const float & ang = Ang[k]; // Determine whether the point is within the window if (ang1 < ang2 && ang1 < ang && ang < ang2) { sumX += resX[k]; sumY += resY[k]; } else if (ang2 < ang1 && ((ang > 0 && ang < ang2) || (ang > ang1 && ang < (float)(2.0*CV_PI)))) { sumX += resX[k]; sumY += resY[k]; } } // if the vector produced from this window is longer than all // previous vectors then this forms the new dominant direction if (sumX*sumX + sumY*sumY > max) { // store largest orientation max = sumX*sumX + sumY*sumY; kpt.angle = getAngle(sumX, sumY); } } } /* ************************************************************************* */ /** * @brief This method computes the upright descriptor (not rotation invariant) of * the provided keypoint * @param kpt Input keypoint * @param desc Descriptor vector * @note Rectangular grid of 24 s x 24 s. Descriptor Length 64. The descriptor is inspired * from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching, * ECCV 2008 */ void KAZE_Descriptor_Invoker::Get_KAZE_Upright_Descriptor_64(const KeyPoint &kpt, float *desc) const { float dx = 0.0, dy = 0.0, mdx = 0.0, mdy = 0.0, gauss_s1 = 0.0, gauss_s2 = 0.0; float rx = 0.0, ry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0; float sample_x = 0.0, sample_y = 0.0; int x1 = 0, y1 = 0, sample_step = 0, pattern_size = 0; int x2 = 0, y2 = 0, kx = 0, ky = 0, i = 0, j = 0, dcount = 0; float fx = 0.0, fy = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0; int dsize = 0, scale = 0, level = 0; std::vector& evolution = *evolution_; // Subregion centers for the 4x4 gaussian weighting float cx = -0.5f, cy = 0.5f; // Set the descriptor size and the sample and pattern sizes dsize = 64; sample_step = 5; pattern_size = 12; // Get the information from the keypoint yf = kpt.pt.y; xf = kpt.pt.x; scale = fRound(kpt.size / 2.0f); level = kpt.class_id; i = -8; // Calculate descriptor for this interest point // Area of size 24 s x 24 s while (i < pattern_size) { j = -8; i = i - 4; cx += 1.0f; cy = -0.5f; while (j < pattern_size) { dx = dy = mdx = mdy = 0.0; cy += 1.0f; j = j - 4; ky = i + sample_step; kx = j + sample_step; ys = yf + (ky*scale); xs = xf + (kx*scale); for (int k = i; k < i + 9; k++) { for (int l = j; l < j + 9; l++) { sample_y = k*scale + yf; sample_x = l*scale + xf; //Get the gaussian weighted x and y responses gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.5f*scale); y1 = (int)(sample_y - 0.5f); x1 = (int)(sample_x - 0.5f); checkDescriptorLimits(x1, y1, options_.img_width, options_.img_height); y2 = (int)(sample_y + 0.5f); x2 = (int)(sample_x + 0.5f); checkDescriptorLimits(x2, y2, options_.img_width, options_.img_height); fx = sample_x - x1; fy = sample_y - y1; res1 = *(evolution[level].Lx.ptr(y1)+x1); res2 = *(evolution[level].Lx.ptr(y1)+x2); res3 = *(evolution[level].Lx.ptr(y2)+x1); res4 = *(evolution[level].Lx.ptr(y2)+x2); rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4; res1 = *(evolution[level].Ly.ptr(y1)+x1); res2 = *(evolution[level].Ly.ptr(y1)+x2); res3 = *(evolution[level].Ly.ptr(y2)+x1); res4 = *(evolution[level].Ly.ptr(y2)+x2); ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4; rx = gauss_s1*rx; ry = gauss_s1*ry; // Sum the derivatives to the cumulative descriptor dx += rx; dy += ry; mdx += fabs(rx); mdy += fabs(ry); } } // Add the values to the descriptor vector gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f); desc[dcount++] = dx*gauss_s2; desc[dcount++] = dy*gauss_s2; desc[dcount++] = mdx*gauss_s2; desc[dcount++] = mdy*gauss_s2; len += (dx*dx + dy*dy + mdx*mdx + mdy*mdy)*gauss_s2*gauss_s2; j += 9; } i += 9; } // convert to unit vector len = sqrt(len); for (i = 0; i < dsize; i++) { desc[i] /= len; } } /* ************************************************************************* */ /** * @brief This method computes the descriptor of the provided keypoint given the * main orientation of the keypoint * @param kpt Input keypoint * @param desc Descriptor vector * @note Rectangular grid of 24 s x 24 s. Descriptor Length 64. The descriptor is inspired * from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching, * ECCV 2008 */ void KAZE_Descriptor_Invoker::Get_KAZE_Descriptor_64(const KeyPoint &kpt, float *desc) const { float dx = 0.0, dy = 0.0, mdx = 0.0, mdy = 0.0, gauss_s1 = 0.0, gauss_s2 = 0.0; float rx = 0.0, ry = 0.0, rrx = 0.0, rry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0; float sample_x = 0.0, sample_y = 0.0, co = 0.0, si = 0.0, angle = 0.0; float fx = 0.0, fy = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0; int x1 = 0, y1 = 0, x2 = 0, y2 = 0, sample_step = 0, pattern_size = 0; int kx = 0, ky = 0, i = 0, j = 0, dcount = 0; int dsize = 0, scale = 0, level = 0; std::vector& evolution = *evolution_; // Subregion centers for the 4x4 gaussian weighting float cx = -0.5f, cy = 0.5f; // Set the descriptor size and the sample and pattern sizes dsize = 64; sample_step = 5; pattern_size = 12; // Get the information from the keypoint yf = kpt.pt.y; xf = kpt.pt.x; scale = fRound(kpt.size / 2.0f); angle = kpt.angle; level = kpt.class_id; co = cos(angle); si = sin(angle); i = -8; // Calculate descriptor for this interest point // Area of size 24 s x 24 s while (i < pattern_size) { j = -8; i = i - 4; cx += 1.0f; cy = -0.5f; while (j < pattern_size) { dx = dy = mdx = mdy = 0.0; cy += 1.0f; j = j - 4; ky = i + sample_step; kx = j + sample_step; xs = xf + (-kx*scale*si + ky*scale*co); ys = yf + (kx*scale*co + ky*scale*si); for (int k = i; k < i + 9; ++k) { for (int l = j; l < j + 9; ++l) { // Get coords of sample point on the rotated axis sample_y = yf + (l*scale*co + k*scale*si); sample_x = xf + (-l*scale*si + k*scale*co); // Get the gaussian weighted x and y responses gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.5f*scale); y1 = fRound(sample_y - 0.5f); x1 = fRound(sample_x - 0.5f); checkDescriptorLimits(x1, y1, options_.img_width, options_.img_height); y2 = (int)(sample_y + 0.5f); x2 = (int)(sample_x + 0.5f); checkDescriptorLimits(x2, y2, options_.img_width, options_.img_height); fx = sample_x - x1; fy = sample_y - y1; res1 = *(evolution[level].Lx.ptr(y1)+x1); res2 = *(evolution[level].Lx.ptr(y1)+x2); res3 = *(evolution[level].Lx.ptr(y2)+x1); res4 = *(evolution[level].Lx.ptr(y2)+x2); rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4; res1 = *(evolution[level].Ly.ptr(y1)+x1); res2 = *(evolution[level].Ly.ptr(y1)+x2); res3 = *(evolution[level].Ly.ptr(y2)+x1); res4 = *(evolution[level].Ly.ptr(y2)+x2); ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4; // Get the x and y derivatives on the rotated axis rry = gauss_s1*(rx*co + ry*si); rrx = gauss_s1*(-rx*si + ry*co); // Sum the derivatives to the cumulative descriptor dx += rrx; dy += rry; mdx += fabs(rrx); mdy += fabs(rry); } } // Add the values to the descriptor vector gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f); desc[dcount++] = dx*gauss_s2; desc[dcount++] = dy*gauss_s2; desc[dcount++] = mdx*gauss_s2; desc[dcount++] = mdy*gauss_s2; len += (dx*dx + dy*dy + mdx*mdx + mdy*mdy)*gauss_s2*gauss_s2; j += 9; } i += 9; } // convert to unit vector len = sqrt(len); for (i = 0; i < dsize; i++) { desc[i] /= len; } } /* ************************************************************************* */ /** * @brief This method computes the extended upright descriptor (not rotation invariant) of * the provided keypoint * @param kpt Input keypoint * @param desc Descriptor vector * @note Rectangular grid of 24 s x 24 s. Descriptor Length 128. The descriptor is inspired * from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching, * ECCV 2008 */ void KAZE_Descriptor_Invoker::Get_KAZE_Upright_Descriptor_128(const KeyPoint &kpt, float *desc) const { float gauss_s1 = 0.0, gauss_s2 = 0.0; float rx = 0.0, ry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0; float sample_x = 0.0, sample_y = 0.0; int x1 = 0, y1 = 0, sample_step = 0, pattern_size = 0; int x2 = 0, y2 = 0, kx = 0, ky = 0, i = 0, j = 0, dcount = 0; float fx = 0.0, fy = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0; float dxp = 0.0, dyp = 0.0, mdxp = 0.0, mdyp = 0.0; float dxn = 0.0, dyn = 0.0, mdxn = 0.0, mdyn = 0.0; int dsize = 0, scale = 0, level = 0; // Subregion centers for the 4x4 gaussian weighting float cx = -0.5f, cy = 0.5f; std::vector& evolution = *evolution_; // Set the descriptor size and the sample and pattern sizes dsize = 128; sample_step = 5; pattern_size = 12; // Get the information from the keypoint yf = kpt.pt.y; xf = kpt.pt.x; scale = fRound(kpt.size / 2.0f); level = kpt.class_id; i = -8; // Calculate descriptor for this interest point // Area of size 24 s x 24 s while (i < pattern_size) { j = -8; i = i - 4; cx += 1.0f; cy = -0.5f; while (j < pattern_size) { dxp = dxn = mdxp = mdxn = 0.0; dyp = dyn = mdyp = mdyn = 0.0; cy += 1.0f; j = j - 4; ky = i + sample_step; kx = j + sample_step; ys = yf + (ky*scale); xs = xf + (kx*scale); for (int k = i; k < i + 9; k++) { for (int l = j; l < j + 9; l++) { sample_y = k*scale + yf; sample_x = l*scale + xf; //Get the gaussian weighted x and y responses gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.5f*scale); y1 = (int)(sample_y - 0.5f); x1 = (int)(sample_x - 0.5f); checkDescriptorLimits(x1, y1, options_.img_width, options_.img_height); y2 = (int)(sample_y + 0.5f); x2 = (int)(sample_x + 0.5f); checkDescriptorLimits(x2, y2, options_.img_width, options_.img_height); fx = sample_x - x1; fy = sample_y - y1; res1 = *(evolution[level].Lx.ptr(y1)+x1); res2 = *(evolution[level].Lx.ptr(y1)+x2); res3 = *(evolution[level].Lx.ptr(y2)+x1); res4 = *(evolution[level].Lx.ptr(y2)+x2); rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4; res1 = *(evolution[level].Ly.ptr(y1)+x1); res2 = *(evolution[level].Ly.ptr(y1)+x2); res3 = *(evolution[level].Ly.ptr(y2)+x1); res4 = *(evolution[level].Ly.ptr(y2)+x2); ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4; rx = gauss_s1*rx; ry = gauss_s1*ry; // Sum the derivatives to the cumulative descriptor if (ry >= 0.0) { dxp += rx; mdxp += fabs(rx); } else { dxn += rx; mdxn += fabs(rx); } if (rx >= 0.0) { dyp += ry; mdyp += fabs(ry); } else { dyn += ry; mdyn += fabs(ry); } } } // Add the values to the descriptor vector gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f); desc[dcount++] = dxp*gauss_s2; desc[dcount++] = dxn*gauss_s2; desc[dcount++] = mdxp*gauss_s2; desc[dcount++] = mdxn*gauss_s2; desc[dcount++] = dyp*gauss_s2; desc[dcount++] = dyn*gauss_s2; desc[dcount++] = mdyp*gauss_s2; desc[dcount++] = mdyn*gauss_s2; // Store the current length^2 of the vector len += (dxp*dxp + dxn*dxn + mdxp*mdxp + mdxn*mdxn + dyp*dyp + dyn*dyn + mdyp*mdyp + mdyn*mdyn)*gauss_s2*gauss_s2; j += 9; } i += 9; } // convert to unit vector len = sqrt(len); for (i = 0; i < dsize; i++) { desc[i] /= len; } } /* ************************************************************************* */ /** * @brief This method computes the extended G-SURF descriptor of the provided keypoint * given the main orientation of the keypoint * @param kpt Input keypoint * @param desc Descriptor vector * @note Rectangular grid of 24 s x 24 s. Descriptor Length 128. The descriptor is inspired * from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching, * ECCV 2008 */ void KAZE_Descriptor_Invoker::Get_KAZE_Descriptor_128(const KeyPoint &kpt, float *desc) const { float gauss_s1 = 0.0, gauss_s2 = 0.0; float rx = 0.0, ry = 0.0, rrx = 0.0, rry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0; float sample_x = 0.0, sample_y = 0.0, co = 0.0, si = 0.0, angle = 0.0; float fx = 0.0, fy = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0; float dxp = 0.0, dyp = 0.0, mdxp = 0.0, mdyp = 0.0; float dxn = 0.0, dyn = 0.0, mdxn = 0.0, mdyn = 0.0; int x1 = 0, y1 = 0, x2 = 0, y2 = 0, sample_step = 0, pattern_size = 0; int kx = 0, ky = 0, i = 0, j = 0, dcount = 0; int dsize = 0, scale = 0, level = 0; std::vector& evolution = *evolution_; // Subregion centers for the 4x4 gaussian weighting float cx = -0.5f, cy = 0.5f; // Set the descriptor size and the sample and pattern sizes dsize = 128; sample_step = 5; pattern_size = 12; // Get the information from the keypoint yf = kpt.pt.y; xf = kpt.pt.x; scale = fRound(kpt.size / 2.0f); angle = kpt.angle; level = kpt.class_id; co = cos(angle); si = sin(angle); i = -8; // Calculate descriptor for this interest point // Area of size 24 s x 24 s while (i < pattern_size) { j = -8; i = i - 4; cx += 1.0f; cy = -0.5f; while (j < pattern_size) { dxp = dxn = mdxp = mdxn = 0.0; dyp = dyn = mdyp = mdyn = 0.0; cy += 1.0f; j = j - 4; ky = i + sample_step; kx = j + sample_step; xs = xf + (-kx*scale*si + ky*scale*co); ys = yf + (kx*scale*co + ky*scale*si); for (int k = i; k < i + 9; ++k) { for (int l = j; l < j + 9; ++l) { // Get coords of sample point on the rotated axis sample_y = yf + (l*scale*co + k*scale*si); sample_x = xf + (-l*scale*si + k*scale*co); // Get the gaussian weighted x and y responses gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.5f*scale); y1 = fRound(sample_y - 0.5f); x1 = fRound(sample_x - 0.5f); checkDescriptorLimits(x1, y1, options_.img_width, options_.img_height); y2 = (int)(sample_y + 0.5f); x2 = (int)(sample_x + 0.5f); checkDescriptorLimits(x2, y2, options_.img_width, options_.img_height); fx = sample_x - x1; fy = sample_y - y1; res1 = *(evolution[level].Lx.ptr(y1)+x1); res2 = *(evolution[level].Lx.ptr(y1)+x2); res3 = *(evolution[level].Lx.ptr(y2)+x1); res4 = *(evolution[level].Lx.ptr(y2)+x2); rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4; res1 = *(evolution[level].Ly.ptr(y1)+x1); res2 = *(evolution[level].Ly.ptr(y1)+x2); res3 = *(evolution[level].Ly.ptr(y2)+x1); res4 = *(evolution[level].Ly.ptr(y2)+x2); ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4; // Get the x and y derivatives on the rotated axis rry = gauss_s1*(rx*co + ry*si); rrx = gauss_s1*(-rx*si + ry*co); // Sum the derivatives to the cumulative descriptor if (rry >= 0.0) { dxp += rrx; mdxp += fabs(rrx); } else { dxn += rrx; mdxn += fabs(rrx); } if (rrx >= 0.0) { dyp += rry; mdyp += fabs(rry); } else { dyn += rry; mdyn += fabs(rry); } } } // Add the values to the descriptor vector gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f); desc[dcount++] = dxp*gauss_s2; desc[dcount++] = dxn*gauss_s2; desc[dcount++] = mdxp*gauss_s2; desc[dcount++] = mdxn*gauss_s2; desc[dcount++] = dyp*gauss_s2; desc[dcount++] = dyn*gauss_s2; desc[dcount++] = mdyp*gauss_s2; desc[dcount++] = mdyn*gauss_s2; // Store the current length^2 of the vector len += (dxp*dxp + dxn*dxn + mdxp*mdxp + mdxn*mdxn + dyp*dyp + dyn*dyn + mdyp*mdyp + mdyn*mdyn)*gauss_s2*gauss_s2; j += 9; } i += 9; } // convert to unit vector len = sqrt(len); for (i = 0; i < dsize; i++) { desc[i] /= len; } } }