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    参数解释很详细

原文地址:https://www.cnblogs.com/yanshw/p/15603757.html