文章目錄
- 前言
- 一、角點檢測
- 1.1 角點特征
- 1.1.1 角點特征概念
- 1.1.2 角點的特點
- 1.1.3 關鍵點繪制代碼實現
- 1.1.4 函數解析
- 1.2 Harris角點檢測
- 1.2.1 Harris角點檢測原理
- 1.2.2 Harris角點檢測公式
- 1.2.3 代碼實現
- 1.2.4 函數解析
- 1.3 Shi-Tomasi角點檢測
- 1.3.1 Shi-Tomasi角點檢測原理
- 1.3.2 Shi-Tomasi角點檢測公式
- 1.3.3 代碼實現
- 1.3.4 函數解析
- 1.4 FAST角點檢測
- 1.4.1 FAST角點檢測原理
- 1.4.2 FAST角點檢測特點和應用
- 1.4.3 代碼實現
- 1.4.4 函數解析
- 1.5 亞像素角點檢測
- 1.5.1 亞像素角點檢測原理
- 1.5.2 亞像素角點檢測公式
- 1.5.3 代碼實現
- 1.5.4 函數解析
- 二、特征點檢測
- 2.1 SIFT(尺度不變特征變換)
- 2.1.1 SIFT原理
- 2.1.2 代碼實現
- 2.1.3 函數解析
- 2.2 SURF(加速穩健特征)
- 2.2.1 SURF原理
- 2.2.2 代碼實現
- 2.2.3 函數解析
- 2.3 ORB(方向快速和旋轉二進制)
- 2.3.1 ORB原理
- 2.3.2 代碼實現
- 2.3.3 函數解析
- 三、特征點匹配
- 3.1 BF匹配器
- 3.1.1 BF匹配器原理
- 3.1.2 代碼實現
- 3.1.3 函數解析
- 3.2 FLANN匹配器
- 3.2.1 FLANN匹配器原理
- 3.2.2 代碼實現
- 3.2.3 函數解析
- 3.3 RANSAC特征點匹配
- 3.3.1 RANSAC原理
- 3.3.2 代碼實現
- 3.3.3 函數解析
- 總結
前言
在計算機視覺領域,特征點檢測與匹配是解決多種問題的核心,包括圖像識別、跟蹤、三維重建和運動分析。OpenCV作為一個功能強大的視覺處理庫,提供了豐富的功能來處理這些任務。本博客旨在提供一個關于OpenCV中特征點檢測與匹配方法的快速入門指南。
我們將從角點檢測開始,探討如Harris、Shi-Tomasi和FAST等經典算法,介紹它們的原理、公式和代碼實現。接著,我們將深入到特征點檢測的高級話題,覆蓋如SIFT、SURF和ORB等算法。每種方法都會有詳細的函數解析,幫助理解其背后的工作原理。最后,我們將討論特征點匹配技術,包括BF匹配器、FLANN匹配器和RANSAC匹配方法,它們在處理不同圖像間的特征點對應關系時至關重要。
一、角點檢測
角點檢測(Corner Detection)是計算機視覺和圖像處理中的一個基本概念,指的是在圖像中識別出具有明顯角點特征的點。在OpenCV中,我們通常使用Harris 角點檢測或Shi-Tomasi角點檢測來實現這一功能。
1.1 角點特征
1.1.1 角點特征概念
角點特征是指在圖像中那些局部特征明顯、在多個方向上具有變化的點。在計算機視覺中,角點是圖像中最重要的特征之一,因為它們通常對圖像的變化(如視角、光照、尺度等)保持不變性。角點特征在圖像處理、模式識別、三維建模、運動追蹤等領域有著廣泛的應用。
1.1.2 角點的特點
- 局部特征:角點是圖像中局部特征的重要表示,能夠代表圖像的關鍵信息。
- 不變性:它們相對穩定,對光照、旋轉、縮放等圖像變化具有一定的抵抗力。
- 高信息量:角點包含了豐富的信息,適合用于圖像匹配、目標跟蹤等。
1.1.3 關鍵點繪制代碼實現
import cv2
import random# 讀取圖像
image = cv2.imread('tulips.jpg')# 生成隨機關鍵點
keypoints = []
num_keypoints = 50 # 假設我們想生成50個關鍵點
for _ in range(num_keypoints):x = random.randint(0, image.shape[1] - 1)y = random.randint(0, image.shape[0] - 1)keypoints.append(cv2.KeyPoint(x, y, 1))# 使用 drawKeypoints 繪制關鍵點
keypoint_image = cv2.drawKeypoints(image, keypoints, None, color=(0, 255, 0), flags=0)# 顯示圖像
cv2.imshow('Random Keypoints', keypoint_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
在這個示例中,我創建了50個隨機位置的關鍵點。cv2.KeyPoint
需要 x 和 y 坐標以及關鍵點的大小(這里設置為 1)。這些點將以綠色標記在圖像上。
1.1.4 函數解析
drawKeypoints
函數是 OpenCV 庫中用于在圖像上繪制關鍵點的一個函數。這個函數的目的是將檢測到的關鍵點可視化,使得這些點在圖像上更容易被識別。
def drawKeypoints(image, keypoints, outImage, color=None, flags=None)
image: 這是源圖像,即你想在其上繪制關鍵點的圖像。這個圖像應該是一個標準的 OpenCV 圖像格式,通常是使用
cv2.imread
讀取的。keypoints: 這個參數是一個關鍵點的列表。這些關鍵點通常是通過特征檢測算法(如 SIFT, SURF, ORB 等)得到的。每個關鍵點通常包含了圖像中一個特定點的位置(x, y 坐標)和其他信息(如方向、大小等)。
outImage: 這是輸出圖像。函數會在這個圖像上繪制關鍵點,并將其返回。如果這個參數是 None,OpenCV 通常會直接在源圖像上繪制關鍵點。
color: 這個可選參數定義了關鍵點的顏色。如果未指定,將使用默認顏色。顏色通常以 (B, G, R) 格式指定,其中 B、G、R 分別代表藍色、綠色和紅色的強度。
flags: 這個可選參數定義了繪制關鍵點時的一些特性。OpenCV 提供了幾個不同的標志來控制如何繪制關鍵點。
例如,cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
會繪制關鍵點的大小和方向,
而cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS
將不會繪制單個的關鍵點。
1.2 Harris角點檢測
1.2.1 Harris角點檢測原理
Harris角點檢測的基本原理是觀察窗口移動時,窗口內像素灰度變化的程度。具體來說,它計算了窗口在各個方向上移動時灰度變化的二階矩陣,并通過此矩陣來判斷一個點是否為角點。角點的特征是無論窗口向哪個方向移動,窗口內的灰度變化都很大。
基本檢測原理如下:
- 圖像窗口和移動:
- 假設有一個小窗口(或掩模),在整個圖像上移動。
- 對于窗口中的每個像素,我們希望了解當窗口在各個方向上移動小距離時,像素強度的變化情況。
- 強度變化函數:
- 強度變化可以用函數 E ( u , v ) E(u,v) E(u,v)來描述,其中 u u u 和 v v v 是窗口在x和y方向上的移動量。
1.2.2 Harris角點檢測公式
Harris角點檢測的核心是計算每個像素的Harris響應值。數學公式為:
E ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) ? I ( x , y ) ] 2 E(u,v) = \sum_{x,y} w(x, y)[I(x + u, y + v) - I(x, y)]^2 E(u,v)=x,y∑?w(x,y)[I(x+u,y+v)?I(x,y)]2
其中, I ( x , y ) I(x, y) I(x,y) 是像素點 (x, y) 的強度, w ( x , y ) w(x, y) w(x,y) 是窗口函數(通常是高斯窗口),用于賦予窗口中心附近的像素更高的權重。
這個公式可以通過泰勒級數展開并簡化,最終表示為:
E ( u , v ) ≈ [ u , v ] M [ u , v ] T E(u,v) ≈ [u, v] M [u, v]^T E(u,v)≈[u,v]M[u,v]T
其中, M M M 是由圖像的梯度導數計算得出的 2 × 2 2 \times 2 2×2矩陣。
Harris響應值的計算可以通過以下步驟進行:
- 計算圖像梯度:
- 需要計算圖像在x和y方向的梯度,通常用Sobel算子來完成。這可以表示為 I x I_x Ix? 和 I y I_y Iy?。
- 計算梯度的乘積:
- 計算梯度的乘積 I x 2 , I y 2 I_x^2, I_y^2 Ix2?,Iy2? 和 I x I y I_xI_y Ix?Iy?。
- 應用高斯濾波:
- 對這些乘積應用高斯濾波(窗口函數),以在局部區域內平滑它們。這里使用高斯窗口是為了給圖像的局部區域賦予更多的權重。
- 構造Harris矩陣:
使用上述結果構造Harris矩陣(也稱為結構張量),其一般形式為:
M = ( ∑ I x 2 ∑ I x I y ∑ I x I y ∑ I y 2 ) M = \begin{pmatrix} \sum I_x^2 & \sum I_xI_y \\ \sum I_xI_y & \sum I_y^2 \end{pmatrix} M=(∑Ix2?∑Ix?Iy??∑Ix?Iy?∑Iy2??)
其中,求和表示在窗口內對應像素值的求和。
- 計算Harris響應值:
計算每個像素的Harris響應值 R:
R = det ( M ) ? k ? ( trace ( M ) ) 2 R = \text{det}(M) - k \cdot (\text{trace}(M))^2 R=det(M)?k?(trace(M))2
其中, det ( M ) \text{det}(M) det(M)是矩陣M的行列式, trace ( M ) \text{trace}(M) trace(M)是矩陣的跡(即對角線元素的和),k是一個經驗常數(通常介于0.04到0.06之間)。
為了理解這個公式如何與M的特征值相關聯,我們可以考慮矩陣M的特征分解。矩陣M的兩個特征值 λ 1 \lambda_1 λ1? 和 λ 2 \lambda_2 λ2? 描述了圖像在該點的兩個主要方向的梯度強度。矩陣M的行列式是這兩個特征值的乘積:
det ( M ) = λ 1 ? λ 2 \text{det}(M) = \lambda_1 \cdot \lambda_2 det(M)=λ1??λ2?
而矩陣的跡是這兩個特征值的和:
trace ( M ) = λ 1 + λ 2 \text{trace}(M) = \lambda_1 + \lambda_2 trace(M)=λ1?+λ2?
因此,Harris響應值R可以重寫為:
R = λ 1 λ 2 ? k ( λ 1 + λ 2 ) 2 R = \lambda_1 \lambda_2 - k (\lambda_1 + \lambda_2)^2 R=λ1?λ2??k(λ1?+λ2?)2
- 角點的識別
- 根據Harris響應值R,可以判斷每個像素是否是角點:
- 如果 R R R 很大,那么該點可能是角點。
- 如果 R R R 很小或接近零,那么該點可能是平坦區域。
- 如果 R R R 為負值,那么該點可能是邊緣。
Harris角點檢測限制:
盡管Harris角點檢測是一種強大的工具,但它也有局限性。例如,它不是尺度不變的,這意味著在圖像尺度顯著變化時,檢測到的角點可能會變化。此外,它對噪聲比較敏感,并且在處理高度結構化的圖像時可能會產生誤檢。
1.2.3 代碼實現
在OpenCV中,可以通過cv2.cornerHarris()
函數實現Harris角點檢測。下面是一個簡單的示例代碼:
import cv2
import numpy as np# 讀取圖像
img = cv2.imread('tulips.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# Harris角點檢測
gray = np.float32(gray)
dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)# 結果閾值化以確定角點位置
dst = cv2.dilate(dst, None)
thresh = 0.01 * dst.max()
img[dst > thresh] = [0, 0, 255]# 繪制圓圈標記角點
for i in range(dst.shape[0]):for j in range(dst.shape[1]):if dst[i, j] > thresh:# 繪制圓圈cv2.circle(img, (j, i), radius=2, color=(0, 255, 0), thickness=1)# 顯示圖像
cv2.imshow('Harris Corners', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
在這段代碼中,cv2.cornerHarris()
函數接受幾個參數:輸入圖像、鄰域大小(blockSize)、用于邊緣檢測的Sobel算子的孔徑大小(ksize)和Harris檢測器的自由參數(k)。函數返回每個像素的Harris響應值。通過設置一個閾值,可以選擇性地顯示角點。
1.2.4 函數解析
cornerHarris
用于角點檢測的一個函數。它實現了 Harris 角點檢測算法,該算法能夠在圖像中有效地識別角點。
def cornerHarris(src, blockSize, ksize, k, dst=None, borderType=None)
src (Source Image): 輸入圖像,它應該是單通道(灰度)的,數據類型可以是 8 位或者浮點型。這是函數進行處理的原始圖像。
blockSize: 它指的是用于角點檢測的鄰域大小。簡單來說,它是在每個像素周圍考慮用于角點檢測的像素塊的大小。較大的塊將考慮更多的像素,可能更適合于檢測大的角點特征。
ksize (Kernel Size): Sobel算子的孔徑參數。這個參數決定了用于計算圖像梯度的Sobel卷積核的大小。這直接影響到圖像梯度的計算,進而影響角點檢測的結果。
k: Harris檢測器的自由參數,用于在響應函數中加權角點的測量。它影響了角點檢測的敏感度,通常在 0.04 到 0.06 之間。
dst (Destination Image): 用于存儲 Harris 檢測器響應的圖像。它的類型是
CV_32FC1
,與輸入圖像src
有相同的大小。borderType: 像素外推法的類型。在進行圖像梯度計算時,需要對邊緣像素周圍進行像素值的外推。這個參數指定了所使用的外推方法。常見的外推方法包括反射外推、常量外推等。
函數的主要功能是在輸入圖像上運行 Harris 角點檢測器。它會計算每個像素的響應值,這些響應值可以用來確定圖像中的角點位置。角點可以被識別為這些響應值的局部最大值。
1.3 Shi-Tomasi角點檢測
Shi-Tomasi角點檢測方法是基于Harris角點檢測的一種改進,它在多個方面提供了更好的性能和精度。Shi-Tomasi方法的核心思想是通過評估圖像的最小特征值來識別角點。
1.3.1 Shi-Tomasi角點檢測原理
Shi-Tomasi方法的基本原理與Harris角點檢測類似,都是基于圖像局部區域的自相關矩陣。不同之處在于,Shi-Tomasi方法使用了自相關矩陣的最小特征值來評估角點的質量。
對于圖像中的每個點,首先計算其周圍區域的自相關矩陣。然后,計算該矩陣的兩個特征值(記為 λ 1 \lambda_1 λ1?和 λ 2 \lambda_2 λ2?)。在Shi-Tomasi方法中,如果這兩個特征值都大于某個閾值,則該點被認為是角點。
1.3.2 Shi-Tomasi角點檢測公式
Shi-Tomasi方法的角點響應函數定義為:
R = min ? ( λ 1 , λ 2 ) R = \min(\lambda_1, \lambda_2) R=min(λ1?,λ2?)
其中, λ 1 , λ 2 \lambda_1, \lambda_2 λ1?,λ2?是自相關矩陣的特征值。
1.3.3 代碼實現
在OpenCV中,Shi-Tomasi角點檢測可以通過 cv2.goodFeaturesToTrack()
函數實現。以下是一個簡單的示例代碼:
import cv2# 讀取圖像
img = cv2.imread('tulips.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# Shi-Tomasi角點檢測
corners = cv2.goodFeaturesToTrack(gray, maxCorners=100, qualityLevel=0.01, minDistance=10)# 繪制角點
for corner in corners:x, y = corner.ravel()cv2.circle(img, (int(x), int(y)), 5, (0, 255, 0), -1)# 顯示圖像
cv2.imshow('Shi-Tomasi Corners', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
在這段代碼中:
cv2.goodFeaturesToTrack()
函數接收幾個參數:輸入圖像、要檢測的角點最大數量(maxCorners
)、角點質量等級(qualityLevel
,一個介于 0 到 1 之間的數,代表最小特征值的相對值)和角點之間的最小距離(minDistance
)。- 檢測到的角點以
(x, y)
坐標的形式返回。
1.3.4 函數解析
goodFeaturesToTrack
用于角點檢測的函數,它實現了 Shi-Tomasi 方法。下面是該函數各個參數的簡要說明:
def goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance, corners=None, mask=None, blockSize=None, useHarrisDetector=None, k=None)
image: 輸入的圖像。它應該是單通道的8位或者浮點型32位圖像。
maxCorners: 要返回的角點的最大數量。如果檢測到的角點多于此數值,將返回最強的
maxCorners
個角點。如果maxCorners <= 0
,則返回檢測到的所有角點。qualityLevel: 角點質量的特征值閾值。這個參數乘以圖像中最佳角點的質量測量值(最小特征值或哈里斯函數響應),得到的乘積用于剔除質量較低的角點。
minDistance: 返回角點之間的最小可能歐幾里得距離。這用于確保選取的角點之間有一定的空間分隔。
corners: 檢測到的角點的輸出向量。
mask: 感興趣區域的掩碼。如果提供,它必須是類型為
CV_8UC1
且大小與image
相同的二值圖像。在掩碼非零的區域內檢測角點。blockSize: 計算導數協方差矩陣時考慮的鄰域大小。
useHarrisDetector: 指示是否使用哈里斯角點檢測器。如果不使用,則應用最小特征值方法(Shi-Tomasi方法)。
k: 僅當使用哈里斯檢測器時使用的自由參數。
1.4 FAST角點檢測
FAST(Features from Accelerated Segment Test)算法是一種廣泛使用的角點檢測方法。它以高速和簡潔著稱,在許多實時圖像處理系統中發揮著重要作用。
1.4.1 FAST角點檢測原理
- 選擇像素點: 選擇圖像中的一個像素點P作為檢測的中心點。
- 設置亮度閾值: 設定一個閾值T,用于與中心像素點P的亮度進行比較。
- 環形鄰域檢測: 在中心點P周圍選擇一個圓形鄰域(通常包含16個像素點),用于判斷P是否為角點。
- 連續性檢查: 檢查圓形鄰域上是否有至少N個連續的像素點其亮度高于(或低于)P的亮度加上(或減去)閾值T。通常N設為12。
- 非極大值抑制: 應用非極大值抑制(Non-Maximum Suppression)技術,去除響應較弱的角點,只保留最顯著的角點。
1.4.2 FAST角點檢測特點和應用
- 速度快: 簡潔的算法結構使得FAST算法執行速度非常快,適用于實時系統和高幀率視頻。
- 廣泛應用: 在機器人導航、視頻追蹤、三維建模和運動檢測等領域有廣泛應用。
- 局限性: 對噪聲敏感,不具備尺度不變性。常與其他算法結合使用以克服這些限制。
1.4.3 代碼實現
下面是一個基本的代碼示例:
import cv2# 讀取圖像
image = cv2.imread('tulips.jpg')# 初始化FAST對象
fast = cv2.FastFeatureDetector_create(50)# 檢測角點
keypoints = fast.detect(image, None)# 在圖像上繪制角點
image_with_keypoints = cv2.drawKeypoints(image, keypoints, None, color=(0, 255, 0))# 顯示圖像
cv2.imshow('FAST Keypoints', image_with_keypoints)
cv2.waitKey(0)
cv2.destroyAllWindows()
1.4.4 函數解析
FastFeatureDetector_create
用于創建 FAST檢測器的函數。
def FastFeatureDetector_create(threshold=None, nonmaxSuppression=None, type=None)
threshold: 用于決定角點的閾值。它是用來判斷一個像素是否為角點的亮度或顏色強度變化的閾值。閾值越高,檢測到的角點越少,但質量可能更高。
nonmaxSuppression: 非極大值抑制。當設置為
True
時,算法會在檢測到的角點周圍使用非極大值抑制,以確保檢測到的角點是局部最大值,這樣可以避免在角點附近檢測到多個相鄰的角點。通常,啟用非極大值抑制會得到更好的結果。type: 指定 FAST 算法的類型。OpenCV 提供了不同版本的 FAST 算法,例如,TYPE_9_16、TYPE_7_12 等。這些類型指定了用于角點檢測的圓形鄰域的不同大小。
1.5 亞像素角點檢測
亞像素角點檢測是一種提高角點定位精度到亞像素級別的技術。傳統的角點檢測方法,如哈里斯角點檢測,通常只能定位到像素級別的精度。而在一些應用中,如高精度的圖像對準和三維重建,需要更高精度的角點定位。亞像素角點檢測通過對角點周圍的像素進行精細分析,實現了這一目標。
1.5.1 亞像素角點檢測原理
亞像素角點檢測的基本原理是利用圖像局部區域的灰度分布信息來精細調整角點的位置。它通常包括以下幾個步驟:
初步角點檢測:首先使用常規方法(如哈里斯角點檢測)在圖像中定位角點的大致位置。
定義局部窗口:圍繞每個初步檢測到的角點,定義一個小的局部窗口。
灰度質心計算:在這個局部窗口中,計算灰度質心,這可以視為圖像局部區域質量的“中心”。基于灰度質心的位置,可以對初步檢測到的角點位置進行微調。
迭代優化:通過迭代的方式細化角點位置,直到滿足一定的精度要求。
1.5.2 亞像素角點檢測公式
亞像素角點檢測的關鍵公式是灰度質心的計算。灰度質心的坐標 ( x c , y c ) (x_c, y_c) (xc?,yc?) 可以用下面的公式計算:
x c = ∑ x i w i ∑ w i , y c = ∑ y i w i ∑ w i x_c = \frac{\sum x_i w_i}{\sum w_i}, \quad y_c = \frac{\sum y_i w_i}{\sum w_i} xc?=∑wi?∑xi?wi??,yc?=∑wi?∑yi?wi??
其中, x i , y i x_i, y_i xi?,yi? 是局部窗口中像素的坐標, w i w_i wi? 是相應像素的灰度值。
1.5.3 代碼實現
import cv2
import numpy as np# 讀取圖像
img = cv2.imread('tulips.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# Harris角點檢測
gray = np.float32(gray)
dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)# 結果閾值化,獲取角點位置
dst = cv2.dilate(dst, None)
_, dst = cv2.threshold(dst, 0.01 * dst.max(), 255, 0)
dst = np.uint8(dst)# 尋找質心并創建關鍵點集
_, _, _, centroids = cv2.connectedComponentsWithStats(dst)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv2.cornerSubPix(gray, np.float32(centroids), (5,5), (-1,-1), criteria)
keypoints = [cv2.KeyPoint(x=corner[0], y=corner[1], _size=20) for corner in corners]# 使用drawKeypoints繪制關鍵點
img_keypoints = cv2.drawKeypoints(img, keypoints, None, color=(0,255,0))# 顯示圖像
cv2.imshow('Harris Corners', img_keypoints)
cv2.waitKey(0)
cv2.destroyAllWindows()
這段代碼的目的是在使用 Harris 角點檢測后,找到檢測到的角點的精確位置,并將它們轉換為關鍵點對象,以便進一步處理或可視化。
1.5.4 函數解析
1. cv2.connectedComponentsWithStats
ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)
cv2.connectedComponentsWithStats
函數用于分析二值圖像,并找出圖像中所有連通區域(即相互連接的像素塊)。dst
是 Harris 角點檢測的結果,通常經過閾值處理以獲得二值圖像。- 函數返回幾個值:
ret
: 連通區域的數量。labels
: 圖像大小的數組,其中的每個元素表示對應像素屬于的連通區域的標簽。stats
: 每個連通區域的統計信息,如區域的大小、邊界框等。centroids
: 每個連通區域的質心(中心點)坐標。
2. cv2.cornerSubPix
亞像素檢測函數
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv2.cornerSubPix(gray, np.float32(centroids), (5,5), (-1,-1), criteria)
- 這部分代碼首先設置了迭代尋找角點的標準,即
criteria
。這里使用了兩個條件:迭代次數達到 100 或者角點位置變化小于 0.001。 cv2.cornerSubPix
函數用于進一步精細化角點的位置。它基于初始的質心位置(即centroids
),在一個小的鄰域內尋找更精確的角點位置。- 參數
gray
是原始的灰度圖像,np.float32(centroids)
是上一步得到的質心坐標,(5,5)
是搜索窗口的大小,(-1,-1)
是死區的半徑,通常不需要更改。
3. 創建關鍵點對象
keypoints = [cv2.KeyPoint(x=corner[0], y=corner[1], _size=20) for corner in corners]
- 這行代碼將
cv2.cornerSubPix
函數找到的角點坐標轉換為 OpenCV 的KeyPoint
對象。 - 每個
KeyPoint
對象包含了角點的位置和其他屬性(如大小、方向等)。這里,我們只設置了位置(x
和y
坐標)和大小(_size
),大小設為 20 僅為了可視化時更容易觀察。
二、特征點檢測
特征點檢測是指在圖像中尋找具有獨特屬性的點,這些點在不同圖像之間可以被匹配和識別。OpenCV提供了幾種常用的特征點檢測方法,如SIFT、SURF和ORB。
環境檢查
運行本章代碼時,請先檢查Python和OpenCV的版本。
尤其在遇到報錯:AttributeError: module 'cv2' has no attribute 'xfeatures2d_SIFT'
或者AttributeError: module 'cv2' has no attribute 'xfeatures2d'
的時候,參考下面的內容。
import sys# 檢查當前Python的版本
current_python_version = sys.version_info# 檢查Python版本是否是3.6
if current_python_version.major != 3 or current_python_version.minor != 6:python_version_message = "當前Python版本不是3.6,建議安裝Python 3.6版本。"
else:python_version_message = "當前Python版本是3.6。滿足運行環境。"print(python_version_message)
import pkg_resources# 設置所需檢查的OpenCV版本
required_opencv_version = "3.4.2.16"
required_opencv_contrib_version = "3.4.2.16"# 檢查安裝的OpenCV和opencv-contrib-python版本
try:installed_opencv_version = pkg_resources.get_distribution("opencv-python").version
except pkg_resources.DistributionNotFound:installed_opencv_version = Nonetry:installed_opencv_contrib_version = pkg_resources.get_distribution("opencv-contrib-python").version
except pkg_resources.DistributionNotFound:installed_opencv_contrib_version = None# 構造安裝提示信息
if installed_opencv_version != required_opencv_version or installed_opencv_contrib_version != required_opencv_contrib_version:opencv_installation_message = """# 卸載之前的OpenCV pip uninstall opencv-python# 安裝指定版本的OpenCV和opencv-contrib-pythonpip install opencv-python==3.4.2.16pip install opencv-contrib-python==3.4.2.16"""
else:opencv_installation_message = "OpenCV和opencv-contrib-python版本正確。"print(opencv_installation_message)
2.1 SIFT(尺度不變特征變換)
SIFT是一種用于檢測和描述圖像中的局部特征的算法。它在不同尺度的圖像上查找關鍵點,并對每個關鍵點周圍的區域進行特征描述。
2.1.1 SIFT原理
SIFT算法的主要思想是在不同尺度空間上尋找關鍵點,并計算這些關鍵點的方向直方圖作為特征。這些關鍵點對尺度和旋轉具有不變性。
- 尺度空間極值檢測:通過高斯差分函數在不同尺度空間查找潛在的興趣點。
- 關鍵點定位:精確確定關鍵點的位置和尺度,去除低對比度的點和邊緣響應點以增強匹配穩定性。
- 方向賦值:為每個關鍵點賦予一個或多個方向,基于局部圖像梯度方向。
- 關鍵點描述:在每個關鍵點周圍的區域內,計算其局部梯度的方向和幅度,生成描述符。
2.1.2 代碼實現
import cv2img = cv2.imread('tulips.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)sift = cv2.xfeatures2d.SIFT_create()
keypoints, descriptors = sift.detectAndCompute(gray, None)img = cv2.drawKeypoints(gray, keypoints, img,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow('SIFT Features', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.1.3 函數解析
SIFT_create
SIFT_create
函數用于創建一個 SIFT 檢測器對象。
def SIFT_create(nfeatures=None, nOctaveLayers=None, contrastThreshold=None, edgeThreshold=None, sigma=None)
nfeatures: 要保留的最佳特征數量。特征按其分數(在 SIFT 算法中作為局部對比度測量)排名。
nOctaveLayers: 每個八度中的層數。D. Lowe 的論文中使用的值是3。八度的數量是根據圖像分辨率自動計算的。
contrastThreshold: 用于過濾掉半均勻(低對比度)區域中的弱特征的對比度閾值。閾值越大,檢測器產生的特征越少。
edgeThreshold: 用于過濾掉邊緣特征的閾值。注意其含義與 contrastThreshold 不同,即 edgeThreshold 越大,過濾掉的特征越少(保留的特征越多)。
sigma: 應用于第0個八度的輸入圖像的高斯模糊的 sigma 值。如果您的圖像是用軟鏡頭拍攝的低品質相機捕獲的,可能需要減小這個值。
detectAndCompute
detectAndCompute
方法用于在圖像中檢測關鍵點并為它們計算描述符。
def detectAndCompute(self, image, mask, descriptors=None, useProvidedKeypoints=None)
image: 要處理的圖像。
mask: 掩碼圖像,用于定義圖像中要處理的區域。
descriptors: 計算得到的描述符將被存儲在這里。
useProvidedKeypoints: 如果為 True,則該方法只計算指定關鍵點的描述符而不檢測新的關鍵點。
這兩個函數結合使用,可以在圖像中檢測并描述關鍵點,這些關鍵點及其描述符可用于后續的圖像匹配和識別任務。
2.2 SURF(加速穩健特征)
SURF是一種比SIFT更快的特征檢測算法,同時保持了相似的特征描述能力。它對于快速和高效的圖像匹配非常有用。
2.2.1 SURF原理
SURF算法改進了SIFT的計算效率。它使用積分圖像快速計算高斯哈爾小波響應,并在多個尺度上尋找關鍵點。
- 尺度空間構建:利用積分圖像提高了尺度空間構建的速度。
- 關鍵點檢測:在不同尺度上使用盒子濾波器(近似高斯濾波器)查找關鍵點。
- 關鍵點描述:計算關鍵點周圍的簡化的Haar波特征描述符。
2.2.2 代碼實現
import cv2img = cv2.imread('tulips.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)surf = cv2.xfeatures2d.SURF_create(1000)
keypoints, descriptors = surf.detectAndCompute(gray, None)img = cv2.drawKeypoints(gray, keypoints, img,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow('SURF Features', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.2.3 函數解析
SURF_create
函數用于創建一個 SURF 檢測器對象。
def SURF_create(hessianThreshold=None, nOctaves=None, nOctaveLayers=None, extended=None, upright=None)
hessianThreshold: 用于 Hessian 關鍵點檢測器的閾值。該值決定了關鍵點的選擇——閾值越高,檢測到的特征點越少但更顯著。
nOctaves: 關鍵點檢測器將使用的金字塔八度數。金字塔的每個八度包含圖像的一個下采樣版本。
nOctaveLayers: 每個八度內的層數。這影響到特征檢測的尺度敏感性。
extended: 擴展描述符標志(true - 使用擴展的128元素描述符;false - 使用64元素描述符)。擴展描述符提供更多的特征信息,但也增加了計算復雜度。
upright: 是否計算特征的方向(true - 不計算特征的方向;false - 計算方向)。如果圖像不會出現旋轉變換,設置為 true 可以加快特征檢測速度。
SURF可以在圖像中檢測并描述關鍵點,這些關鍵點及其描述符可用于圖像匹配、對象識別等后續任務。SURF 由于其計算效率,經常用于實時或資源受限的應用場景。
2.3 ORB(方向快速和旋轉二進制)
ORB是一種結合了FAST關鍵點檢測和BRIEF關鍵點描述的算法,以其速度和效率而著稱。
2.3.1 ORB原理
ORB算法通過結合FAST算法的關鍵點檢測和BRIEF算法的描述子,提供了一種快速且有效的特征點檢測和描述方法。
- FAST關鍵點檢測:使用FAST算法檢測角點。
- BRIEF描述子:通過一種旋轉不變的方式計算關鍵點的BRIEF描述子。
- 多尺度特征:在不同尺度上重復檢測過程以確保尺度不變性。
2.3.2 代碼實現
import cv2img = cv2.imread('tulips.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)orb = cv2.ORB_create(80)
keypoints, descriptors = orb.detectAndCompute(gray, None)img = cv2.drawKeypoints(gray, keypoints, img, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow('ORB Features', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.3.3 函數解析
ORB_create
函數用于創建一個 ORB 檢測器對象。
def ORB_create(nfeatures=None, scaleFactor=None, nlevels=None, edgeThreshold=None, firstLevel=None, WTA_K=None, scoreType=None, patchSize=None, fastThreshold=None)
nfeatures: 要保留的最大特征點數。
scaleFactor: 金字塔降采樣比例,大于1。標準金字塔中scaleFactor=2,意味著每個級別的像素是前一個級別的1/4。
nlevels: 金字塔的層數。最小層的尺寸等于輸入圖像尺寸除以scaleFactor的nlevels - firstLevel次方。
edgeThreshold: 不檢測特征的邊緣大小,應大致匹配patchSize參數。
firstLevel: 將原始圖像放置在金字塔的哪一層。前面的層由放大的原圖填充。
WTA_K: 用于生成每個BRIEF描述符元素的點數。默認值為2,意味著對亮度進行簡單的比較。
scoreType: 特征點評分類型。HARRIS_SCORE 使用 Harris 算法對特征點進行評分;FAST_SCORE 是另一種稍不穩定但計算速度更快的選項。
patchSize: 用于計算BRIEF描述符的補丁大小。
fastThreshold: FAST 算子的閾值。
ORB 的使用可以快速有效地在圖像中檢測和描述關鍵點,這些關鍵點及其描述符可用于圖像匹配、對象識別等任務。ORB 算法因其高效性在移動和實時應用中特別受歡迎。
三、特征點匹配
特征點匹配是在不同圖像中找到相同特征點的過程。在OpenCV中,常用的方法包括BF匹配器(Brute-Force Matcher)和FLANN匹配器(Fast Library for Approximate Nearest Neighbors)。
3.1 BF匹配器
BF匹配器是一種簡單直接的匹配方法,主要通過計算一個特征描述子與其他所有描述子之間的距離,然后選擇距離最小的匹配對。
3.1.1 BF匹配器原理
- 距離計算:BF匹配器對一個特征集合中的每個特征描述子,計算它與另一個特征集合中所有特征描述子之間的距離。
- 最佳匹配選擇:然后,選擇距離最小的特征對作為匹配對。
- 距離度量:常用的距離度量包括歐氏距離、哈明距離等,具體使用哪種距離度量取決于描述子的類型。
3.1.2 代碼實現
tulips_template.jpg
import cv2
import numpy as np# 讀取圖像
img1 = cv2.imread('tulips.jpg', 0)
img2 = cv2.imread('tulips_template.jpg', 0)
img2 = cv2.rotate(img2,cv2.ROTATE_90_COUNTERCLOCKWISE)
# 初始化SIFT檢測器
sift = cv2.xfeatures2d_SIFT.create()# 使用SIFT找到關鍵點和描述子
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)# 創建BF匹配器對象
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)# 執行匹配
matches = bf.match(des1, des2)# 繪制匹配
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)# 展示結果
cv2.imshow('BF Matcher', img3)
cv2.waitKey(0)
cv2.destroyAllWindows()
示例代碼創建了一個 BFMatcher
對象,用 NORM_L2
作為距離度量,并啟用了 crossCheck
。然后,使用 match
方法在兩組描述符 des1
和 des2
之間找到最佳匹配。這種匹配方法特別適用于小型或中等大小的數據集,以及在需要高精度匹配時。
3.1.3 函數解析
BFMatcher
BFMatcher
是一個暴力匹配器(Brute-Force Matcher),用于匹配不同圖像中的描述符。它通過計算每個描述符之間的距離來查找匹配項。參數說明如下:
def create(cls, normType=None, crossCheck=None)
normType: 指定用于比較描述符的距離度量。常用的選項有
NORM_L1
,NORM_L2
,NORM_HAMMING
,NORM_HAMMING2
。NORM_L1
和NORM_L2
適合用于 SIFT 和 SURF
描述符,而NORM_HAMMING
適合于 ORB, BRISK 和 BRIEF。NORM_HAMMING2
適用于 ORB 的
WTA_K 為 3 或 4 時。crossCheck: 如果設置為
True
,則只返回互相匹配的描述符對(即對于每個查詢描述符,在訓練描述符集中找到最近的匹配,并且對于找到的匹配,在查詢描述符集中也是最近的)。這通常會產生質量更高的匹配,但匹配數量可能較少。
match
match
方法用于查找兩組描述符之間的最佳匹配。
def match(self, queryDescriptors, trainDescriptors, mask=None)
queryDescriptors: 查詢描述符集合。
trainDescriptors: 訓練描述符集合。這組描述符不會被添加到類對象存儲的訓練描述符集合中。
mask: 指定允許匹配的查詢和訓練描述符之間的掩碼。如果查詢描述符在掩碼中被屏蔽,則此描述符不會添加匹配。因此,匹配的數量可能小于查詢描述符的數量。
3.2 FLANN匹配器
FLANN匹配器是一種更快的近似匹配方法,適用于大規模數據集。它使用優化的算法快速找到測試數據和訓練集中的近似最近鄰。
3.2.1 FLANN匹配器原理
- 近似最近鄰搜索:FLANN是基于多種優化算法(比如KD樹、層次k均值樹等)的集合,用于快速近似查找最近鄰。
- 自動參數選擇:FLANN能夠根據數據自動選擇最合適的算法和參數,優化搜索效率。
3.2.2 代碼實現
import cv2
import numpy as npimg1 = cv2.imread('tulips.jpg', 0)
img2 = cv2.imread('tulips_template.jpg', 0)
img2 = cv2.rotate(img2, cv2.ROTATE_90_COUNTERCLOCKWISE)
# 初始化SIFT檢測器
sift = cv2.xfeatures2d_SIFT.create()# 使用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匹配器對象
flann = cv2.FlannBasedMatcher(index_params, search_params)# 執行匹配
matches = flann.knnMatch(des1, des2, k=2)# 僅保留好的匹配
good_matches = []
for m, n in matches:if m.distance < 0.7 * n.distance:good_matches.append(m)# 繪制匹配
img3 = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, flags=2)# 展示結果
cv2.imshow('FLANN Matcher', img3)
cv2.waitKey(0)
cv2.destroyAllWindows()
示例代碼創建了一個 FlannBasedMatcher
對象,使用 KDTREE 算法和相關參數。然后,使用 knnMatch
方法在兩組描述符 des1
和 des2
之間找到每個描述符的 2 個最近鄰(即 k=2)。這種方法特別適用于處理大型數據集,在尋找近似最近鄰時可以獲得更好的性能和效率。
3.2.3 函數解析
FlannBasedMatcher
FlannBasedMatcher
是用于特征匹配的類。相比 BFMatcher
(暴力匹配器),它在處理大量數據時更加高效,尤其是當描述符數量很大時。參數說明如下:
index_params
: 字典格式的參數,用于指定索引參數。對于不同的算法,這些參數會有所不同。例如,當使用 KDTREE 算法時,可以設置trees
的數量。
search_params
: 字典格式的參數,用于指定搜索時的參數。例如,checks
參數控制著搜索時的迭代次數,影響搜索的準確性和效率。
knnMatch
knnMatch
方法用于找到每個查詢描述符的 k 個最佳匹配項。參數如下:
queryDescriptors: 查詢描述符集合。
trainDescriptors: 訓練描述符集合。這組描述符不會被添加到類對象存儲的訓練描述符集合中。
k: 每個查詢描述符要查找的最近鄰個數。
mask: 一個掩碼,指定允許的查詢和訓練描述符之間的匹配。
compactResult: 當掩碼不為空時使用的參數。如果為
False
,則返回的匹配向量與查詢描述符的行數相同。如果為True
,則不包含對于完全被掩碼屏蔽的查詢描述符的匹配。
3.3 RANSAC特征點匹配
RANSAC(隨機抽樣一致性算法)是一種魯棒的特征點匹配算法,廣泛用于處理含有大量噪聲的數據。在特征點匹配過程中,RANSAC能有效地識別出正確的匹配點對(內點),并排除錯誤的匹配(外點)。
3.3.1 RANSAC原理
隨機抽樣:從所有匹配點對中隨機選擇一個小的子集來估計變換模型。例如,在計算兩幅圖像間的單應性(Homography)時,通常選擇4對匹配點。
模型估計:使用這個子集來計算變換模型。例如,如果是計算單應性矩陣,這個步驟會產生一個單應性矩陣H。
內點計數:使用估計的模型測試所有的數據點,并計算模型一致的點的數量(內點)。這些點的集合稱為一致性集。
模型驗證:重復上述過程固定次數。每次重復后,如果一致性集的大小超過了之前的最大值,則更新最佳模型為當前模型。
最優模型:最后,使用內點集合來重新估計最優模型。
3.3.2 代碼實現
以下是使用OpenCV進行RANSAC特征點匹配的代碼實例。
import cv2
import numpy as np# 讀取圖像
img1 = cv2.imread('tulips.jpg', 0)
img2 = cv2.imread('tulips_template.jpg', 0)
img2 = cv2.rotate(img2, cv2.ROTATE_90_COUNTERCLOCKWISE)# 初始化SIFT檢測器
sift = cv2.xfeatures2d_SIFT.create()# 使用SIFT找到關鍵點和描述子
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)# 創建FLANN匹配器對象
flann = cv2.FlannBasedMatcher(dict(algorithm=1, trees=5), dict(checks=50))matches = flann.knnMatch(des1, des2, k=2)# 篩選好的匹配點
good_matches = []
for m, n in matches:if m.distance < 0.7 * n.distance:good_matches.append(m)# 提取匹配點的位置
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)# 使用RANSAC找到單應性矩陣
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)matchesMask = mask.ravel().tolist()# 繪制匹配結果
draw_params = dict(matchColor=(0, 255, 0), singlePointColor=None, matchesMask=matchesMask, flags=2)
img3 = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, **draw_params)# 展示結果
cv2.imshow('RANSAC Matcher', img3)
cv2.waitKey(0)
cv2.destroyAllWindows()
在這段代碼中,使用SIFT算法檢測特征點,并通過FLANN匹配器進行匹配。然后,使用cv2.findHomography
函數結合RANSAC算法找到最佳單應性矩陣。其中, src_pts
和 dst_pts
是匹配點對,5.0
是 RANSAC 重投影誤差的閾值。使用 RANSAC 方法可以有效處理異常值或外點,提高單應性矩陣估計的準確性。
3.3.3 函數解析
findHomography
函數用于尋找兩個平面之間的透視變換(單應性矩陣)的方法。這個函數常用于圖像配準、3D重建和計算機視覺中的許多其他應用。以下是該函數的參數說明:
def findHomography(srcPoints, dstPoints, method=None, ransacReprojThreshold=None, mask=None, maxIters=None, confidence=None)
srcPoints: 原始平面中點的坐標。這可以是
CV_32FC2
類型的矩陣或vector<Point2f>
。dstPoints: 目標平面中點的坐標,格式與
srcPoints
相同。method:計算單應性矩陣的方法。可用的方法包括:
0
: 使用所有點的普通最小二乘法。cv2.RANSAC
: 基于 RANSAC 的魯棒方法。cv2.LMEDS
: 最小中值魯棒方法。cv2.RHO
: 基于 PROSAC 的魯棒方法。ransacReprojThreshold(可選,僅在使用 RANSAC 或 RHO 時): 允許的最大重投影誤差,用于將點對視為內點。在像素中測量時,這個值通常設置在 1 到 10 的范圍內。
mask:由魯棒方法(RANSAC 或 LMEDS)設置的可選輸出掩碼。掩碼中的非零值表示內點。
maxIters:RANSAC 的最大迭代次數。
confidence:置信度水平,介于 0 和 1 之間。
函數返回值:
- retval: 單應性矩陣,如果無法估算則返回空矩陣。
- mask: 輸出掩碼,標識每個點對是內點還是外點。
總結
在本博客中,我們詳細探討了OpenCV中用于特征點檢測與匹配的多種方法。從基本的角點檢測到復雜的特征點描述和匹配,這些方法在處理實際計算機視覺問題時發揮著至關重要的作用。通過對每種方法的原理、公式和代碼實現的分析,我們可以更好地理解它們各自的優勢和適用場景。