在OpenCV2簡單的特征匹配中對使用OpenCV2進行特征匹配的步驟做了一個簡單的介紹,其匹配出的結果是非常粗糙的,在這篇文章中對使用OpenCV2進行匹配的細化做一個簡單的總結。主要包括以下幾個內容:
- DescriptorMatcher
- DMatcher
- KNN匹配
- 計算兩視圖的基礎矩陣F,並細化匹配結果
- 計算兩視圖的單應矩陣H,並細化匹配結果
DescriptorMatcher 和 DMatcher
DescriptorMatcher是匹配特征向量的抽象類,在OpenCV2中的特征匹配方法都繼承自該類(例如:BFmatcher,FlannBasedMatcher)。該類主要包含了兩組匹配方法:圖像對之間的匹配以及圖像和一個圖像集之間的匹配。
用於圖像對之間匹配的方法的聲明
// Find one best match for each query descriptor (if mask is empty). CV_WRAP void match( const Mat& queryDescriptors, const Mat& trainDescriptors, CV_OUT vector<DMatch>& matches, const Mat& mask=Mat() ) const; // Find k best matches for each query descriptor (in increasing order of distances). // compactResult is used when mask is not empty. If compactResult is false matches // vector will have the same size as queryDescriptors rows. If compactResult is true // matches vector will not contain matches for fully masked out query descriptors. CV_WRAP void knnMatch( const Mat& queryDescriptors, const Mat& trainDescriptors, CV_OUT vector<vector<DMatch> >& matches, int k, const Mat& mask=Mat(), bool compactResult=false ) const; // Find best matches for each query descriptor which have distance less than // maxDistance (in increasing order of distances). void radiusMatch( const Mat& queryDescriptors, const Mat& trainDescriptors, vector<vector<DMatch> >& matches, float maxDistance, const Mat& mask=Mat(), bool compactResult=false ) const;
方法重載,用於圖像和圖像集匹配的方法聲明
CV_WRAP void match( const Mat& queryDescriptors, CV_OUT vector<DMatch>& matches, const vector<Mat>& masks=vector<Mat>() ); CV_WRAP void knnMatch( const Mat& queryDescriptors, CV_OUT vector<vector<DMatch> >& matches, int k, const vector<Mat>& masks=vector<Mat>(), bool compactResult=false ); void radiusMatch( const Mat& queryDescriptors, vector<vector<DMatch> >& matches, float maxDistance, const vector<Mat>& masks=vector<Mat>(), bool compactResult=false );
DMatcher 是用來保存匹配結果的,主要有以下幾個屬性
CV_PROP_RW int queryIdx; // query descriptor index CV_PROP_RW int trainIdx; // train descriptor index CV_PROP_RW int imgIdx; // train image index CV_PROP_RW float distance;
在圖像匹配時有兩種圖像的集合,查找集(Query Set)和訓練集(Train Set),對於每個Query descriptor,DMatch中保存了和其最好匹配的Train descriptor。另外,每個train image會生成多個train descriptor。
如果是圖像對之間的匹配的話,由於所有的train descriptor都是由一個train image生成的,所以在匹配結果DMatch中所有的imgIdx是一樣的,都為0.
KNNMatch
匹配過程中很可能發生錯誤的匹配,錯誤的匹配主要有兩種:匹配的特征點事錯誤的,圖像上的特征點無法匹配。常用的刪除錯誤的匹配有
- 交叉過濾
如果第一幅圖像的一個特征點和第二幅圖像的一個特征點相匹配,則進行一個相反的檢查,即將第二幅圖像上的特征點與第一幅圖像上相應特征點進行匹配,如果匹配成功,則認為這對匹配是正確的。
OpenCV中的BFMatcher已經包含了這種過濾 BFMatcher matcher(NORM_L2,true),在構造BFMatcher是將第二個參數設置為true。
- 比率測試
KNNMatch,可設置K = 2 ,即對每個匹配返回兩個最近鄰描述符,僅當第一個匹配與第二個匹配之間的距離足夠小時,才認為這是一個匹配。
在抽象基類DescriptorMatcher中封裝了knnMatch方法,具體使用方法如下:
void FeatureMatchTest::knnMatch(vector<DMatch>& matches) { const float minRatio = 1.f / 1.5f; const int k = 2; vector<vector<DMatch>> knnMatches; matcher->knnMatch(leftPattern->descriptors, rightPattern->descriptors, knnMatches, k); for (size_t i = 0; i < knnMatches.size(); i++) { const DMatch& bestMatch = knnMatches[i][0]; const DMatch& betterMatch = knnMatches[i][1]; float distanceRatio = bestMatch.distance / betterMatch.distance; if (distanceRatio < minRatio) matches.push_back(bestMatch); } }
RASIC方法計算基礎矩陣,並細化匹配結果
如果已經知道了兩視圖(圖像)間的多個點的匹配,就可以進行基礎矩陣F的計算了。OpenCV2中可以使用findFundamentalMat方法,其聲明如下:
//! finds fundamental matrix from a set of corresponding 2D points CV_EXPORTS_W Mat findFundamentalMat( InputArray points1, InputArray points2, int method=FM_RANSAC, double param1=3., double param2=0.99, OutputArray mask=noArray());
參數說明:
points1,points2 兩幅圖像間相匹配的點,點的坐標要是浮點數(float或者double)
第三個參數method是用來計算基礎矩陣的具體方法,是一個枚舉值。
param1,param2保持默認值即可。
主要來說下mask參數,有N個匹配點用來計算基礎矩陣,則該值有N個元素,每個元素的值為0或者1.值為0時,代表該匹配點事錯誤的匹配(離群值),只在使用RANSAC和LMeds方法時該值有效,
可以使用該值來刪除錯誤的匹配。
另外,在匹配完成后使用得到的匹配點來計算基礎矩陣時,首先需要將特征點對齊,並且將特征點轉換為2D點,具體實現如下:
//Align all points vector<KeyPoint> alignedKps1, alignedKps2; for (size_t i = 0; i < matches.size(); i++) { alignedKps1.push_back(leftPattern->keypoints[matches[i].queryIdx]); alignedKps2.push_back(rightPattern->keypoints[matches[i].trainIdx]); } //Keypoints to points vector<Point2f> ps1, ps2; for (unsigned i = 0; i < alignedKps1.size(); i++) ps1.push_back(alignedKps1[i].pt); for (unsigned i = 0; i < alignedKps2.size(); i++) ps2.push_back(alignedKps2[i].pt);
使用RANSAC方法計算基礎矩陣后可以得到一個status向量,用來刪除錯誤的匹配
//優化匹配結果 vector<KeyPoint> leftInlier; vector<KeyPoint> rightInlier; vector<DMatch> inlierMatch; int index = 0; for (unsigned i = 0; i < matches.size(); i++) { if (status[i] != 0){ leftInlier.push_back(alignedKps1[i]); rightInlier.push_back(alignedKps2[i]); matches[i].trainIdx = index; matches[i].queryIdx = index; inlierMatch.push_back(matches[i]); index++; } } leftPattern->keypoints = leftInlier; rightPattern->keypoints = rightInlier; matches = inlierMatch;
計算單應矩陣H,並細化匹配結果
同基礎矩陣類似,得到匹配的特征點后也可以計算單應矩陣。
//! computes the best-fit perspective transformation mapping srcPoints to dstPoints. CV_EXPORTS_W Mat findHomography( InputArray srcPoints, InputArray dstPoints, int method=0, double ransacReprojThreshold=3, OutputArray mask=noArray());
參數說明:
srcPoints,dstPoints是兩視圖中匹配的點
method 是計算單應矩陣所使用的方法,是一個枚舉值。
ransacReprojThreshold 是允許的最大反投影錯誤,只在使用RANSAC方法時有效。
mask 同findFundamentalMat 類似,指出匹配的點是不是離群值,用來優化匹配結果。
void FeatureMatchTest::refineMatcheswithHomography(vector<DMatch>& matches, double reprojectionThreshold, Mat& homography){ const int minNumbermatchesAllowed = 8; if (matches.size() < minNumbermatchesAllowed) return; //Prepare data for findHomography vector<Point2f> srcPoints(matches.size()); vector<Point2f> dstPoints(matches.size()); for (size_t i = 0; i < matches.size(); i++) { srcPoints[i] = rightPattern->keypoints[matches[i].trainIdx].pt; dstPoints[i] = leftPattern->keypoints[matches[i].queryIdx].pt; } //find homography matrix and get inliers mask vector<uchar> inliersMask(srcPoints.size()); homography = findHomography(srcPoints, dstPoints, CV_FM_RANSAC, reprojectionThreshold, inliersMask); vector<DMatch> inliers; for (size_t i = 0; i < inliersMask.size(); i++){ if (inliersMask[i]) inliers.push_back(matches[i]); } matches.swap(inliers); }
匹配結果對比
基礎矩陣后的過濾 | 單應矩陣后的過濾 |
![]() |
![]() |
交叉過濾 | KNNMatch |
![]() |
![]() |
代碼說明
定義了Pattern結構用來保存匹配過程中需要用到的數據
struct Pattern { cv::Mat image; std::vector<cv::KeyPoint> keypoints; cv::Mat descriptors; Pattern(cv::Mat& img) : image(img) {} };
將各種匹配方法封裝到了一個類中,在該類的構造函數中填充Pattern取得匹配所需的數據
FeatureMatchTest::FeatureMatchTest(std::shared_ptr<Pattern> left, std::shared_ptr<Pattern> right, std::shared_ptr<cv::DescriptorMatcher> matcher) : leftPattern(left), rightPattern(right), matcher(matcher) { //step1:Create detector int minHessian = 400; SurfFeatureDetector detector(minHessian); //step2:Detecte keypoint detector.detect(leftPattern->image, leftPattern->keypoints); detector.detect(rightPattern->image, rightPattern->keypoints); //step3:Compute descriptor detector.compute(leftPattern->image, leftPattern->keypoints, leftPattern->descriptors); detector.compute(rightPattern->image, rightPattern->keypoints, rightPattern->descriptors); }
看評論好多想參看下這部分代碼的,終於從硬盤上扒拉出來了,上傳到了CSDN上。 下載地址: http://download.csdn.net/detail/brookicv/9729163