一.摘要
SIFT(Scale-Invariant Feature Transform)算法是由David Lowe于1999年提出的一種用于圖像處理和計算機(jī)視覺中的特征提取和匹配方法。它在航拍圖像匹配中具有重要的意義,主要體現(xiàn)在以下幾個方面:
-
尺度不變性
:航拍圖像通常具有大范圍的尺度變化,例如拍攝距離目標(biāo)較遠(yuǎn)或較近的情況。SIFT算法通過在不同尺度下檢測關(guān)鍵點和描述圖像特征,具有尺度不變性,可以有效應(yīng)對航拍圖像的尺度變化。 -
旋轉(zhuǎn)不變性
:航拍圖像中的目標(biāo)可能以不同的姿態(tài)出現(xiàn),例如建筑物在不同方向上的拍攝角度。SIFT算法通過為關(guān)鍵點分配主方向,并構(gòu)建與旋轉(zhuǎn)無關(guān)的特征描述子,具有旋轉(zhuǎn)不變性,可以準(zhǔn)確匹配具有不同姿態(tài)的目標(biāo)。 -
魯棒性
:航拍圖像受到多種因素的影響,如光照變化、陰影、云層等。SIFT算法通過在關(guān)鍵點周圍計算局部梯度方向直方圖構(gòu)建特征描述子,具有一定的魯棒性,能夠應(yīng)對光照變化和部分遮擋的情況。 -
高匹配精度
:SIFT算法通過計算特征描述子之間的相似性或距離進(jìn)行特征匹配,具有較高的匹配精度。在航拍圖像匹配中,SIFT算法可以識別和匹配具有相似特征的地物或目標(biāo),用于航拍圖像的配準(zhǔn)、拼接、地圖制作等任務(wù)。
綜上所述,SIFT算法在航拍圖像匹配中具有尺度不變性、旋轉(zhuǎn)不變性、魯棒性和高匹配精度的特點,能夠有效地提取和匹配航拍圖像中的特征,對于航拍圖像的處理和分析具有重要的意義。
本文分為兩部分,第一部分詳細(xì)講解了SIFT的原理,第二部分利用openCV-python內(nèi)置的SIFT模塊進(jìn)行圖像匹配
二.SIFT算法的原理
SIFT算法的主要步驟如下:
-
尺度空間極值檢測
(Scale-space extrema detection):SIFT算法首先通過在不同尺度上對圖像進(jìn)行高斯平滑來構(gòu)建尺度空間(scale space)。然后,在尺度空間中檢測出關(guān)鍵點,這些關(guān)鍵點對于不同尺度下的圖像特征是穩(wěn)定的。 -
關(guān)鍵點定位
(Keypoint localization):在尺度空間的極值點檢測之后,對每個檢測到的極值點進(jìn)行精確定位,以獲得更準(zhǔn)確的關(guān)鍵點位置。該步驟使用了DoG(Difference of Gaussians)圖像金字塔,通過計算高斯平滑圖像的差異來檢測局部極值點。 -
方向分配
(Orientation assignment):為每個關(guān)鍵點分配一個主方向,以提高特征描述子的旋轉(zhuǎn)不變性。在該步驟中,通過對關(guān)鍵點周圍的圖像梯度方向進(jìn)行統(tǒng)計,確定主要的梯度方向,并將其作為關(guān)鍵點的方向。 -
特征描述
(Feature description):在這一步驟中,使用關(guān)鍵點周圍的圖像區(qū)域計算特征描述子,以描述關(guān)鍵點周圍的圖像結(jié)構(gòu)。SIFT算法使用了局部圖像梯度的方向直方圖來構(gòu)建描述子,該描述子具有尺度不變性和旋轉(zhuǎn)不變性。 -
特征匹配
(Feature matching):通過計算兩幅圖像中的特征描述子之間的距離或相似性來進(jìn)行特征匹配。常用的方法是計算描述子之間的歐氏距離或余弦相似度,然后根據(jù)距離或相似度進(jìn)行特征匹配。
1.尺度空間極值檢測 &關(guān)鍵點定位
首先要明確,ORB是對圖像灰度進(jìn)行檢測,得到一系列的角點,與之不同的額是,sift檢測出來用于特征點匹配的特征點,是一個個具有尺度不變性的區(qū)域:
上圖中的紅色圓圈就是一個個具有尺度不變性的區(qū)域
,后續(xù)的特征點匹配,也是對兩張圖的尺度不變區(qū)域進(jìn)行匹配,那么什么是尺度不變性呢?
尺度不變性&尺度空間
??尺度不變性是指在不同尺度下,對象或特征的外觀和結(jié)構(gòu)保持不變的屬性。
??對于SIFT算法,就是對于圖像中的某個點,在這個點周圍畫圓,作為分析區(qū)域,希望找到一個
f
f
f 函數(shù),與不同尺度的該區(qū)域的圖像相乘,響應(yīng)結(jié)果能夠有一個極值。
??如果說響應(yīng)函數(shù)存在一個極值,而不是平坦的,那就可以說這個區(qū)域具有尺度不變性
(也可以說是一種極值特性)
下圖橫坐標(biāo)為針對該區(qū)域,圖像尺度的不同尺度,縱坐標(biāo)是響應(yīng)結(jié)果。
??(Witkin, 1983)的那篇論文關(guān)于這一塊的描述是:通過在所有可能的尺度上搜索穩(wěn)定的特征,利用尺度的連續(xù)函數(shù)即尺度空間
,可以實現(xiàn)對圖像的尺度變化不變的位置的檢測。
L
(
x
,
y
,
σ
)
=
G
(
x
,
y
,
σ
)
?
I
(
x
,
y
)
L(x, y, \sigma)=G(x, y, \sigma) * I(x, y)
L(x,y,σ)=G(x,y,σ)?I(x,y)
??
I
(
x
,
y
)
I(x,y)
I(x,y)是輸入圖像,
G
G
G是一個變尺度高斯函數(shù)因此,圖像的尺度空間定義為變尺度高斯函數(shù)
G
(
x
,
y
,
σ
)
G(x, y, σ)
G(x,y,σ)與輸入圖像
I
(
x
,
y
)
I(x, y)
I(x,y)的卷積函數(shù)
L
(
x
,
y
,
σ
)
L(x, y,\sigma)
L(x,y,σ),
G
G
G的表達(dá)式為:
G
(
x
,
y
,
σ
)
=
1
2
π
σ
2
e
?
(
x
2
+
y
2
)
/
2
σ
2
G(x, y, \sigma)=\frac{1}{2 \pi \sigma^2} e^{-\left(x^2+y^2\right) / 2 \sigma^2}
G(x,y,σ)=2πσ21?e?(x2+y2)/2σ2
σ
\sigma
σ為尺度空間因子,
σ
\sigma
σ值越小表示圖像被平滑的越少,相應(yīng)的尺度就越小,而當(dāng)
σ
\sigma
σ較大時,圖像的平滑效果會更好,但可能會導(dǎo)致細(xì)節(jié)的丟失。效果如下圖所示,尺度從左到右依次增大。相應(yīng)的圖片也變得更粗糙,(可能得放大看才能看出差別)代碼在文末
??不同的
σ
\sigma
σ對應(yīng)不同的尺度,通過調(diào)整
σ
\sigma
σ可以獲得圖片任意一點(x,y)在不同尺度下的響應(yīng)(也就是所謂的尺度空間)
??也就是說對于任意一個點,都可以獲得其與不同
G
(
σ
)
G(\sigma)
G(σ)的卷積后的響應(yīng),這樣對每個點(x,y)都可以畫出一個縱軸為響應(yīng),橫軸為尺度的圖片。
對于任意一點(x,y),只要不同尺度下的相應(yīng)圖中存在峰值,那么這個點就是我們想要的點。
在整張圖片上重復(fù)上述步驟,可以獲得所有具有尺度不變性的點,如果該點是具有尺度不變性的點,以該點為圓心畫一個半徑為 ( 2 ) ? 尺 度 \sqrt(2)*尺度 (?2)?尺度的圓(這就是第一張圖中蝴蝶周圍圓圈的由來)。
接下來講述如何利用尺度不變性的原理,通過構(gòu)建高斯金字塔來實現(xiàn)關(guān)鍵點檢測
高斯金字塔
SIFT用來了一種比較高效的方法:DOG高斯差分金字塔
首先,用不同的尺度的高斯核函數(shù)構(gòu)造尺度空間(圖左邊所示)
然后,將將較小尺度的圖像從較大尺度的圖像中減去,得到差分圖像(圖右邊所示)
D
(
x
,
y
,
σ
)
=
(
G
(
x
,
y
,
k
σ
)
?
G
(
x
,
y
,
σ
)
)
?
I
(
x
,
y
)
=
L
(
x
,
y
,
k
σ
)
?
L
(
x
,
y
,
σ
)
\begin{aligned} D(x, y, \sigma) & =(G(x, y, k \sigma)-G(x, y, \sigma)) * I(x, y) \\ & =L(x, y, k \sigma)-L(x, y, \sigma)\end{aligned}
D(x,y,σ)?=(G(x,y,kσ)?G(x,y,σ))?I(x,y)=L(x,y,kσ)?L(x,y,σ)?
接下來,在差分金字塔中的每個尺度上,檢測局部極值點作為特征點的候選。對每個像素點,在其相鄰的3×3×3的鄰域(包括當(dāng)前尺度、上下尺度、左右尺度)內(nèi)比較其值與鄰域內(nèi)所有其他像素點(26個)的值,找到局部極值點。以確保在尺度空間
和二維圖像空間
都檢測到極值點。
最后,對于檢測到的局部極值點,由于前面構(gòu)建的尺度空間是離散的:
所以下通過擬合二次曲線來精確定位其準(zhǔn)確的位置和尺度。擬合過程使用每個極值點及其相鄰像素點的響應(yīng)值進(jìn)行插值,得到更精確的特征點位置。
至此就可以檢測出圖像中的特征點,假設(shè)該特征點的尺度為Q,特征點周圍畫一個圓圈(半徑為 ( 2 ) \sqrt(2) (?2)*S)
懶得畫圖了,還是展示這張圖吧
2.方向分配
在SIFT算法的方向分配步驟中,對于每個關(guān)鍵點,可以按照以下步驟進(jìn)行方向分配:
-
確定圓的半徑:計算關(guān)鍵點所在的高斯圖像的
尺度的1.5倍
,得到圓的半徑。 -
統(tǒng)計梯度方向和梯度幅值:以關(guān)鍵點為圓心,以半徑為半徑的圓內(nèi)的所有像素點。對于每個像素點,計算其梯度方向和梯度幅值。可以使用
Sobel
等算子計算圖像的梯度。 -
高斯加權(quán):對于圓內(nèi)的每個像素點,根據(jù)其到關(guān)鍵點的距離應(yīng)用
高斯加權(quán)
。距離關(guān)鍵點越近的像素點,其梯度方向和梯度幅值所占的權(quán)重越大。 -
構(gòu)建梯度方向直方圖:將梯度方向劃分為若干個區(qū)間,例如36個區(qū)間。對于每個像素點,根據(jù)其梯度方向的值和加權(quán)后的梯度幅值,將其貢獻(xiàn)到對應(yīng)的區(qū)間上。
-
選擇主方向:從梯度方向直方圖中選擇具有最大值的方向作為關(guān)鍵點的
主方向
。
通過以上步驟,可以為每個關(guān)鍵點分配一個主方向
,用于后續(xù)的特征描述子計算。這樣可以使特征描述子對圖像的旋轉(zhuǎn)具有不變性,提高特征匹配的準(zhǔn)確性和穩(wěn)定性。
3.特征描述
這部分參考的是:
SIFT算法
想要了解更深入的數(shù)學(xué)原理可以看這篇文章
尺度不變特征轉(zhuǎn)換-SIFT
根據(jù)前面得到的特征點的主方向,將坐標(biāo)軸旋轉(zhuǎn)到主方向,從而實現(xiàn)了旋轉(zhuǎn)不變性
描述符是與特征點所在的尺度有關(guān)的,所以描述特征點是需要在該特征點所在的
高斯尺度
圖像上進(jìn)行的,在高斯尺度
圖像上,以特征點
為中心,將其附近鄰域劃分為dxd個子區(qū)域,論文中取d=4。σ為相對于特征點所在的高斯金字塔的組的基準(zhǔn)層圖像的尺度
如下圖將區(qū)域劃分為4×4的子塊,對每一子塊進(jìn)行8個方向的直方圖統(tǒng)計操作,獲得每個方向的梯度幅值,總共可以組成128維描述向量.
所以SIFT算法的整體流程如下圖展示
概括來說就是:
首先對兩張輸入的圖構(gòu)建尺度金字塔——>進(jìn)行極值檢測——>得到特征點在二維圖像空間的位置,以及尺度空間的位置 σ \sigma σ——>特征點所在的高斯尺度
圖像上,獲取特征點的主方向——>利用尺度信息進(jìn)行區(qū)域歸一化——>利用主方向消除旋轉(zhuǎn)歧義——>計算外觀直方圖,獲得描述子
4 .特征匹配`
分別對參考圖,和實時圖建立特征點的描述子集合。采用歐式距離來度量相似性。
設(shè)d1為實時圖中的點A的描述子和參考圖中與A歐式距離最近的點B之間的距離,d2為參考圖中與A歐式距離次近的點Q和A之間的距離。設(shè)定了一個閾值T。
若要點A和匹配上,則需要
d
1
/
d
2
<
T
d1/d2<T
d1/d2<T
關(guān)鍵點的匹配可以用暴力匹配法,但是計算成本太高,一般采用kd樹的數(shù)據(jù)結(jié)構(gòu)來完成搜索。
比較快的匹配方法有FKNN(Fuzzy K-Nearest Neighbors)和KNN( K-Nearest Neighbors)等。
kd樹也可以融入到FKNN和KNN來加速匹配。
關(guān)于kd樹可以看這個博主講的,搜了半天感覺他講的最清晰:
kd樹詳解
三.代碼
1. 無人機(jī)航拍圖像匹配
(1)導(dǎo)入必要的庫加載圖片
import cv2
import numpy as np
import time
path_1='C:/Users/22812/zz/opencv_match/H1_match/m50.jpg'
path_2='C:/Users/22812/zz/opencv_match/H1_match/m100.jpg'
image1 = cv2.imread(path_1, 0)
image2 = cv2.imread(path_2, 0)
if image1 is None or image2 is None:
print("圖像文件讀取失敗")
else:
print("圖像文件讀取成功")
# 創(chuàng)建彩色圖像副本
color_image1 = cv2.imread(path_1)
color_image2 = cv2.imread(path_2)
if image1 is None or image2 is None:
print("彩色圖像文件讀取失敗")
else:
print("彩色圖像文件讀取成功")
(2)特征提取
# 創(chuàng)建 SIFT 特征提取器
sift = cv2.xfeatures2d.SIFT_create()
# 特征點檢測和描述符提取
start_time = time.time()
keypoints1, descriptors1 = sift.detectAndCompute(image1, None)
keypoints2, descriptors2 = sift.detectAndCompute(image2, None)
end_time = time.time()
(3)特征匹配
# 創(chuàng)建 KNN 匹配器
#matcher = cv2.BFMatcher()#暴力檢測要50多s
matcher = cv2.FlannBasedMatcher()
# 特征點匹配
start_time_matching = time.time()
matches = matcher.knnMatch(descriptors1, descriptors2, k=2) # KNN 匹配,返回最近的兩個匹配點
end_time_matching = time.time()
# 輸出特征點檢測花費的時間
print("特征點檢測花費的時間:", end_time - start_time, "秒")
# 輸出特征點匹配花費的時間
print("特征點匹配花費的時間:", end_time_matching - start_time_matching, "秒")
(4)濾波
ratio_threshold = 0.8
good_matches = []
start_time_filter = time.time()
for m, n in matches:
if m.distance < ratio_threshold * n.distance:
good_matches.append(m)
end_time_filter = time.time()
print("濾波花費的時間:", end_time_filter - start_time_filter, "秒")
用KNN匹配法的匹配結(jié)果在
matches
里,matches
包含了一對最鄰近匹配點,和一對次鄰近匹配點。m.distance < ratio_threshold * n.distance
中m.distance
越小,表明最鄰近匹配點差異越小,越可靠,所以在對精度要求高的時候可以將ratio_threshold
=0.8調(diào)至更小,一般最小0.4,再小有可能導(dǎo)致匹配點不夠4對,不能求解轉(zhuǎn)移矩陣H.
(5)計算轉(zhuǎn)移矩陣
# 提取匹配的特征點的坐標(biāo)
src_points = np.float32([keypoints1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
dst_points = np.float32([keypoints2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
# 計算單應(yīng)矩陣
H, _ = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 3)
#H ,_= cv2.findHomography(src_points, dst_points)
# 輸出單應(yīng)矩陣H
print("Homography Matrix:")
print(H)
(6)誤差計算
# 計算每對匹配點的誤差L2
errors = []
right_point=[]
thrshold=10
for match in good_matches:
kp1 = keypoints1[match.queryIdx]
kp2 = keypoints2[match.trainIdx]
pt1 = np.array([kp1.pt[0], kp1.pt[1], 1])
pt2 = np.array([kp2.pt[0], kp2.pt[1], 1])
pt1_transformed = np.dot(H, pt1)
error = np.linalg.norm(pt1_transformed - pt2) ** 2
if np.linalg.norm(pt1_transformed - pt2)<thrshold :
right_point.append(np.linalg.norm(pt1_transformed - pt2))
errors.append(error)
# 計算誤差的和L1
L1 = sum(errors)
# 計算均方誤差MSE
MSE = np.sqrt(L1) / len(errors)
num_right=len(right_point)
num_all=len(errors)
precision=num_right/num_all
print("MSE:", MSE)
print("總匹配點數(shù):",num_all )
print("正確匹配點數(shù):",num_right )
print("precision:", precision)
(7)可視化文章來源:http://www.zghlxwxcb.cn/news/detail-756826.html
# result = cv2.drawMatches(color_image1, keypoints1, color_image2, keypoints2, good_matches, None)
result = cv2.drawMatches(image1, keypoints1, image2, keypoints2, good_matches, None,
matchColor=(0, 255, 0), singlePointColor=(0, 0, 255), flags=cv2.DrawMatchesFlags_DEFAULT)
# result = cv2.drawMatches(color_image1, keypoints1, color_image2, keypoints2, good_matches, None,
# matchColor=(0, 255, 0), singlePointColor=(0, 0, 255), flags=cv2.DrawMatchesFlags_DEFAULT)
# 調(diào)整線條寬度
line_thickness = 2
for match in good_matches:
pt1 = (int(keypoints1[match.queryIdx].pt[0]), int(keypoints1[match.queryIdx].pt[1]))
pt2 = (int(keypoints2[match.trainIdx].pt[0]), int(keypoints2[match.trainIdx].pt[1]))
cv2.line(result, pt1, pt2, (0, 0, 255), thickness=line_thickness)
# 創(chuàng)建匹配結(jié)果顯示窗口
cv2.namedWindow('Matches', cv2.WINDOW_NORMAL)
cv2.resizeWindow('Matches', 800, 600)
# 顯示匹配結(jié)果
cv2.imshow('Matches', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.高斯核函數(shù)的代碼
import numpy as np
import cv2
import matplotlib.pyplot as plt
# 定義高斯核函數(shù)
def gaussian_kernel(size, sigma):
kernel = np.fromfunction(lambda x, y: (1 / (2 * np.pi * sigma**2)) * np.exp(-((x - size//2)**2 + (y - size//2)**2) / (2 * sigma**2)), (size, size))
return kernel / np.sum(kernel)
# 讀取輸入圖像
input_image = cv2.imread("input_image.jpg", 0) # 以灰度圖像方式讀取
input_image = input_image.astype(np.float32) # 轉(zhuǎn)換為float32類型
# 定義高斯核參數(shù)
kernel_size = 11 # 高斯核尺寸
sigmas = [1.0, 1.5, 2.0] # 不同的高斯核標(biāo)準(zhǔn)差
# 創(chuàng)建子圖
plt.figure(figsize=(15, 5))
# 顯示原圖像
plt.subplot(1, 4, 1)
plt.imshow(input_image, cmap='gray')
plt.title('Original Image')
plt.axis('off')
# 遍歷不同的sigma值
for i, sigma in enumerate(sigmas):
# 創(chuàng)建高斯核
gaussian_filter = gaussian_kernel(kernel_size, sigma)
# 進(jìn)行卷積操作
convolved_image = cv2.filter2D(input_image, -1, gaussian_filter)
# 顯示模糊圖像
plt.subplot(1, 4, i+2)
plt.imshow(convolved_image, cmap='gray')
plt.title(f'Sigma = {sigma}')
plt.axis('off')
# 調(diào)整子圖之間的間距
plt.tight_layout()
# 顯示圖像
plt.show()
參考文獻(xiàn)
[1] Lowe D G. Distinctive image features from scale-invariant keypoints[J]. International journal of computer vision, 2004, 60: 91-110.
[2]Lindeberg T. Feature detection with automatic scale selection[J]. International journal of computer vision, 1998, 30(2): 79-116.
[3] 部分圖來自深藍(lán)學(xué)院網(wǎng)課的ppt文章來源地址http://www.zghlxwxcb.cn/news/detail-756826.html
到了這里,關(guān)于無人機(jī)航拍圖像匹配——SIFT算法實踐(含代碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!