diff --git a/doc/opencv.bib b/doc/opencv.bib index bdfbc8cf1e..975630a18d 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -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}, diff --git a/modules/features2d/include/opencv2/features2d.hpp b/modules/features2d/include/opencv2/features2d.hpp index d588d75c14..c4befc9a00 100644 --- a/modules/features2d/include/opencv2/features2d.hpp +++ b/modules/features2d/include/opencv2/features2d.hpp @@ -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 create(const Ptr& backend, + int maxTilt = 5, int minTilt = 0, float tiltStep = 1.4142135623730951f, float rotateStepBase = 72); + + CV_WRAP virtual void setViewParams(const std::vector& tilts, const std::vector& rolls) = 0; + CV_WRAP virtual void getViewParams(std::vector& tilts, std::vector& 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 . */ diff --git a/modules/features2d/src/affine_feature.cpp b/modules/features2d/src/affine_feature.cpp new file mode 100644 index 0000000000..41518d945d --- /dev/null +++ b/modules/features2d/src/affine_feature.cpp @@ -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 +namespace cv { + +class AffineFeature_Impl CV_FINAL : public AffineFeature +{ +public: + explicit AffineFeature_Impl(const Ptr& 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& keypoints, + OutputArray descriptors, bool useProvidedKeypoints=false) CV_OVERRIDE; + + void setViewParams(const std::vector& tilts, const std::vector& rolls) CV_OVERRIDE; + void getViewParams(std::vector& tilts, std::vector& rolls) const CV_OVERRIDE; + +protected: + void splitKeypointsByView(const std::vector& keypoints_, + std::vector< std::vector >& keypointsByView) const; + + const Ptr backend_; + int maxTilt_; + int minTilt_; + float tiltStep_; + float rotateStepBase_; + + // Tilt factors. + std::vector tilts_; + // Roll factors. + std::vector rolls_; + +private: + AffineFeature_Impl(const AffineFeature_Impl &); // copy disabled + AffineFeature_Impl& operator=(const AffineFeature_Impl &); // assign disabled +}; + +AffineFeature_Impl::AffineFeature_Impl(const Ptr& 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& tilts, + const std::vector& rolls) +{ + CV_Assert(tilts.size() == rolls.size()); + tilts_ = tilts; + rolls_ = rolls; +} + +void AffineFeature_Impl::getViewParams(std::vector& tilts, + std::vector& rolls) const +{ + tilts = tilts_; + rolls = rolls_; +} + +void AffineFeature_Impl::splitKeypointsByView(const std::vector& keypoints_, + std::vector< std::vector >& 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& _tilts, + const std::vector& _rolls, + std::vector< std::vector >& _keypointsCollection, + std::vector& _descriptorCollection, + const Mat& _image, + const Mat& _mask, + const bool _do_keypoints, + const bool _do_descriptors, + const Ptr& _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 wKeypoints; + Mat wDescriptors; + if( !do_keypoints ) + { + const std::vector& keypointsInView = keypointsCollection[a]; + if( keypointsInView.size() == 0 ) // when there are no keypoints in this affine view + continue; + + std::vector 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 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 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& tilts; + const std::vector& rolls; + std::vector< std::vector >& keypointsCollection; + std::vector& descriptorCollection; + const Mat& image; + const Mat& mask; + const bool do_keypoints; + const bool do_descriptors; + const Ptr& backend; +}; + +void AffineFeature_Impl::detectAndCompute(InputArray _image, InputArray _mask, + std::vector& 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 > 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& 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::create(const Ptr& backend, + int maxTilt, int minTilt, float tiltStep, float rotateStepBase) +{ + CV_Assert(minTilt < maxTilt); + CV_Assert(tiltStep > 0); + CV_Assert(rotateStepBase > 0); + return makePtr(backend, maxTilt, minTilt, tiltStep, rotateStepBase); +} + +String AffineFeature::getDefaultName() const +{ + return (Feature2D::getDefaultName() + ".AffineFeature"); +} + +} // namespace diff --git a/modules/features2d/test/test_affine_feature.cpp b/modules/features2d/test/test_affine_feature.cpp new file mode 100644 index 0000000000..f40f21ed8d --- /dev/null +++ b/modules/features2d/test/test_affine_feature.cpp @@ -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 ext = AffineFeature::create(SIFT::create(), 2, 0, 1.4142135623730951f, 144.0f); + Mat mpt, msize, mangle, mresponse, moctave, mclass_id; +#ifdef GENERATE_DATA + // calculate + vector 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(i, 0) = key.pt.x; + mpt.at(i, 1) = key.pt.y; + msize.at(i, 0) = key.size; + mangle.at(i, 0) = key.angle; + mresponse.at(i, 0) = key.response; + moctave.at(i, 0) = key.octave; + mclass_id.at(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 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(i, 0); + validKeypoints[i].pt.y = mpt.at(i, 1); + validKeypoints[i].size = msize.at(i, 0); + validKeypoints[i].angle = mangle.at(i, 0); + validKeypoints[i].response = mresponse.at(i, 0); + validKeypoints[i].octave = moctave.at(i, 0); + validKeypoints[i].class_id = mclass_id.at(i, 0); + } + + // read descriptors + fs["descriptors"] >> validDescriptors; + fs.release(); + + // calc and compare keypoints + vector 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::max(); + float angleDistOfNearest = std::numeric_limits::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 distance; + + for( int i = 0; i < (int)validKeypoints.size(); i++ ) + { + float dist = distance( validDescriptors.ptr(i), calcDescriptors.ptr(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 diff --git a/samples/cpp/asift.cpp b/samples/cpp/asift.cpp new file mode 100644 index 0000000000..568954058d --- /dev/null +++ b/samples/cpp/asift.cpp @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include + +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= ] # Feature to use.\n" + << " [ --flann ] # use Flann-based matcher instead of bruteforce.\n" + << " [ --maxlines= ] # The maximum number of lines in visualizing the matching result.\n" + << " [ --image1= ]\n" + << " [ --image2= ] # Path to images to compare." + << endl; +} + +static double timer() +{ + return getTickCount() / getTickFrequency(); +} + +int main(int argc, char** argv) +{ + vector 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("feature"); + bool useFlann = parser.has("flann"); + int maxlines = parser.get("maxlines"); + fileName.push_back(samples::findFile(parser.get("image1"))); + fileName.push_back(samples::findFile(parser.get("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 backend; + Ptr 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(makePtr(6, 12, 1)); + else + matcher = DescriptorMatcher::create("BruteForce-Hamming"); + } + else if (feature == "brisk") + { + backend = BRISK::create(); + if (useFlann) + matcher = makePtr(makePtr(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 ext = AffineFeature::create(backend); + vector 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 > rawMatches; + vector p1, p2; + vector distances; + matcher->knnMatch(desc1, desc2, rawMatches, 2); + // filter_matches + for (size_t i = 0; i < rawMatches.size(); i++) + { + const vector& 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 status; + vector< pair > 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 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 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 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; +}