基于SIFT圖像特征識(shí)別的匹配方法比較與實(shí)現(xiàn)
1 匹配器選擇
目前常用的匹配器有 BFMatcher and FlannBasedMatcher
1.1 BFMatcher
BFMatcher 全稱是 Brute-Force Matcher(直譯即為暴力匹配器)
大致原理:
對(duì)于 img1 中的每個(gè)描述符, BFMatcher
將其與 img2 中的所有描述符進(jìn)行比較;它計(jì)算兩個(gè)描述符之間的距離度量(例如,歐幾里得距離或漢明距離,默認(rèn)使用歐幾里得距離)并跟蹤最接近的匹配,具有最小距離的描述符對(duì)被認(rèn)為是潛在的匹配
用法示例:
創(chuàng)建一個(gè) Brute-Force 匹配器,并使用匹配器對(duì)兩組描述子進(jìn)行匹配
// 創(chuàng)建匹配器對(duì)象
BFMatcher matcher;
// 使用 K 最近鄰匹配方法進(jìn)行特征匹配
vector<vector<DMatch>> matches;
matcher.knnMatch(descriptors1, descriptors2, matches, 2);
總結(jié)結(jié)論:
BFMatcher
將嘗試所有可能性,這種匹配算法非常慢,匹配所需的時(shí)間隨著添加的特征數(shù)量線性增加,這導(dǎo)致更高的計(jì)算成本,所以尤其是對(duì)于大型數(shù)據(jù)集BFMatcher
是一種簡(jiǎn)單但不一定是最有效的匹配方法
1.2 Flann
Flann 全稱是 Fast Library for Approximate Nearest Neighbors(直譯即為近似近鄰快速庫(kù))
大致原理:
FLANN 旨在快速查找近似最近鄰,尤其是在高維空間中,它不是詳盡地搜索所有數(shù)據(jù)點(diǎn),而是使用各種技術(shù)來(lái)更有效地執(zhí)行搜索,同時(shí)提供相當(dāng)準(zhǔn)確的結(jié)果;FLANN 會(huì)構(gòu)建了一個(gè)高效的數(shù)據(jù)結(jié)構(gòu),用于搜索近似鄰居,F(xiàn)LANN 比 BFMatcher 快得多,但它只能找到近似的最近鄰,這是一個(gè)很好的匹配,但不一定是最好的,可以調(diào)整 FLANN 的參數(shù)以提高精度,但這將以減慢算法速度為代價(jià)(魚(yú)和熊掌不可兼得)
用法示例:
創(chuàng)建一個(gè) FLANN 匹配器,并使用匹配器對(duì)兩組描述子進(jìn)行匹配
// 創(chuàng)建匹配器對(duì)象
FlannBasedMatcher matcher;
// 使用 K 最近鄰匹配方法進(jìn)行特征匹配
vector<vector<DMatch>> matches;
matcher.knnMatch(descriptors1, descriptors2, matches, 2);
總結(jié)結(jié)論:
FLANN 在犧牲一些精度的情況下,提供了更快的搜索速度,特別是在高維空間或大型數(shù)據(jù)集中
1.3 比較總結(jié)
- 準(zhǔn)確度
- BF 匹配器:BF 匹配提供精確的最近鄰搜索,這意味著它可以高精度地找到最接近的匹配項(xiàng);它適用于精度至關(guān)重要的任務(wù),例如某些圖像識(shí)別或?qū)ο蟾櫲蝿?wù)
- FLANN:FLANN 執(zhí)行近似最近鄰搜索,這意味著它會(huì)找到可能不是精確最近鄰的近似匹配。雖然 FLANN 旨在提供良好的結(jié)果,但與 BF 匹配相比,速度的權(quán)衡可能會(huì)導(dǎo)致結(jié)果的準(zhǔn)確性稍差
- 速度
- BF 匹配器:BF 匹配簡(jiǎn)單明了,但計(jì)算成本可能很高,尤其是在高維空間或處理大型數(shù)據(jù)集時(shí);它涉及將一個(gè)描述符與所有其他描述符進(jìn)行比較,導(dǎo)致匹配 N 個(gè)關(guān)鍵點(diǎn)的時(shí)間復(fù)雜度為 O(N^2)
- FLANN 專為高效的最近鄰搜索而設(shè)計(jì),特別是在高維空間中。它在速度方面通常優(yōu)于 BF 匹配;FLANN的算法(KD-Tree、K-Means Tree、LSH等)的選擇和參數(shù)調(diào)整可以進(jìn)一步優(yōu)化搜索速度
測(cè)試:
SIFT+FLANN+比率測(cè)試篩選ratio=0.6 | SIFT+BF+比率測(cè)試篩選ratio=0.6 | |
---|---|---|
效果 | ![]() |
![]() |
匹配+篩選用時(shí) | 0.338931秒 | 0.494063秒 |
可以看出,兩張圖片效果基本相同,都有著極高的質(zhì)量,但是在時(shí)間上,F(xiàn)LANN 比較與 BF 快了很多,效率更高
在許多計(jì)算機(jī)視覺(jué)和機(jī)器學(xué)習(xí)應(yīng)用中,F(xiàn)LANN 是首選,因?yàn)樗谒俣群蜏?zhǔn)確性之間取得了良好的平衡,使其適用于廣泛的任務(wù)
所以我們選擇 FLANN 作為匹配器
2 篩選方法
對(duì)于用不同匹配器得出的結(jié)果都會(huì)出現(xiàn)很多錯(cuò)誤匹配,例如下圖直接使用BF匹配器進(jìn)行匹配:

可以看到有數(shù)不清的點(diǎn)都進(jìn)行了匹配,這樣的效果非常差,這個(gè)時(shí)候篩選方法就顯得十分重要了,利用好的篩選方法可以幫助我們最大程度上提升圖片特征匹配的質(zhì)量
2.1 距離閾值篩選
距離閾值篩選的原理很簡(jiǎn)單:它假設(shè)距離較近的特征點(diǎn)之間更可能是正確的匹配,而距離較遠(yuǎn)的特征點(diǎn)之間更可能是錯(cuò)誤的匹配
-
用BF匹配器找出所有的匹配對(duì)
BFMatcher matcher; vector<DMatch>matches; matcher.match(descriptors1, descriptors2, matches);
-
先通過(guò)遍歷所有的匹配對(duì),找的匹配中的最小距離
double minDist = 1000; for (int i = 0; i < descriptors1.rows; i++) { double dist = matches[i].distance; if (dist < minDist) { minDist = dist; } }
-
再次遍歷所有的匹配對(duì),篩選出距離在距離閾值內(nèi)的匹配
其中
Multiples
是可設(shè)置的倍數(shù),我們用最小距離的 Multiples 倍來(lái)當(dāng)作距離閾值vector<DMatch>good_matches; for (int i = 0; i < descriptors1.rows; i++) { double dist = matches[i].distance; const float Multiples = 2.5; // 倍數(shù)參數(shù) if (dist < Multiples * minDist) { good_matches.push_back(matches[i]); } }
測(cè)試:SIFT+BF+距離閾值篩選
倍數(shù)Multiples | 結(jié)果圖片 |
---|---|
Multiples = 2.5 | ![]() |
Multiples = 2.75 | ![]() |
Multiples = 3.0 | ![]() |
Multiples = 3.25 | ![]() |
2.2 比率測(cè)試篩選 Ratio test
比例測(cè)試的基本思想是比較最近鄰匹配和次近鄰匹配的距離,并根據(jù)它們之間的距離比例來(lái)決定是否保留最近鄰匹配
基本原理:距離最小的匹配是最近鄰匹配,距離第二小的匹配相當(dāng)于隨機(jī)噪音,如果最近鄰匹配無(wú)法與噪音區(qū)分開(kāi)來(lái),那么最近鄰匹配就應(yīng)該被剔除,因?yàn)樗c噪音一樣,沒(méi)有帶來(lái)任何有價(jià)值的信息
-
用BF匹配器找出所有的匹配對(duì)
vector<vector<DMatch>> matches; BFMatcher matcher; matcher.knnMatch(descriptors1, descriptors2, matches, 2);
注意這里使用
knnMatch
函數(shù),matches
也需要設(shè)置為二維數(shù)組其返回每個(gè)查詢描述子的前 K 個(gè)最佳匹配;K 被設(shè)置為
2
,即返回每個(gè)查詢描述子的兩個(gè)最佳匹配:其中matches[i][0]
表示第一個(gè)最佳匹配(最近的鄰居),matches[i][1]
表示第二個(gè)最佳匹配(次相似的特征點(diǎn)) -
設(shè)置比例參數(shù)
ratio
,通常設(shè)置為 0.5 左右,篩選出最佳匹配與第二最佳匹配有明顯區(qū)別的,從而丟棄我們不明確的匹配并保留好的匹配vector<DMatch> good_matches; for (int i = 0; i < matches.size(); ++i) { const float ratio = 0.5; // 比例參數(shù) if (matches[i][0].distance < ratio * matches[i][1].distance) { good_matches.push_back(matches[i][0]); } }
測(cè)試:SIFT+BF+比率測(cè)試篩選
比例ratio | 結(jié)果圖片 |
---|---|
ratio = 0.4 | ![]() |
ratio = 0.5 | ![]() |
ratio = 0.6 | ![]() |
ratio = 0.7 | ![]() |
2.3 比較總結(jié)
通過(guò)兩組測(cè)試進(jìn)行對(duì)比,在匹配對(duì)數(shù)量近似相等的情況下,明顯比率測(cè)試篩選有著明顯的優(yōu)勢(shì),Multiples = 3.25 下已經(jīng)出現(xiàn)了許多的錯(cuò)誤,而 ratio = 0.6 在匹配對(duì)數(shù)量略大的情況下,保持較高的準(zhǔn)確率
ratio = 0.6 | Multiples = 3.25 |
---|---|
![]() |
![]() |
所以最終我們選擇比率測(cè)試篩選的篩選方法
3 特征點(diǎn)匹配實(shí)現(xiàn)(SIFT)
步驟1:導(dǎo)入必要的庫(kù)和頭文件
首先,導(dǎo)入必要的OpenCV庫(kù)和頭文件,設(shè)置圖片路徑:
#include<opencv2/opencv.hpp>
#include<iostream>
#define IMG_PATH1 "test_img\\1\\B23.jpg"
#define IMG_PATH2 "test_img\\1\\B24.jpg"
#define SAVE_PATH "test_img\\1\\img_matches.jpg"
using namespace std;
using namespace cv;
步驟2:加載圖像
加載兩幅要進(jìn)行特征點(diǎn)匹配的圖像。確保圖像文件存在,并將它們放在項(xiàng)目目錄下,或者根據(jù)您的文件路徑進(jìn)行適當(dāng)?shù)母摹?/p>
Mat img1 = imread(IMG_PATH1, IMREAD_GRAYSCALE);
Mat img2 = imread(IMG_PATH2, IMREAD_GRAYSCALE);
if (img1.empty() || img2.empty())
{
cout << "Can't read image" << endl;
return -1;
}
步驟3:創(chuàng)建SIFT檢測(cè)器
創(chuàng)建SIFT檢測(cè)器對(duì)象,可以選擇不同的參數(shù),例如最大特征點(diǎn)數(shù)量、尺度等。在本例中,我們使用默認(rèn)參數(shù)。
Ptr<SIFT> sift = SIFT::create();
Ptr
是 OpenCV 提供的智能指針類,用于管理動(dòng)態(tài)分配的對(duì)象,幫助防止內(nèi)存泄漏和減少手動(dòng)內(nèi)存管理的負(fù)擔(dān)
Ptr
主要用于管理 OpenCV 中的各種對(duì)象,例如圖像、特征檢測(cè)器、匹配器等;它可以自動(dòng)跟蹤和管理對(duì)象的引用計(jì)數(shù),當(dāng)對(duì)象不再需要時(shí),會(huì)自動(dòng)釋放對(duì)象的內(nèi)存,以確保資源被正確釋放
步驟4:檢測(cè)關(guān)鍵點(diǎn)和計(jì)算描述子
使用SIFT檢測(cè)器在兩幅圖像中檢測(cè)關(guān)鍵點(diǎn),并計(jì)算它們的描述子。
vector<KeyPoint> keypoints1, keypoints2;
Mat descriptors1, descriptors2;
sift->detectAndCompute(img1, noArray(), keypoints1, descriptors1);
sift->detectAndCompute(img2, noArray(), keypoints2, descriptors2);
KeyPoint
是 OpenCV 中用于表示圖像中關(guān)鍵點(diǎn)的類,關(guān)鍵點(diǎn)通常用于描述圖像中的特征,例如角點(diǎn)、邊緣、斑點(diǎn)等;KeyPoint
包含了關(guān)鍵點(diǎn)的位置、尺度、方向和響應(yīng)等信息,它是計(jì)算機(jī)視覺(jué)中常用的數(shù)據(jù)結(jié)構(gòu)之一
KeyPoint
類的主要成員包括:
pt
:一個(gè)Point2f
類型的成員,表示關(guān)鍵點(diǎn)在圖像中的二維坐標(biāo)位置(x,y)size
:一個(gè)浮點(diǎn)數(shù),表示關(guān)鍵點(diǎn)的尺度,尺度通常表示關(guān)鍵點(diǎn)的特征區(qū)域大小angle
:一個(gè)浮點(diǎn)數(shù),表示關(guān)鍵點(diǎn)的方向,通常用于指示關(guān)鍵點(diǎn)的主要方向response
:一個(gè)浮點(diǎn)數(shù),表示關(guān)鍵點(diǎn)的響應(yīng)值,響應(yīng)值通常用于評(píng)估關(guān)鍵點(diǎn)的質(zhì)量octave
:一個(gè)整數(shù),表示關(guān)鍵點(diǎn)所在的金字塔層級(jí)(octave),這是與尺度空間相關(guān)的信息class_id
:一個(gè)整數(shù),可用于標(biāo)識(shí)關(guān)鍵點(diǎn)所屬的類別或其他信息
detectAndCompute
是 OpenCV 中常用的函數(shù),通常與特征檢測(cè)和描述子計(jì)算相關(guān)。這個(gè)函數(shù)結(jié)合了特征檢測(cè)和描述子計(jì)算兩個(gè)步驟,用于從圖像中檢測(cè)關(guān)鍵點(diǎn)并計(jì)算關(guān)鍵點(diǎn)的描述子;它的主要作用是在一次操作中完成這兩個(gè)關(guān)鍵的計(jì)算步驟,以提高計(jì)算效率和代碼簡(jiǎn)潔性其中第二參數(shù)是
mask
掩膜,摳出指定區(qū)域,我們先不用設(shè)置為空參數(shù)noArray()
或者Mat()
descriptors 描述子通常是一個(gè)二維矩陣(或多維矩陣),是一個(gè)
Mat
對(duì)象,其中每一行代表一個(gè)關(guān)鍵點(diǎn)的描述子,描述子的維度取決于特征檢測(cè)算法和配置參數(shù),在 SIFT 中,描述子通常是128維的向量
步驟5:特征點(diǎn)匹配
創(chuàng)建一個(gè)Brute-Force匹配器,并使用匹配器對(duì)兩組描述子進(jìn)行匹配。
// 創(chuàng)建匹配器對(duì)象
FlannBasedMatcher matcher;
// 使用 K 最近鄰匹配方法進(jìn)行特征匹配
vector<vector<DMatch>> matches;
matcher.knnMatch(descriptors1, descriptors2, matches, 2);
DMatch
是 OpenCV 中用于存儲(chǔ)特征匹配信息的結(jié)構(gòu)體,它包含了兩個(gè)特征點(diǎn)之間(一對(duì))的匹配信息,包括以下成員:
queryIdx
:特征描述子在查詢圖像中的索引,這個(gè)索引對(duì)應(yīng)于查詢圖像中的一個(gè)特征點(diǎn)trainIdx
:特征描述子在訓(xùn)練圖像中的索引,這個(gè)索引對(duì)應(yīng)于訓(xùn)練圖像中的一個(gè)特征點(diǎn)distance
:描述了兩個(gè)特征描述子之間的距離或相似性度量,通常,距離越小,表示兩個(gè)特征點(diǎn)越相似
步驟6:篩選匹配點(diǎn)
篩選匹配點(diǎn)以獲取最佳匹配,可以使用閾值來(lái)過(guò)濾掉不好的匹配文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-706527.html
// 存儲(chǔ)較好的匹配
vector<DMatch> good_matches;
for (int i = 0; i < matches.size(); ++i) {
const float ratio = 0.5; // 比例參數(shù)
// 僅保留比例參數(shù)內(nèi)的較好匹配
if (matches[i][0].distance < ratio * matches[i][1].distance) {
good_matches.push_back(matches[i][0]);
}
}
步驟7:繪制匹配結(jié)果
繪制匹配結(jié)果,將特征點(diǎn)匹配可視化。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-706527.html
// 繪制好的匹配結(jié)果并保存
Mat img_matches;
drawMatches(img1, keypoints1, img2, keypoints2, good_matches, img_matches);
imwrite("test_img\\img_matches.jpg", img_matches);
// 顯示匹配結(jié)果的窗口
namedWindow("matches", WINDOW_NORMAL);
imshow("matches", img_matches);
// 等待用戶按下任意鍵以關(guān)閉窗口
waitKey(0);
完整測(cè)試代碼:
#include<opencv2/opencv.hpp>
#include<iostream>
#include <chrono> // 用于測(cè)試耗時(shí)
#define IMG_PATH1 "test_img\\1\\B23.jpg"
#define IMG_PATH2 "test_img\\1\\B24.jpg"
#define SAVE_PATH "test_img\\1\\img_matches.jpg"
using namespace std;
using namespace cv;
using namespace chrono; // 用于測(cè)試耗時(shí)
int main()
{
Mat img1 = imread(IMG_PATH1, IMREAD_GRAYSCALE);
Mat img2 = imread(IMG_PATH2, IMREAD_GRAYSCALE);
if (img1.empty() || img2.empty())
{
cout << "Can't read image" << endl;
return -1;
}
Ptr<SIFT> sift = SIFT::create();
vector<KeyPoint> keypoints1, keypoints2;
Mat descriptors1, descriptors2;
sift->detectAndCompute(img1, noArray(), keypoints1, descriptors1);
sift->detectAndCompute(img2, noArray(), keypoints2, descriptors2);
//auto start = system_clock::now();
//--------------
// BF+knn
//--------------
//vector<vector<DMatch>> matches;
//cv::BFMatcher matcher;
//matcher.knnMatch(descriptors1, descriptors2, matches, 2);
//vector<DMatch> good_matches;
//for (int i = 0; i < matches.size(); ++i)
//{
// const float ratio = 0.6; // 比例參數(shù)
// if (matches[i][0].distance < ratio * matches[i][1].distance)
// {
// good_matches.push_back(matches[i][0]);
// }
//}
//--------------
// FLANN+knn
//--------------
FlannBasedMatcher matcher;
vector<vector<DMatch>> matches;
matcher.knnMatch(descriptors1, descriptors2, matches, 2);
vector<cv::DMatch> good_matches;
for (int i = 0; i < matches.size(); ++i)
{
const float ratio = 0.8; // 比例參數(shù)
if (matches[i][0].distance < ratio * matches[i][1].distance)
{
good_matches.push_back(matches[i][0]);
}
}
//--------------
// BF + 距離閾值篩選
//--------------
//BFMatcher matcher;
//vector<DMatch>matches;
//matcher.match(descriptors1, descriptors2, matches);
//
//double minDist = 1000;
//for (int i = 0; i < descriptors1.rows; i++)
//{
// double dist = matches[i].distance;
// if (dist < minDist)
// {
// minDist = dist;
// }
//}
//vector<DMatch>good_matches;
//for (int i = 0; i < descriptors1.rows; i++)
//{
// double dist = matches[i].distance;
// const float Multiples = 2.5; // 倍數(shù)參數(shù)
// if (dist < Multiples * minDist)
// {
// good_matches.push_back(matches[i]);
// }
//}
//auto end = system_clock::now();
//auto duration = duration_cast<microseconds>(end - start);
//cout << "花費(fèi)了"
// << double(duration.count()) * microseconds::period::num / microseconds::period::den
// << "秒" << endl;
Mat img_matches;
drawMatches(img1, keypoints1, img2, keypoints2, good_matches, img_matches);
imwrite(SAVE_PATH, img_matches);
namedWindow("matches", WINDOW_NORMAL);
imshow("matches", img_matches);
waitKey(0);
}
到了這里,關(guān)于基于SIFT圖像特征識(shí)別的匹配方法比較與實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!