1. 程式人生 > 其它 >OpenCV-Python系列之SIFT尺度不變特徵變換(二)

OpenCV-Python系列之SIFT尺度不變特徵變換(二)

我們在上一個教程中談到SIFT總共有四個步驟,之前已經討論了一部分,今天我們將結束這一部分,我們現在接著上一個教程繼續。

刪除不好的極值點(特徵點)

通過比較檢測得到的DoG的區域性極值點實在離散的空間搜尋得到的,由於離散空間是對連續空間取樣得到的結果,因此在離散空間找到的極值點不一定是真正意義上的極值點,因此要設法將不滿足條件的點剔除掉。可以通過尺度空間DoG函式進行曲線擬合尋找極值點,這一步的本質是去掉DoG局部曲率非常不對稱的點
要剔除掉的不符合要求的點主要有兩種:

1. 低對比度的特徵點

2. 不穩定的邊緣響應點

剔除低對比度的特徵點

候選特徵點x,其偏移量定義為Δx,其對比度為D(x)的絕對值∣D(x)∣,對D(x)應用泰勒展開式:

由於x是D(x)的極值點,所以對上式求導並令其為0,得到

然後再把求得的Δx代入到D(x)的泰勒展開式中

設對比度的閾值為T,若∣D(x^)∣≥T,則該特徵點保留,否則剔除掉。

剔除不穩定的邊緣響應點

在邊緣梯度的方向上主曲率值比較大,而沿著邊緣方向則主曲率值較小。候選特徵點的DoG函式D(x)的主曲率與2×2Hessian矩陣H的特徵值成正比。

為了避免求具體的值,可以使用H特徵值得比例。設α=λmax為H的最大特徵值,β=λmin為H的最小特徵值,則

其中,Tr(H)為矩陣H的跡,Det(H)為矩陣H的行列式。
設γ=α/β 表示最大特徵值和最小特徵值的比值,則

上式的結果與兩個特徵值的比例有關,和具體的大小無關,當兩個特徵值想等時其值最小,並且隨著γ的增大而增大。因此為了檢測主曲率是否在某個閾值Tγ下,只需檢測

如果上式成立,則剔除該特徵點,否則保留。

求取特徵點的主方向

經過上面的步驟已經找到了在不同尺度下都存在的特徵點,為了實現影象旋轉不變性,需要給特徵點的方向進行賦值。利用特徵點鄰域畫素的梯度分佈特性來確定其方向引數,再利用影象的梯度直方圖求取關鍵點區域性結構的穩定方向。
找到了特徵點,也就可以得到該特徵點的尺度σσ,也就可以得到特徵點所在的尺度影象

計算以特徵點為中心、以3×1.5σ為半徑的區域影象的幅角和幅值,每個點L(x,y)的梯度的模m(x,y)以及方向θ(x,y)可通過下面公司求得

計算得到梯度方向後,就要使用直方圖統計特徵點鄰域內畫素對應的梯度方向和幅值。梯度方向的直方圖的橫軸是梯度方向的角度(梯度方向的範圍是0到360度,直方圖每36度一個柱共10個柱,或者沒45度一個柱共8個柱),縱軸是梯度方向對應梯度幅值的累加,在直方圖的峰值就是特徵點的主方向。在Lowe的論文還提到了使用高斯函式對直方圖進行平滑以增強特徵點近的鄰域點對關鍵點方向的作用,並減少突變的影響。為了得到更精確的方向,通常還可以對離散的梯度直方圖進行插值擬合。具體而言,關鍵點的方向可以由和主峰值最近的三個柱值通過拋物線插值

得到。在梯度直方圖中,當存在一個相當於主峰值80%能量的柱值時,則可以將這個方向認為是該特徵點輔助方向。所以,一個特徵點可能檢測到多個方向(也可以理解為,一個特徵點可能產生多個座標、尺度相同,但是方向不同的特徵點)。Lowe在論文中指出:

15%的關鍵點具有多方向,而且這些點對匹配的穩定性很關鍵。

得到特徵點的主方向後,對於每個特徵點可以得到三個資訊(x,y,σ,θ),即位置、尺度和方向。由此可以確定一個SIFT特徵區域,一個SIFT特徵區域由三個值表示,中心表示特徵點位置,半徑表示關鍵點的尺度,箭頭表示主方向。具有多個方向的關鍵點可以被複製成多份,然後將方向值分別賦給複製後的特徵點,一個特徵點就產生了多個座標、尺度相等,但是方向不同的特徵點。

生成特徵描述

通過以上的步驟已經找到了SIFT特徵點位置、尺度和方向資訊,下面就需要使用一組向量來描述關鍵點也就是生成特徵點描述子,這個描述符不只包含特徵點,也含有特徵點周圍對其有貢獻的畫素點。描述子應具有較高的獨立性,以保證匹配率
特徵描述符的生成大致有三個步驟:

1. 校正旋轉主方向,確保旋轉不變性。

2. 生成描述子,最終形成一個128維的特徵向量

3. 歸一化處理,將特徵向量長度進行歸一化處理,進一步去除光照的影響。

為了保證特徵向量的旋轉不變性,要以特徵點為中心,在附近鄰域內將座標軸旋轉θ(特徵點的主方向)角度,即將座標軸旋轉為特徵點的主方向。旋轉後鄰域內畫素的新座標為:

旋轉後以主方向為中心取 8×8的視窗。下圖所示,左圖的中央為當前關鍵點的位置,每個小格代表為關鍵點鄰域所在尺度空間的一個畫素,求取每個畫素的梯度幅值與梯度方向,箭頭方向代表該畫素的梯度方向,長度代表梯度幅值,然後利用高斯視窗對其進行加權運算。最後在每個4×4的小塊上繪製8個方向的梯度直方圖,計算每個梯度方向的累加值,即可形成一個種子點,如圖所示。每個特徵點由4個種子點組成,每個種子點有8個方向的向量資訊。這種鄰域方向性資訊聯合增強了演算法的抗噪聲能力,同時對於含有定位誤差的特徵匹配也提供了比較理性的容錯性。

與求主方向不同,此時每個種子區域的梯度直方圖在0-360之間劃分為8個方向區間,每個區間為45度,即每個種子點有8個方向的梯度強度資訊。在實際的計算過程中,為了增強匹配的穩健性,Lowe建議:

對每個關鍵點使用4×4共16個種子點來描述,這樣一個關鍵點就可以產生128維的SIFT特徵向量。

通過對特徵點周圍的畫素進行分塊,計算塊內梯度直方圖,生成具有獨特性的向量,這個向量是該區域影象資訊的一種抽象,具有唯一性。

OpenCV中的SIFT演算法

我們之前所講述的都是理論,現在我們需要進行實踐,首先我們需要注意:

SIFT已經被申請專利了,所以,在OpenCV3.4.3.16 版本後,這個功能就不能用了。

所以我們可以把OpenCV的版本降級:

pip install opencv-python==3.4.2.16
pip install opencv-contrib-python==3.4.2.16

我們來看程式碼:

import cv2
import numpy as np
 
 
def sift_kp(image):
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    sift = cv2.xfeatures2d.SIFT_create()
    kp, des = sift.detectAndCompute(image, None)
    kp_image = cv2.drawKeypoints(gray_image, kp, None)
    return kp_image, kp, des
 
 
def get_good_match(des1, des2):
    bf = cv2.BFMatcher()
    matches = bf.knnMatch(des1, des2, k=2)  # des1為模板圖,des2為匹配圖
    matches = sorted(matches, key=lambda x: x[0].distance / x[1].distance)
    good = []
    for m, n in matches:
        if m.distance < 0.75 * n.distance:
            good.append(m)
    return good
 
 
img1 = cv2.imread(r'd:/t1.jpg')
img2 = cv2.imread(r'd:/t2.jpg')
 
kpimg1, kp1, des1 = sift_kp(img1)
kpimg2, kp2, des2 = sift_kp(img2)
 
cvshow('img1',np.hstack((img1,kpimg1)))
cvshow('img2',np.hstack((img2,kpimg2)))
 
goodMatch = get_good_match(des1, des2)
all_goodmatch_img= cv2.drawMatches(img1, kp1, img2, kp2, goodMatch, None, flags=2)
# goodmatch_img自己設定前多少個goodMatch[:10]
goodmatch_img = cv2.drawMatches(img1, kp1, img2, kp2, goodMatch[:10], None, flags=2)
 
cvshow('all_goodmatch_img', all_goodmatch_img)
cvshow('goodmatch_img', goodmatch_img)

圖1與其SIFT關鍵點檢測:

圖2與其SIFT關鍵點檢測:

match(畫出所有goodmatch):

match(只畫出前N個goodmatch)效果較好:

SIFT的部分就到此結束了,我們在之後會討論更加強大的演算法。

天道酬勤 循序漸進 技壓群雄