您好,登錄后才能下訂單哦!
特征檢測是計算機對一張圖像中最為明顯的特征進行識別檢測并將其勾畫出來。大多數特征檢測都會涉及圖像的角點、邊和斑點的識別、或者是物體的對稱軸。
角點檢測 是由Opencv的cornerHarris函數實現,其他函數參數說明如下:
cv2.cornerHarris(src=gray, blockSize=9, ksize=23, k=0.04) # cornerHarris參數: # src - 數據類型為 float32 的輸入圖像。 # blockSize - 角點檢測中要考慮的領域大小。 # ksize - Sobel 求導中使用的窗口大小 # k - Harris 角點檢測方程中的自由參數,取值參數為 [0,04,0.06].
以國際象棋為例,這是計算機視覺最為常見的分析對象,如圖所示:
角點檢測代碼如下:
import cv2 import numpy as np img = cv2.imread('chess_board.png') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # cornerHarris函數圖像格式為 float32 ,因此需要將圖像轉換 float32 類型 gray = np.float32(gray) # cornerHarris參數: # src - 數據類型為 float32 的輸入圖像。 # blockSize - 角點檢測中要考慮的領域大小。 # ksize - Sobel 求導中使用的窗口大小 # k - Harris 角點檢測方程中的自由參數,取值參數為 [0,04,0.06]. dst = cv2.cornerHarris(src=gray, blockSize=9, ksize=23, k=0.04) # 變量a的閾值為0.01 * dst.max(),如果dst的圖像值大于閾值,那么該圖像的像素點設為True,否則為False # 將圖片每個像素點根據變量a的True和False進行賦值處理,賦值處理是將圖像角點勾畫出來 a = dst>0.01 * dst.max() img[a] = [0, 0, 255] # 顯示圖像 while (True): cv2.imshow('corners', img) if cv2.waitKey(120) & 0xff == ord("q"): break cv2.destroyAllWindows()
運行代碼,結果如圖所示:
但有時候,圖像的像素大小對角點存在一定的影響。比如圖像越小,角點看上去趨向近似一條直線,這樣很容易造成角點的丟失。如果按照上述的檢測方法,會造成角點檢測結果不相符,因此引入DoG和SIFT算法進行檢測。Opencv的SIFT類是DoG和SIFT算法組合。
DoG是對同一圖像使用不同高斯濾波器所得的結果。
SIFT是通過一個特征向量來描述關鍵點周圍區域的情況。
我們以下圖為例:
import cv2 # 讀取圖片并灰度處理 imgpath = 'varese.jpg' img = cv2.imread(imgpath) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 創建SIFT對象 sift = cv2.xfeatures2d.SIFT_create() # 將圖片進行SURF計算,并找出角點keypoints,keypoints是檢測關鍵點 # descriptor是描述符,這是圖像一種表示方式,可以比較兩個圖像的關鍵點描述符,可作為特征匹配的一種方法。 keypoints, descriptor = sift.detectAndCompute(gray, None) # cv2.drawKeypoints() 函數主要包含五個參數: # image: 原始圖片 # keypoints:從原圖中獲得的關鍵點,這也是畫圖時所用到的數據 # outputimage:輸出 # color:顏色設置,通過修改(b,g,r)的值,更改畫筆的顏色,b=藍色,g=綠色,r=紅色。 # flags:繪圖功能的標識設置,標識如下: # cv2.DRAW_MATCHES_FLAGS_DEFAULT 默認值 # cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS # cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG # cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS img = cv2.drawKeypoints(image=img, outImage=img, keypoints = keypoints, flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT, color = (51, 163, 236)) # 顯示圖片 cv2.imshow('sift_keypoints', img) while (True): if cv2.waitKey(120) & 0xff == ord("q"): break cv2.destroyAllWindows()
運行代碼,結果如圖所示:
除了SIFT算法檢測之外,還有SURF特征檢測算法,比SIFT算法快,并吸收了SIFT算法的思想。SURF采用Hessian算法檢測關鍵點,而SURF是提取特征,這個與SIFT很像。Opencv的SURF類是Hessian算法和SURF算法組合。我們根據SIFT的代碼進行修改,代碼如下:
import cv2 # 讀取圖片并灰度處理 imgpath = 'varese.jpg' img = cv2.imread(imgpath) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 創建SURF對象,對象參數float(4000)為閾值,閾值越高,識別的特征越小。 sift = cv2.xfeatures2d.SURF_create(float(4000)) # 將圖片進行SURF計算,并找出角點keypoints,keypoints是檢測關鍵點 # descriptor是描述符,這是圖像一種表示方式,可以比較兩個圖像的關鍵點描述符,可作為特征匹配的一種方法。 keypoints, descriptor = sift.detectAndCompute(gray, None) # cv2.drawKeypoints() 函數主要包含五個參數: # image: 原始圖片 # keypoints:從原圖中獲得的關鍵點,這也是畫圖時所用到的數據 # outputimage:輸出 # color:顏色設置,通過修改(b,g,r)的值,更改畫筆的顏色,b=藍色,g=綠色,r=紅色。 # flags:繪圖功能的標識設置,標識如下: # cv2.DRAW_MATCHES_FLAGS_DEFAULT 默認值 # cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS # cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG # cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS img = cv2.drawKeypoints(image=img, outImage=img, keypoints = keypoints, flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT, color = (51, 163, 236)) # 顯示圖片 cv2.imshow('sift_keypoints', img) while (True): if cv2.waitKey(120) & 0xff == ord("q"): break cv2.destroyAllWindows()
上述代碼我們只修改sift = cv2.xfeatures2d.SURF_create(float(4000))
即可實現SURF特征檢測算法。運行結果如圖所示:
對比SURF和SIFT算法,ORB算法更處于起步階段,在2011年才首次發布。但比前兩者的速度更快。ORB基于FAST關鍵點檢測和BRIEF的描述符技術相結合,因此我們先了解FAST和BRIEF。
FAST:特征檢測算法。
BRIEF:只是一個描述符,這是圖像一種表示方式,可以比較兩個圖像的關鍵點描述符,可作為特征匹配的一種方法。
暴力匹配:比較兩個描述符并產生匹配結果。
在上述的例子中,我們只是將檢測的關鍵點進行勾畫,在這例子中,將使用ORB檢測關鍵點之外,還將兩圖進行匹配,匹配的圖像如下:
實現方法:首先分別對兩圖進行ORB處理,然后將兩圖的關鍵點進行暴力匹配。具體代碼如下:
# ORB算法實現特征檢測+暴力匹配
import numpy as np import cv2 from matplotlib import pyplot as plt # 讀取圖片內容 img1 = cv2.imread('aa.jpg',0) img2 = cv2.imread('bb.png',0) # 使用ORB特征檢測器和描述符,計算關鍵點和描述符 orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(img1,None) kp2, des2 = orb.detectAndCompute(img2,None) # 暴力匹配BFMatcher,遍歷描述符,確定描述符是否匹配,然后計算匹配距離并排序 # BFMatcher函數參數: # normType:NORM_L1, NORM_L2, NORM_HAMMING, NORM_HAMMING2。 # NORM_L1和NORM_L2是SIFT和SURF描述符的優先選擇,NORM_HAMMING和NORM_HAMMING2是用于ORB算法 bf = cv2.BFMatcher(normType=cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1,des2) matches = sorted(matches, key = lambda x:x.distance) # matches是DMatch對象,具有以下屬性: # DMatch.distance - 描述符之間的距離。 越低越好。 # DMatch.trainIdx - 訓練描述符中描述符的索引 # DMatch.queryIdx - 查詢描述符中描述符的索引 # DMatch.imgIdx - 訓練圖像的索引。 # 使用plt將兩個圖像的匹配結果顯示出來 img3 = cv2.drawMatches(img1=img1,keypoints1=kp1,img2=img2,keypoints2=kp2, matches1to2=matches, outImg=img2, flags=2) plt.imshow(img3),plt.show()
運行結果如圖所示:
# SURF和SIFT算法+暴力匹配
暴力匹配BFMatcher是一種匹配方法,只要提供兩個關鍵點即可實現匹配。若將上述例子改為SURF和SIFT算法,只需修改以下代碼:
將orb = cv2.ORB_create()改為 orb = cv2.xfeatures2d.SURF_create(float(4000)) 將bf = cv2.BFMatcher(normType=cv2.NORM_HAMMING, crossCheck=True)改為 bf = cv2.BFMatcher(normType=cv2.NORM_L1, crossCheck=True)
# 獲取匹配關鍵點的坐標位置
在上述例子中,matches是DMatch對象,DMatch是以列表的形式表示,每個元素代表兩圖能匹配得上的點。如果想獲取某個點的坐標位置,在上述例子添加以下代碼:
# 由于匹配順序是:matches = bf.match(des1,des2),先des1后des2。 # 因此,kp1的索引由DMatch對象屬性為queryIdx決定,kp2的索引由DMatch對象屬性為trainIdx決定 # 獲取aa.jpg的關鍵點位置 x,y = kp1[matches[0].queryIdx].pt cv2.rectangle(img1, (int(x),int(y)), (int(x) + 5, int(y) + 5), (0, 255, 0), 2) cv2.imshow('a', img1) # 獲取bb.png的關鍵點位置 x,y = kp2[matches[0].trainIdx].pt cv2.rectangle(img2, (int(x1),int(y1)), (int(x1) + 5, int(y1) + 5), (0, 255, 0), 2) cv2.imshow('b', img2) # 使用plt將兩個圖像的第一個匹配結果顯示出來 img3 = cv2.drawMatches(img1=img1,keypoints1=kp1,img2=img2,keypoints2=kp2, matches1to2=matches[:1], outImg=img2, flags=2) plt.imshow(img3),plt.show()
運行結果如圖所示:
上述講到的暴力匹配是使用BFMatcher匹配器實現的,然后由match函數實現匹配。接下來講解K-最近鄰匹配(KNN),并在BFMatcher匹配下實現。在所有機器學習的算法中,KNN可能是最為簡單的算法。針對上述例子,改為KNN匹配,實現代碼如下:
import numpy as np import cv2 from matplotlib import pyplot as plt # 讀取圖片內容 img1 = cv2.imread('aa.jpg',0) img2 = cv2.imread('bb.png',0) # 使用ORB特征檢測器和描述符,計算關鍵點和描述符 orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(img1,None) kp2, des2 = orb.detectAndCompute(img2,None) # 暴力匹配BFMatcher,遍歷描述符,確定描述符是否匹配,然后計算匹配距離并排序 # BFMatcher函數參數: # normType:NORM_L1, NORM_L2, NORM_HAMMING, NORM_HAMMING2。 # NORM_L1和NORM_L2是SIFT和SURF描述符的優先選擇,NORM_HAMMING和NORM_HAMMING2是用于ORB算法 bf = cv2.BFMatcher(normType=cv2.NORM_HAMMING, crossCheck=True) # knnMatch 函數參數k是返回符合匹配的個數,暴力匹配match只返回最佳匹配結果。 matches = bf.knnMatch(des1,des2,k=1) # 使用plt將兩個圖像的第一個匹配結果顯示出來 # 若使用knnMatch進行匹配,則需要使用drawMatchesKnn函數將結果顯示 img3 = cv2.drawMatchesKnn(img1=img1,keypoints1=kp1,img2=img2,keypoints2=kp2, matches1to2=matches, outImg=img2, flags=2) plt.imshow(img3),plt.show()
最后是介紹FLANN匹配,相對暴力匹配BFMatcher來講,這匹配算法比較準確、快速和使用方便。FLANN具有一種內部機制,可以根據數據本身選擇最合適的算法來處理數據集。值得注意的是,FLANN匹配器只能使用SURF和SIFT算法。
FLANN實現方式如下:
import numpy as np import cv2 from matplotlib import pyplot as plt queryImage = cv2.imread('aa.jpg',0) trainingImage = cv2.imread('bb.png',0) # 只使用SIFT 或 SURF 檢測角點 sift = cv2.xfeatures2d.SIFT_create() # sift = cv2.xfeatures2d.SURF_create(float(4000)) kp1, des1 = sift.detectAndCompute(queryImage,None) kp2, des2 = sift.detectAndCompute(trainingImage,None) # 設置FLANN匹配器參數 # algorithm設置可參考https://docs.opencv.org/3.1.0/dc/d8c/namespacecvflann.html indexParams = dict(algorithm=0, trees=5) searchParams = dict(checks=50) # 定義FLANN匹配器 flann = cv2.FlannBasedMatcher(indexParams,searchParams) # 使用 KNN 算法實現匹配 matches = flann.knnMatch(des1,des2,k=2) # 根據matches生成相同長度的matchesMask列表,列表元素為[0,0] matchesMask = [[0,0] for i in range(len(matches))] # 去除錯誤匹配 for i,(m,n) in enumerate(matches): if m.distance < 0.7*n.distance: matchesMask[i] = [1,0] # 將圖像顯示 # matchColor是兩圖的匹配連接線,連接線與matchesMask相關 # singlePointColor是勾畫關鍵點 drawParams = dict(matchColor = (0,255,0), singlePointColor = (255,0,0), matchesMask = matchesMask, flags = 0) resultImage = cv2.drawMatchesKnn(queryImage,kp1,trainingImage,kp2,matches,None,**drawParams) plt.imshow(resultImage,),plt.show()
運行結果如圖所示:
FLANN的單應性匹配,單應性是一個條件,該條件表面當兩幅圖像中的一副出像投影畸變時,他們還能匹配。FLANN的單應性實現代碼如下:
import numpy as np import cv2 from matplotlib import pyplot as plt MIN_MATCH_COUNT = 10 img1 = cv2.imread('tattoo_seed.jpg',0) img2 = cv2.imread('hush.jpg',0) # 使用SIFT檢測角點 sift = cv2.xfeatures2d.SIFT_create() # 獲取關鍵點和描述符 kp1, des1 = sift.detectAndCompute(img1,None) kp2, des2 = sift.detectAndCompute(img2,None) # 定義FLANN匹配器 index_params = dict(algorithm = 1, trees = 5) search_params = dict(checks = 50) flann = cv2.FlannBasedMatcher(index_params, search_params) # 使用KNN算法匹配 matches = flann.knnMatch(des1,des2,k=2) # 去除錯誤匹配 good = [] for m,n in matches: if m.distance < 0.7*n.distance: good.append(m) # 單應性 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) # findHomography 函數是計算變換矩陣 # 參數cv2.RANSAC是使用RANSAC算法尋找一個最佳單應性矩陣H,即返回值M # 返回值:M 為變換矩陣,mask是掩模 M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0) # ravel方法將數據降維處理,最后并轉換成列表格式 matchesMask = mask.ravel().tolist() # 獲取img1的圖像尺寸 h,w = img1.shape # pts是圖像img1的四個頂點 pts = np.float32([[0,0],[0,h-1],[w-1,h-1],[w-1,0]]).reshape(-1,1,2) # 計算變換后的四個頂點坐標位置 dst = cv2.perspectiveTransform(pts,M) # 根據四個頂點坐標位置在img2圖像畫出變換后的邊框 img2 = cv2.polylines(img2,[np.int32(dst)],True,(255,0,0),3, cv2.LINE_AA) else: print("Not enough matches are found - %d/%d") % (len(good),MIN_MATCH_COUNT) matchesMask = None # 顯示匹配結果 draw_params = dict(matchColor = (0,255,0), # draw matches in green color singlePointColor = None, matchesMask = matchesMask, # draw only inliers flags = 2) img3 = cv2.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params) plt.imshow(img3, 'gray'),plt.show()
運行結果如下所示:
單應性實際應用
從上述的例子可以看到,單應性是在兩圖片匹配的時候,其中某一圖片發生變換處理,變換后圖像會呈現一種立體空間的視覺效果,圖像發生變換程度稱為變換矩陣。以下例子將圖像中的書本替換成其他書本,例子中所使用圖片如下:
我們根據圖1和圖2計算變換矩陣,然后通過變換矩陣將圖3進行變換,最后將圖3加入到圖1中,實現圖片替換。實現代碼如下:
import numpy as np import cv2 from matplotlib import pyplot as plt img1 = cv2.imread('logo.jpg',0) img2 = cv2.imread('book.jpg',0) # 使用SIFT檢測角點 sift = cv2.xfeatures2d.SIFT_create() # 獲取關鍵點和描述符 kp1, des1 = sift.detectAndCompute(img1,None) kp2, des2 = sift.detectAndCompute(img2,None) # 定義FLANN匹配器 index_params = dict(algorithm = 1, trees = 5) search_params = dict(checks = 50) flann = cv2.FlannBasedMatcher(index_params, search_params) # 使用KNN算法匹配 matches = flann.knnMatch(des1,des2,k=2) # 去除錯誤匹配 good = [] for m,n in matches: if m.distance < 0.7*n.distance: good.append(m) # 單應性實際應用 # 改變數組的表現形式,不改變數據內容,數據內容是每個關鍵點的坐標位置 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) # findHomography 函數是計算變換矩陣 # 參數cv2.RANSAC是使用RANSAC算法尋找一個最佳單應性矩陣H,即返回值M # 返回值:M 為變換矩陣,mask是掩模 M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0) # 獲取img1的圖像尺寸 h,w = img1.shape # pts是圖像img1的四個頂點 pts = np.float32([[0,0],[0,h-1],[w-1,h-1],[w-1,0]]).reshape(-1,1,2) # 計算變換后的四個頂點坐標位置 dst = cv2.perspectiveTransform(pts,M) # 圖片替換 img3 = cv2.imread('aa.png',0) # 降維處理 b = np.int32(dst).reshape(4, 2) x,y = img2.shape # 根據變換矩陣將圖像img3進行變換處理 res = cv2.warpPerspective(img3, M, (y,x)) img_temp = img2.copy() # 將圖像img2的替換區域進行填充處理 cv2.fillConvexPoly(img_temp, b, 0) # 將變換后的img3圖像替換到圖像img2 cv2.imshow('bb',img_temp) res = img_temp + res cv2.imshow('aa',res) plt.imshow(res),plt.show()
運行結果如圖所示:
從結果可以看到,替換后的圖像周邊出現黑色線條,這是正常的現象。在上圖最左邊的圖bb可以看到,黑色區域是由圖1和圖2檢測匹配所得的結果,如果匹配結果會存在一定的誤差,這個誤差是由多個因素所導致的。
在實際中,我們根據一張圖片在眾多的圖片中查找匹配率最高的圖片。如果按照上面的例子,也可以實現,但每次匹配時都需要重新檢測圖片的特征數據,這樣會導致程序運行效率。因此,我們可以將圖片的特征數據進行保存,每次匹配時,只需讀取特征數據進行匹配即可。我們以下圖為例:
我們根據圖1在圖2中查找最佳匹配的圖片。首先獲取圖2的全部圖片的特征數據,將代碼保存在features.py:
import cv2 import numpy as np from os import walk from os.path import join def create_descriptors(folder): files = [] for (dirpath, dirnames, filenames) in walk(folder): files.extend(filenames) for f in files: if '.jpg' in f: save_descriptor(folder, f, cv2.xfeatures2d.SIFT_create()) def save_descriptor(folder, image_path, feature_detector): # 判斷圖片是否為npy格式 if image_path.endswith("npy"): return # 讀取圖片并檢查特征 img = cv2.imread(join(folder,image_path), 0) keypoints, descriptors = feature_detector.detectAndCompute(img, None) # 設置文件名并將特征數據保存到npy文件 descriptor_file = image_path.replace("jpg", "npy") np.save(join(folder, descriptor_file), descriptors) if __name__=='__main__': path = 'E:\\anchors' create_descriptors(path)
運行代碼,結果如圖所示:
我們將圖片的特征數據保存在npy文件。下一步是根據圖1與這些特征數據文件進行匹配,從而找出最佳匹配的圖片。代碼存在matching.py:
from os.path import join from os import walk import numpy as np import cv2 query = cv2.imread('tattoo_seed.jpg', 0) folder = 'E:\\anchors' descriptors = [] # 獲取特征數據文件名 for (dirpath, dirnames, filenames) in walk(folder): for f in filenames: if f.endswith("npy"): descriptors.append(f) print(descriptors) # 使用SIFT算法檢查圖像的關鍵點和描述符 sift = cv2.xfeatures2d.SIFT_create() query_kp, query_ds = sift.detectAndCompute(query, None) # 創建FLANN匹配器 index_params = dict(algorithm=0, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) potential_culprits = {} for d in descriptors: # 將圖像query與特征數據文件的數據進行匹配 matches = flann.knnMatch(query_ds, np.load(join(folder, d)), k=2) # 清除錯誤匹配 good = [] for m, n in matches: if m.distance < 0.7 * n.distance: good.append(m) # 輸出每張圖片與目標圖片的匹配數目 print("img is %s ! matching rate is (%d)" % (d, len(good))) potential_culprits[d] = len(good) # 獲取最多匹配數目的圖片 max_matches = None potential_suspect = None for culprit, matches in potential_culprits.items(): if max_matches == None or matches > max_matches: max_matches = matches potential_suspect = culprit print("potential suspect is %s" % potential_suspect.replace("npy", "").upper())
代碼運行后,輸出結果如圖所示:
從輸出的結果可以看到,圖1與圖2的hush.jpg最為匹配,如圖所示:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。