P6: Feature Matching
Goal:學習實作局部特徵描述符的特徵匹配
了解兩種關鍵點匹配方法: template matching和feature descriptor matching。
實作5個關鍵點描述符的 OpenCV 特徵匹配方法:SIFT、SURF、ORB、BRISK、FREAK。
作業環境
Window 11
Visual Studio 2019
OpenCV 4.5.5
CMake 3.24.3
CPU:11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz 2.42 GHz
RAM:16.0 GB
Theory explanation
(1) template matching
是一種影像處理的技術。在一張影像中尋找特定模板位置,目的是找到與給定模板最相似的區域,並確定其在原始影像中的位置。將模板圖像滑動或匹配到原始影像中的各個位置,然後計算模板和該位置下原始影像區域之間的相似度或差異度,可以選擇相似度或差異度最高(或最低)的位置作為最佳匹配點。相似度度量通常使用像素之間的差異或相關性,常見的度量方法包括歐式距離、相關係數、相關性匹配等。模板匹配應用廣泛,例如:物體檢測、特徵追蹤、圖像配準和文字識別。需要注意的是,模板匹配對於光線、尺度、旋轉等變化較大的情況可能不太適用,因為它僅僅比對像素的相似度或差異度。
尋找特定模板位置
template
(2) knn(K-Nearest Neighbors) matching
一種在影像處理和模式識別中常用的匹配算法。基於鄰近搜尋的方法,用於在特徵空間中找到與給定特徵最相似的K個鄰居,選取距離最小或相似度最高的特徵點作為匹配結果。KNN matching的特點是簡單且直觀,並且不需要事先訓練模型。它在處理影像匹配和物體識別等問題時非常有用。應用在特徵匹配、物體識別、近似搜索。KNN matching在處理大數據集時可能效率較低,因為它需要計算待匹配點與所有樣本之間的距離。KNN matching還需要選擇合適的K值,過小的K值可能會導致噪聲影響,而過大的K值可能會導致匹配不精確。因此KNN matching,需要根據具體情況進行參數調節和性能優化。
相似性一般用空間內兩個點的距離來度量。距離越大,表示兩個越不相似。計算距離的方式有很多種,如右圖。Euclidean Distance是 Minkowski Distance在p=2時的特例。
(3) radius matching
一種影像處理和特徵匹配的方法,它用於找到與給定特徵點在特徵空間中距離在一定範圍內的所有鄰居。根據給定的半徑範圍,將距離在該範圍內的特徵點視為鄰居。選取距離在半徑範圍內的特徵點作為匹配結果。需要指定一個半徑值,該半徑值決定了搜索鄰居的範圍。適當調整半徑值可以獲得精確的匹配結果。較小的半徑值可能會導致缺少匹配點,而較大的半徑值可能會導致匹配點過多或不精確。常應用在特徵匹配、特徵追蹤、特徵匹配篩選。
(4) cross check
在特徵匹配中用於確定匹配的一致性和減少錯誤匹配。在傳統的特徵匹配算法中,會計算兩張影像(或影像中的特徵點)之間的特徵描述符距離或相似度,然後根據一個閾值來判斷是否為匹配點。但單純使用這種方法可能會導致一些錯誤的匹配,因為有些特徵點的描述符可能與其他特徵點非常相似。
Cross check :
提取兩幅影像(或影像中的特徵點)的特徵描述符。
對於待匹配的特徵點 A,在第一幅影像中找到與其距離最近的特徵點 B。
然後在第二幅影像中找到與特徵點 B 距離最近的特徵點 C。
如果特徵點 C 的最近鄰是特徵點 A,那麼將 A 與 C 視為匹配點。
Cross check可以過濾掉那些單向匹配的錯誤,因為如果 A 是 B 的最近鄰,而 C 不是 A 的最近鄰,那麼這樣的匹配就可能是錯誤的。通過對特徵點的交叉檢查,可以增加匹配的一致性,減少錯誤匹配的機會。Cross check 簡單且易於實現,可以有效地降低不正確匹配的數量,通常與其他特徵匹配算法結合使用,如最近鄰算法(Nearest Neighbor)或基於距離閾值的匹配。Cross check 需要考慮匹配的一致性,但可能無法處理重復或遮擋的情況。在某些應用中,可能需要進一步的檢查和過濾,以確保得到高質量的匹配結果。
(5) ratio test
在特徵匹配中過濾掉不可靠的匹配點,從而提高匹配的準確性。這種方法可能會遇到一個問題,即某些特徵點的最近鄰可能與其距離非常接近,這導致了多個近似匹配。
Ratio test :
提取兩張影像(或影像中的特徵點)的特徵描述符。
對於待匹配的特徵點 A,在第一幅影像中找到與其距離最近的兩個特徵點 B 和 C。
計算 B 和 C 的距離比值 R = 距離(A, B) / 距離(A, C)。
如果 R 的值小於一個預先設定的閾值,則將 A 與 B 視為匹配點。
Ratio test的原理是基於觀察:對於正確的匹配點,其最近鄰的距離應該明顯小於次近鄰的距離,因此 R 的值應該接近於 1。而對於錯誤的近似匹配點,它們的最近鄰和次近鄰的距離可能非常接近,導致 R 的值接近於 1,這樣的匹配點就可以通過Ratio test被濾除。通常,我們將閾值設定為一個小於 1 的值,例如 0.8 或 0.9。這樣的設定可以過濾掉那些不可靠的近似匹配,保留更可靠的匹配點。
Matching local templates
影像匹配和模板匹配,使用FAST detector來檢測關鍵點,並使用模板匹配方法在兩張影像中找到最佳匹配的關鍵點。
patches.cpp
#include <iostream>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/objdetect.hpp>
int main()
{
// image matching
// 1. Read input images
cv::Mat image1= cv::imread("C:/Users/User/Desktop/CV_testimage/image1.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat image2= cv::imread("C:/Users/User/Desktop/CV_testimage/image2.jpg", cv::IMREAD_GRAYSCALE);
// 2. Define keypoints vector
std::vector<cv::KeyPoint> keypoints1;
std::vector<cv::KeyPoint> keypoints2;
// 3. Define feature detector
cv::Ptr<cv::FeatureDetector> ptrDetector; // generic detector
ptrDetector= cv::FastFeatureDetector::create(80); // 選用 FAST detector
// 4. Keypoint detection
//檢測關鍵點
ptrDetector->detect(image1,keypoints1);
ptrDetector->detect(image2,keypoints2);
std::cout << "Number of keypoints (image 1): " << keypoints1.size() << std::endl;
std::cout << "Number of keypoints (image 2): " << keypoints2.size() << std::endl;
// 5. Define a square neighborhood
//定義特定大小的矩形,表示關鍵點周圍的]圖像塊
const int nsize(11); // size of the neighborhood
cv::Rect neighborhood(0, 0, nsize, nsize); // 11x11
cv::Mat patch1;
cv::Mat patch2;
// 6. For all keypoints in first image
// find best match in second image
//在第二章圖中找出與第一幅圖像中的每個關鍵點最匹配的
cv::Mat result;
std::vector<cv::DMatch> matches;
//for all keypoints in image 1
//針對imaage1的全部關鍵點
for (int i=0; i<keypoints1.size(); i++) {
// define image1 patch 定義image1圖像塊
neighborhood.x = keypoints1[i].pt.x-nsize/2;
neighborhood.y = keypoints1[i].pt.y-nsize/2;
// if neighborhood of points outside image, then continue with next point
//如果鄰域超出圖像範圍,就繼續處理下一個點
if (neighborhood.x<0 || neighborhood.y<0 ||
neighborhood.x+nsize >= image1.cols || neighborhood.y+nsize >= image1.rows)
continue;
//patch in image 1
patch1 = image1(neighborhood);
// reset best correlation value;
//存放最匹配的值
cv::DMatch bestMatch;
//for all keypoints in image 2
//針對imaage2的全部關鍵點
for (int j=0; j<keypoints2.size(); j++) {
// define image2 patch 定義image2圖像塊
neighborhood.x = keypoints2[j].pt.x-nsize/2;
neighborhood.y = keypoints2[j].pt.y-nsize/2;
// if neighborhood of points outside image, then continue with next point
if (neighborhood.x<0 || neighborhood.y<0 ||
neighborhood.x + nsize >= image2.cols || neighborhood.y + nsize >= image2.rows)
continue;
// patch in image 2
patch2 = image2(neighborhood);
// match the two patches匹配兩個圖像塊
cv::matchTemplate(patch1,patch2,result, cv::TM_SQDIFF);
// check if it is a best match
//檢查是否為最佳匹配
if (result.at<float>(0,0) < bestMatch.distance) {
bestMatch.distance= result.at<float>(0,0);
bestMatch.queryIdx= i;
bestMatch.trainIdx= j;
}
}
// add the best match
//添加最佳匹配
matches.push_back(bestMatch);
}
std::cout << "Number of matches: " << matches.size() << std::endl;
// extract the 50 best matches 提取50個最佳匹配項
std::nth_element(matches.begin(),matches.begin()+50,matches.end());
matches.erase(matches.begin()+50,matches.end());
std::cout << "Number of matches (after): " << matches.size() << std::endl;
// Draw the matching results
//畫出匹配結果
cv::Mat matchImage;
cv::drawMatches(image1,keypoints1, // first image
image2,keypoints2, // second image
matches, // vector of matches
matchImage, // produced image
cv::Scalar(0,0,255), // line color 紅色
cv::Scalar(0,0,255)); // point color 紅色
// Display the image of matches
cv::namedWindow("Matches");
cv::imshow("Matches",matchImage);
// Match template
// define a template
cv::Mat target(image1,cv::Rect(80,105,30,30));
// Display the template
cv::namedWindow("Template");
cv::imshow("Template",target);
// define search region
cv::Mat roi(image2,
// here top half of the image
cv::Rect(0,0,image2.cols,image2.rows/2));
// perform template matching
cv::matchTemplate(
roi, // search region
target, // template
result, // result
cv::TM_SQDIFF); // similarity measure
// find most similar location
double minVal, maxVal;
cv::Point minPt, maxPt;
cv::minMaxLoc(result, &minVal, &maxVal, &minPt, &maxPt);
// draw rectangle at most similar location
// at minPt in this case
cv::rectangle(roi, cv::Rect(minPt.x, minPt.y, target.cols , target.rows), 255);
// Display the template
cv::namedWindow("Best");
cv::imshow("Best",image2);
cv::waitKey();
return 0;
}
cv::Ptr<cv::FeatureDetector> ptrDetector;可以指向任何特徵檢測器,所以只需要在調用函數時更換檢測器,就可以運用在各種興趣點檢測。
使用FAST detector:cv::FastFeatureDetector::create(80);
檢測關鍵點
用cv::matchTemplate()來比較兩個圖像塊。
image:用於搜索的輸入圖像, 8-bit 或 32-bit floating-point, 大小:W * H
templ: 用於匹配的模板,和image類型相同, 大小 w * h
result:匹配結果圖像, single-channel 32-bit floating-point, 大小 (W-w+1) * (H-h+1)
method:用於比較的方法,TM_SQDIFF(最小值給出最佳匹配)。[1](TemplateMatchModes)
mask :可選掩碼。 必須與 templ 具有相同的大小。 必須具有與模板相同數量的通道或只有一個通道,然後用於所有模板和圖像通道。
[1]TemplateMatchModes:描述了可用比較方法的公式(I 表示圖像,T 模板,R 表示結果,M 表示可選掩碼)。 求和是在模板和/或圖像塊上完成的:x′=0...w−1,y′=0...h−1。函數完成比較後,可以使用 minMaxLoc 函數找到最佳匹配作為全局最小值(使用 TM_SQDIFF 時)或最大值(使用 TM_CCORR 或 TM_CCOEFF 時)。 在彩色圖像的情況下,分子中的模板求和和分母中的每個求和在所有通道上完成,並且每個通道使用單獨的平均值。 也就是說,該函數可以採用顏色模板和彩色圖像。 結果仍然是單通道圖像,更易於分析。
drawMatches():從兩個圖像中繪製找到的關鍵點匹配項。
img1:第一個源圖像。
keypoints1:來自第一個源圖像的關鍵點。
img2:第二個源圖像。
keypoints2:來自第二個源圖像的關鍵點。
matches1to2:從第一張圖匹配到第二張圖,也就是說keypoints1[i]在keypoints2[matches[i]]中有一個對應點。
outImg:輸出圖像。 它的內容取決於定義在輸出圖像中繪製的內容的標誌值。 請參閱下面可能的標誌位值。
matchColor:匹配的顏色(線和連接的關鍵點)。 如果 matchColor==Scalar::all(-1) ,顏色是隨機生成的。
singlePointColor:單個關鍵點(圓圈)的顏色,表示關鍵點不匹配。 如果 singlePointColor==Scalar::all(-1) ,顏色是隨機生成的。
matchesMask:確定繪製哪些匹配項的掩碼。 如果掩碼為空,則繪製所有匹配項。
flags:設置繪圖功能的標誌。 可能的標誌位值由 DrawMatchesFlags 定義。
原圖:
image1(左)、image2(右)
輸出匹配的數量。
從matches中提取前50個最佳匹配。
匹配結果
Template
cv::Mat target(image1,cv::Rect(80,105,30,30));
找到最相似的位置,並在該位置繪製矩形
原圖:
image11(左)、image22(右)
輸出匹配的數量。
從matches中提取前50個最佳匹配。
匹配結果
Template
cv::Mat target(image1,cv::Rect(80,105,30,30));
找到最相似的位置,並在該位置繪製矩形
Describing and matching local intensity patterns
使用SURF和SIFT兩種特徵檢測器,進行影像匹配和特徵檢測,並進行特徵點檢測、描述子提取、顯示匹配和匹配結果。
matcher.cpp
#include <iostream>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/objdetect.hpp>
#include <opencv2/xfeatures2d.hpp>
int main()
{
// image matching
// 1. Read input images
cv::Mat image1= cv::imread("C:/Users/User/Desktop/CV_testimage/P6/image1.jpg",cv::IMREAD_GRAYSCALE);
cv::Mat image2= cv::imread("C:/Users/User/Desktop/CV_testimage/P6/image2.jpg",cv::IMREAD_GRAYSCALE);
//cv::Mat image1 = cv::imread("C:/Users/User/Desktop/CV_testimage/P5/p51.jpg", cv::IMREAD_GRAYSCALE);
//cv::Mat image2 = cv::imread("C:/Users/User/Desktop/CV_testimage/P5/p52.jpg", cv::IMREAD_GRAYSCALE);
// 2. Define keypoints vector
std::vector<cv::KeyPoint> keypoints1;
std::vector<cv::KeyPoint> keypoints2;
// 3. Define feature detector
// Construct the SURF feature detector object
cv::Ptr<cv::Feature2D> ptrFeature2D = cv::xfeatures2d::SURF::create(2000.0);
// to test with SIFT instead of SURF
// cv::Ptr<cv::Feature2D> ptrFeature2D = cv::xfeatures2d::SIFT::create(74);
// 4. Keypoint detection
// Detect the SURF features
ptrFeature2D->detect(image1,keypoints1);
ptrFeature2D->detect(image2,keypoints2);
// Draw feature points
cv::Mat featureImage;
cv::drawKeypoints(image1,keypoints1,featureImage,cv::Scalar(255,255,0),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
// Display the corners
cv::namedWindow("SURF");
cv::imshow("SURF",featureImage);
std::cout << "Number of SURF keypoints (image 1): " << keypoints1.size() << std::endl;
std::cout << "Number of SURF keypoints (image 2): " << keypoints2.size() << std::endl;
// SURF includes both the detector and descriptor extractor
// 5. Extract the descriptor
cv::Mat descriptors1;
cv::Mat descriptors2;
ptrFeature2D->compute(image1,keypoints1,descriptors1);
ptrFeature2D->compute(image2,keypoints2,descriptors2);
// Construction of the matcher
cv::BFMatcher matcher(cv::NORM_L2);
// to test with crosscheck (symmetry) test
// note: must not be used in conjunction with ratio test
// cv::BFMatcher matcher(cv::NORM_L2, true); // with crosscheck
// Match the two image descriptors
std::vector<cv::DMatch> matches;
matcher.match(descriptors1,descriptors2, matches);
// draw matches
cv::Mat imageMatches;
cv::drawMatches(
image1, keypoints1, // 1st image and its keypoints
image2, keypoints2, // 2nd image and its keypoints
matches, // the matches
imageMatches, // the image produced
cv::Scalar(0, 255, 255), // color of lines
cv::Scalar(0, 255, 255), // color of points
std::vector< char >(), // masks if any
cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS | cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
// Display the image of matches
cv::namedWindow("SURF Matches");
cv::imshow("SURF Matches",imageMatches);
std::cout << "Number of matches: " << matches.size() << std::endl;
// perform the ratio test
// find the best two matches of each keypoint
std::vector<std::vector<cv::DMatch> > matches2;
matcher.knnMatch(descriptors1, descriptors2,
matches2,
2); // find the k (2) best matches
matches.clear();
// perform ratio test
double ratioMax= 0.6;
std::vector<std::vector<cv::DMatch> >::iterator it;
for (it= matches2.begin(); it!= matches2.end(); ++it) {
// first best match/second best match
if ((*it)[0].distance/(*it)[1].distance < ratioMax) {
// it is an acceptable match
matches.push_back((*it)[0]);
}
}
// matches is the new match set
cv::drawMatches(
image1,keypoints1, // 1st image and its keypoints
image2,keypoints2, // 2nd image and its keypoints
matches, // the matches
imageMatches, // the image produced
cv::Scalar(255,0,0), // color of lines
cv::Scalar(255,0,0), // color of points
std::vector< char >(), // masks if any
cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS | cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
std::cout << "Number of matches (after ratio test): " << matches.size() << std::endl;
// Display the image of matches
cv::namedWindow("SURF Matches (ratio test at 0.6)");
cv::imshow("SURF Matches (ratio test at 0.6)",imageMatches);
// radius match
float maxDist = 0.3;
matches2.clear();
matcher.radiusMatch(descriptors1, descriptors2, matches2,
maxDist); // maximum acceptable distance
// between the 2 descriptors
cv::drawMatches(
image1, keypoints1, // 1st image and its keypoints
image2, keypoints2, // 2nd image and its keypoints
matches2, // the matches
imageMatches, // the image produced
cv::Scalar(0, 255, 0), // color of lines
cv::Scalar(0, 255, 0), // color of points
std::vector<std::vector< char >>(), // masks if any
cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS | cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
int nmatches = 0;
for (int i = 0; i< matches2.size(); i++) nmatches += matches2[i].size();
std::cout << "Number of matches (with max radius): " << nmatches << std::endl;
// Display the image of matches
cv::namedWindow("SURF Matches (with max radius)");
cv::imshow("SURF Matches (with max radius)", imageMatches);
// scale-invariance test
// Read input images
image1= cv::imread("C:/Users/User/Desktop/CV_testimage/P6/image1.jpg",cv::IMREAD_GRAYSCALE);
image2= cv::imread("C:/Users/User/Desktop/CV_testimage/P6/image2.jpg",cv::IMREAD_GRAYSCALE);
std::cout << "Number of SIFT keypoints (image 1): " << keypoints1.size() << std::endl;
std::cout << "Number of SIFT keypoints (image 2): " << keypoints2.size() << std::endl;
// Extract the keypoints and descriptors
ptrFeature2D = cv::SIFT::create();
ptrFeature2D->detectAndCompute(image1, cv::noArray(), keypoints1, descriptors1);
ptrFeature2D->detectAndCompute(image2, cv::noArray(), keypoints2, descriptors2);
// Match the two image descriptors
matcher.match(descriptors1,descriptors2, matches);
// extract the 50 best matches
std::nth_element(matches.begin(),matches.begin()+50,matches.end());
matches.erase(matches.begin()+50,matches.end());
// draw matches
cv::drawMatches(
image1, keypoints1, // 1st image and its keypoints
image2, keypoints2, // 2nd image and its keypoints
matches, // the matches
imageMatches, // the image produced
cv::Scalar(0, 0, 255), // color of lines
cv::Scalar(0, 0, 255), // color of points
std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS| cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
// Display the image of matches
cv::namedWindow("Multi-scale SIFT Matches");
cv::imshow("Multi-scale SIFT Matches",imageMatches);
std::cout << "Number of matches: " << matches.size() << std::endl;
cv::waitKey();
return 0;
}
使用SURF
使用SIFT
normType:NORM_L1、NORM_L2、NORM_HAMMING、NORM_HAMMING2中的一種。 L1 和 L2 規範是 SIFT 和 SURF 描述符的首選,NORM_HAMMING 應與 ORB、BRISK 和 BRIEF 一起使用,當 WTA_K==3 或 4 時,NORM_HAMMING2 應與 ORB 一起使用(請參閱 ORB::ORB 構造函數說明)。
crossCheck:如果為 false,這將是 BFMatcher 在為每個查詢描述符找到 k 個最近鄰居時的默認行為。 如果 crossCheck==true,那麼 k=1 的 knnMatch() 方法將只返回對 (i,j),這樣對於第 i 個查詢描述符,匹配器集合中的第 j 個描述符是最近的,反之亦然,即 BFMatcher 只會返回一致的對。 當有足夠的匹配時,這種技術通常會以最少的異常值產生最佳結果。 這是 D. Lowe 在 SIFT 論文中使用的比率測試的替代方法。
matcher.match()匹配兩張圖像的描述符
match():找到每個描述符的最佳匹配
queryDescriptors:查詢描述符集。
trainDescriptors:描述符的訓練集。 該集合不會添加到存儲在類對像中的列車描述符集合中。
matches Matches:如果查詢描述符在 mask 中被屏蔽掉,則不為該描述符添加匹配。 因此,匹配大小可能小於查詢描述符計數。
mask:指定輸入查詢和描述符訓練矩陣之間允許匹配的掩碼。
在此方法的第一個變體中,traindescriptors 作為輸入參數傳遞。 在該方法的第二個變體中,使用了由 DescriptorMatcher::add 設置的 traindescriptors 集合。 可以傳遞可選的掩碼(或多個掩碼)以指定可以匹配哪些查詢和訓練描述符。 即,僅當 mask.at\<uchar\>(i,j) 非零時,queryDescriptors[i] 才能與 trainDescriptors[j] 匹配。
knnMatch():為每個描述符找到 k 個最佳匹配項。
queryDescriptors:查詢描述符集。
trainDescriptors:描述符的訓練集。 該集合不會添加到存儲在類對像中的列車描述符集合中。
mask:指定輸入查詢和描述符訓練矩陣之間允許匹配的掩碼。
matches Matches:每個matches[i]是k個或更少的同一個查詢描述符的匹配。
k:每個查詢描述符找到的最佳匹配的計數,如果查詢描述符總共少於 k 個可能的匹配項,則更少。
compactResult:當掩碼(或多個掩碼)不為空時使用的參數。 如果 compactResult 為 false,則匹配向量的大小與 queryDescriptors 行的大小相同。 如果 compactResult 為真,則匹配向量不包含完全屏蔽的查詢描述符的匹配項。
DescriptorMatcher::match 方法的這些擴展變體為每個查詢描述符找到幾個最佳匹配。 匹配項以距離遞增的順序返回。 有關查詢和訓練描述符的詳細信息,請參閱 DescriptorMatcher::match。
radiusMatch():對於每個查詢描述符,查找不超過指定距離的訓練描述符。
queryDescriptors:查詢描述符集。
trainDescriptors:描述符的訓練集。 該集合不會添加到存儲在類對像中的列車描述符集合中。
matches:找到匹配項。
compactResult:當掩碼(或多個掩碼)不為空時使用的參數。 如果 compactResult 為 false,則匹配向量的大小與 queryDescriptors 行的大小相同。 如果 compactResult 為真,則匹配向量不包含完全屏蔽的查詢描述符的匹配項。
maxDistance:匹配描述符之間距離的閾值。 這裡的距離是指公制距離(例如漢明距離),而不是坐標之間的距離(以像素為單位)!
mask:指定輸入查詢和描述符訓練矩陣之間允許匹配的掩碼。
對於每個查詢描述符,該方法找到查詢描述符和訓練描述符之間的距離等於或小於 maxDistance 的訓練描述符。 找到的匹配項以距離遞增的順序返回。
原圖:
image1(左)、image2(右)
SURF feature points
SURF matches
SURF matches +ratio test(ratio=0.6)
SURF matches + radius match (max radius)
Multi-scale SIFT Matches
原圖:
image11(左)、image22(右)
SURF feature points
SURF matches
SURF matches +ratio test(ratio=0.6)
SURF matches + radius match (max radius)
Multi-scale SIFT Matches
Describing keypoints with binary features
binaryDescriptor.cpp
#include <iostream>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/objdetect.hpp>
#include <opencv2/xfeatures2d.hpp>
int main()
{
// image matching
// 1. Read input images
//cv::Mat image1= cv::imread("C:/Users/User/Desktop/CV_testimage/P6/image1.jpg", cv::IMREAD_GRAYSCALE);
//cv::Mat image2= cv::imread("C:/Users/User/Desktop/CV_testimage/P6/image2.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat image1 = cv::imread("C:/Users/User/Desktop/CV_testimage/P5/p51.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat image2 = cv::imread("C:/Users/User/Desktop/CV_testimage/P5/p52.jpg", cv::IMREAD_GRAYSCALE);
// 2. Define keypoint vectors and descriptors
std::vector<cv::KeyPoint> keypoints1;
std::vector<cv::KeyPoint> keypoints2;
cv::Mat descriptors1;
cv::Mat descriptors2;
// 3. Define feature detector/descriptor
// Construct the ORB feature object
cv::Ptr<cv::Feature2D> feature =
cv::ORB::create(60);
// cv::BRISK::create(80);
// 4. Keypoint detection and description
// Detect the ORB features
feature->detectAndCompute(image1, cv::noArray(), keypoints1, descriptors1);
feature->detectAndCompute(image2, cv::noArray(), keypoints2, descriptors2);
// Draw feature points
cv::Mat featureImage;
cv::drawKeypoints(image1,keypoints1,featureImage,cv::Scalar(0,0,255),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
// Display the corners
cv::namedWindow("ORB");
cv::imshow("ORB",featureImage);
std::cout << "Number of ORB keypoints (image 1): " << keypoints1.size() << std::endl;
std::cout << "Number of ORB keypoints (image 2): " << keypoints2.size() << std::endl;
// to describe with FREAK (use with BRISK)
// feature = cv::xfeatures2d::FREAK::create();
// feature->compute(image1, keypoints1, descriptors1);
// feature->compute(image1, keypoints2, descriptors2);
// Construction of the matcher
cv::BFMatcher matcher(
cv::NORM_HAMMING); // always use hamming norm
// for binary descriptors
// Match the two image descriptors
std::vector<cv::DMatch> matches;
matcher.match(descriptors1,descriptors2, matches);
// draw matches
cv::Mat imageMatches;
cv::drawMatches(
image1,keypoints1, // 1st image and its keypoints
image2,keypoints2, // 2nd image and its keypoints
matches, // the matches
imageMatches, // the image produced
cv::Scalar(0,0,255), // color of lines
cv::Scalar(0,0,255), // color of points
std::vector< char >(), // masks if any
cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS | cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
// Display the image of matches
cv::namedWindow("ORB Matches");
cv::imshow("ORB Matches", imageMatches);
// cv::namedWindow("FREAK Matches");
// cv::imshow("FREAK Matches", imageMatches);
std::cout << "Number of matches: " << matches.size() << std::endl;
cv::waitKey();
return 0;
}
原圖:
image1(左)、image2(右)
原圖:
image11(左)、image22(右)
ORB
ORB 描述子通過簡單比較強度值,提取出每個關鍵點的表徵,然後在關鍵點周圍的鄰域內隨機選取一對像素點,創建一個二值描述子。比較這兩個像素點的強度值,如果第一個點的強度值較大,就把對應描述子的bit設為 1,否則就設為 0。對一批隨機像素點對進行上述處理,就產生了一個由若干bit組成的描述子,通常採用 128 到 512 位(成對地測試)。接下來判斷用哪些像素點對構建描述子。雖然像素點對是隨機選取的,但只要它們被選中,就要進行同樣的二值測試,並構建全部關鍵點的描述子,以確保結果的一致性。
Detect the ORB features
cv::BFMatcher matcher(cv::NORM_HAMMING);
std::vector<cv::DMatch> matches;
matcher.match(descriptors1,descriptors2, matches);
Detect the ORB features
cv::BFMatcher matcher(cv::NORM_HAMMING);
std::vector<cv::DMatch> matches;
matcher.match(descriptors1,descriptors2, matches);
BRISK
BRISK 描述子的情況也非常類似,也是成對地比較強度值。但有兩點不同:第一,它不是從 31×31 的鄰域中隨機選取像素,而是從一系列等間距的同心圓(由 60 個點組成)的採樣模式中選取;第二,這些採樣點的強度值都經過高斯平滑處理,處理中使用的 σ 值與該像素到圓心的距離成正比。 BRISK 據此選取了 512 對點。
Detect the BRISK features
cv::BFMatcher matcher(cv::NORM_HAMMING);
std::vector<cv::DMatch> matches;
matcher.match(descriptors1,descriptors2, matches);
Detect the BRISK features
cv::BFMatcher matcher(cv::NORM_HAMMING);
std::vector<cv::DMatch> matches;
matcher.match(descriptors1,descriptors2, matches);
FREAK(Fast Retina Keypoint)
是一種二值描述子,但沒有對應的檢測器。可以應用於所有已檢測到的關鍵點,例如 SIFT、SURF 或 ORB。與 BRISK 一樣,FREAK 描述子也基於用同心圓定義的採樣模式。為了獲得它的強度值,每個像素都用高斯內核進行濾波,當與中心點的距離增加時,內核的尺寸也隨之增大。FREAK 還引入了階梯式比較描述子的概念。具體做法是,先執行表示較粗略信息的前 128位(用較大的高斯內核在外圍進行測試)。只有對比的描述子通過了第一步測試,後面的測試才能進行。
Detect the BRISK features
cv::BFMatcher matcher(cv::NORM_HAMMING);
std::vector<cv::DMatch> matches;
matcher.match(descriptors1,descriptors2, matches);
Detect the BRISK features
cv::BFMatcher matcher(cv::NORM_HAMMING);
std::vector<cv::DMatch> matches;
matcher.match(descriptors1,descriptors2, matches);
Reference
OpenCV 4 Computer Vision Application Programming Cookbook, by D. M. Escrivá, R. Laganiere, Fourth Edition, Packt Publishing, 2019
OpenCV计算机视觉编程攻略(第3版),2018年 5 月北京第 1 次印刷 ,R. Laganiere,翻譯 相银初
OpenCV: Object Detection,OpenCV,Dec 25,2021, https://docs.opencv.org/4.5.5/df/dfb/group__imgproc__object.html#ga586ebfb0a7fb604b35a23d85391329be
OpenCV: Feature Detection,OpenCV,Dec 25,2021, https://docs.opencv.org/4.5.5/d4/dc6/tutorial_py_template_matching.html
OpenCV: Template Matching,OpenCV,Dec 25,2021, https://docs.opencv.org/4.5.5/d4/dc6/tutorial_py_template_matching.html
K-近鄰演算法, 維基百科,自由的百科全書, Feb 21,2023, https://zh.wikipedia.org/zh-tw/K-%E8%BF%91%E9%82%BB%E7%AE%97%E6%B3%95
c# - What is cross check in computer vision? ,But I'm Not A Wrapper Class,Stack Overflow,Jun 25, 2014, https://stackoverflow.com/questions/24419963/what-is-cross-check-in-computer-vision
机器学习算法之——K最近邻(k-Nearest Neighbor,KNN)分类算法原理讲解,Charmve,知乎,Oct 10, 2020, https://zhuanlan.zhihu.com/p/110913279