文章目錄
- 理解特征
- 目標
- 解釋
- Harris角點檢測
- 目標
- 理論
- OpenCV 中的 Harris 角點檢測器
- 亞像素級精度角點檢測
- 練習
- Shi-Tomasi角點檢測器與優質跟蹤特征
- 目標
- 理論基礎
- 代碼
- SIFT(尺度不變特征變換)簡介
- 目標
- 理論
- 1、尺度空間極值檢測
- 2、關鍵點定位
- 3、方向分配
- 4、關鍵點描述符
- 5、關鍵點匹配
- OpenCV 中的 SIFT 算法
- SURF(加速穩健特征)簡介
- 目標
- 理論
- OpenCV 中的 SURF 算法
- FAST角點檢測算法
- 目標
- 理論背景
- 使用FAST算法進行特征檢測
- 機器學習角點檢測器
- 非極大值抑制
- 概述
- OpenCV中的FAST特征檢測器
- 附加資源
- BRIEF(二進制魯棒獨立基本特征)
- 目標
- 理論基礎
- OpenCV中的STAR(CenSurE)特征檢測器
- OpenCV中的BRIEF描述符
- 補充資源
- ORB (定向FAST與旋轉BRIEF)
- 目標
- 理論基礎
- OpenCV 中的 ORB 特征檢測
- 附加資源
- 特征匹配
- 目標
- 暴力匹配器基礎
- 使用ORB描述符進行暴力匹配
- 什么是這個匹配器對象?
- 基于 SIFT 描述符與比率測試的暴力匹配
- 基于FLANN的匹配器
- 特征匹配與單應性變換實現物體查找
- 目標
- 基礎概念
- 代碼
理解特征
https://docs.opencv.org/4.x/df/d54/tutorial_py_features_meaning.html
目標
在本章中,我們將嘗試理解什么是特征、為什么特征很重要,以及為什么角點很重要等內容。
解釋
大多數人都玩過拼圖游戲。你會得到一張圖像分割成的許多小塊,需要正確組裝它們以形成完整的大圖。問題在于,你是如何做到的? 能否將同樣的原理應用到計算機程序中,讓計算機也能玩拼圖?如果計算機能玩拼圖,為什么我們不能給它許多自然風景的真實照片,讓它將這些圖像拼接成一張大圖呢?如果計算機能將多張自然圖像拼接成一張,那么給它大量建筑物或結構的照片,讓它創建出3D模型又如何呢?
這些問題和想象不斷延伸。但一切都取決于最基本的問題:你如何玩拼圖?如何將許多打亂的圖像碎片排列成一張完整的大圖?如何將多張自然圖像拼接成一張?
答案是,我們尋找那些獨特、易于追蹤和比較的特定模式或特征。如果要給這樣的特征下定義,可能很難用語言表達,但我們知道它們是什么。如果有人讓你指出一個可以在多張圖像間比較的好特征,你一定能指出來。這就是為什么連小孩子都能輕松玩這類游戲。我們在圖像中搜索這些特征,找到它們,在其他圖像中尋找相同的特征并進行對齊。就這樣(在拼圖中,我們更關注不同圖像間的連續性)。這些能力是我們與生俱來的。
因此,我們的基本問題擴展為更多具體問題:這些特征是什么?(答案也必須是計算機能理解的)。
很難說人類是如何找到這些特征的。這已經編碼在我們的大腦中。但如果我們深入觀察一些圖片并搜索不同模式,會發現一些有趣的東西。例如,看下面這張圖:
這張圖非常簡單。頂部給出了六個小圖像塊,問題是找出它們在原圖中的精確位置。你能找到多少個正確結果?
A和B是平坦區域,覆蓋范圍很大,很難精確定位這些小塊的位置。
C和D則簡單得多。它們是建筑物的邊緣。你可以找到大致位置,但精確位置仍較難確定,因為邊緣上的模式是相同的。但在邊緣處,模式會發生變化。因此,邊緣比平坦區域是更好的特征,但仍不夠理想(在拼圖中,邊緣適合比較連續性)。
最后,E和F是建筑物的角落,很容易找到。因為無論在哪個角落移動這些小塊,看起來都會不同。所以它們可以被視為好的特征。現在我們來看一張更簡單(且廣泛使用的圖像)以便更好理解:
與上面類似,藍色塊是平坦區域,難以找到和追蹤。無論怎么移動藍色塊,看起來都一樣。黑色塊有一條邊緣。如果沿垂直方向(即梯度方向)移動,它會變化;如果沿邊緣平行移動,看起來則相同。紅色塊是一個角落,無論怎么移動,看起來都不同,意味著它是唯一的。因此,角落被認為是圖像中的好特征(不僅是角落,在某些情況下斑點也被視為好特征)。
現在我們已經回答了"這些特征是什么?"的問題。但下一個問題出現了:如何找到它們?或者說如何找到角落?我們以直觀的方式回答了這個問題,即在圖像中尋找那些在周圍所有區域移動(小幅移動)時變化最大的區域。這將在后續章節中轉化為計算機語言。因此,尋找這些圖像特征被稱為特征檢測。
我們在圖像中找到了特征。一旦找到,你應該能在其他圖像中找到相同的特征。這是如何做到的?我們圍繞特征選取一個區域,用自己的語言描述它,比如"上部是藍天,下部是建筑物區域,建筑物上有玻璃等",然后在其他圖像中搜索相同區域。本質上,你是在描述特征。同樣,計算機也應該描述特征周圍的區域,以便在其他圖像中找到它。這種描述被稱為特征描述。一旦有了特征及其描述,你就可以在所有圖像中找到相同的特征,并對齊、拼接它們或進行其他操作。
因此,在本模塊中,我們將探討OpenCV中的不同算法來尋找特征、描述它們、匹配它們等。
生成于 2025年4月30日 星期三 23:08:42,由 doxygen 1.12.0 生成
Harris角點檢測
https://docs.opencv.org/4.x/dc/d0d/tutorial_py_features_harris.html
目標
在本章中,
- 我們將理解 Harris 角點檢測背后的概念。
- 我們將學習以下函數:
cv.cornerHarris()
、cv.cornerSubPix()
理論
在上一章中,我們了解到角點是圖像中所有方向上強度變化劇烈的區域。Chris Harris 和 Mike Stephens 在1988年的論文《A Combined Corner and Edge Detector》中首次嘗試尋找這些角點,因此該方法現在被稱為 Harris 角點檢測器。他們將這一簡單想法轉化為數學形式,本質上是通過計算位移 \((u,v)\) 在各個方向上的強度差異來實現。其數學表達式如下:
\[E(u,v) = \sum_{x,y} \underbrace{w(x,y)}\text{窗口函數} \, [\underbrace{I(x+u,y+v)}\text{平移后強度}-\underbrace{I(x,y)}_\text{原始強度}]^2\]
窗口函數可以是矩形窗口或高斯窗口,用于為下方像素賦予權重。
為了檢測角點,我們需要最大化函數 \(E(u,v)\),即最大化第二項。通過對上述方程應用泰勒展開并進行數學推導(完整推導過程可參考任何標準教材),最終得到:
\[E(u,v) \approx \begin{bmatrix} u & v \end{bmatrix} M \begin{bmatrix} u \\ v \end{bmatrix}\]
其中
\[M = \sum_{x,y} w(x,y) \begin{bmatrix}I_x I_x & I_x I_y \\
I_x I_y & I_y I_y \end{bmatrix}\]
這里,\(I_x\) 和 \(I_y\) 分別是圖像在x和y方向上的導數(可通過 cv.Sobel()
輕松計算)。
接下來是關鍵部分:他們構建了一個評分方程,用于判斷窗口是否包含角點:
\[R = \det(M) - k(\operatorname{trace}(M))^2\]
其中
- \(\det(M) = \lambda_1 \lambda_2\)
- \(\operatorname{trace}(M) = \lambda_1 + \lambda_2\)
- \(\lambda_1\) 和 \(\lambda_2\) 是矩陣 \(M\) 的特征值
通過特征值的大小可以判定區域類型:
- 當 \(|R|\) 較小(即 \(\lambda_1\) 和 \(\lambda_2\) 都較小時),該區域為平坦區域;
- 當 \(R<0\)(即 \(\lambda_1 >> \lambda_2\) 或相反情況時),該區域為邊緣;
- 當 \(R\) 較大(即 \(\lambda_1\) 和 \(\lambda_2\) 都較大且近似相等時),該區域為角點。
這一關系可通過下圖直觀表示:
Harris角點檢測的結果是一幅包含這些評分的灰度圖像。通過設定合適的閾值篩選評分,即可得到圖像中的角點。我們將通過簡單圖像進行演示。
OpenCV 中的 Harris 角點檢測器
OpenCV 提供了 cv.cornerHarris()
函數來實現這一功能。其參數說明如下:
- img - 輸入圖像,需為灰度圖且類型為 float32
- blockSize - 角點檢測時考慮的鄰域大小
- ksize - 所用 Sobel 算子的孔徑參數
- k - Harris 檢測器方程中的自由參數
參考以下示例:
import numpy as np
import cv2 as cvfilename = 'chessboard.png'
img = cv.imread(filename)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)gray = np.float32(gray)
dst = cv.cornerHarris(gray,2,3,0.04)#result is dilated for marking the corners, not important
dst = cv.dilate(dst,None)# Threshold for an optimal value, it may vary depending on the image.
img[dst>0.01*dst.max()]=[0,0,255]cv.imshow('dst',img)
if cv.waitKey((0) \& 0xff == 27:cv.destroyAllWindows()
以下是三個結果:
亞像素級精度角點檢測
有時您可能需要以最高精度定位角點。OpenCV提供了 cv.cornerSubPix()
函數,可對檢測到的角點進行亞像素級精度的優化。以下是一個示例:
首先需要檢測Harris角點,然后將這些角點的質心(一個角點可能包含多個像素點,我們取其質心)傳入函數進行優化。圖中紅色像素標記的是Harris角點,綠色像素標記的是優化后的角點。
使用該函數時,需要定義迭代終止條件。當達到指定迭代次數或滿足特定精度要求時(以先達到的條件為準),迭代將停止。此外,還需定義搜索角點的鄰域大小。
import numpy as np
import cv2 as cvfilename = 'chessboard2.jpg'
img = cv.imread(filename)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)# find Harris corners
gray = np.float32(gray)
dst = cv.cornerHarris(gray,2,3,0.04)
dst = cv.dilate(dst,None)
ret, dst = cv.threshold(dst,0.01*dst.max(),255,0)
dst = np.uint8(dst)# find centroids
ret, labels, stats, centroids = [cv.connectedComponentsWithStats`](https://docs.opencv.org/4.x/d3/dc0/group__imgproc__shape.html#ga107a78bf7cd25dec05fb4dfc5c9e765f)(dst)# define the criteria to stop and refine the corners
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = [cv.cornerSubPix`](https://docs.opencv.org/4.x/dd/d1a/group__imgproc__feature.html#ga354e0d7c86d0d9da75de9b9701a9a87e)(gray,np.float32(centroids),(5,5),(-1,-1),criteria)# Now draw them
res = np.hstack((centroids,corners))
res = np.int0(res)
img[res[:,1],res[:,0]]=[0,0,255]
img[res[:,3],res[:,2]] = [0,255,0]cv.imwrite('subpixel5.png',img)
以下是結果展示,其中放大窗口內標注了若干關鍵位置以便觀察:
練習
由 doxygen 1.12.0 生成于 2025年4月30日 星期三 23:08:42,適用于 OpenCV
Shi-Tomasi角點檢測器與優質跟蹤特征
https://docs.opencv.org/4.x/d4/d8c/tutorial_py_shi_tomasi.html
目標
在本章中,
- 我們將學習另一種角點檢測器:Shi-Tomasi角點檢測器
- 我們將了解函數:
cv.goodFeaturesToTrack()
理論基礎
在上一章中,我們學習了Harris角點檢測器。1994年,J. Shi和C. Tomasi在其論文**《Good Features to Track》**中對該算法進行了小幅改進,相比Harris角點檢測器取得了更好的效果。Harris角點檢測器中的評分函數定義為:
\[R = \lambda_1 \lambda_2 - k(\lambda_1+\lambda_2)^2\]
而Shi-Tomasi算法將其改進為:
\[R = \min(\lambda_1, \lambda_2)\]
當該值大于設定閾值時,即判定為角點。若我們在\(\lambda_1 - \lambda_2\)空間中進行可視化(如同Harris角點檢測器中的做法),可得到如下圖像:
從圖中可見,只有當\(\lambda_1\)和\(\lambda_2\)均超過最小值\(\lambda_{\min}\)時,該區域才會被判定為角點(綠色區域)。
代碼
OpenCV 提供了一個函數 cv.goodFeaturesToTrack()
。該函數通過 Shi-Tomasi 方法(或 Harris 角點檢測,如果指定的話)在圖像中尋找 N 個最強的角點。通常,圖像應為灰度圖像。你需要指定要查找的角點數量,以及一個 0-1 之間的質量等級參數,該參數表示角點的最低質量閾值,低于此閾值的角點將被舍棄。此外,還需提供檢測到的角點之間的最小歐氏距離。
基于這些信息,函數會在圖像中尋找角點。所有低于質量閾值的角點會被舍棄,剩余的角點按質量降序排序。然后函數選取最強的角點,舍棄最小距離范圍內的鄰近角點,最終返回 N 個最強角點。
在下面的示例中,我們將嘗試找出 25 個最佳角點:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltimg = cv.imread('blox.jpg')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)corners = cv.goodFeaturesToTrack(gray,25,0.01,10)
corners = np.int0(corners)for i in corners:x,y = i.ravel()cv.circle(img,(x,y),3,255,-1)plt.imshow(img),plt.show()
查看下方結果:
該函數更適合用于追蹤場景,我們將在后續相關部分具體展開說明。
生成于 2025年4月30日 星期三 23:08:42,由 doxygen 1.12.0 為 OpenCV 生成
SIFT(尺度不變特征變換)簡介
https://docs.opencv.org/4.x/da/df5/tutorial_py_sift_intro.html
目標
在本章節中,
- 我們將學習SIFT算法的核心概念
- 我們將掌握如何檢測SIFT關鍵點并提取描述符
理論
在前幾章中,我們學習了一些角點檢測器,如Harris角點檢測等。它們具有旋轉不變性,這意味著即使圖像旋轉,我們仍能找到相同的角點。這很直觀,因為旋轉后的圖像中角點依然是角點。但縮放呢?如果圖像被縮放,一個角點可能就不再是角點了。例如,觀察下面這張簡單的圖片。在小圖像中某個窗口內的角點,當放大到相同窗口時可能就變成了平坦區域。因此,Harris角點不具備尺度不變性。
2004年,英屬哥倫比亞大學的D.Lowe在其論文《尺度不變關鍵點的獨特圖像特征》中提出了一種新算法——尺度不變特征變換(SIFT),該算法能夠提取關鍵點并計算其描述符。(這篇論文易于理解,被認為是關于SIFT的最佳資料。這里的解釋只是對該論文的簡要概述)。
SIFT算法主要包含四個步驟,我們將逐一介紹。
1、尺度空間極值檢測
從上面的圖像可以明顯看出,我們無法使用相同的窗口來檢測不同尺度的關鍵點。對于小角點沒有問題,但要檢測更大的角點就需要更大的窗口。為此,我們采用尺度空間濾波技術。該方法通過計算圖像在不同σ\sigmaσ值下的高斯拉普拉斯算子(LoG)。由于σ\sigmaσ的變化,LoG作為斑點檢測器可以識別不同尺寸的斑點。簡而言之,σ\sigmaσ起到了尺度參數的作用。例如在上圖中,低σ\sigmaσ的高斯核對小角點響應強烈,而高σ\sigmaσ的高斯核則更適合大角點。因此,我們可以在尺度和空間上尋找局部極大值,從而得到一系列\((x,y,\sigma)\)值,這意味著在σ\sigmaσ尺度下的(x,y)位置可能存在關鍵點。
但LoG計算成本較高,因此SIFT算法采用高斯差分(DoG)來近似LoG。高斯差分是通過對圖像分別用兩個不同σ\sigmaσ(設為σ\sigmaσ和\(k\sigma\))進行高斯模糊后求差得到的。這個過程會在高斯金字塔的不同八度空間中進行。如下圖所示:
得到DoG后,我們就在不同尺度和空間中搜索局部極值點。例如,將圖像中的每個像素與其8個相鄰像素、以及相鄰尺度上的9+9個像素進行比較。如果它是局部極值點,則視為潛在關鍵點。這本質上表示該關鍵點在該尺度下具有最佳表征。如下圖所示:
關于參數設置,論文提供了經驗數據:最優值為八度空間數=4、尺度層級數=5、初始\(\sigma=1.6\)、\(k=\sqrt{2}\)等。
2、關鍵點定位
一旦找到潛在的關鍵點位置,需要對其進行細化以獲得更精確的結果。研究者們利用尺度空間的泰勒級數展開來獲取極值點的更精確位置,如果該極值點的強度低于閾值(論文中設為0.03),則會被剔除。在OpenCV中,該閾值被稱為contrastThreshold。
DoG(差分高斯)對邊緣有較強的響應,因此也需要去除邊緣點。為此,采用了與Harris角點檢測器類似的概念。研究者使用2x2的Hessian矩陣(H)來計算主曲率。根據Harris角點檢測器的原理可知,對于邊緣點,一個特征值會遠大于另一個。因此這里采用了一個簡單的判斷函數:如果該比值大于某個閾值(OpenCV中稱為edgeThreshold),則該關鍵點會被丟棄。論文中該閾值設為10。
通過這一過程,算法剔除了所有低對比度的關鍵點和邊緣關鍵點,最終保留的是具有顯著特征的點。
3、方向分配
現在為每個關鍵點分配一個方向,以實現圖像旋轉不變性。根據關鍵點的尺度,在其周圍選取一個鄰域區域,并計算該區域內的梯度幅值和方向。創建一個包含36個柱狀區間的方向直方圖(覆蓋360度范圍),該直方圖通過梯度幅值和高斯加權圓形窗口(其中σ\sigmaσ等于關鍵點尺度的1.5倍)進行加權處理。取直方圖中的最高峰值,同時任何達到該峰值80%以上的次高峰也會被納入方向計算。這一過程會生成位置和尺度相同但方向不同的關鍵點,從而提升匹配的穩定性。
4、關鍵點描述符
此時關鍵點描述符已創建完成。具體流程如下:
- 提取關鍵點周圍16x16像素的鄰域區域
- 將該區域劃分為16個4x4大小的子塊
- 為每個子塊創建8個方向的梯度直方圖
- 最終生成共128維的直方圖特征向量作為關鍵點描述符
此外,還采用了多種措施來確保描述符對光照變化、旋轉等因素具有魯棒性。
5、關鍵點匹配
通過識別最近鄰來匹配兩幅圖像之間的關鍵點。但在某些情況下,第二接近的匹配可能與第一接近的匹配非常接近。這可能是由于噪聲或其他原因造成的。此時,會計算最接近距離與第二接近距離的比值。如果該比值大于0.8,則拒絕這些匹配點。根據論文所述,這種方法可以消除約90%的錯誤匹配,同時僅丟棄5%的正確匹配。
以上是SIFT算法的概要。如需了解更多細節和深入理解,強烈建議閱讀原始論文。
OpenCV 中的 SIFT 算法
現在讓我們看看 OpenCV 提供的 SIFT 功能。需要注意的是,這些功能原先僅存在于 opencv contrib 倉庫 中,但由于其專利已在 2020 年過期,因此現在已被納入主代碼庫。我們將從關鍵點檢測和繪制開始講解。
首先需要創建一個 SIFT 對象。我們可以為其傳遞不同的可選參數,這些參數在文檔中都有詳細說明。
import numpy as np
import cv2 as cvimg = cv.imread('home.jpg')
gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)sift = cv.SIFT_create()
kp = sift.detect(gray,None)img=cv.drawKeypoints(gray,kp,img)cv.imwrite('sift_keypoints.jpg',img)
sift.detect() 函數用于在圖像中尋找關鍵點。若只需搜索圖像的一部分,可以傳入掩碼參數。每個關鍵點都是一個特殊結構體,包含多種屬性,例如 (x,y) 坐標、有效鄰域大小、表示方向的角度、反映關鍵點強度的響應值等。
OpenCV 還提供了 cv.drawKeyPoints() 函數,可在關鍵點位置繪制小圓圈。如果傳入 cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS 標志,函數會繪制與關鍵點大小匹配的圓圈,并顯示其方向。參考以下示例:
查看下方兩組結果:
計算描述符時,OpenCV 提供兩種方法:
-
若已找到關鍵點,可調用 sift.compute() 基于現有關鍵點計算描述符。例如:kp,des = sift.compute(gray,kp)
-
若未檢測關鍵點,可直接通過 sift.detectAndCompute() 函數一步獲取關鍵點和描述符。
我們將演示第二種方法:
sift = cv.SIFT_create()
kp, des = sift.detectAndCompute(gray,None)
這里 kp
將是一個關鍵點列表,des
是一個形狀為 \(\text{(Number of Keypoints)} \times 128\) 的 numpy 數組。
現在我們已經獲取了關鍵點、描述符等信息。接下來我們將學習如何在不同圖像中匹配關鍵點,這部分內容將在后續章節中展開。
生成于 2025年4月30日 星期三 23:08:42,由 doxygen 1.12.0 為 OpenCV 生成
SURF(加速穩健特征)簡介
https://docs.opencv.org/4.x/df/dd2/tutorial_py_surf_intro.html
目標
在本章中,
- 我們將了解SURF的基礎知識
- 我們將學習OpenCV中的SURF功能
理論
在上一章中,我們學習了用于關鍵點檢測和描述的SIFT算法。但它的速度相對較慢,人們需要更快的版本。2006年,Bay、H.、Tuytelaars、T.和Van Gool、L三人發表了另一篇論文《SURF: Speeded Up Robust Features》,提出了一種名為SURF的新算法。顧名思義,它是SIFT的加速版本。
在SIFT中,Lowe使用高斯差分(Difference of Gaussian)來近似拉普拉斯高斯(Laplacian of Gaussian)以構建尺度空間。SURF更進一步,用盒式濾波器(Box Filter)來近似LoG。下圖展示了這種近似方法的示意圖。這種近似的一大優勢是,盒式濾波器的卷積運算可以借助積分圖像輕松計算,并且可以并行處理不同尺度。此外,SURF依賴Hessian矩陣的行列式來確定尺度和位置。
在方向分配階段,SURF對大小為6s的鄰域計算水平和垂直方向的小波響應,并施加適當的高斯權重。然后將這些響應繪制在下圖所示的空間中。通過計算60度滑動方向窗口內所有響應的總和來估計主導方向。有趣的是,利用積分圖像可以非常容易地在任何尺度下計算小波響應。對于許多不需要旋轉不變性的應用場景,可以跳過方向計算步驟,從而加速處理。SURF提供了稱為Upright-SURF(U-SURF)的功能,在速度提升的同時能保持對±15°旋轉的魯棒性。OpenCV通過upright標志位支持兩種模式:設為0時計算方向,設為1時不計算方向(速度更快)。
在特征描述階段,SURF使用水平和垂直方向的小波響應(同樣借助積分圖像簡化計算)。取關鍵點周圍20s×20s的鄰域(s表示尺度),將其劃分為4×4的子區域。每個子區域生成一個向量:\(v=( \sum{d_x}, \sum{d_y}, \sum{|d_x|}, \sum{|d_y|})\),最終形成64維的SURF特征描述符。更低的維度能提高計算和匹配速度,同時保持較好的特征區分度。
為了增強區分度,SURF還提供了128維擴展版本。分別計算\(d_y < 0\)和\(d_y \geq 0\)時的\(d_x\)和\(|d_x|\)之和,類似地根據\(d_x\)的符號對\(d_y\)和\(|d_y|\)進行分組,從而使特征數量翻倍。OpenCV通過extended標志位(0表示64維,1表示128維,默認為128維)支持兩種模式。
另一個重要改進是利用拉普拉斯算子(Hessian矩陣的跡)的符號信息。由于檢測階段已計算該值,因此不會增加額外開銷。拉普拉斯符號可以區分暗背景上的亮斑與相反情況。在匹配階段,僅對比具有相同對比類型的特征(如下圖)。這種方法在不降低描述符性能的前提下加速了匹配過程。
簡而言之,SURF通過多項改進提升了各步驟的速度。分析表明其速度比SIFT快3倍,同時保持相當的性能。SURF擅長處理模糊和旋轉圖像,但在視角變化和光照變化場景下表現不佳。
OpenCV 中的 SURF 算法
OpenCV 提供了與 SIFT 類似的 SURF 功能。您可以通過一些可選條件來初始化 SURF 對象,例如 64/128 維描述符、直立/常規 SURF 等。所有細節在文檔中都有詳細說明。然后就像我們在 SIFT 中所做的那樣,我們可以使用 SURF.detect()、SURF.compute() 等方法來查找關鍵點和描述符。
首先我們將看到一個簡單的演示,展示如何查找 SURF 關鍵點和描述符并繪制它們。所有示例都在 Python 終端中展示,因為它與 SIFT 的使用方式完全相同。
>>> img = cv.imread('fly.png', cv.IMREAD_GRAYSCALE)# Create SURF object. You can specify params here or later.# Here I set Hessian Threshold to 400
>>> surf = cv.xfeatures2d.SURF_create(400)# Find keypoints and descriptors directly
>>> kp, des = surf.detectAndCompute(img,None)>>> len(kp)699
1199個關鍵點太多,無法在一張圖片中全部顯示。我們將其減少到約50個以便在圖像上繪制。在匹配時可能需要所有特征點,但現在不需要。因此我們提高了Hessian閾值。
# Check present Hessian threshold
>>> print( surf.getHessianThreshold() )
400.0# We set it to some 50000、Remember, it is just for representing in picture.# In actual cases, it is better to have a value 300-500
>>> surf.setHessianThreshold(50000)# Again compute keypoints and check its number.
>>> kp, des = surf.detectAndCompute(img,None)>>> print( len(kp) )
47
少于50,讓我們在圖像上繪制它。
>>> img2 = cv.drawKeypoints(img,kp,None,(255,0,0),4)>>> plt.imshow(img2),plt.show()
請查看下方結果。可以看到,SURF 更像是一個斑點檢測器。它檢測到了蝴蝶翅膀上的白色斑點。你可以用其他圖像進行測試。
現在我想應用 U-SURF,這樣它就不會檢測方向。
# Check upright flag, if it False, set it to True
>>> print( surf.getUpright() )
False>>> surf.setUpright(True)# Recompute the feature points and draw it
>>> kp = surf.detect(img,None)
>>> img2 = cv.drawKeypoints(img,kp,None,(255,0,0),4)>>> plt.imshow(img2),plt.show()
查看下方結果。所有方向都顯示為同一朝向,速度比之前更快。如果您處理的場景中方向不是問題(比如全景圖拼接等),這種方法更合適。
最后我們檢查描述符尺寸,如果僅為64維則將其改為128維。
# Find size of descriptor
>>> print( surf.descriptorSize() )
64# That means flag, "extended" is False.
>>> surf.getExtended()False# So we make it to True to get 128-dim descriptors.
>>> surf.setExtended(True)
>>> kp, des = surf.detectAndCompute(img,None)
>>> print( surf.descriptorSize() )
128
>>> print( des.shape )
(47, 128)
剩余部分將在另一章節中進行匹配處理。
生成于 2025年4月30日 星期三 23:08:42,由 doxygen 1.12.0 為 OpenCV 生成
FAST角點檢測算法
https://docs.opencv.org/4.x/df/d0c/tutorial_py_fast.html
目標
在本章節中,
- 我們將了解FAST算法的基礎知識
- 我們將使用OpenCV提供的FAST算法功能來尋找角點
理論背景
我們之前探討了多種特征檢測器,其中不少表現優異。但從實時應用的角度來看,它們的處理速度仍顯不足。一個典型案例是計算資源有限的SLAM(同步定位與建圖)移動機器人。
針對這一問題,Edward Rosten和Tom Drummond在2006年的論文《Machine learning for high-speed corner detection》中提出了FAST(基于加速分段測試的特征)算法(2010年進行了修訂)。以下是該算法的核心概要,更多細節請參閱原始論文(本文所有圖片均引自該論文)。
使用FAST算法進行特征檢測
1、在圖像中選擇一個待檢測是否為興趣點的像素點\(p\),其灰度值為\(I_p\)。
2、選擇合適的閾值\(t\)。
3、以待測像素為中心,取半徑為3的圓周上的16個像素點(如下圖所示)。
1、如果圓周上存在連續\(n\)個像素點(圖中白色虛線所示),這些像素的灰度值均大于\(I_p + t\)或均小于\(I_p ? t\),則判定\(p\)為角點。通常取\(n=12\)。
2、提出了一種高速測試法來快速排除大量非角點:僅檢測位置1、9、5、13四個像素點(先檢測1和9,若符合條件再檢測5和13)。如果\(p\)是角點,這四個點中至少有三個點同時滿足大于\(I_p + t\)或小于\(I_p ? t\)。若不滿足該條件,則可立即排除\(p\)為角點的可能。通過該測試的候選點再使用完整的圓周檢測進行驗證。雖然該檢測器本身性能優異,但仍存在以下不足:
* 當n<12時無法有效排除候選點
* 像素點選取策略非最優,其效率依賴于檢測順序和角點分布特征
* 高速測試的中間結果未被充分利用
* 相鄰區域會檢測出多個特征點
前三個問題通過機器學習方法解決,最后一個問題采用非極大值抑制處理。
機器學習角點檢測器
1、選擇一組圖像用于訓練(最好來自目標應用領域)
2、在每張圖像上運行FAST算法以尋找特征點
3、對于每個特征點,將其周圍16個像素存儲為向量。對所有圖像執行此操作,得到特征向量\(P\)
4、這16個像素中的每個像素(例如\(x\))可能處于以下三種狀態之一:
1、根據這些狀態,特征向量\(P\)被劃分為三個子集:\(P_d\)、\(P_s\)和\(P_b\)
2、定義一個新的布爾變量\(K_p\),當\(p\)是角點時值為真,否則為假
3、使用ID3算法(決策樹分類器)通過變量\(K_p\)查詢每個子集,獲取關于真實類別的知識。算法會選擇能提供最多角點判斷信息的像素\(x\),該信息量通過\(K_p\)的熵來度量
4、遞歸地將此方法應用于所有子集,直到熵降為零
5、最終生成的決策樹可用于在其他圖像中進行快速檢測
非極大值抑制
在相鄰位置檢測到多個興趣點是另一個常見問題。這可以通過非極大值抑制(Non-maximum Suppression)來解決。
- 為所有檢測到的特征點計算評分函數\(V\)。\(V\)表示中心點\(p\)與周圍16個像素點絕對差值的總和。
- 比較兩個相鄰關鍵點的\(V\)值。
- 舍棄其中\(V\)值較低的關鍵點。
概述
該算法比其他現有的角點檢測器快數倍。
但它對高噪聲水平不夠魯棒,且依賴于閾值參數。
OpenCV中的FAST特征檢測器
它的調用方式與OpenCV中其他特征檢測器相同。如果需要,你可以指定閾值、是否應用非極大值抑制以及使用的鄰域類型等參數。
對于鄰域類型,定義了三種標志:
- cv.FAST_FEATURE_DETECTOR_TYPE_5_8
- cv.FAST_FEATURE_DETECTOR_TYPE_7_12
- cv.FAST_FEATURE_DETECTOR_TYPE_9_16
下面是一個簡單的代碼示例,展示如何檢測并繪制FAST特征點。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltimg = cv.imread('blox.jpg', cv.IMREAD_GRAYSCALE) # \`<opencv_root>/samples/data/blox.jpg\`# Initiate FAST object with default values
fast = cv.FastFeatureDetector_create()# find and draw the keypoints
kp = fast.detect(img,None)
img2 = cv.drawKeypoints(img, kp, None, color=(255,0,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_true.png', img2)# Disable nonmaxSuppression
fast.setNonmaxSuppression(0)
kp = fast.detect(img, None)print( "Total Keypoints without nonmaxSuppression: {}".format(len(kp)) )img3 = cv.drawKeypoints(img, kp, None, color=(255,0,0))cv.imwrite('fast_false.png', img3)
查看結果。第一張圖展示了帶非極大值抑制的FAST算法效果,第二張圖則是不帶非極大值抑制的效果:
附加資源
1、Edward Rosten 和 Tom Drummond 的論文《Machine learning for high speed corner detection》,發表于第9屆歐洲計算機視覺會議(ECCV 2006)第1卷,430-443頁。
2、Edward Rosten、Reid Porter 和 Tom Drummond 的論文《Faster and better: a machine learning approach to corner detection》,發表于《IEEE模式分析與機器智能匯刊》2010年第32卷,105-119頁。
由 doxygen 1.12.0 生成于 2025年4月30日 星期三 23:08:42,適用于 OpenCV
BRIEF(二進制魯棒獨立基本特征)
https://docs.opencv.org/4.x/dc/d7d/tutorial_py_brief.html
目標
在本章中
- 我們將了解 BRIEF 算法的基礎知識
理論基礎
我們知道SIFT算法使用128維向量作為描述符。由于采用浮點數存儲,每個描述符需要占用512字節。類似地,SURF描述符(64維)也至少需要256字節。為成千上萬個特征點生成這樣的向量會消耗大量內存,這在資源受限的應用場景(尤其是嵌入式系統)中難以實現。內存消耗越大,特征匹配所需的時間也越長。
實際上,并非所有維度都對匹配過程必不可少。我們可以通過PCA(主成分分析)、LDA(線性判別分析)等方法進行數據壓縮。甚至還可以采用LSH(局部敏感哈希)等哈希技術,將SIFT的浮點描述符轉換為二進制字符串。這些二進制字符串通過漢明距離進行特征匹配,由于現代CPU的SSE指令集能高效執行異或運算和位計數操作,這種方法能顯著提升匹配速度。但需要注意的是,我們必須先獲取描述符才能進行哈希轉換,這并沒有解決內存占用的根本問題。
此時BRIEF算法應運而生。它提供了一種直接獲取二進制字符串的捷徑,無需先計算描述符。該算法對平滑后的圖像塊進行處理,通過獨特方式(論文中有詳細說明)選擇一組\(n_d\)個(x,y)坐標對,然后對這些坐標點進行像素強度比較。例如,對于第一個坐標對\(p\)和\(q\),若\(I§ < I(q)\)則輸出1,否則輸出0。對所有\(n_d\)個坐標對執行此操作,最終生成\(n_d\)維的比特串。
這個\(n_d\)可以取值128、256或512。OpenCV支持所有這些維度,但默認使用256(OpenCV以字節為單位表示,因此實際值為16、32和64)。獲得比特串后,就可以使用漢明距離進行描述符匹配。
需要特別強調的是,BRIEF是一種特征描述符算法,本身不包含特征檢測功能。因此需要配合SIFT、SURF等其他特征檢測器使用。原論文推薦使用CenSurE快速檢測器,實驗表明BRIEF對CenSurE特征點的描述效果甚至略優于SURF特征點。
簡而言之,BRIEF是一種更高效的特征描述符計算與匹配方法。除非存在大幅度的平面內旋轉,否則該方法都能保持較高的識別率。
OpenCV中的STAR(CenSurE)特征檢測器
STAR是一種源自CenSurE的特征檢測器。但與使用正方形、六邊形和八邊形等多邊形來逼近圓形的CenSurE不同,STAR通過兩個重疊的正方形(一個直立,一個旋轉45度)來模擬圓形。這些多邊形是雙層結構的,可以看作具有粗邊框的多邊形,其邊框與內部區域的權重符號相反。
相比其他尺度空間檢測器,STAR具有更優的計算特性,能夠實現實時處理。與SIFT和SURF在子采樣像素上尋找極值(這會影響較大尺度下的精度)不同,CenSurE在金字塔所有尺度上都使用全空間分辨率來創建特征向量。
OpenCV中的BRIEF描述符
以下代碼展示了如何借助CenSurE檢測器計算BRIEF描述符。
注意:使用此功能需要安裝opencv contrib。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltimg = cv.imread('simple.jpg', cv.IMREAD_GRAYSCALE)# Initiate FAST detector
star = cv.xfeatures2d.StarDetector_create()# Initiate BRIEF extractor
brief = cv.xfeatures2d.BriefDescriptorExtractor_create()# find the keypoints with STAR
kp = star.detect(img,None)# compute the descriptors with BRIEF
kp, des = brief.compute(img, kp)print( brief.descriptorSize() )
print( des.shape )
函數 brief.getDescriptorSize() 返回以字節為單位的 \(n_d) 大小,默認值為 32。匹配操作將在后續章節中討論。
補充資源
1、Michael Calonder、Vincent Lepetit、Christoph Strecha 和 Pascal Fua 合著的論文《BRIEF: 二進制魯棒獨立基本特征》,發表于2010年9月在希臘克里特島伊拉克利翁舉行的第11屆歐洲計算機視覺會議(ECCV),由Springer出版社LNCS系列收錄。
2. 維基百科關于局部敏感哈希(LSH)的條目。
本文檔由 doxygen 1.12.0 生成于 2025年4月30日 星期三 23:08:42,針對 OpenCV 項目。
ORB (定向FAST與旋轉BRIEF)
https://docs.opencv.org/4.x/d1/d89/tutorial_py_orb.html
目標
在本章中,
- 我們將了解ORB的基礎知識
理論基礎
作為OpenCV的愛好者,ORB最令人振奮的一點在于它誕生于"OpenCV實驗室"。該算法由Ethan Rublee、Vincent Rabaud、Kurt Konolige和Gary R. Bradski在2011年的論文**《ORB:SIFT與SURF的高效替代方案》**中提出。正如標題所示,ORB在計算成本、匹配性能以及專利問題上都是SIFT和SURF的優秀替代方案。是的,SIFT和SURF受專利保護,使用時需要付費,但ORB完全免費!
ORB本質上是FAST關鍵點檢測器與BRIEF描述符的融合體,并通過多項改進來提升性能。首先使用FAST算法定位關鍵點,然后應用Harris角點測量篩選出最優的N個點。算法還采用圖像金字塔來生成多尺度特征。但存在一個問題:FAST不計算方向。那么如何實現旋轉不變性呢?研究者們提出了以下改進方案。
算法會計算以角點為中心的圖像塊強度加權質心。從角點指向質心的向量方向即為該點的朝向。為增強旋轉不變性,在半徑為\(r\)的圓形區域內計算x和y方向的矩,其中\(r\)表示圖像塊尺寸。
在描述符方面,ORB采用BRIEF描述符。但我們已經知道BRIEF在旋轉情況下表現欠佳。因此ORB根據關鍵點方向對BRIEF進行"導向"調整:對于包含\(n\)個二進制測試的特征集\((x_i, y_i)\),定義一個\(2 \times n\)的坐標矩陣\(S\),然后利用圖像塊方向\(\theta\)計算旋轉矩陣,對\(S\)進行旋轉得到導向版本\(S_\theta\)。
ORB將角度離散化為\(2 \pi /30\)(12度)的增量,并預先計算BRIEF模式的查找表。只要關鍵點方向\(\theta\)在不同視角下保持一致,就能使用正確的點集\(S_\theta\)來計算描述符。
BRIEF有個重要特性:每個二進制特征都具有高方差且均值接近0.5。但經過關鍵點方向調整后,這個特性會減弱導致分布更分散。高方差使特征更具區分度,因為其對不同輸入會產生差異化響應。另一個理想特性是測試間不相關,這樣每個測試都能獨立發揮作用。為解決這些問題,ORB通過貪婪搜索從所有可能的二進制測試中篩選出同時具備高方差、均值接近0.5且互不相關的組合,最終得到rBRIEF。
在描述符匹配方面,采用改進傳統局部敏感哈希(LSH)的多探針LSH算法。論文指出ORB速度遠超SURF和SIFT,且ORB描述符性能優于SURF。對于全景拼接等低功耗設備應用場景,ORB是絕佳選擇。
OpenCV 中的 ORB 特征檢測
通常,我們需要通過 cv.ORB()
函數或使用 feature2d 通用接口來創建 ORB 對象。該函數包含多個可選參數,其中最常用的有:
nFeatures
:指定要保留的最大特征數量(默認為 500)scoreType
:決定使用 Harris 評分還是 FAST 評分對特征進行排序(默認為 Harris 評分)
另一個重要參數 WTA_K
決定了生成定向 BRIEF 描述符每個元素所需的點數。默認值為 2,即每次選擇兩個點。此時匹配使用 NORM_HAMMING
距離。若 WTA_K
設為 3 或 4(表示需要 3 或 4 個點來生成 BRIEF 描述符),則匹配距離由 NORM_HAMMING2
定義。
以下是一個展示 ORB 用法的簡單代碼示例:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltimg = cv.imread('simple.jpg', cv.IMREAD_GRAYSCALE)# Initiate ORB detector
orb = cv.ORB_create()# find the keypoints with ORB
kp = orb.detect(img,None)# compute the descriptors with ORB
kp, des = orb.compute(img, kp)# draw only keypoints location,not size and orientation
img2 = cv.drawKeypoints(img, kp, None, color=(0,255,0), flags=0)
plt.imshow(img2), plt.show()
查看下方結果:
ORB特征匹配我們將在另一章節中實現。
附加資源
1、Ethan Rublee、Vincent Rabaud、Kurt Konolige、Gary R. Bradski 合著:ORB:SIFT 或 SURF 的高效替代方案。ICCV 2011:2564-2571。
本文檔由 doxygen 1.12.0 生成于 2025年4月30日 星期三 23:08:42,適用于 OpenCV。
特征匹配
https://docs.opencv.org/4.x/dc/dc3/tutorial_py_matcher.html
目標
在本章中:
- 我們將學習如何在一幅圖像中匹配其他圖像的特征點。
- 我們將使用OpenCV中的暴力匹配器(Brute-Force matcher)和快速近似最近鄰匹配器(FLANN Matcher)。
暴力匹配器基礎
暴力匹配器原理很簡單。它會提取第一組特征中的一個描述符,通過某種距離計算方式與第二組中的所有其他特征進行匹配,然后返回最接近的那個匹配結果。
使用BF匹配器時,首先需要通過 cv.BFMatcher()
創建BFMatcher對象。該函數接收兩個可選參數:第一個是normType,用于指定距離度量方式,默認使用 cv.NORM_L2
(cv.NORM_L1
也可用),適用于SIFT、SURF等算法。對于ORB、BRIEF、BRISK等基于二進制字符串的描述符,則應使用 cv.NORM_HAMMING
以漢明距離作為度量標準。若ORB使用WTA_K == 3或4,則需改用 cv.NORM_HAMMING2
。
第二個參數是布爾變量crossCheck,默認為false。若設為true,匹配器僅返回滿足雙向匹配條件的特征對——即集合A中的第i個描述符與集合B中的第j個描述符互為最佳匹配。這種方式能提供穩定的匹配結果,可作為SIFT論文中D.Lowe提出的比率檢驗法的替代方案。
創建匹配器后,有兩個核心方法:BFMatcher.match() 返回最佳匹配結果,BFMatcher.knnMatch() 則返回用戶指定數量k的最佳匹配(適用于需要進一步處理的場景)。
類似于用 cv.drawKeypoints()
繪制關鍵點,cv.drawMatches()
可繪制匹配結果:水平堆疊兩幅圖像并繪制連接線顯示最佳匹配。另有 cv.drawMatchesKnn 可繪制所有k個最佳匹配(例如k=2時為每個關鍵點繪制兩條匹配線),此時需要通過掩碼選擇性繪制。
下面我們將分別展示SIFT和ORB的示例(兩者使用不同的距離度量方式)。
使用ORB描述符進行暴力匹配
這里我們將看到一個簡單的特征匹配示例。在這個案例中,我有一張查詢圖像(queryImage)和一張訓練圖像(trainImage)。我們將嘗試通過特征匹配在訓練圖像中定位查詢圖像。(使用的圖像是/samples/data/box.png和/samples/data/box_in_scene.png)
我們使用ORB描述符進行特征匹配。現在讓我們從加載圖像、查找描述符等步驟開始。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as pltimg1 = cv.imread('box.png',cv.IMREAD_GRAYSCALE) # queryImage
img2 = cv.imread('box_in_scene.png',cv.IMREAD_GRAYSCALE) # trainImage# Initiate ORB detector
orb = cv.ORB_create()# find the keypoints and descriptors with ORB
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
接下來我們創建一個使用 cv.NORM_HAMMING
距離度量(因為我們使用的是 ORB)的 BFMatcher 對象,并開啟 crossCheck 以獲得更好的結果。然后使用 Matcher.match() 方法獲取兩幅圖像中的最佳匹配。我們按距離升序排序,使最佳匹配(距離較小)排在前面。最后僅繪制前 10 個匹配(僅為可視化考慮,可根據需要增加數量)。
# 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 10 matches.
img3 = cv.drawMatches(img1,kp1,img2,kp2,matches[:10],None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)plt.imshow(img3),plt.show()
我得到的結果如下:
什么是這個匹配器對象?
matches = bf.match(des1,des2)
這行代碼的結果是一個 DMatch 對象列表。這個 DMatch 對象具有以下屬性:
- DMatch.distance - 描述符之間的距離。數值越小,匹配效果越好。
- DMatch.trainIdx - 訓練描述符集合中描述符的索引
- DMatch.queryIdx - 查詢描述符集合中描述符的索引
- DMatch.imgIdx - 訓練圖像的索引
基于 SIFT 描述符與比率測試的暴力匹配
這次我們將使用 BFMatcher.knnMatch()
方法來獲取 k 個最佳匹配。在本例中,我們設定 k=2,以便應用 D.Lowe 在其論文中闡述的比率測試方法。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as pltimg1 = cv.imread('box.png',cv.IMREAD_GRAYSCALE) # queryImage
img2 = cv.imread('box_in_scene.png',cv.IMREAD_GRAYSCALE) # trainImage# 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)# BFMatcher with default params
bf = cv.BFMatcher()
matches = bf.knnMatch(des1,des2,k=2)# Apply ratio test
good = []
for m,n in matches:if m.distance < 0.75*n.distance:good.append([m])# cv.drawMatchesKnn expects list of lists as matches.
img3 = cv.drawMatchesKnn(img1, kp1, img2, kp2, good,None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)plt.imshow(img3),plt.show()
查看下方結果:
基于FLANN的匹配器
FLANN代表快速近似最近鄰庫(Fast Library for Approximate Nearest Neighbors)。它包含了一系列針對大數據集和高維特征進行快速最近鄰搜索優化的算法。在處理大型數據集時,FLANN比BFMatcher運行得更快。我們將通過第二個示例來了解基于FLANN的匹配器。
使用基于FLANN的匹配器時,需要傳遞兩個字典參數來指定使用的算法及其相關參數等。第一個是IndexParams。對于不同算法,需要傳遞的參數信息在FLANN文檔中有詳細說明。簡而言之,對于SIFT、SURF等算法,可以傳遞以下參數:
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
使用 ORB 時,可以傳遞以下參數。文檔中建議使用注釋中的值,但在某些情況下這些值無法提供所需結果。其他值則工作正常:
FLANN_INDEX_LSH = 6
index_params= dict(algorithm = FLANN_INDEX_LSH, table_number = 6, # 12key_size = 12, # 20multi_probe_level = 1) #2
第二個字典是 SearchParams。它指定了索引中的樹應該被遞歸遍歷的次數。數值越高精度越好,但耗時也越長。如需修改該值,請傳入 search_params = dict(checks=100)
。
掌握這些信息后,我們就可以開始了。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as pltimg1 = cv.imread('box.png',cv.IMREAD_GRAYSCALE) # queryImage
img2 = cv.imread('box_in_scene.png',cv.IMREAD_GRAYSCALE) # trainImage# 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 parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50) # or pass empty dictionaryflann = cv.FlannBasedMatcher(index_params,search_params)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.7*n.distance:matchesMask[i]=[1,0]draw_params = dict(matchColor = (0,255,0), singlePointColor = (255,0,0), matchesMask = matchesMask, flags = cv.DrawMatchesFlags_DEFAULT)img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,matches,None,**draw_params)plt.imshow(img3,),plt.show()
查看下方結果:
生成于 2025年4月30日 星期三 23:08:42,由 doxygen 1.12.0 為 OpenCV 生成
特征匹配與單應性變換實現物體查找
https://docs.opencv.org/4.x/d1/de0/tutorial_py_feature_homography.html
目標
在本章中,
- 我們將結合使用 calib3d 模塊的特征匹配和 findHomography 功能,在復雜圖像中識別已知物體。
基礎概念
在上一次課程中我們做了什么?我們使用了一張查詢圖像(queryImage),在其中找到了一些特征點;然后選取另一張訓練圖像(trainImage),同樣提取其特征,并在兩幅圖像間尋找最佳匹配。簡而言之,我們在雜亂的圖像中定位到了目標物體的某些部分。這些信息足以在訓練圖像上精確定位目標物體。
為此,我們可以調用calib3d模塊中的 cv.findHomography()
函數。當傳入兩組圖像中的匹配點集時,該函數會計算出物體的透視變換矩陣。接著通過 cv.perspectiveTransform()
即可定位目標物體,此過程至少需要四個正確匹配點才能計算變換。
我們注意到匹配過程中可能存在誤差,這些誤差會影響最終結果。為了解決這個問題,算法采用RANSAC或LEAST_MEDIAN方法(可通過標志位選擇)。那些能提供正確估計的良好匹配被稱為內點(inliers),其余則稱為外點(outliers)。cv.findHomography()
會返回一個掩碼,用于標識內點和外點。
現在就開始實踐吧!
代碼
首先,按照慣例,我們會在圖像中尋找 SIFT 特征,并通過比率測試來篩選最佳匹配項。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltMIN_MATCH_COUNT = 10img1 = cv.imread('box.png', cv.IMREAD_GRAYSCALE) # queryImage
img2 = cv.imread('box_in_scene.png', cv.IMREAD_GRAYSCALE) # trainImage# 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_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)flann = cv.FlannBasedMatcher(index_params, search_params)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)
現在我們設定一個條件:至少需要10個匹配點(由MIN_MATCH_COUNT定義)才能判定找到目標物體。否則,直接顯示"匹配點數量不足"的提示信息。
如果找到足夠的匹配點,我們會提取兩幅圖像中匹配關鍵點的位置坐標。這些坐標被用于計算透視變換矩陣。得到這個3x3變換矩陣后,我們用它來將queryImage的角點坐標轉換為trainImage中對應的點坐標,最后繪制出轉換結果。
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`](https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html#ga4abc2ece9fab9398f2e560d53c8c9780)(src_pts, dst_pts, cv.RANSAC,5.0)matchesMask = mask.ravel().tolist()h,w = img1.shapepts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)dst = cv.perspectiveTransform(pts,M)img2 = cv.polylines(img2,[np.int32(dst)],True,255,3, cv.LINE_AA)else:print( "Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT) )matchesMask = None
最后,我們繪制出內點(如果成功找到目標)或匹配的關鍵點(如果失敗)。
draw_params = dict(matchColor = (0,255,0), # draw matches in green colorsinglePointColor = None, matchesMask = matchesMask, # draw only inliersflags = 2)img3 = cv.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)plt.imshow(img3, 'gray'),plt.show()
查看下方結果。在雜亂的圖像中,目標物體以白色標記:
生成于 2025年4月30日 星期三 23:08:42,由 doxygen 1.12.0 為 OpenCV 創建
2025-07-19(六)