parent
670acd99f9
commit
c02b241e52
5 changed files with 754 additions and 0 deletions
@ -0,0 +1,138 @@ |
|||||||
|
#include <iostream> |
||||||
|
#include <opencv2/core.hpp> |
||||||
|
#include <opencv2/videoio.hpp> |
||||||
|
#include <opencv2/highgui.hpp> |
||||||
|
#include <opencv2/imgproc.hpp> |
||||||
|
#include <opencv2/features2d.hpp> |
||||||
|
#include <opencv2/flann.hpp> |
||||||
|
#include <opencv2/xfeatures2d.hpp> |
||||||
|
|
||||||
|
using namespace cv; |
||||||
|
using namespace cv::xfeatures2d; |
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
// This program demonstrates the GMS matching strategy.
|
||||||
|
int main(int argc, char* argv[]) |
||||||
|
{ |
||||||
|
const char* keys = |
||||||
|
"{ h help | | print help message }" |
||||||
|
"{ l left | | specify left (reference) image }" |
||||||
|
"{ r right | | specify right (query) image }" |
||||||
|
"{ camera | 0 | specify the camera device number }" |
||||||
|
"{ nfeatures | 10000 | specify the maximum number of ORB features }" |
||||||
|
"{ fastThreshold | 20 | specify the FAST threshold }" |
||||||
|
"{ drawSimple | true | do not draw not matched keypoints }" |
||||||
|
"{ withRotation | false | take rotation into account }" |
||||||
|
"{ withScale | false | take scale into account }"; |
||||||
|
|
||||||
|
CommandLineParser cmd(argc, argv, keys); |
||||||
|
if (cmd.has("help")) |
||||||
|
{ |
||||||
|
std::cout << "Usage: gms_matcher [options]" << std::endl; |
||||||
|
std::cout << "Available options:" << std::endl; |
||||||
|
cmd.printMessage(); |
||||||
|
return EXIT_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
Ptr<Feature2D> orb = ORB::create(cmd.get<int>("nfeatures")); |
||||||
|
orb.dynamicCast<cv::ORB>()->setFastThreshold(cmd.get<int>("fastThreshold")); |
||||||
|
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming"); |
||||||
|
|
||||||
|
if (!cmd.get<String>("left").empty() && !cmd.get<String>("right").empty()) |
||||||
|
{ |
||||||
|
Mat imgL = imread(cmd.get<String>("left")); |
||||||
|
Mat imgR = imread(cmd.get<String>("right")); |
||||||
|
|
||||||
|
std::vector<KeyPoint> kpRef, kpCur; |
||||||
|
Mat descRef, descCur; |
||||||
|
orb->detectAndCompute(imgL, noArray(), kpRef, descRef); |
||||||
|
orb->detectAndCompute(imgR, noArray(), kpCur, descCur); |
||||||
|
|
||||||
|
std::vector<DMatch> matchesAll, matchesGMS; |
||||||
|
matcher->match(descCur, descRef, matchesAll); |
||||||
|
|
||||||
|
matchGMS(imgR.size(), imgL.size(), kpCur, kpRef, matchesAll, matchesGMS, cmd.get<bool>("withRotation"), cmd.get<bool>("withScale")); |
||||||
|
std::cout << "matchesGMS: " << matchesGMS.size() << std::endl; |
||||||
|
|
||||||
|
Mat frameMatches; |
||||||
|
if (cmd.get<bool>("drawSimple")) |
||||||
|
drawMatches(imgR, kpCur, imgL, kpRef, matchesGMS, frameMatches, Scalar::all(-1), Scalar::all(-1), |
||||||
|
std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS); |
||||||
|
else |
||||||
|
drawMatches(imgR, kpCur, imgL, kpRef, matchesGMS, frameMatches); |
||||||
|
imshow("Matches GMS", frameMatches); |
||||||
|
waitKey(); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
std::vector<KeyPoint> kpRef; |
||||||
|
Mat descRef; |
||||||
|
|
||||||
|
VideoCapture capture(cmd.get<int>("camera")); |
||||||
|
//Camera warm-up
|
||||||
|
for (int i = 0; i < 10; i++) |
||||||
|
{ |
||||||
|
Mat frame; |
||||||
|
capture >> frame; |
||||||
|
} |
||||||
|
|
||||||
|
Mat frameRef; |
||||||
|
for (;;) |
||||||
|
{ |
||||||
|
Mat frame; |
||||||
|
capture >> frame; |
||||||
|
|
||||||
|
if (frameRef.empty()) |
||||||
|
{ |
||||||
|
frame.copyTo(frameRef); |
||||||
|
orb->detectAndCompute(frameRef, noArray(), kpRef, descRef); |
||||||
|
} |
||||||
|
|
||||||
|
TickMeter tm; |
||||||
|
tm.start(); |
||||||
|
std::vector<KeyPoint> kp; |
||||||
|
Mat desc; |
||||||
|
orb->detectAndCompute(frame, noArray(), kp, desc); |
||||||
|
tm.stop(); |
||||||
|
double t_orb = tm.getTimeMilli(); |
||||||
|
|
||||||
|
tm.reset(); |
||||||
|
tm.start(); |
||||||
|
std::vector<DMatch> matchesAll, matchesGMS; |
||||||
|
matcher->match(desc, descRef, matchesAll); |
||||||
|
tm.stop(); |
||||||
|
double t_match = tm.getTimeMilli(); |
||||||
|
|
||||||
|
matchGMS(frame.size(), frameRef.size(), kp, kpRef, matchesAll, matchesGMS, cmd.get<bool>("withRotation"), cmd.get<bool>("withScale")); |
||||||
|
tm.stop(); |
||||||
|
Mat frameMatches; |
||||||
|
if (cmd.get<bool>("drawSimple")) |
||||||
|
drawMatches(frame, kp, frameRef, kpRef, matchesGMS, frameMatches, Scalar::all(-1), Scalar::all(-1), |
||||||
|
std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS); |
||||||
|
else |
||||||
|
drawMatches(frame, kp, frameRef, kpRef, matchesGMS, frameMatches); |
||||||
|
|
||||||
|
String label = format("ORB: %.2f ms", t_orb); |
||||||
|
putText(frameMatches, label, Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,0,255)); |
||||||
|
label = format("Matching: %.2f ms", t_match); |
||||||
|
putText(frameMatches, label, Point(20, 40), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,0,255)); |
||||||
|
label = format("GMS matching: %.2f ms", tm.getTimeMilli()); |
||||||
|
putText(frameMatches, label, Point(20, 60), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,0,255)); |
||||||
|
putText(frameMatches, "Press r to reinitialize the reference image.", Point(frameMatches.cols-380, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,0,255)); |
||||||
|
putText(frameMatches, "Press esc to quit.", Point(frameMatches.cols-180, 40), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,0,255)); |
||||||
|
|
||||||
|
imshow("Matches GMS", frameMatches); |
||||||
|
int c = waitKey(30); |
||||||
|
if (c == 27) |
||||||
|
break; |
||||||
|
else if (c == 'r') |
||||||
|
{ |
||||||
|
frame.copyTo(frameRef); |
||||||
|
orb->detectAndCompute(frameRef, noArray(), kpRef, descRef); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return EXIT_SUCCESS; |
||||||
|
} |
@ -0,0 +1,460 @@ |
|||||||
|
// 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 is the implementation of the paper |
||||||
|
* GMS: Grid-based Motion Statistics for Fast, Ultra-robust Feature Correspondence. |
||||||
|
* JiaWang Bian, Wen-Yan Lin, Yasuyuki Matsushita, Sai-Kit Yeung, Tan Dat Nguyen, Ming-Ming Cheng |
||||||
|
* IEEE CVPR, 2017 |
||||||
|
* ProjectPage: http://jwbian.net/gms
|
||||||
|
*********************************************************************/ |
||||||
|
|
||||||
|
#include "precomp.hpp" |
||||||
|
#include <algorithm> |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
namespace cv |
||||||
|
{ |
||||||
|
namespace xfeatures2d |
||||||
|
{ |
||||||
|
// 8 possible rotation and each one is 3 X 3
|
||||||
|
const int mRotationPatterns[8][9] = { |
||||||
|
{ |
||||||
|
1,2,3, |
||||||
|
4,5,6, |
||||||
|
7,8,9 |
||||||
|
}, |
||||||
|
{ |
||||||
|
4,1,2, |
||||||
|
7,5,3, |
||||||
|
8,9,6 |
||||||
|
}, |
||||||
|
{ |
||||||
|
7,4,1, |
||||||
|
8,5,2, |
||||||
|
9,6,3 |
||||||
|
}, |
||||||
|
{ |
||||||
|
8,7,4, |
||||||
|
9,5,1, |
||||||
|
6,3,2 |
||||||
|
}, |
||||||
|
{ |
||||||
|
9,8,7, |
||||||
|
6,5,4, |
||||||
|
3,2,1 |
||||||
|
}, |
||||||
|
{ |
||||||
|
6,9,8, |
||||||
|
3,5,7, |
||||||
|
2,1,4 |
||||||
|
}, |
||||||
|
{ |
||||||
|
3,6,9, |
||||||
|
2,5,8, |
||||||
|
1,4,7 |
||||||
|
}, |
||||||
|
{ |
||||||
|
2,3,6, |
||||||
|
1,5,9, |
||||||
|
4,7,8 |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 5 level scales
|
||||||
|
const double mScaleRatios[5] = { 1.0, 1.0 / 2, 1.0 / std::sqrt(2.0), std::sqrt(2.0), 2.0 }; |
||||||
|
|
||||||
|
class GMSMatcher |
||||||
|
{ |
||||||
|
public: |
||||||
|
// OpenCV Keypoints & Correspond Image Size & Nearest Neighbor Matches
|
||||||
|
GMSMatcher(const vector<KeyPoint>& vkp1, const Size& size1, const vector<KeyPoint>& vkp2, const Size& size2, |
||||||
|
const vector<DMatch>& vDMatches, const double thresholdFactor) : mThresholdFactor(thresholdFactor) |
||||||
|
{ |
||||||
|
// Input initialize
|
||||||
|
normalizePoints(vkp1, size1, mvP1); |
||||||
|
normalizePoints(vkp2, size2, mvP2); |
||||||
|
mNumberMatches = vDMatches.size(); |
||||||
|
convertMatches(vDMatches, mvMatches); |
||||||
|
|
||||||
|
// Grid initialize
|
||||||
|
mGridSizeLeft = Size(20, 20); |
||||||
|
mGridNumberLeft = mGridSizeLeft.width * mGridSizeLeft.height; |
||||||
|
|
||||||
|
// Initialize the neighbor of left grid
|
||||||
|
mGridNeighborLeft = Mat::zeros(mGridNumberLeft, 9, CV_32SC1); |
||||||
|
initalizeNeighbors(mGridNeighborLeft, mGridSizeLeft); |
||||||
|
} |
||||||
|
|
||||||
|
~GMSMatcher() {} |
||||||
|
|
||||||
|
// Get Inlier Mask
|
||||||
|
// Return number of inliers
|
||||||
|
int getInlierMask(vector<bool> &vbInliers, const bool withRotation = false, const bool withScale = false); |
||||||
|
|
||||||
|
|
||||||
|
private: |
||||||
|
// Normalized Points
|
||||||
|
vector<Point2f> mvP1, mvP2; |
||||||
|
|
||||||
|
// Matches
|
||||||
|
vector<pair<int, int> > mvMatches; |
||||||
|
|
||||||
|
// Number of Matches
|
||||||
|
size_t mNumberMatches; |
||||||
|
|
||||||
|
// Grid Size
|
||||||
|
Size mGridSizeLeft, mGridSizeRight; |
||||||
|
int mGridNumberLeft; |
||||||
|
int mGridNumberRight; |
||||||
|
|
||||||
|
// x : left grid idx
|
||||||
|
// y : right grid idx
|
||||||
|
// value : how many matches from idx_left to idx_right
|
||||||
|
Mat mMotionStatistics; |
||||||
|
|
||||||
|
//
|
||||||
|
vector<int> mNumberPointsInPerCellLeft; |
||||||
|
|
||||||
|
// Inldex : grid_idx_left
|
||||||
|
// Value : grid_idx_right
|
||||||
|
vector<int> mCellPairs; |
||||||
|
|
||||||
|
// Every Matches has a cell-pair
|
||||||
|
// first : grid_idx_left
|
||||||
|
// second : grid_idx_right
|
||||||
|
vector<pair<int, int> > mvMatchPairs; |
||||||
|
|
||||||
|
// Inlier Mask for output
|
||||||
|
vector<bool> mvbInlierMask; |
||||||
|
|
||||||
|
//
|
||||||
|
Mat mGridNeighborLeft; |
||||||
|
Mat mGridNeighborRight; |
||||||
|
|
||||||
|
double mThresholdFactor; |
||||||
|
|
||||||
|
|
||||||
|
// Assign Matches to Cell Pairs
|
||||||
|
void assignMatchPairs(const int GridType); |
||||||
|
|
||||||
|
void convertMatches(const vector<DMatch> &vDMatches, vector<pair<int, int> > &vMatches); |
||||||
|
|
||||||
|
int getGridIndexLeft(const Point2f &pt, const int type); |
||||||
|
|
||||||
|
int getGridIndexRight(const Point2f &pt); |
||||||
|
|
||||||
|
vector<int> getNB9(const int idx, const Size& GridSize); |
||||||
|
|
||||||
|
void initalizeNeighbors(Mat &neighbor, const Size& GridSize); |
||||||
|
|
||||||
|
void normalizePoints(const vector<KeyPoint> &kp, const Size &size, vector<Point2f> &npts); |
||||||
|
|
||||||
|
// Run
|
||||||
|
int run(const int rotationType); |
||||||
|
|
||||||
|
void setScale(const int scale); |
||||||
|
|
||||||
|
// Verify Cell Pairs
|
||||||
|
void verifyCellPairs(const int rotationType); |
||||||
|
}; |
||||||
|
|
||||||
|
void GMSMatcher::assignMatchPairs(const int gridType) |
||||||
|
{ |
||||||
|
for (size_t i = 0; i < mNumberMatches; i++) |
||||||
|
{ |
||||||
|
Point2f &lp = mvP1[mvMatches[i].first]; |
||||||
|
Point2f &rp = mvP2[mvMatches[i].second]; |
||||||
|
|
||||||
|
int lgidx = mvMatchPairs[i].first = getGridIndexLeft(lp, gridType); |
||||||
|
int rgidx = -1; |
||||||
|
|
||||||
|
if (gridType == 1) |
||||||
|
{ |
||||||
|
rgidx = mvMatchPairs[i].second = getGridIndexRight(rp); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
rgidx = mvMatchPairs[i].second; |
||||||
|
} |
||||||
|
|
||||||
|
if (lgidx < 0 || rgidx < 0) continue; |
||||||
|
|
||||||
|
mMotionStatistics.at<int>(lgidx, rgidx)++; |
||||||
|
mNumberPointsInPerCellLeft[lgidx]++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Convert OpenCV DMatch to Match (pair<int, int>)
|
||||||
|
void GMSMatcher::convertMatches(const vector<DMatch> &vDMatches, vector<pair<int, int> > &vMatches) |
||||||
|
{ |
||||||
|
vMatches.resize(mNumberMatches); |
||||||
|
for (size_t i = 0; i < mNumberMatches; i++) |
||||||
|
vMatches[i] = pair<int, int>(vDMatches[i].queryIdx, vDMatches[i].trainIdx); |
||||||
|
} |
||||||
|
|
||||||
|
int GMSMatcher::getGridIndexLeft(const Point2f &pt, const int type) |
||||||
|
{ |
||||||
|
int x = 0, y = 0; |
||||||
|
|
||||||
|
if (type == 1) { |
||||||
|
x = cvFloor(pt.x * mGridSizeLeft.width); |
||||||
|
y = cvFloor(pt.y * mGridSizeLeft.height); |
||||||
|
} |
||||||
|
|
||||||
|
if (type == 2) { |
||||||
|
x = cvFloor(pt.x * mGridSizeLeft.width + 0.5); |
||||||
|
y = cvFloor(pt.y * mGridSizeLeft.height); |
||||||
|
} |
||||||
|
|
||||||
|
if (type == 3) { |
||||||
|
x = cvFloor(pt.x * mGridSizeLeft.width); |
||||||
|
y = cvFloor(pt.y * mGridSizeLeft.height + 0.5); |
||||||
|
} |
||||||
|
|
||||||
|
if (type == 4) { |
||||||
|
x = cvFloor(pt.x * mGridSizeLeft.width + 0.5); |
||||||
|
y = cvFloor(pt.y * mGridSizeLeft.height + 0.5); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if (x >= mGridSizeLeft.width || y >= mGridSizeLeft.height) |
||||||
|
return -1; |
||||||
|
|
||||||
|
return x + y * mGridSizeLeft.width; |
||||||
|
} |
||||||
|
|
||||||
|
int GMSMatcher::getGridIndexRight(const Point2f &pt) |
||||||
|
{ |
||||||
|
int x = cvFloor(pt.x * mGridSizeRight.width); |
||||||
|
int y = cvFloor(pt.y * mGridSizeRight.height); |
||||||
|
|
||||||
|
return x + y * mGridSizeRight.width; |
||||||
|
} |
||||||
|
|
||||||
|
int GMSMatcher::getInlierMask(vector<bool> &vbInliers, const bool withRotation, const bool withScale) |
||||||
|
{ |
||||||
|
int max_inlier = 0; |
||||||
|
|
||||||
|
if (!withScale && !withRotation) |
||||||
|
{ |
||||||
|
setScale(0); |
||||||
|
max_inlier = run(1); |
||||||
|
vbInliers = mvbInlierMask; |
||||||
|
return max_inlier; |
||||||
|
} |
||||||
|
|
||||||
|
if (withRotation && withScale) |
||||||
|
{ |
||||||
|
for (int scale = 0; scale < 5; scale++) |
||||||
|
{ |
||||||
|
setScale(scale); |
||||||
|
for (int rotationType = 1; rotationType <= 8; rotationType++) |
||||||
|
{ |
||||||
|
int num_inlier = run(rotationType); |
||||||
|
|
||||||
|
if (num_inlier > max_inlier) |
||||||
|
{ |
||||||
|
vbInliers = mvbInlierMask; |
||||||
|
max_inlier = num_inlier; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return max_inlier; |
||||||
|
} |
||||||
|
|
||||||
|
if (withRotation && !withScale) |
||||||
|
{ |
||||||
|
setScale(0); |
||||||
|
for (int rotationType = 1; rotationType <= 8; rotationType++) |
||||||
|
{ |
||||||
|
int num_inlier = run(rotationType); |
||||||
|
|
||||||
|
if (num_inlier > max_inlier) |
||||||
|
{ |
||||||
|
vbInliers = mvbInlierMask; |
||||||
|
max_inlier = num_inlier; |
||||||
|
} |
||||||
|
} |
||||||
|
return max_inlier; |
||||||
|
} |
||||||
|
|
||||||
|
if (!withRotation && withScale) |
||||||
|
{ |
||||||
|
for (int scale = 0; scale < 5; scale++) |
||||||
|
{ |
||||||
|
setScale(scale); |
||||||
|
int num_inlier = run(1); |
||||||
|
|
||||||
|
if (num_inlier > max_inlier) |
||||||
|
{ |
||||||
|
vbInliers = mvbInlierMask; |
||||||
|
max_inlier = num_inlier; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
return max_inlier; |
||||||
|
} |
||||||
|
|
||||||
|
return max_inlier; |
||||||
|
} |
||||||
|
|
||||||
|
// Get Neighbor 9
|
||||||
|
vector<int> GMSMatcher::getNB9(const int idx, const Size& gridSize) |
||||||
|
{ |
||||||
|
vector<int> NB9(9, -1); |
||||||
|
|
||||||
|
int idx_x = idx % gridSize.width; |
||||||
|
int idx_y = idx / gridSize.width; |
||||||
|
|
||||||
|
for (int yi = -1; yi <= 1; yi++) |
||||||
|
{ |
||||||
|
for (int xi = -1; xi <= 1; xi++) |
||||||
|
{ |
||||||
|
int idx_xx = idx_x + xi; |
||||||
|
int idx_yy = idx_y + yi; |
||||||
|
|
||||||
|
if (idx_xx < 0 || idx_xx >= gridSize.width || idx_yy < 0 || idx_yy >= gridSize.height) |
||||||
|
continue; |
||||||
|
|
||||||
|
NB9[xi + 4 + yi * 3] = idx_xx + idx_yy * gridSize.width; |
||||||
|
} |
||||||
|
} |
||||||
|
return NB9; |
||||||
|
} |
||||||
|
|
||||||
|
void GMSMatcher::initalizeNeighbors(Mat &neighbor, const Size& gridSize) |
||||||
|
{ |
||||||
|
for (int i = 0; i < neighbor.rows; i++) |
||||||
|
{ |
||||||
|
vector<int> NB9 = getNB9(i, gridSize); |
||||||
|
int *data = neighbor.ptr<int>(i); |
||||||
|
memcpy(data, &NB9[0], sizeof(int) * 9); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Normalize Key Points to Range(0 - 1)
|
||||||
|
void GMSMatcher::normalizePoints(const vector<KeyPoint> &kp, const Size &size, vector<Point2f> &npts) |
||||||
|
{ |
||||||
|
const size_t numP = kp.size(); |
||||||
|
const int width = size.width; |
||||||
|
const int height = size.height; |
||||||
|
npts.resize(numP); |
||||||
|
|
||||||
|
for (size_t i = 0; i < numP; i++) |
||||||
|
{ |
||||||
|
npts[i].x = kp[i].pt.x / width; |
||||||
|
npts[i].y = kp[i].pt.y / height; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int GMSMatcher::run(const int rotationType) |
||||||
|
{ |
||||||
|
mvbInlierMask.assign(mNumberMatches, false); |
||||||
|
|
||||||
|
// Initialize Motion Statisctics
|
||||||
|
mMotionStatistics = Mat::zeros(mGridNumberLeft, mGridNumberRight, CV_32SC1); |
||||||
|
mvMatchPairs.assign(mNumberMatches, pair<int, int>(0, 0)); |
||||||
|
|
||||||
|
for (int gridType = 1; gridType <= 4; gridType++) |
||||||
|
{ |
||||||
|
// initialize
|
||||||
|
mMotionStatistics.setTo(0); |
||||||
|
mCellPairs.assign(mGridNumberLeft, -1); |
||||||
|
mNumberPointsInPerCellLeft.assign(mGridNumberLeft, 0); |
||||||
|
|
||||||
|
assignMatchPairs(gridType); |
||||||
|
verifyCellPairs(rotationType); |
||||||
|
|
||||||
|
// Mark inliers
|
||||||
|
for (size_t i = 0; i < mNumberMatches; i++) |
||||||
|
{ |
||||||
|
if (mCellPairs[mvMatchPairs[i].first] == mvMatchPairs[i].second) |
||||||
|
mvbInlierMask[i] = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return (int) count(mvbInlierMask.begin(), mvbInlierMask.end(), true); //number of inliers
|
||||||
|
} |
||||||
|
|
||||||
|
void GMSMatcher::setScale(const int scale) |
||||||
|
{ |
||||||
|
// Set Scale
|
||||||
|
mGridSizeRight.width = cvRound(mGridSizeLeft.width * mScaleRatios[scale]); |
||||||
|
mGridSizeRight.height = cvRound(mGridSizeLeft.height * mScaleRatios[scale]); |
||||||
|
mGridNumberRight = mGridSizeRight.width * mGridSizeRight.height; |
||||||
|
|
||||||
|
// Initialize the neighbor of right grid
|
||||||
|
mGridNeighborRight = Mat::zeros(mGridNumberRight, 9, CV_32SC1); |
||||||
|
initalizeNeighbors(mGridNeighborRight, mGridSizeRight); |
||||||
|
} |
||||||
|
|
||||||
|
void GMSMatcher::verifyCellPairs(const int rotationType) |
||||||
|
{ |
||||||
|
const int *CurrentRP = mRotationPatterns[rotationType - 1]; |
||||||
|
|
||||||
|
for (int i = 0; i < mGridNumberLeft; i++) |
||||||
|
{ |
||||||
|
if (sum(mMotionStatistics.row(i))[0] == 0) |
||||||
|
{ |
||||||
|
mCellPairs[i] = -1; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
int max_number = 0; |
||||||
|
for (int j = 0; j < mGridNumberRight; j++) |
||||||
|
{ |
||||||
|
int *value = mMotionStatistics.ptr<int>(i); |
||||||
|
if (value[j] > max_number) |
||||||
|
{ |
||||||
|
mCellPairs[i] = j; |
||||||
|
max_number = value[j]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int idx_grid_rt = mCellPairs[i]; |
||||||
|
|
||||||
|
const int *NB9_lt = mGridNeighborLeft.ptr<int>(i); |
||||||
|
const int *NB9_rt = mGridNeighborRight.ptr<int>(idx_grid_rt); |
||||||
|
|
||||||
|
int score = 0; |
||||||
|
double thresh = 0; |
||||||
|
int numpair = 0; |
||||||
|
|
||||||
|
for (size_t j = 0; j < 9; j++) |
||||||
|
{ |
||||||
|
int ll = NB9_lt[j]; |
||||||
|
int rr = NB9_rt[CurrentRP[j] - 1]; |
||||||
|
if (ll == -1 || rr == -1) |
||||||
|
continue; |
||||||
|
|
||||||
|
score += mMotionStatistics.at<int>(ll, rr); |
||||||
|
thresh += mNumberPointsInPerCellLeft[ll]; |
||||||
|
numpair++; |
||||||
|
} |
||||||
|
|
||||||
|
thresh = mThresholdFactor * std::sqrt(thresh / numpair); |
||||||
|
|
||||||
|
if (score < thresh) |
||||||
|
mCellPairs[i] = -2; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void matchGMS( const Size& size1, const Size& size2, const vector<KeyPoint>& keypoints1, const vector<KeyPoint>& keypoints2, |
||||||
|
const vector<DMatch>& matches1to2, vector<DMatch>& matchesGMS, const bool withRotation, const bool withScale, |
||||||
|
const double thresholdFactor ) |
||||||
|
{ |
||||||
|
GMSMatcher gms(keypoints1, size1, keypoints2, size2, matches1to2, thresholdFactor); |
||||||
|
vector<bool> inlierMask; |
||||||
|
gms.getInlierMask(inlierMask, withRotation, withScale); |
||||||
|
|
||||||
|
matchesGMS.clear(); |
||||||
|
for (size_t i = 0; i < inlierMask.size(); i++) { |
||||||
|
if (inlierMask[i]) |
||||||
|
matchesGMS.push_back(matches1to2[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} //namespace xfeatures2d
|
||||||
|
} //namespace cv
|
@ -0,0 +1,119 @@ |
|||||||
|
// 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" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
using namespace cv; |
||||||
|
using namespace cv::xfeatures2d; |
||||||
|
|
||||||
|
class CV_GMSMatcherTest : public cvtest::BaseTest |
||||||
|
{ |
||||||
|
public: |
||||||
|
CV_GMSMatcherTest(); |
||||||
|
~CV_GMSMatcherTest(); |
||||||
|
|
||||||
|
protected: |
||||||
|
virtual void run(int); |
||||||
|
|
||||||
|
bool combinations[4][2]; |
||||||
|
double eps[3][4]; //3 imgs x 4 combinations
|
||||||
|
double correctMatchDistThreshold; |
||||||
|
}; |
||||||
|
|
||||||
|
CV_GMSMatcherTest::CV_GMSMatcherTest() |
||||||
|
{ |
||||||
|
combinations[0][0] = false; combinations[0][1] = false; |
||||||
|
combinations[1][0] = false; combinations[1][1] = true; |
||||||
|
combinations[2][0] = true; combinations[2][1] = false; |
||||||
|
combinations[3][0] = true; combinations[3][1] = true; |
||||||
|
|
||||||
|
//Threshold = truncate(min(acc_win32, acc_win64))
|
||||||
|
eps[0][0] = 0.9313; |
||||||
|
eps[0][1] = 0.9223; |
||||||
|
eps[0][2] = 0.9313; |
||||||
|
eps[0][3] = 0.9223; |
||||||
|
|
||||||
|
eps[1][0] = 0.8199; |
||||||
|
eps[1][1] = 0.7964; |
||||||
|
eps[1][2] = 0.8199; |
||||||
|
eps[1][3] = 0.7964; |
||||||
|
|
||||||
|
eps[2][0] = 0.7098; |
||||||
|
eps[2][1] = 0.6659; |
||||||
|
eps[2][2] = 0.6939; |
||||||
|
eps[2][3] = 0.6457; |
||||||
|
|
||||||
|
correctMatchDistThreshold = 5.0; |
||||||
|
} |
||||||
|
|
||||||
|
CV_GMSMatcherTest::~CV_GMSMatcherTest() {} |
||||||
|
|
||||||
|
void CV_GMSMatcherTest::run( int ) |
||||||
|
{ |
||||||
|
ts->set_failed_test_info(cvtest::TS::OK); |
||||||
|
|
||||||
|
Mat imgRef = imread(string(ts->get_data_path()) + "detectors_descriptors_evaluation/images_datasets/graf/img1.png"); |
||||||
|
|
||||||
|
Ptr<Feature2D> orb = ORB::create(10000); |
||||||
|
vector<KeyPoint> keypointsRef, keypointsCur; |
||||||
|
Mat descriptorsRef, descriptorsCur; |
||||||
|
orb->detectAndCompute(imgRef, noArray(), keypointsRef, descriptorsRef); |
||||||
|
|
||||||
|
vector<DMatch> matchesAll, matchesGMS; |
||||||
|
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming"); |
||||||
|
|
||||||
|
const int startImg = 2; |
||||||
|
const int nImgs = 3; |
||||||
|
for (int num = startImg; num < startImg+nImgs; num++) |
||||||
|
{ |
||||||
|
string imgPath = string(ts->get_data_path()) + format("detectors_descriptors_evaluation/images_datasets/graf/img%d.png", num); |
||||||
|
Mat imgCur = imread(imgPath); |
||||||
|
orb->detectAndCompute(imgCur, noArray(), keypointsCur, descriptorsCur); |
||||||
|
|
||||||
|
matcher->match(descriptorsCur, descriptorsRef, matchesAll); |
||||||
|
|
||||||
|
string xml = string(ts->get_data_path()) + format("detectors_descriptors_evaluation/images_datasets/graf/H1to%dp.xml", num); |
||||||
|
FileStorage fs(xml, FileStorage::READ); |
||||||
|
if (!fs.isOpened()) |
||||||
|
{ |
||||||
|
ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Mat H1toCur; |
||||||
|
fs[format("H1%d", num)] >> H1toCur; |
||||||
|
|
||||||
|
for (int comb = 0; comb < 4; comb++) |
||||||
|
{ |
||||||
|
matchGMS(imgCur.size(), imgRef.size(), keypointsCur, keypointsRef, matchesAll, matchesGMS, combinations[comb][0], combinations[comb][1]); |
||||||
|
|
||||||
|
int nbCorrectMatches = 0; |
||||||
|
for (size_t i = 0; i < matchesGMS.size(); i++) |
||||||
|
{ |
||||||
|
Point2f ptRef = keypointsRef[matchesGMS[i].trainIdx].pt; |
||||||
|
Point2f ptCur = keypointsCur[matchesGMS[i].queryIdx].pt; |
||||||
|
Mat matRef = (Mat_<double>(3,1) << ptRef.x, ptRef.y, 1); |
||||||
|
Mat matTrans = H1toCur * matRef; |
||||||
|
Point2f ptTrans( (float) (matTrans.at<double>(0,0)/matTrans.at<double>(2,0)), |
||||||
|
(float) (matTrans.at<double>(1,0)/matTrans.at<double>(2,0))); |
||||||
|
|
||||||
|
if (norm(ptTrans-ptCur) < correctMatchDistThreshold) |
||||||
|
nbCorrectMatches++; |
||||||
|
} |
||||||
|
|
||||||
|
double ratio = nbCorrectMatches / (double) matchesGMS.size(); |
||||||
|
if (ratio < eps[num-startImg][comb]) |
||||||
|
{ |
||||||
|
ts->printf( cvtest::TS::LOG, "Invalid accuracy for image %s and combination withRotation=%d withScale=%d, " |
||||||
|
"matches ratio is %f, ratio threshold is %f, distance threshold is %f.\n", |
||||||
|
imgPath.substr(imgPath.size()-8).c_str(), combinations[comb][0], combinations[comb][1], ratio, |
||||||
|
eps[num-startImg][comb], correctMatchDistThreshold); |
||||||
|
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(XFeatures2d_GMSMatcher, gms_matcher_regression) { CV_GMSMatcherTest test; test.safe_run(); } |
Loading…
Reference in new issue