最近在做图像检测上的项目,需要进行图像匹配的相关内容,查阅了opencv的匹配方法,整理了模版匹配和特征点匹配的内容,并总结成博客。
模板匹配
模版匹配是一种最原始、最基本的识别方法。简单来说,模板匹配就是用一幅已知的模板图片在目标图片上依次滑动(类似于滑窗法),每次滑动都计算模板与模板下方的目标子图的相似度。
但模板匹配具有一定的局限性,也是我在实际操作时发现的。局限性主要表现在模版图片只能在目标图标上进行平行移动,若原图像中的匹配目标发生旋转或大小变化,该算法无效。也就是说,如果模版图片和目标子图上的方向、大小不一致,则无法匹配成功。
特征点匹配
特征匹配(FBM),也称为角点匹配,就是指将从影像中提取的特征作为共轭实体,而将所提特征属性或描述参数(实际上是特征的特征)作为匹配实体,通过计算匹配实体之间的相似性测度以实现共轭实体配准的影像匹配方法。即指寻找两幅图像之间的特征像素点的对应关系,从而确定图像的位置关系。
在匹配目标发生旋转或大小变化时,该算法依旧有效。
模板匹配就是用一幅已知的模板图片在目标图片上依次滑动(类似于滑窗法),每次滑动都计算模板与模板下方的目标子图的相似度。
如果是单个目标的匹配,只需要取相似度最大值所在的位置就可以得到匹配位置。
如果要匹配多个目标,设定一个合理的阈值,只要相似度大于阈值则认为是匹配的目标。
# cv2中使用matchTemplate进行模版匹配
result = cv2.matchTemplate(target,template,cv2.method)
matchTemplate提供了六种模版匹配方法:
cv2.TM_SQDIFF(差值平方和匹配):该方法计算模板与某个子图的对应像素的差值平方和。因此最佳的匹配结果在结果为0处,越相似值越小,值越大匹配结果越差。
cv2.TM_SQDIFF_NORMED(标准化差值平方和匹配):该方法使用归一化的平方差进行匹配,最佳匹配也在结果为0处。
cv2.TM_CCORR(相关匹配法):该方法使用源图像与模板图像的卷积结果进行匹配,因此,最佳匹配位置在值最大处,值越小匹配结果越差。
cv2.TM_CCORR_NORMED(归一化相关匹配):归一化的相关性匹配方法,去除了亮度线性变化对相似度计算的影响。可以保证图像和模板同时变量或变暗k倍时结果不变。与相关性匹配方法类似,最佳匹配位置也是在值最大处,越相似值越大。
cv2.TM_CCOEFF(相关性系数匹配方法):该方法使用源图像与其均值的差、模板与其均值的差二者之间的相关性进行匹配,最佳匹配结果在值等于1处,最差匹配结果在值等于-1处,值等于0直接表示二者不相关。
cv2.TM_CCOEFF_NORMED(归一化的相关性系数匹配方法):把图像和模板都减去了各自的平均值,再各自除以各自的方差,保证图像和模板分别改变光照不影响计算结果,计算出的相关系数限制在-1到1之间,1 表示完全相同,-1 表示两幅图像的亮度正好相反,0 表示没有线性关系。负值表示效果不好,正值表示匹配效果好。
#opencv模板匹配----单目标匹配
import cv2
#读取目标图片
target = cv2.imread("target.jpg")
#读取模板图片
template = cv2.imread("template.jpg")
#获得模板图片的高宽尺寸
theight, twidth = template.shape[:2]
#执行模板匹配,采用的匹配方式cv2.TM_SQDIFF_NORMED
result = cv2.matchTemplate(target,template,cv2.TM_SQDIFF_NORMED)
#归一化处理
cv2.normalize( result, result, 0, 1, cv2.NORM_MINMAX, -1 )
#寻找矩阵(一维数组当做向量,用Mat定义)中的最大值和最小值的匹配结果及其位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
#对于cv2.TM_SQDIFF及cv2.TM_SQDIFF_NORMED方法min_val越趋近与0匹配度越好,匹配位置定点取min_loc,对于其他方法max_val越趋近于1匹配度越好,匹配位置取max_loc
strmin_val = str(min_val)
#绘制矩形边框,将匹配区域标注出来
cv2.rectangle(target,min_loc,(min_loc[0]+twidth,min_loc[1]+theight),(0,0,225),2)
#显示结果,并将匹配值显示在标题栏上
cv2.imshow("MatchResult----MatchingValue="+strmin_val,target)
cv2.waitKey()
cv2.destroyAllWindows()
对图片提取SIFT特征并利用FLANN方法判别图像的相似度并可视化。
基于FLANN的匹配器(FLANN based Matcher)的特征匹配。FLANN库全称是Fast Library for Approximate Nearest Neighbors,它是目前最完整的(近似)最近邻开源库。不但实现了一系列查找算法,还包含了一种自动选取最快算法的机制。
SIFT的大概流程描述:
在图中选择出一些比较有特色的点,并把他们编码成特征向量,现在要做两个图的特征点匹配,所以要用SIFT对两个图分别做特征点选择,并且每个特征点编码成相同维度的特征向量,比如都是长度为512的向量。两个图上的特征点数量是不一样的,比如第一个图有3个特征点,第二个图有5个特征点,要做特征点匹配的话,计算第一个图中3个点跟第二个图里的5个点两两之间的距离,这个距离一般就是向量的欧式距离。特征点匹配中,就是要找两个图中比较相似的特征点,特征点的特征向量越相近,说明这两个点附近的情况就越相似。
ratio test 筛选的思想:
首先找到图一中某一个特征点在图二中的最相似点和次相似点,然后把最相近点的距离跟次相近点的距离相除,获得一个ratio,这个值从直观上理解就是,最相近点比次相近点 更加相似的程度。ratio越小,就表示相近点比次相近点要更加相似很多,相反ratio越大,就表示相近点比次相近点都是一般相似。ratio test就是用ratio值去筛选,它认为:ratio小于一个特定的阈值,这对特征点匹配才会被接受,否则就筛选掉。因为ratio小的特征点对,说明最相近点比其它匹配点(次相近点在这里就代表了其它所有的特征点)都要明显很多,说明这一对匹配是非常有特色的,是可以保留的,保留了基本不会错,因为没有其它干扰因素;ratio比较大的时候,就说明这对匹配是存疑的,把最相近点换成次相近点的相似度也差不多,所以无法断定这一对特征点有没有匹配错,那么就把其筛选掉。Lowe推荐ratio的阈值为0.8。
我这边是有五个类的模版图,在目标图中进行特征匹配,判断属于哪一个类。我的思路则是依次进行特征匹配,比较最后特征点对点多少,从而判断更接近哪一类别。
import cv2
import os
from matplotlib import pyplot as plt
def FLANN():
# flags=0
# 灰色读入目标图像
targetPath = 'target.jpg'
trainingImage = cv2.imread(targetPath, flags=0)
# 灰色读所有模板图片
templatePath = 'templatePath/'
icons = os.listdir(templatePath)
iconMatch= dict({'name': '未识别', 'value': 0})
for icon in icons:
queryImage = cv2.imread(templatePath + icon, 0)
# 使用SIFT 检测角点
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(queryImage, None)
kp2, des2 = sift.detectAndCompute(trainingImage, None)
# 设置FLANN匹配器参数,定义FLANN匹配器,使用 KNN 算法实现匹配
indexParams = dict(algorithm=0, trees=5)
searchParams = dict(checks=50)
flann = cv2.FlannBasedMatcher(indexParams,searchParams)
matches = flann.knnMatch(des1,des2,k=2)
# 根据matches生成相同长度的matchesMask列表,列表元素为[0,0]
matchesMask = [[0,0] for i in range(len(matches))]
matchNumber = 0
# 去除错误匹配, 此处阈值设定为0.7
for i,(m,n) in enumerate(matches):
if m.distance < 0.7 * n.distance:
matchesMask[i] = [1,0]
matchNumber = matchNumber+1
# 将图像显示
# matchColor是两图的匹配连接线,连接线与matchesMask相关
# singlePointColor是勾画关键点
drawParams = dict(matchColor = (0,255,0), matchesMask = matchesMask[:50], flags = 0)
resultImage = cv2.drawMatchesKnn(queryImage,kp1,trainingImage,kp2,matches[:50],None,**drawParams)
if matchNumber > iconMatch['value']:
iconMatch['name'] = icon.split('_')[0]
iconMatch['value'] = matchNumber
return resultImage, iconMatch
if __name__ == '__main__':
resultImage, res = FLANN()
plt.imshow(resultImage)
plt.show()