@ -0,0 +1,2 @@ |
||||
// This file is needed for compilation on some platforms e.g. with XCode generator
|
||||
// Related issue: https://gitlab.kitware.com/cmake/cmake/-/issues/17457
|
@ -0,0 +1,2 @@ |
||||
// This file is needed for compilation on some platforms e.g. with XCode generator
|
||||
// Related issue: https://gitlab.kitware.com/cmake/cmake/-/issues/17457
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 5.2 KiB |
@ -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
|
@ -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
|
@ -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; |
||||
} |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.2 KiB |