Skip to content

Add SURF and BRISK feature types #702

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/aliceVision/feature/openCV/ImageDescriber_BRISK_OCV.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// This file is part of the AliceVision project.
// Copyright (c) 2016 AliceVision contributors.
// Copyright (c) 2012 openMVG contributors.
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

#include "ImageDescriber_BRISK_OCV.hpp"

#include "aliceVision/image/all.hpp"

#include <opencv2/opencv.hpp>
#include <opencv2/core/eigen.hpp>

namespace aliceVision {
namespace feature {

bool ImageDescriber_BRISK_OCV::describe(const image::Image<unsigned char>& image,
std::unique_ptr<Regions>& regions,
const image::Image<unsigned char>* mask)
{
cv::Mat img;
cv::eigen2cv(image.GetMat(), img);

std::vector< cv::KeyPoint > vec_keypoints;
cv::Mat m_desc;

cv::Ptr<cv::Feature2D> extractor = cv::BRISK::create(int thresh = 30,
int octaves = 3,
float patternScale = 1.0f);

extractor->detectAndCompute(img, cv::Mat(), vec_keypoints, m_desc);

if (vec_keypoints.empty())
{
return false;
}

allocate(regions);

// Build alias to cached data
BRISK_Float_Regions* regionsCasted = dynamic_cast<BRISK_Float_Regions*>(regions.get());

// Reserve some memory for faster keypoint saving
regionsCasted->Features().reserve(vec_keypoints.size());
regionsCasted->Descriptors().reserve(vec_keypoints.size());

typedef Descriptor<float, 64> DescriptorT;
DescriptorT descriptor;
int cpt = 0;

for(const auto& i_keypoint : vec_keypoints)
{
SIOPointFeature feat(i_keypoint.pt.x, i_keypoint.pt.y, i_keypoint.size, i_keypoint.angle);
regionsCasted->Features().push_back(feat);

memcpy(descriptor.getData(),
m_desc.ptr<typename DescriptorT::bin_type>(cpt),
DescriptorT::static_size*sizeof(DescriptorT::bin_type));

regionsCasted->Descriptors().push_back(descriptor);
++cpt;
}

return true;
}

} //namespace feature
} //namespace aliceVision
109 changes: 109 additions & 0 deletions src/aliceVision/feature/openCV/ImageDescriber_BRISK_OCV.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// This file is part of the AliceVision project.
// Copyright (c) 2016 AliceVision contributors.
// Copyright (c) 2012 openMVG contributors.
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

#pragma once

#include <aliceVision/feature/imageDescriberCommon.hpp>
#include <aliceVision/feature/ImageDescriber.hpp>
#include <aliceVision/feature/regionsFactory.hpp>

#include <aliceVision/system/Logger.hpp>

namespace aliceVision {

namespace image {

template <typename T>
class Image;

} //namespace image

namespace feature {

/**
* @brief Create an ImageDescriber interface for OpenCV BRISK feature extractor
* Regions is the same as BRISK floating point
*/
class ImageDescriber_BRISK_OCV : public ImageDescriber
{
public:

/**
* @brief Check if the image describer use CUDA
* @return True if the image describer use CUDA
*/
bool useCuda() const override
{
return false;
}

/**
* @brief Check if the image describer use float image
* @return True if the image describer use float image
*/
bool useFloatImage() const override
{
return false;
}

/**
* @brief Get the corresponding EImageDescriberType
* @return EImageDescriberType
*/
virtual EImageDescriberType getDescriberType() const override

Check notice on line 57 in src/aliceVision/feature/openCV/ImageDescriber_BRISK_OCV.hpp

View check run for this annotation

codefactor.io / CodeFactor

src/aliceVision/feature/openCV/ImageDescriber_BRISK_OCV.hpp#L57

"virtual" is redundant since function is already declared as "override". (readability/inheritance)
{
return EImageDescriberType::BRISK_OCV;
}

/**
* @brief Get the total amount of RAM needed for a
* feature extraction of an image of the given dimension.
* @param[in] width The image width
* @param[in] height The image height
* @return total amount of memory needed
*/
std::size_t getMemoryConsumption(std::size_t width, std::size_t height) const override
{
return 3 * width * height * sizeof(unsigned char);
}

/**
* @brief Use a preset to control the number of detected regions
* @param[in] preset The preset configuration
* @return True if configuration succeed. (here always false)
*/
void setConfigurationPreset(EImageDescriberPreset preset) override
{
ALICEVISION_LOG_DEBUG("Image describer preset ignored for BRISK_OCV.");
}

/**
* @brief Detect regions on the 8-bit image and compute their attributes (description)
* @param[in] image Image.
* @param[out] regions The detected regions and attributes (the caller must delete the allocated data)
* @param[in] mask 8-bit grayscale image for keypoint filtering (optional)
* Non-zero values depict the region of interest.
* @return True if detection succed.
*/
bool describe(const image::Image<unsigned char>& image,
std::unique_ptr<Regions>& regions,
const image::Image<unsigned char>* mask = NULL) override;


/**
* @brief Allocate Regions type depending of the ImageDescriber
* @param[in,out] regions
*/
void allocate(std::unique_ptr<Regions> &regions) const override
{
regions.reset( new BRISK_Float_Regions );
}

};

} //namespace feature
} //namespace aliceVision
180 changes: 180 additions & 0 deletions src/aliceVision/feature/openCV/ImageDescriber_SURF_OCV.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// This file is part of the AliceVision project.
// Copyright (c) 2016 AliceVision contributors.
// Copyright (c) 2012 openMVG contributors.
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

#include "ImageDescriber_SURF_OCV.hpp"

#include <aliceVision/image/all.hpp>
#include <aliceVision/system/Timer.hpp>
#include <aliceVision/system/Logger.hpp>

#include <opencv2/opencv.hpp>
#include <opencv2/core/eigen.hpp>
#include <opencv2/xfeatures2d.hpp>

namespace aliceVision {
namespace feature {

void SURF_openCV_Params::setConfigurationPreset(EImageDescriberPreset preset)
{
switch(preset)
{
case EImageDescriberPreset::LOW:
hessianThreshold = 340;
maxTotalKeypoints = 1000;
break;
case EImageDescriberPreset::MEDIUM:
hessianThreshold = 380;
maxTotalKeypoints = 5000;
break;
case EImageDescriberPreset::NORMAL:
hessianThreshold = 420;
maxTotalKeypoints = 10000;
break;
case EImageDescriberPreset::HIGH:
hessianThreshold = 460;
maxTotalKeypoints = 20000;
break;
case EImageDescriberPreset::ULTRA:
hessianThreshold = 500;
maxTotalKeypoints = 40000;
break;
default:
throw std::out_of_range("Invalid image describer preset enum");
}
}

bool ImageDescriber_SURF_openCV::describe(const image::Image<unsigned char>& image,
std::unique_ptr<Regions> &regions,
const image::Image<unsigned char> * mask)
{

// Convert for opencv
cv::Mat img;
cv::eigen2cv(image.GetMat(), img);

// Create a SURF detector
std::vector< cv::KeyPoint > v_keypoints;
cv::Mat m_desc;
std::size_t maxDetect = 0; //< No max value by default
if(_params.maxTotalKeypoints)
if(!_params.gridSize) //< If no grid filtering, use opencv to limit the number of features
maxDetect = _params.maxTotalKeypoints;

cv::Ptr<cv::Feature2D> surfdetector = cv::xfeatures2d::SURF::create(maxDetect,
_params.hessianThreshold,
_params.nOctave,
_params.nOctaveLayers,
_params.extend,
_params.upright);


// Detect SURF keypoints
auto detect_start = std::chrono::steady_clock::now();
surfdetector->detect(img, v_keypoints);
auto detect_end = std::chrono::steady_clock::now();
auto detect_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(detect_end - detect_start);

ALICEVISION_LOG_TRACE("SURF: hessianThreshold: " << _params.hessianThreshold << std::endl);
ALICEVISION_LOG_TRACE("Detect SURF: " << detect_elapsed.count() << " milliseconds." << std::endl);
ALICEVISION_LOG_TRACE("Image size: " << img.cols << " x " << img.rows << std::endl);
ALICEVISION_LOG_TRACE("Grid size: " << _params.gridSize << ", maxTotalKeypoints: " << _params.maxTotalKeypoints << std::endl);
ALICEVISION_LOG_TRACE("Number of detected features: " << v_keypoints.size() << std::endl);

// cv::KeyPoint::response: the response by which the most strong keypoints have been selected.
// Can be used for the further sorting or subsampling.
std::sort(v_keypoints.begin(), v_keypoints.end(), [](const cv::KeyPoint& a, const cv::KeyPoint& b) { return a.size > b.size; });

// Grid filtering of the keypoints to ensure a global repartition
if(_params.gridSize && _params.maxTotalKeypoints)
{
// Only filter features if we have more features than the maxTotalKeypoints
if(v_keypoints.size() > _params.maxTotalKeypoints)
{
std::vector< cv::KeyPoint > filtered_keypoints;
std::vector< cv::KeyPoint > rejected_keypoints;
filtered_keypoints.reserve(std::min(v_keypoints.size(), _params.maxTotalKeypoints));
rejected_keypoints.reserve(v_keypoints.size());

cv::Mat countFeatPerCell(_params.gridSize, _params.gridSize, cv::DataType<int>::type, cv::Scalar(0));
const std::size_t keypointsPerCell = _params.maxTotalKeypoints / countFeatPerCell.total();
const double regionWidth = image.Width() / double(countFeatPerCell.cols);
const double regionHeight = image.Height() / double(countFeatPerCell.rows);

ALICEVISION_LOG_TRACE ("Grid filtering -- keypointsPerCell: " << keypointsPerCell
<< ", regionWidth: " << regionWidth
<< ", regionHeight: " << regionHeight << std::endl);

for(const cv::KeyPoint& keypoint: v_keypoints)
{
const std::size_t cellX = std::min(std::size_t(keypoint.pt.x / regionWidth), _params.gridSize);
const std::size_t cellY = std::min(std::size_t(keypoint.pt.y / regionHeight), _params.gridSize);
// std::cout << "- keypoint.pt.x: " << keypoint.pt.x << ", keypoint.pt.y: " << keypoint.pt.y << std::endl;
// std::cout << "- cellX: " << cellX << ", cellY: " << cellY << std::endl;
// std::cout << "- countFeatPerCell: " << countFeatPerCell << std::endl;
// std::cout << "- gridSize: " << _params.gridSize << std::endl;

const int count = countFeatPerCell.at<int>(cellX, cellY);
countFeatPerCell.at<int>(cellX, cellY) = count + 1;
if(count < keypointsPerCell)
filtered_keypoints.push_back(keypoint);
else
rejected_keypoints.push_back(keypoint);
}
// If we don't have enough features (less than maxTotalKeypoints) after the grid filtering (empty regions in the grid for example).
// We add the best other ones, without repartition constraint.
if( filtered_keypoints.size() < _params.maxTotalKeypoints )
{
const std::size_t remainingElements = std::min(rejected_keypoints.size(), _params.maxTotalKeypoints - filtered_keypoints.size());
ALICEVISION_LOG_TRACE("Grid filtering -- Copy remaining points: " << remainingElements << std::endl);
filtered_keypoints.insert(filtered_keypoints.end(), rejected_keypoints.begin(), rejected_keypoints.begin() + remainingElements);
}

v_keypoints.swap(filtered_keypoints);
}
}
ALICEVISION_LOG_TRACE("Number of features: " << v_keypoints.size() << std::endl);

// Compute SURF descriptors
auto desc_start = std::chrono::steady_clock::now();
surfdetector->compute(img, v_keypoints, m_desc);
auto desc_end = std::chrono::steady_clock::now();
auto desc_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(desc_end - desc_start);
ALICEVISION_LOG_TRACE("Compute descriptors: " << desc_elapsed.count() << " milliseconds." << std::endl);

allocate(regions);

// Build alias to cached data
SURF_Regions * regionsCasted = dynamic_cast<SURF_Regions*>(regions.get());
// Reserve some memory for faster keypoint saving
regionsCasted->Features().reserve(v_keypoints.size());
regionsCasted->Descriptors().reserve(v_keypoints.size());

// Prepare a column vector with the sum of each descriptor
cv::Mat m_surfsum;
cv::reduce(m_desc, m_surfsum, 1, cv::REDUCE_SUM);

// Copy keypoints and descriptors in the regions
int cpt = 0;
for(const auto& i_kp : v_keypoints)
{
SIOPointFeature feat(i_kp.pt.x, i_kp.pt.y, i_kp.size, i_kp.angle);
regionsCasted->Features().push_back(feat);

Descriptor<unsigned char, 128> desc;
for(std::size_t j = 0; j < 128; ++j)
{
desc[j] = static_cast<unsigned char>(512.0*sqrt(m_desc.at<float>(cpt, j)/m_surfsum.at<float>(cpt, 0)));
}
regionsCasted->Descriptors().push_back(desc);
++cpt;
}

return true;
}

} //namespace feature
} //namespace aliceVision
Loading
Loading