模板匹配
业务描述:从 一张图 中找到 和 模板图片 “非常相似” 的区域,获取该区域坐标;
原理简介:用 模板图像 在 原图上 滑动,然后计算 滑到的区域 和 模板 的相似程度,如像素差,把该值 记录在 对应位置,过程类似卷积;
滑完后,找到 相似程度 最大的 坐标,还原到 原图的坐标,加上 模板的宽高,就得到了 原图上 和模板相似的 区域;
最大的缺点是 如果 图片有旋转或者缩放,是无法进行正确匹配的
单目标匹配
一张图上只找 一个 和 模板 最相似的区域;
注意:即使 这个区域 和 模板 完全不一样,也会找到这个 极值点,比较死板吧;最直观的优化是 设定一个阈值,大于阈值 才算 匹配
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 参数解释很详细