转载请注明地址
目录
1、特征检测和特征匹配方法
(1)特征检测算法
(2)特征匹配算法
(3)各种特征检测算法的比较
2、特征匹配的基本步骤(附带主要的函数)
(1)图像预处理——灰度化(模板——查询集queryImg,待匹配图像——训练集trainingImg)
(2)创建特征检测器——用于检测模板和图像上的特征点
(3)利用上述特征检测器获得特征点和特征描述符
特征点的解释:
特征描述符的解释:
(4)创建特征匹配器——用于对模板和待匹配图像间进行特征点之间的匹配
匹配的一般思路:
(5)选取优良的匹配点——Lowe's算法+KNN近邻算法
(6)绘制匹配对间的匹配线
3、Python代码实现和主要函数分析
(1)导入包
(2)kps,des = sift.detectAndCompute(IMg, None)函数
参数
返回值
(3)cv.FlannBasedMatcher(indexParams,searchParams)
(4)matches=flann.knnMatch(queryDescriptors, trainDescriptors, k, mask=None, compactResult=None)
参数:
返回值:
matches含有三个属性:queryIdx,trainIdx,distance(特征点描述符的欧式距离)
(5)drawMatchesKnn(img1, keypoints1, img2, keypoints2,**drawparams)
参数:
问题1:怎么提取出kps中关键点的坐标?
问题2:matches的shape是怎样的?
问题3:opencv中match与KnnMatch的区别
4、其他函数
(1)cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
(2)dst = cv2.perspectiveTransform(pts, M)
5、OpenCV仿射变换、投影变换的重要函数
Q&A:
6、实际代码
(1)KNNMATCH+DRAWKNNMATCH+LOWE'S TEST
(2)match+drawmatch
看了很多文章,比较杂乱,打算自己整理一下1特征匹配这方面的知识,主要思路是先抛出一个例子和代码,然后对代码逐步解析,和对关键代码的参数以及返回值作解析
1、特征检测和特征匹配方法
参考:
https://blog.csdn.net/m0_37598482/article/details/78846215
https://www.jianshu.com/p/14b92d3fd6f8
一幅图像中总存在着其独特的像素点,这些点我们可以认为就是这幅图像的特征,成为特征点。计算机视觉领域中的很重要的图像特征匹配就是一特征点为基础而进行的,所以,如何定义和找出一幅图像中的特征点就非常重要。这篇文章我总结了视觉领域最常用的几种特征点以及特征匹配的方法。
在计算机视觉领域,兴趣点(也称关键点或特征点)的概念已经得到了广泛的应用, 包括目标识别、 图像配准、 视觉跟踪、 三维重建等。 这个概念的原理是, 从图像中选取某些特征点并对图像进行局部分析,而非观察整幅图像。 只要图像中有足够多可检测的兴趣点,并且这些兴趣点各不相同且特征稳定, 能被精确地定位,上述方法就十分有效。
(1)特征检测算法
- Harris:该算法用于检测角点;
- SIFT:该算法用于检测斑点;https://blog.csdn.net/qq_40369926/article/details/88597406
- SURF:该算法用于检测角点;
- FAST:该算法用于检测角点;
- BRIEF:该算法用于检测斑点;
- ORB:该算法代表带方向的FAST算法与具有旋转不变性的BRIEF算法;
(2)特征匹配算法
- 暴力(Brute-Force)匹配法;
- 基于FLANN匹配法;
- 可以采用单应性进行空间验证。
(3)各种特征检测算法的比较
参考:https://www.cnblogs.com/jsxyhelu/p/7834416.html
- 计算速度: ORB>>SURF>>SIFT(各差一个量级)
- 旋转鲁棒性: SURF>ORB~SIFT(表示差不多)
- 模糊鲁棒性: SURF>ORB~SIFT
- 尺度变换鲁棒性: SURF>SIFT>ORB(ORB并不具备尺度变换性)
2、特征匹配的基本步骤(附带主要的函数)
https://blog.csdn.net/qq_41007606/article/details/81875193
(1)图像预处理——灰度化(模板——查询集queryImg,待匹配图像——训练集trainingImg)
#1、#读取要匹配的灰度照片
queryImage=cv.imread("b2_ROI_Template3.jpg",0)
trainingImage=cv.imread("b2_target.jpg",0)
(2)创建特征检测器——用于检测模板和图像上的特征点
sift=cv.xfeatures2d.SIFT_create()
(3)利用上述特征检测器获得特征点和特征描述符
kp1, des1 = sift.detectAndCompute(queryImage,None)
kp2, des2 = sift.detectAndCompute(trainingImage,None)
特征点的解释:
具体的概念根据不同的特征检测算法而异,如果要看确切的关键点的概念,可以深入到每一种特征检测算法当中去
如FAST特征检测算法的定义是:
跟Harris检测器的情况一样, FAST算法源于对构成角点的定义。FAST对角点的定义基于候选特征点周围的图像强度值。 以某个点为中心作一个圆, 根据圆上的像素值判断该点是否为关键点。 如果存在这样一段圆弧, 它的连续长度超过周长的3/4, 并且它上面所有像素的强度值都与圆心的强度值明显不同(全部更黑或更亮) , 那么就认定这是一个关键点。
关键点我们可以理解为可以用来代表一个物体的特征的点,如一个长方体,特征点就是8个点,就能够说明他的大小和形状了
特征描述符的解释:
https://blog.csdn.net/shiyongraow/article/details/78347234
是一种算法和方法,输入1个图像,返回多个特征向量(主要用来处理图像的局部,往往会把多个特征向量组成一个一维的向量)。主要用于图像匹配(视觉检测),匹配图像中的物品。
通俗一点就是通过一种算法对该特征点一定区域的灰度值或者说特征进行了计算,将计算得到的多个结果组成一个一维数组,而这个数组就可以称之为该点的特征描述符
还是以长方体为例:【面积、周长、最小外接球心、最大内接球心...】这一组数据就是该长方体的特征描述符,不同的是,图像的特征描述符一般具有尺度不变性等,如一个图片的HU不变矩组合成了一个7元素的一维矩阵,也可以作为描述符
Hu不变矩
(4)创建特征匹配器——用于对模板和待匹配图像间进行特征点之间的匹配
flann=cv.FlannBasedMatcher(indexParams,searchParams)
匹配的一般思路:
从模板中取一个特征点,然后在从待匹配图像中根据匹配算法(匹配度)寻找最优匹配点,记录该匹配点对。
但是在lowes大神提出了另一种方法来优化它,即通过优化来选取比较优良的匹配点
(5)选取优良的匹配点——Lowe's算法+KNN近邻算法
matches=flann.knnMatch(des1,des2,k=2)
for i, (m,n) in enumerate(matches):#返回索引号和两个匹配的信息if m.distance< 0.5*n.distance: #m表示大图像上最匹配点的距离,n表示次匹配点的距离,若比值小于0.5则舍弃matchesMask[i]=[1,0]
我们需要进一步筛选匹配点,来获取优秀的匹配点,这就是所谓的“去粗取精”。这里我们采用了Lowe’s算法来进一步获取优秀匹配点。
为了排除因为图像遮挡和背景混乱而产生的无匹配关系的关键点,SIFT的作者Lowe提出了比较最近邻距离与次近邻距离的SIFT匹配方式:取一幅图像中的一个SIFT关键点,并找出其与另一幅图像中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离得到的比率ratio少于某个阈值T,则接受这一对匹配点。
因为对于错误匹配,由于特征空间的高维性,相似的距离可能有大量其他的错误匹配,从而它的ratio值比较高。显然降低这个比例阈值T,SIFT匹配点数目会减少,但更加稳定,反之亦然。Lowe推荐ratio的阈值为0.8,但作者对大量任意存在尺度、旋转和亮度变化的两幅图片进行匹配,结果表明ratio取值在0. 4~0. 6 之间最佳,小于0. 4的很少有匹配点,大于0. 6的则存在大量错误匹配点,所以建议ratio的取值原则如下:
ratio=0. 4:对于准确度要求高的匹配;
ratio=0. 6:对于匹配点数目要求比较多的匹配;
ratio=0. 5:一般情况下。
(6)绘制匹配对间的匹配线
resultimage=cv.drawMatchesKnn(queryImage,kp1,trainingImage,kp2,matches,None,**drawParams)
3、Python代码实现和主要函数分析
(1)导入包
import cv2 as cv
from matplotlib import pyplot as plt
还需要安装这个包:pip install opencv-contrib-python==3.4.2.16
(2)kps,des = sift.detectAndCompute(IMg, None)函数
参数
img:需要提取特征点的灰度图
None:照写即可
返回值
kps:返回的是特征点所包含的信息,是一个Dmatch数据类型
这里的kps就是关键点。它所包含的信息有:
angle:角度,表示关键点的方向,通过Lowe大神的论文可以知道,为了保证方向不变形,SIFT算法通过对关键点周围邻域进行梯度运算,求得该点方向。-1为初值。
class_id——当要对图片进行分类时,我们可以用class_id对每个特征点进行区分,未设定时为-1,需要靠自己设定
octave——代表是从金字塔哪一层提取的得到的数据。
pt——关键点点的坐标
response——响应程度,代表该点强壮大小,更确切的说,是该点角点的程度。
size——该点直径的大小
des:返回特征点的特征描述符,是一个一维列表,列表元素为Dmatch类型
(3)cv.FlannBasedMatcher(indexParams,searchParams)
根据设置好的参数返回一个特征匹配器,参数是通过字典的方式传送给函数的
FLANN_INDEX_KDTREE=0
indexParams=dict(algorithm=FLANN_INDEX_KDTREE,trees=5)#指定匹配的算法和kd树的层数
searchParams= dict(checks=50)#指定返回的个数#4、根据设置的参数创建特征匹配器
flann=cv.FlannBasedMatcher(indexParams,searchParams)
(4)matches=flann.knnMatch(queryDescriptors, trainDescriptors, k, mask=None, compactResult=None)
参数:
opencv中match与KnnMatch的区别(看问题3)
queryDescriptors:查询集的特征描述符,即模板
trainDescriptors:训练集的特征描述符,即待匹配图像
k:根据KNN近邻算法来返回最匹配的前K个匹配点,默认为1
返回值:
matches:返回的是最匹配的两个特征点的信息,返回的类型是一个列表,列表元素的类型是Dmatch数据类型,每一个列表元素又是一个列表,这个列表元素的个数和k一样,因为封装的就是匹配前k个点的信息
matches含有三个属性:queryIdx,trainIdx,distance(特征点描述符的欧式距离)
参考:https://blog.csdn.net/weixin_44072651/article/details/89262277
queryIdx:测试图像的特征点描述符的下标(第几个特征点描述符),同时也是描述符对应特征点的下标。
trainIdx:样本图像的特征点描述符下标,同时也是描述符对应特征点的下标。
distance:代表这怡翠匹配的特征点描述符的欧式距离,数值越小也就说明俩个特征点越相近。
注意:这里的遍历matches时,需要理解matches一次返回的是几个点,即k=几得清楚
bf = cv.BFMatcher_create()
matches = bf.match(des1, des2)
for matche in matches:print(matche)print(matche.queryIdx)print(matche.trainIdx)print(matche.distance)
(5)drawMatchesKnn(img1, keypoints1, img2, keypoints2,**drawparams)
#绘制的参数,匹配连线的颜色,特征点的颜色,需要画哪些匹配,flags=0绘制点和线,=2不画特征点
drawParams=dict(matchColor=(0,0,255),singlePointColor=(255,0,0),matchesMask=matchesMask,flags=0) #给特征点和匹配的线定义颜色
resultimage=cv.drawMatchesKnn(queryImage,kp1,trainingImage,kp2,matches,None,**drawParams) #画出匹配的结果
参数:
img1:模板灰度图像
kp1:模板特征点信息
img2:待匹配灰度图像
kp2:待匹配图像特征点信息
**drawparam:绘制特征点匹配时的参数,用字典的形式传入,含有matchColor、singlePointColor、matchesMask、flags=0等参数,其中matchColor表示匹配连线颜色,singlePointColor表示特征点颜色,matchesMask表示画哪些匹配,flags=0表示画特征点和连线,flags=2表示不画特征点
问题1:怎么提取出kps中关键点的坐标?
答:通过特征描述符的索引下表和特征点的.pt属性获取
# 获取关键点的坐标src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)print(src_pts)dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
问题2:matches的shape是怎样的?
答:假设取前k个匹配点,查询机即模板含有m个关键点,则matches.shape = (1,m,k)
问题3:opencv中match与KnnMatch的区别
答:区别主要在于前面一个返回的是一个特征点的信息,后面返回的是多个特征点的信息,即前面的是返回最优匹配的点,而后面这个则是返回最优和次优匹配两个点
参考:https://blog.csdn.net/weixin_44072651/article/details/89262277
4、其他函数
(1)cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
参考:https://blog.csdn.net/martinkeith/article/details/104995093
https://blog.csdn.net/ei1990/article/details/78338928
计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列) ,使用最小均方误差或者RANSAC方法
(2)dst = cv2.perspectiveTransform(pts, M)
理论参考:https://blog.csdn.net/oppo62258801/article/details/78642218
对二维或者三维矢量进行透射变换,也就是对输入二维坐标点或者三维坐标点进行投射变换
5、OpenCV仿射变换、投影变换的重要函数
estimateRigidTransform():计算多个二维点对或者图像之间的最优仿射变换矩阵 (2行x3列),H可以是部分自由度,比如各向一致的切变。
getAffineTransform():计算3个二维点对之间的仿射变换矩阵H(2行x3列),自由度为6.
warpAffine():对输入图像进行仿射变换
findHomography: 计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列) ,使用最小均方误差或者RANSAC方法 。
getPerspectiveTransform():计算4个二维点对之间的透射变换矩阵 H(3行x3列)
warpPerspective(): 对输入图像进行透射变换
perspectiveTransform():对二维或者三维矢量进行透射变换,也就是对输入二维坐标点或者三维坐标点进行投射变换。
estimateAffine3D:计算多个三维点对之间的最优三维仿射变换矩阵H (3行x4列)
transform():对输入的N维矢量进行变换,可用于进行仿射变换、图像色彩变换.
findFundamentalMat:计算多个点对之间的基矩阵H。
cvStereoCalibrate():中T类型要求了3*1,对与其他形参float和double都支持
cvStereoRectigy():只支持double类型
cvStereoRectifyUncalibrated():立体校正算法Hartley算法效果和F矩阵及图像数量有关,
ps:
【如果用cvStereoCalibrate()函数计算处理的F矩阵效果和Bouguet算法(cvStereoRectigy())效果一样】
【如果用cvFindFundamentalMat()函数计算F矩阵,没有Bougut算法好】
【用Hartley算法(cvStereoRectifyUncalibrated())校正时,别忘了实现要用cvUndistortPoints()去除相机畸变,Bouguet算法(cvStereoRectigy())没有这个要求,实际上它在函数内部校正了相机的畸变。】
Q&A:
问题1:如何计算3个二维点对之间的仿射变换矩阵?
答:使用getAffineTransform()。
问题2:如何计算多个二维点对之间的仿射变换矩阵(使用误差最小准则 )?
答:使用estimateRigidTransform()。
问题3:如何计算多个二维点对之间的投影变换矩阵(使用误差最小准则 )
答:findHomography()。
问题4:如何计算4个二维点对之间的透射变换?
答:使用getPerspectiveTransform()。
问题5:如何计算多个三维点对之间的仿射变换?
答:使用estimateAffine3D。
问题6:如何对输入图像进行仿射变换?
答:使用warpAffine()。
问题7:如何对输入图像进行透射变换?
答:使用perspectiveTransform()。
问题8:如何对输入的二维点对进行仿射变换?
答:使用transform()。
问题9:如何对输入的三维点对进行投影变换?
答:使用perspectiveTransform()。
6、实际代码
代码参考:https://blog.csdn.net/zhuisui_woxin/article/details/84400439
(1)KNNMATCH+DRAWKNNMATCH+LOWE'S TEST
#
'''
基于FLANN的匹配器(FLANN based Matcher)
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, # 20multi_probe_level = 1) #2
'''
import cv2 as cv
from matplotlib import pyplot as plt#1、#读取要匹配的灰度照片
queryImage=cv.imread("b2_ROI_Template3.jpg",0)
trainingImage=cv.imread("b2_target.jpg",0)#2、#创建sift检测器,这个sift检测器主要是用于检测模板和待匹配图像的特征关键点点,
sift=cv.xfeatures2d.SIFT_create()
#利用创建好的特征点检测器去检测两幅图像的特征关键点,
# 其中kp含有角度、关键点坐标等多个信息,具体怎么提取出坐标点的坐标不清楚,
# des是特征描述符,每一个特征点对应了一个特征描述符,由一维特征向量构成
kp1, des1 = sift.detectAndCompute(queryImage,None)
kp2, des2 = sift.detectAndCompute(trainingImage,None)#3、设置Flannde参数,这里是为了下一步匹配做准备
FLANN_INDEX_KDTREE=0
indexParams=dict(algorithm=FLANN_INDEX_KDTREE,trees=5)#指定匹配的算法和kd树的层数
searchParams= dict(checks=50)#指定返回的个数#4、根据设置的参数创建特征匹配器
flann=cv.FlannBasedMatcher(indexParams,searchParams)
#利用创建好的特征匹配器利用k近邻算法来用模板的特征描述符去匹配图像的特征描述符,k指的是返回前k个最匹配的特征区域
matches=flann.knnMatch(des1,des2,k=2)#返回的是最匹配的两个特征点的信息,返回的类型是一个列表,列表元素的类型是Dmatch数据类型,具体是什么我也不知道
#设置好初始匹配值,用来存放特征点
matchesMask=[[0,0] for i in range (len(matches))]#[[0, 0], [0, 0]... [0, 0]]个数为len(matches)
for i, (m,n) in enumerate(matches):#返回索引号和两个匹配的信息'''比较最近邻距离与次近邻距离的SIFT匹配方式:取一幅图像中的一个SIFT关键点,并找出其与另一幅图像中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离得到的比率ratio少于某个阈值T,则接受这一对匹配点。因为对于错误匹配,由于特征空间的高维性,相似的距离可能有大量其他的错误匹配,从而它的ratio值比较高。显然降低这个比例阈值T,SIFT匹配点数目会减少,但更加稳定,反之亦然。Lowe推荐ratio的阈值为0.8,但作者对大量任意存在尺度、旋转和亮度变化的两幅图片进行匹配,结果表明ratio取值在0. 4~0. 6 之间最佳,小于0. 4的很少有匹配点,大于0. 6的则存在大量错误匹配点,所以建议ratio的取值原则如下:ratio=0. 4:对于准确度要求高的匹配;ratio=0. 6:对于匹配点数目要求比较多的匹配;ratio=0. 5:一般情况下。'''if m.distance< 0.5*n.distance: #m表示大图像上最匹配点的距离,n表示次匹配点的距离,若比值小于0.5则舍弃matchesMask[i]=[1,0]
#绘制的参数,匹配连线的颜色,特征点的颜色,需要画哪些匹配,flags=0绘制点和线,=2不画特征点
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()
绘制出了所有的特征点,以及相互匹配的特征点的匹配线
(2)match+drawmatch
# 基于FLANN的匹配器(FLANN based Matcher)定位图片
import numpy as np
import cv2
from matplotlib import pyplot as pltMIN_MATCH_COUNT = 5 # 设置最低特征点匹配数量为10
template = cv2.imread('h3_target_Template.jpg', 0) # queryImage
target = cv2.imread('h3_target.jpg', 0) # trainImage
# Initiate SIFT detector创建sift检测器
sift = cv2.xfeatures2d.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 = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# store all the good matches as per Lowe's ratio test.
good = []
# 舍弃大于0.7的匹配
for m, n in matches:if m.distance < 0.7 * n.distance:good.append(m)
print(good)
if len(good) >= MIN_MATCH_COUNT:# 获取关键点的坐标src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)print(src_pts)dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)# 计算变换矩阵和MASK# 计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列) ,使用最小均方误差或者RANSAC方法M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)matchesMask = mask.ravel().tolist()#先将mask变成一维,再将矩阵转化为列表h, w = template.shape# 使用得到的变换矩阵对原图像的四个角进行变换,获得在目标图像上对应的坐标pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)dst = cv2.perspectiveTransform(pts, M)cv2.polylines(target, [np.int32(dst)], True, 0, 2, cv2.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 = cv2.drawMatches(template, kp1, target, kp2, good, None, **draw_params)
plt.imshow(result, 'gray')
plt.show()