mirror of
https://github.com/opencv/opencv.git
synced 2024-11-27 20:50:25 +08:00
Merge pull request #17907 from Yosshi999:gsoc_asift-py2cpp
* Implement ASIFT in C++ * '>>' should be '> >' within a nested template * add a sample for asift usage * bugfix empty keypoints cause crash * simpler initialization for mask * suppress the number of lines * correct tex document * type casting * add descriptorsize for asift * smaller testdata for asift * more smaller test data * add OpenCV short license header
This commit is contained in:
parent
ce74285c5e
commit
922108060d
@ -584,6 +584,16 @@
|
||||
pages = {1033--1040},
|
||||
publisher = {IEEE}
|
||||
}
|
||||
@article{YM11,
|
||||
author = {Yu, Guoshen and Morel, Jean-Michel},
|
||||
title = {ASIFT: An Algorithm for Fully Affine Invariant Comparison},
|
||||
year = {2011},
|
||||
pages = {11--38},
|
||||
journal = {Image Processing On Line},
|
||||
volume = {1},
|
||||
doi = {10.5201/ipol.2011.my-asift},
|
||||
url = {http://www.ipol.im/pub/algo/my_affine_sift/}
|
||||
}
|
||||
@inproceedings{LCS11,
|
||||
author = {Leutenegger, Stefan and Chli, Margarita and Siegwart, Roland Yves},
|
||||
title = {BRISK: Binary robust invariant scalable keypoints},
|
||||
|
@ -245,6 +245,31 @@ typedef Feature2D DescriptorExtractor;
|
||||
//! @{
|
||||
|
||||
|
||||
/** @brief Class for implementing the wrapper which makes detectors and extractors to be affine invariant,
|
||||
described as ASIFT in @cite YM11 .
|
||||
*/
|
||||
class CV_EXPORTS_W AffineFeature : public Feature2D
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@param backend The detector/extractor you want to use as backend.
|
||||
@param maxTilt The highest power index of tilt factor. 5 is used in the paper as tilt sampling range n.
|
||||
@param minTilt The lowest power index of tilt factor. 0 is used in the paper.
|
||||
@param tiltStep Tilt sampling step \f$\delta_t\f$ in Algorithm 1 in the paper.
|
||||
@param rotateStepBase Rotation sampling step factor b in Algorithm 1 in the paper.
|
||||
*/
|
||||
CV_WRAP static Ptr<AffineFeature> create(const Ptr<Feature2D>& backend,
|
||||
int maxTilt = 5, int minTilt = 0, float tiltStep = 1.4142135623730951f, float rotateStepBase = 72);
|
||||
|
||||
CV_WRAP virtual void setViewParams(const std::vector<float>& tilts, const std::vector<float>& rolls) = 0;
|
||||
CV_WRAP virtual void getViewParams(std::vector<float>& tilts, std::vector<float>& rolls) const = 0;
|
||||
CV_WRAP virtual String getDefaultName() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
typedef AffineFeature AffineFeatureDetector;
|
||||
typedef AffineFeature AffineDescriptorExtractor;
|
||||
|
||||
|
||||
/** @brief Class for extracting keypoints and computing descriptors using the Scale Invariant Feature Transform
|
||||
(SIFT) algorithm by D. Lowe @cite Lowe04 .
|
||||
*/
|
||||
|
358
modules/features2d/src/affine_feature.cpp
Normal file
358
modules/features2d/src/affine_feature.cpp
Normal file
@ -0,0 +1,358 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// This file is based on code issued with the following license.
|
||||
/*********************************************************************
|
||||
* Software License Agreement (BSD License)
|
||||
*
|
||||
* Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
* Copyright (C) 2008-2013, Willow Garage Inc., all rights reserved.
|
||||
* Copyright (C) 2013, Evgeny Toropov, 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:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions 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
|
||||
* COPYRIGHT OWNER 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.
|
||||
*********************************************************************/
|
||||
|
||||
/*
|
||||
Guoshen Yu, Jean-Michel Morel, ASIFT: An Algorithm for Fully Affine
|
||||
Invariant Comparison, Image Processing On Line, 1 (2011), pp. 11–38.
|
||||
https://doi.org/10.5201/ipol.2011.my-asift
|
||||
*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include <iostream>
|
||||
namespace cv {
|
||||
|
||||
class AffineFeature_Impl CV_FINAL : public AffineFeature
|
||||
{
|
||||
public:
|
||||
explicit AffineFeature_Impl(const Ptr<Feature2D>& backend,
|
||||
int maxTilt, int minTilt, float tiltStep, float rotateStepBase);
|
||||
|
||||
int descriptorSize() const CV_OVERRIDE
|
||||
{
|
||||
return backend_->descriptorSize();
|
||||
}
|
||||
|
||||
int descriptorType() const CV_OVERRIDE
|
||||
{
|
||||
return backend_->descriptorType();
|
||||
}
|
||||
|
||||
int defaultNorm() const CV_OVERRIDE
|
||||
{
|
||||
return backend_->defaultNorm();
|
||||
}
|
||||
|
||||
void detectAndCompute(InputArray image, InputArray mask, std::vector<KeyPoint>& keypoints,
|
||||
OutputArray descriptors, bool useProvidedKeypoints=false) CV_OVERRIDE;
|
||||
|
||||
void setViewParams(const std::vector<float>& tilts, const std::vector<float>& rolls) CV_OVERRIDE;
|
||||
void getViewParams(std::vector<float>& tilts, std::vector<float>& rolls) const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
void splitKeypointsByView(const std::vector<KeyPoint>& keypoints_,
|
||||
std::vector< std::vector<KeyPoint> >& keypointsByView) const;
|
||||
|
||||
const Ptr<Feature2D> backend_;
|
||||
int maxTilt_;
|
||||
int minTilt_;
|
||||
float tiltStep_;
|
||||
float rotateStepBase_;
|
||||
|
||||
// Tilt factors.
|
||||
std::vector<float> tilts_;
|
||||
// Roll factors.
|
||||
std::vector<float> rolls_;
|
||||
|
||||
private:
|
||||
AffineFeature_Impl(const AffineFeature_Impl &); // copy disabled
|
||||
AffineFeature_Impl& operator=(const AffineFeature_Impl &); // assign disabled
|
||||
};
|
||||
|
||||
AffineFeature_Impl::AffineFeature_Impl(const Ptr<FeatureDetector>& backend,
|
||||
int maxTilt, int minTilt, float tiltStep, float rotateStepBase)
|
||||
: backend_(backend), maxTilt_(maxTilt), minTilt_(minTilt), tiltStep_(tiltStep), rotateStepBase_(rotateStepBase)
|
||||
{
|
||||
int i = minTilt_;
|
||||
if( i == 0 )
|
||||
{
|
||||
tilts_.push_back(1);
|
||||
rolls_.push_back(0);
|
||||
i++;
|
||||
}
|
||||
float tilt = 1;
|
||||
for( ; i <= maxTilt_; i++ )
|
||||
{
|
||||
tilt *= tiltStep_;
|
||||
float rotateStep = rotateStepBase_ / tilt;
|
||||
int rollN = cvFloor(180.0f / rotateStep);
|
||||
if( rollN * rotateStep == 180.0f )
|
||||
rollN--;
|
||||
for( int j = 0; j <= rollN; j++ )
|
||||
{
|
||||
tilts_.push_back(tilt);
|
||||
rolls_.push_back(rotateStep * j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AffineFeature_Impl::setViewParams(const std::vector<float>& tilts,
|
||||
const std::vector<float>& rolls)
|
||||
{
|
||||
CV_Assert(tilts.size() == rolls.size());
|
||||
tilts_ = tilts;
|
||||
rolls_ = rolls;
|
||||
}
|
||||
|
||||
void AffineFeature_Impl::getViewParams(std::vector<float>& tilts,
|
||||
std::vector<float>& rolls) const
|
||||
{
|
||||
tilts = tilts_;
|
||||
rolls = rolls_;
|
||||
}
|
||||
|
||||
void AffineFeature_Impl::splitKeypointsByView(const std::vector<KeyPoint>& keypoints_,
|
||||
std::vector< std::vector<KeyPoint> >& keypointsByView) const
|
||||
{
|
||||
for( size_t i = 0; i < keypoints_.size(); i++ )
|
||||
{
|
||||
const KeyPoint& kp = keypoints_[i];
|
||||
CV_Assert( kp.class_id >= 0 && kp.class_id < (int)tilts_.size() );
|
||||
keypointsByView[kp.class_id].push_back(kp);
|
||||
}
|
||||
}
|
||||
|
||||
class skewedDetectAndCompute : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
skewedDetectAndCompute(
|
||||
const std::vector<float>& _tilts,
|
||||
const std::vector<float>& _rolls,
|
||||
std::vector< std::vector<KeyPoint> >& _keypointsCollection,
|
||||
std::vector<Mat>& _descriptorCollection,
|
||||
const Mat& _image,
|
||||
const Mat& _mask,
|
||||
const bool _do_keypoints,
|
||||
const bool _do_descriptors,
|
||||
const Ptr<Feature2D>& _backend)
|
||||
: tilts(_tilts),
|
||||
rolls(_rolls),
|
||||
keypointsCollection(_keypointsCollection),
|
||||
descriptorCollection(_descriptorCollection),
|
||||
image(_image),
|
||||
mask(_mask),
|
||||
do_keypoints(_do_keypoints),
|
||||
do_descriptors(_do_descriptors),
|
||||
backend(_backend) {}
|
||||
|
||||
void operator()( const cv::Range& range ) const CV_OVERRIDE
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
const int begin = range.start;
|
||||
const int end = range.end;
|
||||
|
||||
for( int a = begin; a < end; a++ )
|
||||
{
|
||||
Mat warpedImage, warpedMask;
|
||||
Matx23f pose, invPose;
|
||||
affineSkew(tilts[a], rolls[a], warpedImage, warpedMask, pose);
|
||||
invertAffineTransform(pose, invPose);
|
||||
|
||||
std::vector<KeyPoint> wKeypoints;
|
||||
Mat wDescriptors;
|
||||
if( !do_keypoints )
|
||||
{
|
||||
const std::vector<KeyPoint>& keypointsInView = keypointsCollection[a];
|
||||
if( keypointsInView.size() == 0 ) // when there are no keypoints in this affine view
|
||||
continue;
|
||||
|
||||
std::vector<Point2f> pts_, pts;
|
||||
KeyPoint::convert(keypointsInView, pts_);
|
||||
transform(pts_, pts, pose);
|
||||
wKeypoints.resize(keypointsInView.size());
|
||||
for( size_t wi = 0; wi < wKeypoints.size(); wi++ )
|
||||
{
|
||||
wKeypoints[wi] = keypointsInView[wi];
|
||||
wKeypoints[wi].pt = pts[wi];
|
||||
}
|
||||
}
|
||||
backend->detectAndCompute(warpedImage, warpedMask, wKeypoints, wDescriptors, !do_keypoints);
|
||||
if( do_keypoints )
|
||||
{
|
||||
// KeyPointsFilter::runByPixelsMask( wKeypoints, warpedMask );
|
||||
if( wKeypoints.size() == 0 )
|
||||
{
|
||||
keypointsCollection[a].clear();
|
||||
continue;
|
||||
}
|
||||
std::vector<Point2f> pts_, pts;
|
||||
KeyPoint::convert(wKeypoints, pts_);
|
||||
transform(pts_, pts, invPose);
|
||||
|
||||
keypointsCollection[a].resize(wKeypoints.size());
|
||||
for( size_t wi = 0; wi < wKeypoints.size(); wi++ )
|
||||
{
|
||||
keypointsCollection[a][wi] = wKeypoints[wi];
|
||||
keypointsCollection[a][wi].pt = pts[wi];
|
||||
keypointsCollection[a][wi].class_id = a;
|
||||
}
|
||||
}
|
||||
if( do_descriptors )
|
||||
wDescriptors.copyTo(descriptorCollection[a]);
|
||||
}
|
||||
}
|
||||
private:
|
||||
void affineSkew(float tilt, float phi,
|
||||
Mat& warpedImage, Mat& warpedMask, Matx23f& pose) const
|
||||
{
|
||||
int h = image.size().height;
|
||||
int w = image.size().width;
|
||||
Mat rotImage;
|
||||
|
||||
Mat mask0;
|
||||
if( mask.empty() )
|
||||
mask0 = Mat(h, w, CV_8UC1, 255);
|
||||
else
|
||||
mask0 = mask;
|
||||
pose = Matx23f(1,0,0,
|
||||
0,1,0);
|
||||
|
||||
if( phi == 0 )
|
||||
image.copyTo(rotImage);
|
||||
else
|
||||
{
|
||||
phi = phi * (float)CV_PI / 180;
|
||||
float s = std::sin(phi);
|
||||
float c = std::cos(phi);
|
||||
Matx22f A(c, -s, s, c);
|
||||
Matx<float, 4, 2> corners(0, 0, (float)w, 0, (float)w,(float)h, 0, (float)h);
|
||||
Mat tf(corners * A.t());
|
||||
Mat tcorners;
|
||||
tf.convertTo(tcorners, CV_32S);
|
||||
Rect rect = boundingRect(tcorners);
|
||||
h = rect.height; w = rect.width;
|
||||
pose = Matx23f(c, -s, -(float)rect.x,
|
||||
s, c, -(float)rect.y);
|
||||
warpAffine(image, rotImage, pose, Size(w, h), INTER_LINEAR, BORDER_REPLICATE);
|
||||
}
|
||||
if( tilt == 1 )
|
||||
warpedImage = rotImage;
|
||||
else
|
||||
{
|
||||
float s = 0.8f * sqrt(tilt * tilt - 1);
|
||||
GaussianBlur(rotImage, rotImage, Size(0, 0), s, 0.01);
|
||||
resize(rotImage, warpedImage, Size(0, 0), 1.0/tilt, 1.0, INTER_NEAREST);
|
||||
pose(0, 0) /= tilt;
|
||||
pose(0, 1) /= tilt;
|
||||
pose(0, 2) /= tilt;
|
||||
}
|
||||
if( phi != 0 || tilt != 1 )
|
||||
warpAffine(mask0, warpedMask, pose, warpedImage.size(), INTER_NEAREST);
|
||||
}
|
||||
|
||||
|
||||
const std::vector<float>& tilts;
|
||||
const std::vector<float>& rolls;
|
||||
std::vector< std::vector<KeyPoint> >& keypointsCollection;
|
||||
std::vector<Mat>& descriptorCollection;
|
||||
const Mat& image;
|
||||
const Mat& mask;
|
||||
const bool do_keypoints;
|
||||
const bool do_descriptors;
|
||||
const Ptr<Feature2D>& backend;
|
||||
};
|
||||
|
||||
void AffineFeature_Impl::detectAndCompute(InputArray _image, InputArray _mask,
|
||||
std::vector<KeyPoint>& keypoints,
|
||||
OutputArray _descriptors,
|
||||
bool useProvidedKeypoints)
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
bool do_keypoints = !useProvidedKeypoints;
|
||||
bool do_descriptors = _descriptors.needed();
|
||||
Mat image = _image.getMat(), mask = _mask.getMat();
|
||||
Mat descriptors;
|
||||
|
||||
if( (!do_keypoints && !do_descriptors) || _image.empty() )
|
||||
return;
|
||||
|
||||
std::vector< std::vector<KeyPoint> > keypointsCollection(tilts_.size());
|
||||
std::vector< Mat > descriptorCollection(tilts_.size());
|
||||
|
||||
if( do_keypoints )
|
||||
keypoints.clear();
|
||||
else
|
||||
splitKeypointsByView(keypoints, keypointsCollection);
|
||||
|
||||
parallel_for_(Range(0, (int)tilts_.size()), skewedDetectAndCompute(tilts_, rolls_, keypointsCollection, descriptorCollection,
|
||||
image, mask, do_keypoints, do_descriptors, backend_));
|
||||
|
||||
if( do_keypoints )
|
||||
for( size_t i = 0; i < keypointsCollection.size(); i++ )
|
||||
{
|
||||
const std::vector<KeyPoint>& keys = keypointsCollection[i];
|
||||
keypoints.insert(keypoints.end(), keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
if( do_descriptors )
|
||||
{
|
||||
_descriptors.create((int)keypoints.size(), backend_->descriptorSize(), backend_->descriptorType());
|
||||
descriptors = _descriptors.getMat();
|
||||
int iter = 0;
|
||||
for( size_t i = 0; i < descriptorCollection.size(); i++ )
|
||||
{
|
||||
const Mat& descs = descriptorCollection[i];
|
||||
if( descs.empty() )
|
||||
continue;
|
||||
Mat roi(descriptors, Rect(0, iter, descriptors.cols, descs.rows));
|
||||
descs.copyTo(roi);
|
||||
iter += descs.rows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ptr<AffineFeature> AffineFeature::create(const Ptr<Feature2D>& backend,
|
||||
int maxTilt, int minTilt, float tiltStep, float rotateStepBase)
|
||||
{
|
||||
CV_Assert(minTilt < maxTilt);
|
||||
CV_Assert(tiltStep > 0);
|
||||
CV_Assert(rotateStepBase > 0);
|
||||
return makePtr<AffineFeature_Impl>(backend, maxTilt, minTilt, tiltStep, rotateStepBase);
|
||||
}
|
||||
|
||||
String AffineFeature::getDefaultName() const
|
||||
{
|
||||
return (Feature2D::getDefaultName() + ".AffineFeature");
|
||||
}
|
||||
|
||||
} // namespace
|
185
modules/features2d/test/test_affine_feature.cpp
Normal file
185
modules/features2d/test/test_affine_feature.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
// #define GENERATE_DATA // generate data in debug mode
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifndef GENERATE_DATA
|
||||
static bool isSimilarKeypoints( const KeyPoint& p1, const KeyPoint& p2 )
|
||||
{
|
||||
const float maxPtDif = 1.f;
|
||||
const float maxSizeDif = 1.f;
|
||||
const float maxAngleDif = 2.f;
|
||||
const float maxResponseDif = 0.1f;
|
||||
|
||||
float dist = (float)cv::norm( p1.pt - p2.pt );
|
||||
return (dist < maxPtDif &&
|
||||
fabs(p1.size - p2.size) < maxSizeDif &&
|
||||
abs(p1.angle - p2.angle) < maxAngleDif &&
|
||||
abs(p1.response - p2.response) < maxResponseDif &&
|
||||
(p1.octave & 0xffff) == (p2.octave & 0xffff) // do not care about sublayers and class_id
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(Features2d_AFFINE_FEATURE, regression)
|
||||
{
|
||||
Mat image = imread(cvtest::findDataFile("features2d/tsukuba.png"));
|
||||
string xml = cvtest::TS::ptr()->get_data_path() + "asift/regression_cpp.xml.gz";
|
||||
ASSERT_FALSE(image.empty());
|
||||
|
||||
Mat gray;
|
||||
cvtColor(image, gray, COLOR_BGR2GRAY);
|
||||
|
||||
// Default ASIFT generates too large descriptors. This test uses small maxTilt to suppress the size of testdata.
|
||||
Ptr<AffineFeature> ext = AffineFeature::create(SIFT::create(), 2, 0, 1.4142135623730951f, 144.0f);
|
||||
Mat mpt, msize, mangle, mresponse, moctave, mclass_id;
|
||||
#ifdef GENERATE_DATA
|
||||
// calculate
|
||||
vector<KeyPoint> calcKeypoints;
|
||||
Mat calcDescriptors;
|
||||
ext->detectAndCompute(gray, Mat(), calcKeypoints, calcDescriptors, false);
|
||||
|
||||
// create keypoints XML
|
||||
FileStorage fs(xml, FileStorage::WRITE);
|
||||
ASSERT_TRUE(fs.isOpened()) << xml;
|
||||
std::cout << "Creating keypoints XML..." << std::endl;
|
||||
|
||||
mpt = Mat(calcKeypoints.size(), 2, CV_32F);
|
||||
msize = Mat(calcKeypoints.size(), 1, CV_32F);
|
||||
mangle = Mat(calcKeypoints.size(), 1, CV_32F);
|
||||
mresponse = Mat(calcKeypoints.size(), 1, CV_32F);
|
||||
moctave = Mat(calcKeypoints.size(), 1, CV_32S);
|
||||
mclass_id = Mat(calcKeypoints.size(), 1, CV_32S);
|
||||
|
||||
for( size_t i = 0; i < calcKeypoints.size(); i++ )
|
||||
{
|
||||
const KeyPoint& key = calcKeypoints[i];
|
||||
mpt.at<float>(i, 0) = key.pt.x;
|
||||
mpt.at<float>(i, 1) = key.pt.y;
|
||||
msize.at<float>(i, 0) = key.size;
|
||||
mangle.at<float>(i, 0) = key.angle;
|
||||
mresponse.at<float>(i, 0) = key.response;
|
||||
moctave.at<int>(i, 0) = key.octave;
|
||||
mclass_id.at<int>(i, 0) = key.class_id;
|
||||
}
|
||||
|
||||
fs << "keypoints_pt" << mpt;
|
||||
fs << "keypoints_size" << msize;
|
||||
fs << "keypoints_angle" << mangle;
|
||||
fs << "keypoints_response" << mresponse;
|
||||
fs << "keypoints_octave" << moctave;
|
||||
fs << "keypoints_class_id" << mclass_id;
|
||||
|
||||
// create descriptor XML
|
||||
fs << "descriptors" << calcDescriptors;
|
||||
fs.release();
|
||||
#else
|
||||
const float badCountsRatio = 0.01f;
|
||||
const float badDescriptorDist = 1.0f;
|
||||
const float maxBadKeypointsRatio = 0.15f;
|
||||
const float maxBadDescriptorRatio = 0.15f;
|
||||
|
||||
// read keypoints
|
||||
vector<KeyPoint> validKeypoints;
|
||||
Mat validDescriptors;
|
||||
FileStorage fs(xml, FileStorage::READ);
|
||||
ASSERT_TRUE(fs.isOpened()) << xml;
|
||||
|
||||
fs["keypoints_pt"] >> mpt;
|
||||
ASSERT_EQ(mpt.type(), CV_32F);
|
||||
fs["keypoints_size"] >> msize;
|
||||
ASSERT_EQ(msize.type(), CV_32F);
|
||||
fs["keypoints_angle"] >> mangle;
|
||||
ASSERT_EQ(mangle.type(), CV_32F);
|
||||
fs["keypoints_response"] >> mresponse;
|
||||
ASSERT_EQ(mresponse.type(), CV_32F);
|
||||
fs["keypoints_octave"] >> moctave;
|
||||
ASSERT_EQ(moctave.type(), CV_32S);
|
||||
fs["keypoints_class_id"] >> mclass_id;
|
||||
ASSERT_EQ(mclass_id.type(), CV_32S);
|
||||
|
||||
validKeypoints.resize(mpt.rows);
|
||||
for( int i = 0; i < (int)validKeypoints.size(); i++ )
|
||||
{
|
||||
validKeypoints[i].pt.x = mpt.at<float>(i, 0);
|
||||
validKeypoints[i].pt.y = mpt.at<float>(i, 1);
|
||||
validKeypoints[i].size = msize.at<float>(i, 0);
|
||||
validKeypoints[i].angle = mangle.at<float>(i, 0);
|
||||
validKeypoints[i].response = mresponse.at<float>(i, 0);
|
||||
validKeypoints[i].octave = moctave.at<int>(i, 0);
|
||||
validKeypoints[i].class_id = mclass_id.at<int>(i, 0);
|
||||
}
|
||||
|
||||
// read descriptors
|
||||
fs["descriptors"] >> validDescriptors;
|
||||
fs.release();
|
||||
|
||||
// calc and compare keypoints
|
||||
vector<KeyPoint> calcKeypoints;
|
||||
ext->detectAndCompute(gray, Mat(), calcKeypoints, noArray(), false);
|
||||
|
||||
float countRatio = (float)validKeypoints.size() / (float)calcKeypoints.size();
|
||||
ASSERT_LT(countRatio, 1 + badCountsRatio) << "Bad keypoints count ratio.";
|
||||
ASSERT_GT(countRatio, 1 - badCountsRatio) << "Bad keypoints count ratio.";
|
||||
|
||||
int badPointCount = 0, commonPointCount = max((int)validKeypoints.size(), (int)calcKeypoints.size());
|
||||
for( size_t v = 0; v < validKeypoints.size(); v++ )
|
||||
{
|
||||
int nearestIdx = -1;
|
||||
float minDist = std::numeric_limits<float>::max();
|
||||
float angleDistOfNearest = std::numeric_limits<float>::max();
|
||||
|
||||
for( size_t c = 0; c < calcKeypoints.size(); c++ )
|
||||
{
|
||||
if( validKeypoints[v].class_id != calcKeypoints[c].class_id )
|
||||
continue;
|
||||
float curDist = (float)cv::norm( calcKeypoints[c].pt - validKeypoints[v].pt );
|
||||
if( curDist < minDist )
|
||||
{
|
||||
minDist = curDist;
|
||||
nearestIdx = (int)c;
|
||||
angleDistOfNearest = abs( calcKeypoints[c].angle - validKeypoints[v].angle );
|
||||
}
|
||||
else if( curDist == minDist ) // the keypoints whose positions are same but angles are different
|
||||
{
|
||||
float angleDist = abs( calcKeypoints[c].angle - validKeypoints[v].angle );
|
||||
if( angleDist < angleDistOfNearest )
|
||||
{
|
||||
nearestIdx = (int)c;
|
||||
angleDistOfNearest = angleDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( nearestIdx == -1 || !isSimilarKeypoints( validKeypoints[v], calcKeypoints[nearestIdx] ) )
|
||||
badPointCount++;
|
||||
}
|
||||
float badKeypointsRatio = (float)badPointCount / (float)commonPointCount;
|
||||
std::cout << "badKeypointsRatio: " << badKeypointsRatio << std::endl;
|
||||
ASSERT_LT( badKeypointsRatio , maxBadKeypointsRatio ) << "Bad accuracy!";
|
||||
|
||||
// Calc and compare descriptors. This uses validKeypoints for extraction.
|
||||
Mat calcDescriptors;
|
||||
ext->detectAndCompute(gray, Mat(), validKeypoints, calcDescriptors, true);
|
||||
|
||||
int dim = validDescriptors.cols;
|
||||
int badDescriptorCount = 0;
|
||||
L1<float> distance;
|
||||
|
||||
for( int i = 0; i < (int)validKeypoints.size(); i++ )
|
||||
{
|
||||
float dist = distance( validDescriptors.ptr<float>(i), calcDescriptors.ptr<float>(i), dim );
|
||||
if( dist > badDescriptorDist )
|
||||
badDescriptorCount++;
|
||||
}
|
||||
float badDescriptorRatio = (float)badDescriptorCount / (float)validKeypoints.size();
|
||||
std::cout << "badDescriptorRatio: " << badDescriptorRatio << std::endl;
|
||||
ASSERT_LT( badDescriptorRatio, maxBadDescriptorRatio ) << "Too many descriptors mismatched.";
|
||||
#endif
|
||||
}
|
||||
|
||||
}} // namespace
|
199
samples/cpp/asift.cpp
Normal file
199
samples/cpp/asift.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/features2d.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <opencv2/calib3d.hpp>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
static void help(char** argv)
|
||||
{
|
||||
cout
|
||||
<< "This is a sample usage of AffineFeature detector/extractor.\n"
|
||||
<< "And this is a C++ version of samples/python/asift.py\n"
|
||||
<< "Usage: " << argv[0] << "\n"
|
||||
<< " [ --feature=<sift|orb|brisk> ] # Feature to use.\n"
|
||||
<< " [ --flann ] # use Flann-based matcher instead of bruteforce.\n"
|
||||
<< " [ --maxlines=<number(50 as default)> ] # The maximum number of lines in visualizing the matching result.\n"
|
||||
<< " [ --image1=<image1(aero1.jpg as default)> ]\n"
|
||||
<< " [ --image2=<image2(aero3.jpg as default)> ] # Path to images to compare."
|
||||
<< endl;
|
||||
}
|
||||
|
||||
static double timer()
|
||||
{
|
||||
return getTickCount() / getTickFrequency();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
vector<String> fileName;
|
||||
cv::CommandLineParser parser(argc, argv,
|
||||
"{help h ||}"
|
||||
"{feature|brisk|}"
|
||||
"{flann||}"
|
||||
"{maxlines|50|}"
|
||||
"{image1|aero1.jpg|}{image2|aero3.jpg|}");
|
||||
if (parser.has("help"))
|
||||
{
|
||||
help(argv);
|
||||
return 0;
|
||||
}
|
||||
string feature = parser.get<string>("feature");
|
||||
bool useFlann = parser.has("flann");
|
||||
int maxlines = parser.get<int>("maxlines");
|
||||
fileName.push_back(samples::findFile(parser.get<string>("image1")));
|
||||
fileName.push_back(samples::findFile(parser.get<string>("image2")));
|
||||
if (!parser.check())
|
||||
{
|
||||
parser.printErrors();
|
||||
cout << "See --help (or missing '=' between argument name and value?)" << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
Mat img1 = imread(fileName[0], IMREAD_GRAYSCALE);
|
||||
Mat img2 = imread(fileName[1], IMREAD_GRAYSCALE);
|
||||
if (img1.empty())
|
||||
{
|
||||
cerr << "Image " << fileName[0] << " is empty or cannot be found" << endl;
|
||||
return 1;
|
||||
}
|
||||
if (img2.empty())
|
||||
{
|
||||
cerr << "Image " << fileName[1] << " is empty or cannot be found" << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
Ptr<Feature2D> backend;
|
||||
Ptr<DescriptorMatcher> matcher;
|
||||
|
||||
if (feature == "sift")
|
||||
{
|
||||
backend = SIFT::create();
|
||||
if (useFlann)
|
||||
matcher = DescriptorMatcher::create("FlannBased");
|
||||
else
|
||||
matcher = DescriptorMatcher::create("BruteForce");
|
||||
}
|
||||
else if (feature == "orb")
|
||||
{
|
||||
backend = ORB::create();
|
||||
if (useFlann)
|
||||
matcher = makePtr<FlannBasedMatcher>(makePtr<flann::LshIndexParams>(6, 12, 1));
|
||||
else
|
||||
matcher = DescriptorMatcher::create("BruteForce-Hamming");
|
||||
}
|
||||
else if (feature == "brisk")
|
||||
{
|
||||
backend = BRISK::create();
|
||||
if (useFlann)
|
||||
matcher = makePtr<FlannBasedMatcher>(makePtr<flann::LshIndexParams>(6, 12, 1));
|
||||
else
|
||||
matcher = DescriptorMatcher::create("BruteForce-Hamming");
|
||||
}
|
||||
else
|
||||
{
|
||||
cerr << feature << " is not supported. See --help" << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
cout << "extracting with " << feature << "..." << endl;
|
||||
Ptr<AffineFeature> ext = AffineFeature::create(backend);
|
||||
vector<KeyPoint> kp1, kp2;
|
||||
Mat desc1, desc2;
|
||||
|
||||
ext->detectAndCompute(img1, Mat(), kp1, desc1);
|
||||
ext->detectAndCompute(img2, Mat(), kp2, desc2);
|
||||
cout << "img1 - " << kp1.size() << " features, "
|
||||
<< "img2 - " << kp2.size() << " features"
|
||||
<< endl;
|
||||
|
||||
cout << "matching with " << (useFlann ? "flann" : "bruteforce") << "..." << endl;
|
||||
double start = timer();
|
||||
// match and draw
|
||||
vector< vector<DMatch> > rawMatches;
|
||||
vector<Point2f> p1, p2;
|
||||
vector<float> distances;
|
||||
matcher->knnMatch(desc1, desc2, rawMatches, 2);
|
||||
// filter_matches
|
||||
for (size_t i = 0; i < rawMatches.size(); i++)
|
||||
{
|
||||
const vector<DMatch>& m = rawMatches[i];
|
||||
if (m.size() == 2 && m[0].distance < m[1].distance * 0.75)
|
||||
{
|
||||
p1.push_back(kp1[m[0].queryIdx].pt);
|
||||
p2.push_back(kp2[m[0].trainIdx].pt);
|
||||
distances.push_back(m[0].distance);
|
||||
}
|
||||
}
|
||||
vector<uchar> status;
|
||||
vector< pair<Point2f, Point2f> > pointPairs;
|
||||
Mat H = findHomography(p1, p2, status, RANSAC);
|
||||
int inliers = 0;
|
||||
for (size_t i = 0; i < status.size(); i++)
|
||||
{
|
||||
if (status[i])
|
||||
{
|
||||
pointPairs.push_back(make_pair(p1[i], p2[i]));
|
||||
distances[inliers] = distances[i];
|
||||
// CV_Assert(inliers <= (int)i);
|
||||
inliers++;
|
||||
}
|
||||
}
|
||||
distances.resize(inliers);
|
||||
|
||||
cout << "execution time: " << fixed << setprecision(2) << (timer()-start)*1000 << " ms" << endl;
|
||||
cout << inliers << " / " << status.size() << " inliers/matched" << endl;
|
||||
|
||||
cout << "visualizing..." << endl;
|
||||
vector<int> indices(inliers);
|
||||
cv::sortIdx(distances, indices, SORT_EVERY_ROW+SORT_ASCENDING);
|
||||
|
||||
// explore_match
|
||||
int h1 = img1.size().height;
|
||||
int w1 = img1.size().width;
|
||||
int h2 = img2.size().height;
|
||||
int w2 = img2.size().width;
|
||||
Mat vis = Mat::zeros(max(h1, h2), w1+w2, CV_8U);
|
||||
img1.copyTo(Mat(vis, Rect(0, 0, w1, h1)));
|
||||
img2.copyTo(Mat(vis, Rect(w1, 0, w2, h2)));
|
||||
cvtColor(vis, vis, COLOR_GRAY2BGR);
|
||||
|
||||
vector<Point2f> corners(4);
|
||||
corners[0] = Point2f(0, 0);
|
||||
corners[1] = Point2f((float)w1, 0);
|
||||
corners[2] = Point2f((float)w1, (float)h1);
|
||||
corners[3] = Point2f(0, (float)h1);
|
||||
vector<Point2i> icorners;
|
||||
perspectiveTransform(corners, corners, H);
|
||||
transform(corners, corners, Matx23f(1,0,(float)w1,0,1,0));
|
||||
Mat(corners).convertTo(icorners, CV_32S);
|
||||
polylines(vis, icorners, true, Scalar(255,255,255));
|
||||
|
||||
for (int i = 0; i < min(inliers, maxlines); i++)
|
||||
{
|
||||
int idx = indices[i];
|
||||
const Point2f& pi1 = pointPairs[idx].first;
|
||||
const Point2f& pi2 = pointPairs[idx].second;
|
||||
circle(vis, pi1, 2, Scalar(0,255,0), -1);
|
||||
circle(vis, pi2 + Point2f((float)w1,0), 2, Scalar(0,255,0), -1);
|
||||
line(vis, pi1, pi2 + Point2f((float)w1,0), Scalar(0,255,0));
|
||||
}
|
||||
if (inliers > maxlines)
|
||||
cout << "only " << maxlines << " inliers are visualized" << endl;
|
||||
imshow("affine find_obj", vis);
|
||||
|
||||
// Mat vis2 = Mat::zeros(max(h1, h2), w1+w2, CV_8U);
|
||||
// Mat warp1;
|
||||
// warpPerspective(img1, warp1, H, Size(w1, h1));
|
||||
// warp1.copyTo(Mat(vis2, Rect(0, 0, w1, h1)));
|
||||
// img2.copyTo(Mat(vis2, Rect(w1, 0, w2, h2)));
|
||||
// imshow("warped", vis2);
|
||||
|
||||
waitKey();
|
||||
cout << "done" << endl;
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user