模板匹配
業務描述:從 一張圖 中找到 和 模板圖片 “非常相似” 的區域,獲取該區域坐標;
原理簡介:用 模板圖像 在 原圖上 滑動,然后計算 滑到的區域 和 模板 的相似程度,如像素差,把該值 記錄在 對應位置,過程類似卷積;
滑完后,找到 相似程度 最大的 坐標,還原到 原圖的坐標,加上 模板的寬高,就得到了 原圖上 和模板相似的 區域;
最大的缺點是 如果 圖片有旋轉或者縮放,是無法進行正確匹配的
單目標匹配
一張圖上只找 一個 和 模板 最相似的區域;
注意:即使 這個區域 和 模板 完全不一樣,也會找到這個 極值點,比較死板吧;最直觀的優化是 設定一個閾值,大於閾值 才算 匹配
Opencv 提供了 多種 相似度 度量方式,示例如下
################ 單目標匹配 ### 用 模板 在 原圖上 滑動,然后計算像素差(匹配程度),把像素差放到對應的位置上,(類似於卷積過程) # 全部滑動完畢后,找到像素差最小的坐標,還原到原圖得到原圖坐標,加上模板寬高就是 目標在原圖上的位置 ## 注意,只是取最小差,即使 原圖中沒有和模板相似的區域,也有個最小差,這時仍然會取最小差,比較死板, # 比較直觀的優化思路是設定一個閾值,至少保證大於閾值才算匹配 img0 = cv.imread('imgs/bus.jpg', 0) print('img0 shape', img0.shape) template = cv.imread('imgs/template2.jpg', 0) # 模板可以和原圖上的匹配區域 像素 不完全一致,取最小差即可,但對應位置必須一致 print('template shape', template.shape) w, h = template.shape[: : -1] # 列表中所有的6種比較方法 methods = [cv.TM_CCOEFF, cv.TM_CCOEFF_NORMED, cv.TM_CCORR, cv.TM_CCORR_NORMED, cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED] for method in methods: print(method) img = img0.copy() # 應用模板匹配 res = cv.matchTemplate(img, template, method) print('res shape', res.shape) min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res) # 如果方法是TM_SQDIFF或TM_SQDIFF_NORMED,則取最小值 if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]: top_left = min_loc # (x, y) else: top_left = max_loc bottom_right = (top_left[0] + w, top_left[1] + h) cv.rectangle(img, top_left, bottom_right, 255, 2) plt.subplot(121), plt.imshow(res, cmap='gray') plt.title('Matching Result'), plt.xticks([]), plt.yticks([]) plt.subplot(122), plt.imshow(img, cmap='gray') plt.title('Detected Point'), plt.xticks([]), plt.yticks([]) plt.suptitle(method) plt.show()
輸出效果圖
模板
1. 模板是直接從原圖上 摳下來的
2. 我故意給 模板 加了一些噪聲,也匹配正確了
平滑過程分析
通過 輸入尺寸來 說明 平滑過程
img0 shape (903, 1204) # 原圖 template shape (55, 61) # 模板 res shape (849, 1144) # 匹配后的矩陣 903-55+1=849,1204-61+1=1144
多目標匹配
一張圖中 有 多個 和 模板相似的 區域,原理沒變化,只是 加了個 閾值,大於閾值的 都算匹配
注意:由於旋轉和縮放會導致算法失效,故匹配效果不佳
################ 多目標匹配 # 一張圖中存在多個與模板相似的地方,原理同單目標檢測, img_rgb = cv.imread('imgs/balls.jpg') img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY) template = cv.imread('imgs/ballst.jpg', 0) w, h = template.shape[::-1] res = cv.matchTemplate(img_gray, template, cv.TM_CCOEFF_NORMED) threshold = 0.6 loc = np.where(res >= threshold) print(loc) for pt in zip(*loc[::-1]): cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2) cv.imwrite('res3.png', img_rgb)
輸出效果圖
模板
特征點匹配
也是一種模板匹配,只是 對 旋轉和縮放 依然有效
暴力匹配 - brute force - BFMatch
暴力匹配很簡單,首先在 模板特征點描述符中找一個特征點,去匹配目標圖中所有特征點描述符,匹配使用 距離 來衡量,返回 距離最近的特征點
cv.BFMatcher 創建匹配器,兩個參數 def create(self, normType=None, crossCheck=None): normType:計算距離的方式,缺省條件下為 cv2.NORM_L2 crossCheck:
normType 默認值為cv2.Norm_L2, 適用於SIFT,SURF算法,還有一個參數為cv2.Norm_L1;
如果是ORB,BRIEF,BRISK算法等,要是用cv2.NORM_HAMMING,如果ORB算法的參數設置為VTA_K==3或4,normType就應該設置為cv2.NORM_HAMMING2
crossCheck,默認值是False。如果設置為True,匹配條件會更加嚴格。舉例來說,如果A圖像中的i點和B圖像中的j點距離最近,並且B中的j點到A中的i點距離也最近,相互匹配,這個匹配結果才會返回。
示例代碼
# 讀取需要特征匹配的兩張照片,格式為灰度圖 template = cv.imread("imgs/ballt.jpg", 0) target = cv.imread("imgs/balln.jpg", 0) orb = cv.ORB_create() # 建立orb特征檢測器 kp1, des1 = orb.detectAndCompute(template, None) # 計算template中的特征點和描述符 kp2, des2 = orb.detectAndCompute(target, None) # 計算target中的特征點和描述符 bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True) # 建立匹配關系 mathces = bf.match(des1, des2) # 匹配描述符 mathces = sorted(mathces, key=lambda x: x.distance) # 據距離來排序 print([i.distance for i in mathces]) # [3.0, 3.0, 4.0, 9.0, 10.0, 13.0, 13.0, 20.0, 21.0, 23.0, 23.0, 25.0, 25.0, 29.0, 30.0, 32.0, # 51.0, 56.0, 70.0, 70.0, 74.0] mask = np.empty((30, 30)) result = cv.drawMatches(template, kp1, target, kp2, mathces[: 6], None, flags=2) # 畫出匹配關系 plt.imshow(result), plt.show() # matplotlib描繪出來
輸出效果圖
FLANN匹配
示例代碼
''' 1.FLANN代表近似最近鄰居的快速庫。它代表一組經過優化的算法,用於大數據集中的快速最近鄰搜索以及高維特征。 2.對於大型數據集,它的工作速度比BFMatcher快。 3.需要傳遞兩個字典來指定要使用的算法及其相關參數等 對於SIFT或SURF等算法,可以用以下方法: index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) 對於ORB,可以使用以下參數: index_params= dict(algorithm = FLANN_INDEX_LSH, table_number = 6, # 12 這個參數是searchParam,指定了索引中的樹應該遞歸遍歷的次數。值越高精度越高 key_size = 12, # 20 multi_probe_level = 1) #2 ''' queryImage = cv.imread("imgs/ballt.jpg", 0) trainingImage = cv.imread("imgs/balln.jpg", 0) sift = cv.SIFT_create() # 創建sift檢測器 kp1, des1 = sift.detectAndCompute(queryImage, None) kp2, des2 = sift.detectAndCompute(trainingImage, None) # 設置Flannde參數 FLANN_INDEX_KDTREE = 0 indexParams = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) searchParams = dict(checks=50) flann = cv.FlannBasedMatcher(indexParams, searchParams) # 建立匹配關系 matches = flann.knnMatch(des1, des2, k=2) # 開始匹配 # 設置好初始匹配值 matchesMask = [[0, 0] for i in range(len(matches))] for i, (m, n) in enumerate(matches): if m.distance < 0.5 * n.distance: # 舍棄小於0.5的匹配結果 matchesMask[i] = [1, 0] print(matchesMask) drawParams = dict(matchColor=(0, 0, 255), singlePointColor=(255, 0, 0), matchesMask=matchesMask, flags=0) # 給特征點和匹配的線定義顏色 resultimage = cv.drawMatchesKnn(queryImage, kp1, trainingImage, kp2, matches, None, **drawParams) # 畫出匹配的結果 plt.imshow(resultimage,), plt.show()
輸出效果圖
對縮放好像效果一般,可能球 確實不同吧
FLANN單應性匹配
處理不在同一 平面角度 的照片
基於FLANN的匹配器(FLANN based Matcher)定位圖片
示例代碼
MIN_MATCH_COUNT = 10 # 設置最低特征點匹配數量為10 template = cv.imread("imgs/ballt.jpg", 0) target = cv.imread("imgs/balln.jpg", 0) # Initiate SIFT detector創建sift檢測器 sift = cv.SIFT_create() # find the keypoints and descriptors with SIFT kp1, des1 = sift.detectAndCompute(template, None) kp2, des2 = sift.detectAndCompute(target, None) # 創建設置FLANN匹配 FLANN_INDEX_KDTREE = 0 index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) search_params = dict(checks=50) flann = cv.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch(des1, des2, k=2) # store all the good matches as per Lowe's ratio test. good = [] for m, n in matches: if m.distance < 0.7 * n.distance: # 舍棄大於0.7的匹配 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) #計算變換矩陣和MASK M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0) matchesMask = mask.ravel().tolist() h, w = template.shape # 使用得到的變換矩陣對原圖像的四個角進行變換,獲得在目標圖像上對應的坐標 pts = np.float32([ [0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0] ]).reshape(-1, 1, 2) dst = cv.perspectiveTransform(pts, M) cv.polylines(target, [np.int32(dst)], True, 0, 2, cv.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), singlePointColor=None, matchesMask=matchesMask, flags=2) result = cv.drawMatches(template, kp1, target, kp2, good, None, **draw_params) plt.imshow(result, 'gray') plt.show()
輸出效果圖
小結
應用擴展
1. 圖像拼接,在圖像拼接前,找到各個圖像的特征點很重要
參考資料:
https://blog.csdn.net/zhuisui_woxin/article/details/84400439 opencv+python實現圖像匹配----模板匹配、特征點匹配
https://zhuanlan.zhihu.com/p/35226009 Opencv for python(2)--圖像匹配
https://baijiahao.baidu.com/s?id=1659458483678227764&wfr=spider&for=pc
https://www.jb51.net/article/173182.htm Python使用Opencv實現圖像特征檢測與匹配的方法
https://www.jianshu.com/p/ed57ee1056ab OpenCV-Python教程:41.特征匹配
https://blog.csdn.net/weixin_39614546/article/details/110884130 參數解釋很詳細