sift算法使用
實(shí)際項(xiàng)目中一般都直接使用封裝好的sift算法。以前為了用sift,都是用的舊版本:opencv-contib-python=3.4.2.17,現(xiàn)在sift專利過期了,新版的opencv直接可以使用sift算法,opencv-python==4.5.1版本測(cè)試可以使用。
sift算法理論部分參考前面文章:sift算法理解
關(guān)于sift,opencv中主要有這個(gè)幾個(gè)函數(shù):
1.1 sift特征點(diǎn)檢測(cè)
cv2.SIFT_create()
創(chuàng)建sift對(duì)象,官方文檔:https://docs.opencv.org/4.5.3/d7/d60/classcv_1_1SIFT.html
sift = cv2.SIFT_create(nfeatures=0, nOctaveLayers=3, contrastThreshold=0.04, edgeThreshold=10, sigma=1.6)
參數(shù):
nfeatures: 需要保留的特征點(diǎn)的個(gè)數(shù),特征按分?jǐn)?shù)排序(分?jǐn)?shù)取決于局部對(duì)比度)
nOctaveLayers:每一組高斯差分金字塔的層數(shù),sift論文中用的3。高斯金字塔的組數(shù)通過圖片分辨率計(jì)算得到
contrastThreshold: 對(duì)比度閾值,用于過濾低對(duì)比度區(qū)域中的特征點(diǎn)。閾值越大,檢測(cè)器產(chǎn)生的特征越少。 (sift論文用的0.03,nOctaveLayers若為3, 設(shè)置參數(shù)為0.09,實(shí)際值為:contrastThreshold/nOctaveLayers)
edgeThreshold:用于過濾掉類似圖片邊界處特征的閾值(邊緣效應(yīng)產(chǎn)生的特征),注意其含義與contrastThreshold不同,即edgeThreshold越大,檢測(cè)器產(chǎn)生的特征越多(過濾掉的特征越少);sift論文中用的10;
sigma:第一組高斯金字塔高斯核的sigma值,sift論文中用的1.6。 (圖片較模糊,或者光線較暗的圖片,降低這個(gè)參數(shù))
descriptorType:特征描述符的數(shù)據(jù)類型,支持CV_32F和CV_8U
返回值:sift對(duì)象(cv2.Feature2D對(duì)象)
cv2.Feature2D.detect()
檢測(cè)特征關(guān)鍵點(diǎn),官方文檔:https://docs.opencv.org/4.5.3/d0/d13/classcv_1_1Feature2D.html#a8be0d1c20b08eb867184b8d74c15a677
keypoints = cv2.Feature2D.detect(image, mask)
參數(shù):
image:需要檢測(cè)關(guān)鍵點(diǎn)的圖片
mask:掩膜,為0的區(qū)域表示不需要檢測(cè)關(guān)鍵點(diǎn),大于0的區(qū)域檢測(cè)
返回值:
keypoints:檢測(cè)到的關(guān)鍵點(diǎn)
cv2.Feature2D.compute()
生成特征關(guān)鍵點(diǎn)的描述符,官方文檔:https://docs.opencv.org/4.5.3/d0/d13/classcv_1_1Feature2D.html#a8be0d1c20b08eb867184b8d74c15a677
keypoints, descriptors = cv.Feature2D.compute(image, keypoints)
參數(shù):
image:需要生成描述子的圖片
keypoints: 需要生成描述子的關(guān)鍵點(diǎn)
返回值:
keypoints:關(guān)鍵點(diǎn)(原始關(guān)鍵點(diǎn)中,不能生成描述子的關(guān)鍵點(diǎn)會(huì)被移除;)
descriptors:關(guān)鍵點(diǎn)對(duì)應(yīng)的描述子
cv2.Feature2D.detectAndCompute()
檢測(cè)關(guān)鍵點(diǎn),并生成描述符,是上面detect()和compute()的綜合
keypoints, descriptors = cv.Feature2D.detectAndCompute(image, mask)
cv2.drawKetpoints()
繪制檢測(cè)到的關(guān)鍵點(diǎn),官方文檔:https://docs.opencv.org/4.5.3/d4/d5d/group__features2d__draw.html#ga5d2bafe8c1c45289bc3403a40fb88920
outImage = cv2.drawKeypoints(image, keypoints, outImage, color, flags)
參數(shù):
image:檢測(cè)關(guān)鍵點(diǎn)的原始圖像
keypoints:檢測(cè)到的關(guān)鍵點(diǎn)
outImage:繪制關(guān)鍵點(diǎn)后的圖像,其內(nèi)容取決于falgs的設(shè)置
color:繪制關(guān)鍵點(diǎn)采用的顏色
flags:
cv2.DRAW_MATCHES_FLAGS_DEFAULT:默認(rèn)值,匹配了的關(guān)鍵點(diǎn)和單獨(dú)的關(guān)鍵點(diǎn)都會(huì)被繪制
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS: 繪制關(guān)鍵點(diǎn),且每個(gè)關(guān)鍵點(diǎn)都繪制圓圈和方向
cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG:
cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS:只繪制匹配的關(guān)鍵點(diǎn),單獨(dú)的關(guān)鍵點(diǎn)不繪制
flags的原始含義如下:

keypoint
https://docs.opencv.org/4.5.3/d2/d29/classcv_1_1KeyPoint.html#aea339bc868102430087b659cd0709c11
上述檢測(cè)到的keypoint,在opencv中是一個(gè)類對(duì)象,其具有如下幾個(gè)屬性:
angle: 特征點(diǎn)的方向,值在0-360
class_id: 用于聚類id,沒有進(jìn)行聚類時(shí)為-1
octave: 特征點(diǎn)所在的高斯金差分字塔組
pt: 特征點(diǎn)坐標(biāo)
response: 特征點(diǎn)響應(yīng)強(qiáng)度,代表了該點(diǎn)時(shí)特征點(diǎn)的程度(特征點(diǎn)分?jǐn)?shù)排序時(shí),會(huì)根據(jù)特征點(diǎn)強(qiáng)度)
size:特征點(diǎn)領(lǐng)域直徑
descriptor
檢測(cè)點(diǎn)對(duì)應(yīng)的descriptor,是一個(gè)128維的向量。
sift簡單使用
opencv中sift特征點(diǎn)檢測(cè)和繪制,使用代碼和結(jié)果如下:(opencv版本:opencv-python==4.5.1.48)
import cv2
img = cv2.imread(r"./lenna.png")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift = cv2.SIFT_create(nfeatures=0, nOctaveLayers=3, contrastThreshold=0.04, edgeThreshold=10, sigma=1.6)
keypoints, descriptors = sift.detectAndCompute(img_gray, None)
for keypoint,descriptor in zip(keypoints, descriptors):
print("keypoint:", keypoint.angle, keypoint.class_id, keypoint.octave, keypoint.pt, keypoint.response, keypoint.size)
print("descriptor: ", descriptor.shape)
img = cv2.drawKeypoints(image=img_gray, outImage=img, keypoints=keypoints,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,
color=(51, 163, 236))
cv2.imshow("img_gray", img_gray)
cv2.imshow("new_img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

1.2 sift特征點(diǎn)匹配
通過sift得到圖片特征點(diǎn)后,一般會(huì)進(jìn)行圖片之間的特征點(diǎn)匹配。
1. 匹配方法
opencv中的特征點(diǎn)匹配主要有兩種方法:BFMatcher,F(xiàn)lannBasedMatcher:
BFMatcher
官方文檔:https://docs.opencv.org/4.5.3/d3/da1/classcv_1_1BFMatcher.html
Brute Froce Matcher: 簡稱暴力匹配,意思就是嘗試所有可能匹配,實(shí)現(xiàn)最佳匹配。其繼承于類cv2.DescriptorMatcher,
matcher = cv2.BFMatcher(normType=cv2.NORM_L2, crossCheck=False) # 創(chuàng)建BFMatcher對(duì)象
FlannBasedMatcher
官方文檔:https://docs.opencv.org/4.5.3/dc/de2/classcv_1_1FlannBasedMatcher.html
Flann-based descriptor matcher: 最近鄰近似匹配。 是一種近似匹配方法,并不追求完美,因此速度更快。 可以調(diào)整FlannBasedMatcher參數(shù)改變匹配精度或算法速度。其繼承于類cv2.DescriptorMatcher。
FLANN_INDEX_KDTREE = 0 # 建立FLANN匹配器的參數(shù)
indexParams = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) # 配置索引,密度樹的數(shù)量為5
searchParams = dict(checks=50) # 指定遞歸次數(shù)
matcher = cv2.FlannBasedMatcher(indexParams, searchParams) # 建立FlannBasedMatcher對(duì)象
indexParams:
algorithm:
FLANN_INDEX_LINEAR: 線性暴力(brute-force)搜索
FLANN_INDEX_KDTREE: 隨機(jī)kd樹,平行搜索。默認(rèn)trees=4
FLANN_INDEX_KMEANS: 層次k均值樹。默認(rèn)branching=32,iterations=11,centers_init = CENTERS_RANDOM, cb_index =0.2
FLANN_INDEX_COMPOSITE: 隨機(jī)kd樹和層次k均值樹來構(gòu)建索引。默認(rèn)trees =4,branching =32,iterations =11,centers_init=CENTERS_RANDOM,cb_index =0.2
searchParams:
SearchParams (checks=32, eps=0, sorted=true)
checks: 默認(rèn)32
eps: 默認(rèn)為0
sorted: 默認(rèn)True
2. 繪制匹配
KMatch
官方文檔:https://docs.opencv.org/4.5.3/d4/de0/classcv_1_1DMatch.html
上述通過匹配方法得到的匹配,在opencv中都用KMatch類表示,其具有幾個(gè)屬性如下:
queryIdx:查詢點(diǎn)的索引
trainIdx:被查詢點(diǎn)的索引
distance:查詢點(diǎn)和被查詢點(diǎn)之間的距離
下面代碼中,每個(gè)點(diǎn)尋找兩個(gè)最近鄰匹配點(diǎn),即對(duì)于kp1中的每個(gè)關(guān)鍵點(diǎn),在kp2中尋找兩個(gè)和它距離最近的特征點(diǎn),所以每個(gè)關(guān)鍵點(diǎn)產(chǎn)生兩組匹配,即兩個(gè)KMatch類。kp1相當(dāng)于索引關(guān)鍵點(diǎn),對(duì)應(yīng)queryIdx; kp2相當(dāng)于查詢關(guān)鍵點(diǎn),對(duì)應(yīng)trainIdx。
import cv2
img1 = cv2.imread("iphone1.png")
img2 = cv2.imread("iphone2.png")
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 采用暴力匹配
matcher = cv2.BFMatcher()
matches = matcher.knnMatch(des1, des2, k=2) # k=2,表示尋找兩個(gè)最近鄰
# 上面每個(gè)點(diǎn)尋找兩個(gè)最近鄰匹配點(diǎn),即對(duì)于kp1中的每個(gè)關(guān)鍵點(diǎn),在kp2中尋找兩個(gè)和它距離最近的特征點(diǎn),所以每個(gè)關(guān)鍵點(diǎn)產(chǎn)生兩組匹配,即兩個(gè)KMatch類
# kp1相當(dāng)于索引關(guān)鍵點(diǎn),對(duì)應(yīng)queryIdx; kp2相當(dāng)于查詢關(guān)鍵點(diǎn),對(duì)應(yīng)trainIdx
for m in matches: # 若尋找三個(gè)最近鄰點(diǎn),則m包括三個(gè)KMacth
print(m[0].queryIdx, m[0].queryIdx, m[0].distance) # m[0]表示距離最近的那個(gè)匹配
print(m[1].queryIdx, m[1].queryIdx, m[1].distance) # m[1]表示距離第二近的那個(gè)匹配
參考:https://blog.csdn.net/wphkadn/article/details/85805105
drawMatches()
opencv中drawMatches()能繪制匹配特征點(diǎn)
官方文檔:https://docs.opencv.org/4.5.3/d4/d5d/group__features2d__draw.html#ga5d2bafe8c1c45289bc3403a40fb88920
outImg = cv2.drawMatches(img1, keypoints1, img2, keypoints2, matches1to2, outImg, matchColor, singlePointColor, matchesMask, flags)
參數(shù):
img1:圖像1
keypoints1:圖像1的特征點(diǎn)
img2:圖像2
keypoints1:圖像2的特征點(diǎn)
matches1to2:圖像1特征點(diǎn)到圖像2特征點(diǎn)的匹配,keypoints1[i]和keypoints2[matches[i]]為匹配點(diǎn)
outImg: 繪制完的輸出圖像
matchColor:匹配特征點(diǎn)和其連線的顏色,-1時(shí)表示顏色隨機(jī)
singlePointColor:未匹配點(diǎn)的顏色,-1時(shí)表示顏色隨機(jī)
matchesMask: mask決定那些匹配點(diǎn)被畫出,若為空,則畫出所有匹配點(diǎn)
flags: 和上述cv2.drawKeypoints()中flags取值一樣
下面代碼中對(duì)sift提取的特征點(diǎn)進(jìn)行匹配,采用暴力法匹配,并根據(jù)knn近鄰法中兩個(gè)鄰居的距離,篩選出了部分匹配點(diǎn),代碼和結(jié)果如下:
import cv2
import numpy as np
#自己繪制匹配連線
def drawMatchesKnn_cv2(img1, kp1, img2, kp2, goodMatch):
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
vis = np.zeros((max(h1, h2), w1 + w2, 3), np.uint8)
vis[:h1, :w1] = img1
vis[:h2, w1:w1 + w2] = img2
p1 = [kpp.queryIdx for kpp in goodMatch]
p2 = [kpp.trainIdx for kpp in goodMatch]
post1 = np.int32([kp1[pp].pt for pp in p1])
post2 = np.int32([kp2[pp].pt for pp in p2]) + (w1, 0)
for (x1, y1), (x2, y2) in zip(post1, post2):
cv2.line(vis, (x1, y1), (x2, y2), (0, 0, 255))
cv2.imshow("match", vis)
img1 = cv2.imread("iphone1.png")
img2 = cv2.imread("iphone2.png")
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 采用暴力匹配
matcher = cv2.BFMatcher()
matches = matcher.knnMatch(des1, des2, k=2) # k=2,表示尋找兩個(gè)最近鄰
# 采用最近鄰近似匹配
# FLANN_INDEX_KDTREE = 0 # 建立FLANN匹配器的參數(shù)
# indexParams = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) # 配置索引,密度樹的數(shù)量為5
# searchParams = dict(checks=50) # 指定遞歸次數(shù)
# matcher = cv2.FlannBasedMatcher(indexParams, searchParams) # 建立FlannBasedMatcher對(duì)象
# matches = matcher.knnMatch(des1, des2, k=2) # k=2,表示尋找兩個(gè)最近鄰
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
out_img1 = np.zeros((max(h1, h2), w1 + w2, 3), np.uint8)
out_img1[:h1, :w1] = img1
out_img1[:h2, w1:w1 + w2] = img2
out_img1 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, matches, out_img1)
good_match = []
for m, n in matches:
if m.distance < 0.5*n.distance: # 如果第一個(gè)鄰近距離比第二個(gè)鄰近距離的0.5倍小,則保留
good_match.append(m)
out_img2 = np.zeros((max(h1, h2), w1 + w2, 3), np.uint8)
out_img2[:h1, :w1] = img1
out_img2[:h2, w1:w1 + w2] = img2
# p1 = [kp1[kpp.queryIdx] for kpp in good_match] # kp1中挑選處的關(guān)鍵點(diǎn)
# p2 = [kp2[kpp.trainIdx] for kpp in good_match] # kp2中挑選處的關(guān)鍵點(diǎn)
out_img2 = cv2.drawMatches(img1, kp1, img2, kp2, good_match, out_img2)
# drawMatchesKnn_cv2(img1, kp1, img2, kp2, good_match)
cv2.imshow("out_img1", out_img1)
cv2.imshow("out_img2", out_img2)
cv2.waitKey(0)
cv2.destroyAllWindows()



浙公網(wǎng)安備 33010602011771號(hào)