一、前言 從學單片機開始鼓搗C語言,到現在為了學CV鼓搗Python,期間在CSDN、簡書、博客園和github這些地方得到了很多幫助,所以也想把自己做的一些小東西分享給大家,希望能幫助到別人。記錄人生的第一篇博客,mark。 二、圖像檢測步驟 1. 讀取兩張圖片 第一張是需要檢測的小物體,第二章圖 ...
一、前言
從學單片機開始鼓搗C語言,到現在為了學CV鼓搗Python,期間在CSDN、簡書、博客園和github這些地方得到了很多幫助,所以也想把自己做的一些小東西分享給大家,希望能幫助到別人。記錄人生的第一篇博客,mark。
二、圖像檢測步驟
1. 讀取兩張圖片
第一張是需要檢測的小物體,第二章圖片是小物體放置在大場景中。代碼與輸出結果如下所示:
import numpy as np import matplotlib.pyplot as plt import cv2 def my_show(img): plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) return # 讀取圖片 img_small=cv2.imread('small.jpg',1) img_big=cv2.imread('big.jpg',1) # 顯示圖片 plt.figure(figsize=(10,10)) plt.subplot(121) my_show(img_small) plt.subplot(122) my_show(img_big)
2. 提取圖片中的特征點
這一步就像我們在區分不同的人的時候,一眼看到外貌就知道此人是誰,而外貌就是這個人的特征。我們希望提取該物體的特征點,以便在不同的場景中識別出來。圖片是由像素點構成,但是像素點包含的信息太零散了,一般是識別物體的邊緣或者角點作為特征信息。常用的特征描述演算法如下:
SIFT:https://blog.csdn.net/zddblog/article/details/7521424
harris corner detection: https://www.jianshu.com/p/efc81fdb8afb
Hog: https://lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf
SURF: https://www.vision.ee.ethz.ch/~surf/eccv06.pdf
BRISK: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.371.1343&rep=rep1&type=pdf
Orb: http://www.willowgarage.com/sites/default/files/orb_final.pdf
我們項目中用到的是SIFT演算法,SIFT精確度高,對尺度、亮度以及旋轉的魯棒性強,不過計算時間長,所需的計算資源較多。SURF是SIFT的加速版本,有興趣的小伙伴可以瞭解一下。SIFT演算法運行後,可以得到的特征點的位置以及特征向量。(PS:SIFT演算法申請了專利,所以在opencv3.4.2版本後不能使用了,需要用SIFT的請安裝3.4.2的opencv)
import numpy as np import matplotlib.pyplot as plt import cv2 def my_show(img): plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) return # 讀取圖片 img_small=cv2.imread('small.jpg',1) img_big=cv2.imread('big.jpg',1) # 提取特征點 sift=cv2.xfeatures2d.SIFT_create() kp1,des1=sift.detectAndCompute(img_small,None) kp2,des2=sift.detectAndCompute(img_big,None) # 在圖中顯示特征點 img_small_sift=cv2.drawKeypoints(img_small,kp1,outImage=np.array([]),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) img_big_sift=cv2.drawKeypoints(img_big,kp2,outImage=np.array([]),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) plt.figure(figsize=(15,15)) plt.subplot(121) my_show(img_small_sift) plt.subplot(122) my_show(img_big_sift)
3.特征點匹配
在分別提取了兩張圖的特征點後,就需要進行特征點匹配啦。這裡需要註意的是,兩張圖的特征點數量一般情況下是不一樣的,opencv演算法里預設用第一張圖的特征點來匹配,所以匹配矩陣的行數與第一張圖特征點的行數一致。常用的匹配方法:
Brute-Force: 暴力搜索,用圖1的特征點逐一與圖二的特征點比較,找到歐式距離最小的點作為匹配點。不過這樣匹配的錯誤點就會很多,所以常用交叉匹配法消除錯誤匹配。交叉匹配法指的是,反過來匹配一次,用匹配到圖2的點反過來匹配圖1的點,如果匹配結果與原來相同,則認為是正確匹配。
KNN: K近鄰匹配,在匹配的時候選擇K個和特征點最相似的點,如果這K個點之間的區別足夠大,則選擇最相似的那個點作為匹配點,通常選擇K = 2。KNN匹配也會出現一些誤匹配,這時候需要對比第一鄰近與第二鄰近之間的距離大小,假如 distance_1< (0.5~0.7)*distance_2, 則認為是正確匹配。
FLANN: FLANN是快速最近鄰搜索包(Fast Library for Approximate Nearest Neighbors)的簡稱,是最近鄰搜索的演算法的集合。
一般我們會選擇調用cv2.Brute-Force或者cv2.FlannBasedMatcher來進行特征點匹配,FLANN裡邊就包含的KNN、KD樹還有其他的最近鄰演算法。
4.計算單應性矩陣
這裡我們需要在大場景中用矩形框出匹配的小物體,所以就要計算單應性矩陣,然後做投影變換。RANSAC(Random Sample Consensus)隨機抽樣一致性演算法是計算單應性矩陣的有效方法,並且在尋找單應性矩陣的過程中可以進一步剔除錯誤匹配點。RANSAC演算法的步驟如下:
- 隨機抽取四個匹配點對計算投影矩陣,需要檢驗四個點是否共線
- 圖片1特征點坐標齊次變換後(3,1)乘上投影矩陣(3,3),然後計算變換後的特征點坐標與圖片2特征點坐標的歐氏距離,小於設定的閾值則記錄為內點,反之則為內點
- 判斷此次內點的數量是否比以往記錄的內點最大值多,如果是,則更新投影矩陣,如果不是,則不更新
- 判定迴圈次數是夠達到設定次數或內點數占全部匹配點的比例達到設定比例,則跳出迴圈,否則跳到步驟1繼續迴圈
尋找到最優的單應性矩陣後,將框住物體一的矩陣經投影變換後在圖片二上畫出來,完成目標檢測框選。以下代碼摘自Brook@CV的博客並修改,如有侵權,請通知刪除
import numpy as np import cv2 from matplotlib import pyplot as plt MIN_MATCH_COUNT = 10 img1 = cv2.imread('small.jpg',1) img2 = cv2.imread('big.jpg',1) # 使用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,dim = 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,(0,0,255),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.figure(figsize=(20,20)) plt.imshow(cv2.cvtColor(img3,cv2.COLOR_BGR2RGB)) plt.show()
最後給大家放上原圖可以跑一下程式!