4.5FAST Algorithm for Corner Detection
4.5.1FAST算法
我們已了解多種特征檢測器,其中許多效果出色。但從實時應用的角度來看,它們的速度仍不夠快。一個典型例子是計算資源有限的SLAM(同步定位與建圖)移動機器人。
為解決此問題,Edward Rosten和Tom Drummond在2006年的論文《機器學習用于高速角點檢測》中提出了FAST(加速分段測試特征)算法(2010年進行了修訂)。以下是算法的基本概要,更多細節請參閱原始論文
- 選擇圖像中的一個像素點?
p
,判斷其是否為關鍵點。設其強度值為?I?
。 - 選擇合適的閾值?
t
。 - 考慮以待測像素?
p
?為中心的 16 個像素組成的圓形鄰域(見下圖)。
現在,如果在該圓形鄰域(16 個像素)內,存在?n
?個連續的像素,它們全都比?I? + t
?更亮,或者全都比?I? - t
?更暗(如上圖中的白色虛線所示),則像素?p
?被判定為一個角點。n
?通常被選定為 12。
有人提出了一種高速測試方法來排除大量的非角點。該測試僅檢查位于 1、9、5 和 13 位置的四個像素(首先測試 1 和 9 是否過亮或過暗。如果是,則再檢查 5 和 13)。如果?p
?是一個角點,那么這四個點中至少有三個必須全部比?I? + t
?更亮,或者全部比?I? - t
?更暗。如果這兩種情況都不滿足,那么?p
?就不可能是一個角點。然后,可以對通過此測試的候選點應用完整的段測試標準,即檢查圓周上的所有像素。該檢測器本身表現出高性能,但也存在幾個弱點:
- 當?
n
?< 12 時,它無法拒絕那么多的候選點。 - 像素點的選擇并非最優,因為其效率取決于問題的(檢測)順序和角點外觀的分布。
- 高速測試的結果被丟棄了(未被充分利用)。
- 在相鄰位置會檢測到多個特征點。
前三個問題通過機器學習方法解決。最后一個問題使用非極大值抑制 (Non-maximal Suppression) 解決。
4.5.2機器學習角點檢測器
FAST 算法比其他現有的角點檢測器快好幾倍。但它對高噪聲水平不魯棒,且依賴于閾值的選擇。
cv.FastFeatureDetector_create()功能: 創建一個 FAST 檢測器對象。參數: 此函數可以接受多個參數來配置檢測器的行為,最常見的包括:threshold: 中心像素與周圍圓形鄰域像素的強度差閾值。用來判斷一個像素是更亮、更暗還是相似。值越小,檢測到的角點越多,但也可能包含更多噪聲。
nonmaxSuppression: 是否啟用非極大值抑制。
True (默認): 啟用。對于相鄰的多個角點,只保留響應最強的那個。這通常能獲得更好的、分布更均勻的角點。
False: 禁用。可能會在一小片區域檢測到多個密集的角點。
type: 指定高速測試的模式。通常是 cv.FAST_FEATURE_DETECTOR_TYPE_9_16(檢查 9 個連續像素,使用 16 像素的圓)或其他變體。通常使用默認值即可。
返回值: 返回一個 FastFeatureDetector 對象,你可以用它來調用 .detect() 等方法。
fast.detect()功能: 使用之前創建的 FAST 檢測器在圖像中查找關鍵點。參數:image: 輸入的圖像(必須是單通道灰度圖)。
mask: 可選參數。指定搜索關鍵點的區域。只有在掩模非零的區域才會進行檢測。
返回值: 返回一個關鍵點列表。每個關鍵點都是一個特殊的對象,包含以下重要屬性:pt: 關鍵點的 (x, y) 坐標。例如 keypoint.pt 會返回 (123.0, 45.0)。
size: 關鍵點的有效鄰域直徑。
response: 關鍵點的響應強度(例如,FAST 的評分函數 V)。響應越強,該點越可能是“好”的角點。
angle: 關鍵點的方向(度),-1 表示未計算。
octave: 檢測到該關鍵點的金字塔層級。
cv.drawKeypoints(image, keypoints, outImage, color=None, flags=None)
image: 輸入圖像。通常是你想在上面繪制關鍵點的原始圖像(彩色或灰度)。
keypoints: 關鍵點列表。這是由 fast.detect(), sift.detect(), orb.detect() 等特征檢測器函數返回的結果。
outImage: 輸出圖像。繪制了關鍵點后的圖像。可以是 None,也可以是一個已存在的圖像數組。
color: 關鍵點的顏色。指定繪制關鍵點時使用的顏色。
格式通常為 (B, G, R) 元組,例如 (0, 255, 0) 表示綠色。
默認是隨機顏色。
flags: 繪制標志。這是一個非常重要的參數,它控制著關鍵點的繪制方式。是 cv.DRAW_MATCHES_FLAGS_* 系列的常量。
cv.DRAW_MATCHES_FLAGS_DEFAULT (或 None): 默認方式。只繪制關鍵點的中心點(一個小圓點)。
cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS: 推薦使用。為每個關鍵點繪制一個帶方向的圓,圓的尺寸表示關鍵點的大小,從圓心發出的射線表示關鍵點的方向。這種方式信息量更大。
cv.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS: 不繪制單個的關鍵點(通常在與 DRAW_RICH_KEYPOINTS 結合使用時意義不大)。
cv.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG: 在輸出的圖像矩陣上直接繪制,而不是先創建一個副本。(通常不直接使用)
返回值: 返回繪制了關鍵點的輸出圖像。
完整工作流程總結
-
導入 OpenCV:?
import cv2 as cv
-
讀取圖像并轉為灰度圖:?
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
-
創建檢測器:?
fast = cv.FastFeatureDetector_create(...)
-
檢測關鍵點:?
kp = fast.detect(gray, None)
-
(可選) 繪制/使用關鍵點:?
cv.drawKeypoints(...)
?或直接訪問?kp
?列表中的屬性進行計算。
demo:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltimg = cv.imread('image4.png', cv.IMREAD_GRAYSCALE)fast = cv.FastFeatureDetector_create()kp = fast.detect(img,None)
img2 = cv.drawKeypoints(img,kp,None,(0,255,0))# Print all default params
print( "Threshold: {}".format(fast.getThreshold()) )
print( "nonmaxSuppression:{}".format(fast.getNonmaxSuppression()) )
print( "neighborhood: {}".format(fast.getType()) )
print( "Total Keypoints with nonmaxSuppression: {}".format(len(kp)) )cv.imwrite('fast_true1.png', img2)fast.setNonmaxSuppression(0)
kp = fast.detect(img,None)print( "Total Keypoints without nonmaxSuppression: {}".format(len(kp)) )img3 = cv.drawKeypoints(img,kp,None,color=(0,255,0))
cv.imwrite('fast_true2.png', img3)
4.6BRIEF (Binary Robust Independent Elementary Features)
我們知道 SIFT 使用 128 維向量作為描述符。由于使用浮點數,它基本上需要 512 字節。類似地,SURF 也至少需要 256 字節(對于 64 維)。為成千上萬個特征創建這樣的向量會占用大量內存,這對于資源受限的應用(尤其是嵌入式系統)是不可行的。內存越大,匹配所需的時間就越長。
但實際匹配可能并不需要所有這些維度。我們可以使用多種方法(如 PCA、LDA 等)來壓縮它。甚至還有其他方法,如使用 LSH(局部敏感哈希)進行哈希處理,將 SIFT 的浮點描述符轉換為二進制字符串。這些二進制字符串使用漢明距離進行特征匹配。這提供了更好的加速效果,因為計算漢明距離僅需進行異或運算和位計數,這在具有 SSE 指令的現代 CPU 中非常快。但這里,我們需要先找到描述符,然后才能應用哈希,這并沒有解決我們最初的內存問題。
BRIEF?就在此時應運而生。它提供了一種捷徑,無需先找到描述符就能直接獲得二進制字符串。它取一個平滑后的圖像塊,并以一種獨特的方式(論文中 explained)選擇一組位置對。然后對這些位置對進行像素強度比較。例如,令第一個位置對為 p ?和 q 。如果
,則結果為 1,否則為 0。這對所有
個位置對進行處理,得到一個
維的比特串。
這個 可以是 128、256 或 512。OpenCV 支持所有這些尺寸,但默認情況下是 256(OpenCV 以字節表示。因此實際值為 16、32 和 64 字節)。一旦得到這個比特串,你就可以使用漢明距離來匹配這些描述符。
重要的一點是,BRIEF 是一種特征描述符,它不提供任何查找特征點的方法。因此,你必須使用其他任何特征檢測器,如 SIFT、SURF 等。該論文推薦使用 CenSurE(一種快速檢測器),并且 BRIEF 對于 CenSurE 特征點的效果甚至比 SURF 點略好。
簡而言之,BRIEF 是一種更快的特征描述符計算與匹配方法。除非存在大的面內旋轉,否則它也能提供較高的識別率。
OpenCV 中的 STAR(CenSurE)
STAR 是一種源自 CenSurE 的特征檢測器。然而,與使用正方形、六邊形和八邊形等多邊形來逼近圓形的 CenSurE 不同,STAR 使用 2 個重疊的正方形來模擬圓形:1 個直立,1 個旋轉 45 度。這些多邊形是雙層的。它們可以看作是具有粗邊框的多邊形。邊框和內部封閉區域的權重符號相反。這比其他尺度空間檢測器具有更好的計算特性,并且能夠實時實現。與 SIFT 和 SURF 在子采樣像素上尋找極值(這會在較大尺度上影響精度)不同,CenSurE 在金字塔的所有尺度上使用全空間分辨率來創建特征向量。
4.7ORB (Oriented FAST and Rotated BRIEF)
作為一名 OpenCV 愛好者,關于 ORB 最重要的一點是它出自“OpenCV Labs”。該算法由 Ethan Rublee, Vincent Rabaud, Kurt Konolige 和 Gary R. Bradski 在他們 2011 年的論文?《ORB: SIFT 或 SURF 的高效替代品》?中提出。正如標題所言,它在計算成本、匹配性能,尤其是專利方面,是 SIFT 和 SURF 的一個很好的替代品。是的,SIFT 和 SURF 是申請了專利的,使用它們需要付費。但 ORB 沒有!
ORB 基本上是?FAST 關鍵點檢測器和?BRIEF 描述符的融合,并進行了許多修改以增強性能。首先,它使用 FAST 尋找關鍵點,然后應用?Harris 角點測度從中找出前 N 個最好的點。它還使用圖像金字塔來產生多尺度特征。但有一個問題是,FAST 不計算方向。那么旋轉不變性如何實現呢?作者提出了以下修改。
它計算了以角點為中心的圖像塊的強度加權質心。從該角點指向質心的向量方向即為該點的方向。為了提高旋轉不變性,使用 x 和 y 計算圖像矩,計算應在半徑為 ?r (?r ?是圖像塊的大小)的圓形區域內進行。
對于描述符,ORB 使用 BRIEF 描述符。但我們已經知道 BRIEF 在旋轉情況下表現很差。因此 ORB 所做的就是根據關鍵點的方向來“引導”(steer)BRIEF。對于在位置的任何包含 ?n ?個二進制測試的特征集,定義一個
的矩陣 ?S ,其中包含了這些像素的坐標。然后利用圖像塊的方向 theta,找到其旋轉矩陣并旋轉 ?S ,得到被引導(旋轉)后的版本
。
ORB 將角度離散化為 $ 2\pi/30 $(12 度)的增量,并構建了一個預計算 BRIEF 模式的查找表。只要關鍵點方向 $\theta$ 在不同視角下保持一致,就會使用正確的點集來計算其描述符。
BRIEF 有一個重要特性:每個位特征具有高方差且其均值接近 0.5。但一旦其沿關鍵點方向被引導后,就會失去這個屬性而變得更加分散。高方差使得特征更具區分度,因為它對輸入有不同的響應。另一個理想特性是測試之間不相關,這樣每個測試都會對結果有貢獻。為了解決所有這些問題,ORB 在所有可能的二進制測試中運行一種貪婪搜索,以找到那些同時具有高方差、均值接近 0.5 且不相關的測試。結果被稱為?rBRIEF(Rotation-aware BRIEF)。
對于描述符匹配,它使用了在傳統 LSH 基礎上改進的多探針 LSH(multi-probe LSH)。論文指出,ORB 比 SURF 和 SIFT 快得多,并且 ORB 描述符的性能優于 SURF。ORB 是低功耗設備(如進行全景拼接等應用)的一個非常好的選擇。
cv.ORB_create()功能: 創建一個 ORB 檢測器對象。參數: 此函數可以接受多個參數來配置 ORB 檢測器的行為。以下是一些最常用的參數:nfeatures: 保留的最佳特征點的最大數量。默認值為 500。
scaleFactor: 構建圖像金字塔時的尺度因子。例如,scaleFactor=1.2 表示每層金字塔是下一層的 1.2 倍。值越大,金字塔層數越少,計算越快,但可能漏掉某些尺度的特征。 默認值為 1.2。
nlevels: 圖像金字塔的層數。默認值為 8。
edgeThreshold: 圖像邊界的閾值,由于特征點需要完整的鄰域,邊界上的點會被忽略。默認值為 31。
firstLevel: 將原圖像作為金字塔的第幾層。默認值為 0。
WTA_K: 用于生成 rBRIEF 描述符的每個元素的點數。默認是 2(即一次比較兩個點,產生 0 或 1)。如果為 3 或 4,則一次比較 3 或 4 個點,取亮度最高的那個點的索引(0,1,2 或 0,1,2,3),這會生成需要更多位來表示的描述符。
scoreType: 關鍵點評分類型。cv.ORB_HARRIS_SCORE(默認)使用 Harris 角點響應函數來排名特征點;cv.ORB_FAST_SCORE 使用 FAST 方法的一個稍低質量的替代方案,但速度更快。
patchSize: 用于生成描述符的圖像塊大小。默認值為 31。
返回值: 返回一個 ORB 對象,你可以用它來調用 .detect(), .compute(), 或 .detectAndCompute() 等方法。
orb.detect()功能: 使用之前創建的 ORB 檢測器在圖像中查找關鍵點。注意:此方法只檢測關鍵點的位置、尺度和方向,不計算描述符。參數:image: 輸入的圖像(必須是單通道灰度圖)。
mask: 可選參數。指定搜索關鍵點的區域。只有在掩模非零的區域才會進行檢測。
返回值: 返回一個關鍵點列表。每個關鍵點都是一個特殊的對象,包含以下重要屬性:pt: 關鍵點的 (x, y) 坐標。例如 keypoint.pt 會返回 (123.0, 45.0)。
size: 關鍵點的有效鄰域直徑。
angle: 關鍵點的方向(度),由 ORB 算法計算得出,提供了旋轉不變性。
response: 關鍵點的響應強度(例如,Harris 角點響應或 FAST 得分)。響應越強,該點越可能是“好”的特征點。
octave: 檢測到該關鍵點的金字塔層級。
demo:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltimg = cv.imread('image4.png')
img1 = cv.cvtColor(img,cv.COLOR_BGR2GRAY)orb = cv.ORB_create()kp = orb.detect(img1,None)kp,des = orb.compute(img1,kp)img2 = cv.drawKeypoints(img,kp,None,color=(255,0,0),flags=0)
cv.imwrite('orb.jpg',img2)
4.8Feature Matching
4.8.1Brute-Force 匹配器基礎
Brute-Force 匹配器很簡單。它獲取第一組(查詢圖像)中一個特征的描述符,并使用某種距離計算方式與第二組(訓練圖像)中的所有其他特征進行匹配。然后返回最接近的一個。
對于 BF 匹配器,我們首先需要使用?cv.BFMatcher()
?創建 BFMatcher 對象。它接受兩個可選參數:
-
第一個是?
normType
(規范類型)。它指定要使用的距離測量方式。默認是?cv.NORM_L2
。這對于 SIFT、SURF 等描述符很好(cv.NORM_L1
?也可用)。對于基于二進制字符串的描述符,如 ORB、BRIEF、BRISK 等,應該使用?cv.NORM_HAMMING
,它使用漢明距離作為測量方式。如果 ORB 使用?WTA_K == 3
或?4
(即每個元素用多個比特表示),則應使用?cv.NORM_HAMMING2
。 -
第二個參數是一個布爾變量?
crossCheck
(交叉驗證),默認為 false。如果為 true,匹配器只返回那些滿足 (i,j) 條件的匹配項:集合 A 中的第 i 個描述符在集合 B 中的最佳匹配是第 j 個描述符,并且反過來,集合 B 中的第 j 個描述符在集合 A 中的最佳匹配也必須是第 i 個描述符。也就是說,兩組中的兩個特征應該互相匹配。它能提供一致的結果,并且是 SIFT 論文中 D.Lowe 提出的比率檢驗(ratio test)的一個很好的替代方案。
一旦創建完成,有兩個重要的方法:BFMatcher.match()
?和?BFMatcher.knnMatch()
。第一個方法返回最佳匹配。第二個方法返回?k 個最佳匹配,其中 k 由用戶指定。當我們需要對這些匹配進行額外處理時,這個方法會很有用。
就像我們使用?cv.drawKeypoints()
?來繪制關鍵點一樣,cv.drawMatches()
?幫助我們繪制匹配項。它將兩幅圖像水平堆疊,并從第一幅圖像到第二幅圖像繪制線條來顯示最佳匹配。還有一個?cv.drawMatchesKnn
?可以繪制所有 k 個最佳匹配。如果 k=2,它將為每個關鍵點繪制兩條匹配線。因此,如果我們想選擇性地繪制,必須傳遞一個掩碼(mask)。
4.8.2Brute-Force Matching with ORB Descriptors
demo:
import numpy as np
import cv2 as cv
import matplotlib.pyplot as pltimg1 = cv.imread('image4.png',cv.IMREAD_GRAYSCALE)
img2 = cv.imread('image4.png',cv.IMREAD_GRAYSCALE)# Initiate ORB detector
orb = cv.ORB_create()kp1,des1 = orb.detectAndCompute(img1,None)
kp2,des2 = orb.detectAndCompute(img2,None)# create BFMatcher object
bf = cv.BFMatcher(cv.NORM_HAMMING,crossCheck=True)# Match descriptors.
matches = bf.match(des1,des2)# Sort them in the order of their distance.
matches = sorted(matches,key = lambda x:x.distance)# Draw first 30 matches.
img3 = cv.drawMatches(img1,kp1,img2,kp2,matches[:30],None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS,matchColor=[0,0,255])plt.imshow(img3),plt.show()
4.8.3What is this Matcher Object?
什么是這個匹配器對象(Matcher Object)?
matches = bf.match(des1, des2)
?這行代碼的結果是一個由?DMatch 對象?組成的列表。這個 DMatch 對象具有以下屬性:
-
DMatch.distance?- 描述符之間的距離。距離越小,匹配越好。
-
DMatch.trainIdx?- 在訓練描述符集(第二張圖片?
des2
)中的描述符的索引。 -
DMatch.queryIdx?- 在查詢描述符集(第一張圖片?
des1
)中的描述符的索引。 -
DMatch.imgIdx?- 訓練圖像(第二張圖片)的索引。
4.8.4Brute-Force Matching with SIFT Descriptors and Ratio Test
我們將使用BFMatcher.knnMatch()來獲得k個最佳匹配。在這個例子中,我們將采取k=2
demo:
from turtle import distance
import numpy as np
import cv2 as cv
import matplotlib.pyplot as pltimg1 = cv.imread('image4.png',cv.IMREAD_GRAYSCALE)
img2 = cv.imread('image4.png',cv.IMREAD_GRAYSCALE)# Initiate SIFT detector
sift = cv.SIFT_create()kp1,des1 = sift.detectAndCompute(img1,None)
kp2,des2 = sift.detectAndCompute(img2,None)# BFMatcher with default params
bf = cv.BFMatcher()
matches = bf.knnMatch(des1,des2,k=2)
# print(matches)
# Apply ratio test
good = []
for m,n in matches:if m.distance < 0.75*n.distance:good.append([m])img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)plt.imshow(img3),plt.show()
4.8.5基于 FLANN 的匹配器
FLANN 代表快速近似最近鄰庫(Fast Library for Approximate Nearest Neighbors)。它包含了一系列算法,這些算法針對在大數據集中進行快速最近鄰搜索和高維特征進行了優化。對于大型數據集,它比 BFMatcher 工作得更快。我們將看到第二個基于 FLANN 匹配器的例子。
對于基于 FLANN 的匹配器,我們需要傳遞兩個字典,這兩個字典指定了要使用的算法及其相關參數等。
第一個是?IndexParams(索引參數)。對于各種算法,需要傳遞的信息在 FLANN 文檔中有說明。總結如下:
對于 SIFT/SURF(浮點型描述符):使用?KDTree
?算法,你可以傳遞以下參數:
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
trees=5:構建 5 棵 KDTree,搜索時并行查詢,提高召回率。
對于 ORB/BRIEF(二進制描述符):使用?LSH
(局部敏感哈希)算法:
FLANN_INDEX_LSH = 6
index_params = dict(algorithm = FLANN_INDEX_LSH,table_number = 6, # 推薦值:12key_size = 12, # 推薦值:20 multi_probe_level = 1) # 推薦值:2
第二個字典是?SearchParams(搜索參數)。它指定了索引中的樹應該被遞歸遍歷的次數。值越高精度越好,但也需要更多時間。如果你想改變這個值,可以傳遞?search_params = dict(checks=100)
。search_params
(搜索參數)作用是控制搜索的精細程度。checks
:指定遞歸遍歷樹的次數。值越高,搜索越徹底,找到最近鄰的概率越大,但速度越慢。
search_params = dict(checks=50) # 默認值可能是32
demo:
from turtle import distance
import numpy as np
import cv2 as cv
import matplotlib.pyplot as pltimg1 = cv.imread('image4.png',cv.IMREAD_GRAYSCALE)
img2 = cv.imread('image4.png',cv.IMREAD_GRAYSCALE)# Initiate SIFT detector
sift = cv.SIFT_create()kp1,des1 = sift.detectAndCompute(img1,None)
kp2,des2 = sift.detectAndCompute(img2,None)# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_param = dict(algorithm =FLANN_INDEX_KDTREE,tree = 5)
search_param = dict(checks=50)flann = cv.FlannBasedMatcher(index_param,search_param)matches = flann.knnMatch(des1,des2,k=2)# Need to draw only good matches, so create a mask
matchesMask =[[0,0] for i in range(len(matches))]# ratio test as per Lowe's paper
for i,(m,n) in enumerate(matches):if m.distance < 0.9*n.distance:matchesMask[i]=[1,0]draw_params = dict(matchColor = (0,255,0),singlePointColor = (255,125,0),matchesMask = matchesMask,flags = cv.DrawMatchesFlags_DEFAULT)img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,matches,None,**draw_params)plt.imshow(img3,),plt.show()
4.9Feature Matching + Homography to find Objects
給定兩張有重疊區域的圖像(比如從不同角度拍攝的同一本書、同一棟建筑),本節目標是通過特征匹配找到它們之間的對應點,并計算出一個變換矩陣(單應性矩陣),從而可以將一張圖像“投影”或“對齊”到另一張圖像的視角上。
cv.findHomography()
?-?計算變換矩陣
作用:?根據一系列對應的點對,計算出描述兩個平面之間透視變換關系的?3x3 單應性矩陣 (H)。
H, mask = cv.findHomography(srcPoints, dstPoints, method=0, ransacReprojThreshold=3.0, maxIters=2000, confidence=0.995)
參數詳解:srcPoints: 源平面中點的坐標。通常是第一張圖像(查詢圖像)中的關鍵點坐標。形狀為 (N, 1, 2) 的 NumPy 數組,其中 N 是點的數量。
dstPoints: 目標平面中點的坐標。與 srcPoints 一一對應。是第二張圖像(訓練圖像)中的關鍵點坐標。形狀必須與 srcPoints 相同。
method: 計算 H 矩陣的方法。
0: 常規方法,使用所有點。如果存在錯誤匹配(離群點),結果會非常差。
cv.RANSAC (推薦): 使用 RANSAC 算法。這是一種魯棒的方法,即使輸入的點對中存在大量錯誤匹配,它也能估算出正確的模型。它會找出一個最能符合內點 (inliers) 的模型。
cv.LMEDS: 最小中值方法。
ransacReprojThreshold: 僅當 method=cv.RANSAC 時有效。它是一個距離閾值(單位為像素),用來判斷一個點是否是內點。這個參數很重要!
原理: 對于每一個點,算法使用計算出的 H 矩陣將 srcPoint 變換到目標平面,得到一個預測點。然后計算這個預測點與真實的 dstPoint 之間的歐氏距離。如果這個距離小于 ransacReprojThreshold,則該點被標記為內點。
如何設置: 值越大,能容忍的誤差越大,被認為是內點的點就越多(可能包含一些錯誤點)。值越小,要求越嚴格,內點越少但更精確。通常設置在 1.0 到 10.0 之間,具體取決于你匹配的精度和圖像分辨率。
maxIters: RANSAC 的最大迭代次數。
confidence: 置信度,表示算法期望找到的內點比例。
返回值:H: 計算得到的 3x3 單應性矩陣。如果沒有找到解,則返回 None。
mask: 可選輸出(尤其在使用 cv.RANSAC 或 cv.LMEDS 時非常有用)。它是一個形狀為 (N, 1) 的數組,其中元素為 0 或 1。
mask[i] == 1: 表示第 i 對點被算法判定為內點,即它符合最終計算出的 H 模型。
mask[i] == 0: 表示第 i 對點是外點 (outlier),即錯誤匹配。
cv.perspectiveTransform()
?-?應用變換矩陣
作用:?對一個或多個點執行透視變換。它使用一個已經計算好的變換矩陣(比如由?cv.findHomography()
?得到的 H),將輸入的點從源空間變換到目標空間。
dst = cv.perspectiveTransform(src, m)
參數詳解:src: 輸入的點集。可以是單個點 [x, y],但更常見的是多個點的集合。其形狀必須是 (N, 1, 2),其中 N 是點的數量。注意: 這里的點坐標是 (x, y),而不是圖像索引 (row, col)。
m: 變換矩陣。對于透視變換,這就是一個 3x3 的矩陣(例如單應性矩陣 H)。
返回值:dst: 變換后的點集。形狀與 src 相同,為 (N, 1, 2)。
核心區別與聯系:廚師與食譜的比喻
特性 | cv.findHomography() | cv.perspectiveTransform() |
---|---|---|
角色 | 廚師 (Chef) | 學徒 (Apprentice) |
任務 | 創造食譜 (H矩陣)。根據一些原材料(對應點對srcPoints, dstPoints ),研究出烹飪的配方和步驟(計算變換矩陣 H)。 | 執行食譜。已經拿到了廚師寫好的詳細食譜(H 矩陣),只需要按照食譜把新的原材料(新的點集?src )做熟(變換)即可。 |
輸入 | 兩套對應的點集 | 一套點集 + 一個變換矩陣 |
輸出 | 一個變換矩陣 (+ 內點掩碼) | 變換后的點集 |
過程 | 復雜的數學計算和優化(如 RANSAC) | 直接的矩陣乘法運算 |
demo:輸入兩張圖像,計算單應性矩陣:
我們設置一個條件,即至少有10個匹配(由MIN_MATCH_COUNT定義)在那里找到對象。否則,只需顯示一條消息,說沒有足夠的匹配。
如果找到足夠的匹配項,我們將提取兩個圖像中匹配的關鍵點的位置。他們通過尋找透視轉換。一旦我們得到這個3x3變換矩陣,我們就用它來將queryImage的角轉換為trainImage中的相應點。然后我們畫它。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as pltMIN_MATCH_COUNT = 10# 讀取圖像 - 注意:應該是兩張不同的圖像!
# 例如:img1 = 'object.png', img2 = 'scene_with_object.png'
img1 = cv.imread('image1.png', cv.IMREAD_GRAYSCALE) # 查詢圖像(小物體)
img2 = cv.imread('image2.png', cv.IMREAD_GRAYSCALE) # 訓練圖像(包含物體的場景)# 檢查圖像是否成功加載
if img1 is None or img2 is None:print("錯誤:無法加載圖像!請檢查文件路徑。")exit()# Initiate SIFT detector
sift = cv.SIFT_create()# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)# 使用FLANN匹配器
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)flann = cv.FlannBasedMatcher(index_params, search_params)# 進行KNN匹配
matches = flann.knnMatch(des1, des2, k=2)# store all the good matches as per Lowe's ratio test.
good = []
for m, n in matches:if m.distance < 0.7 * n.distance:good.append(m)if len(good) > MIN_MATCH_COUNT:src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)matchesMask = mask.ravel().tolist()h, w = img1.shape # 修正這里# 定義源圖像的四個角點pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]).reshape(-1, 1, 2)dst = cv.perspectiveTransform(pts, M)# 在目標圖像上繪制變換后的邊界框img2_with_box = cv.polylines(img2, [np.int32(dst)], True, 255, 3, cv.LINE_AA)print(f"找到 {len(good)} 個良好匹配,其中 {sum(matchesMask)} 個是內點")else:print(f"未找到足夠匹配 - {len(good)}/{MIN_MATCH_COUNT}")matchesMask = Noneimg2_with_box = img2 # 如果沒有找到足夠匹配,使用原始圖像draw_params = dict(matchColor=(0, 255, 0), # draw matches in green colorsinglePointColor=None,matchesMask=matchesMask, # draw only inliersflags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)# 繪制匹配結果
img3 = cv.drawMatches(img1, kp1, img2_with_box, kp2, good, None, **draw_params)# 顯示結果
plt.figure(figsize=(15, 10))
plt.imshow(img3, cmap='gray')
plt.title('特征匹配與單應性變換結果')
plt.axis('off')
plt.show()
參考:OpenCV: Feature Detection and Description