OpenCV4【22】-模板匹配與特征點匹配


模板匹配

業務描述:從 一張圖 中找到 和 模板圖片 “非常相似” 的區域,獲取該區域坐標;

原理簡介:用 模板圖像 在 原圖上 滑動,然后計算 滑到的區域 和 模板 的相似程度,如像素差,把該值 記錄在 對應位置,過程類似卷積;

滑完后,找到 相似程度 最大的 坐標,還原到 原圖的坐標,加上 模板的寬高,就得到了 原圖上 和模板相似的 區域;

 

最大的缺點是 如果 圖片有旋轉或者縮放,是無法進行正確匹配的

 

單目標匹配

一張圖上只找 一個 和 模板 最相似的區域;

注意:即使 這個區域 和 模板 完全不一樣,也會找到這個 極值點,比較死板吧;最直觀的優化是 設定一個閾值,大於閾值 才算 匹配

 

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    參數解釋很詳細


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM