1,Harris角点检测
如果粉色窗口向四周移动,窗口内的像素没有变化则认定为平坦区域,如果窗口向上移动无明显变化,而左右移动有变化则认定为边缘,如果窗口向任意方向移动均有明显变化则为角点,如下图
dst不是输入参数,是输出参数,输出检测出来的角点
2,Shi-Tomasi角点检测
Shi-Tomasi是Harris角点检测
Harris角点检测算的稳定性和k有关,而k是个经验值,不是设定最佳值。而Shi-Tomasi好处(区别)在于不需要再对k值进行设定
第一个参数maxCorners是要检测的角点数量,无限制就是把能检测到的角点全部输出。qualityLevel是角点质量,实际上就是Harris检测时的代码img[dst>0.01*dst.max()],将很小的角点去掉,使用ShiTimasi方法就不用这一步了,直接在参数内填数值即可。第三个参数minDistance指两个交点间最小的距离,如果小于所设定的最小距离,则角点会舍弃只保留一个。第四个参数mask就是掩码,要检测哪个区域就创建一个mask,只保留要检测的区域,其他区域变为黑色。第五个参数blockSize与Harris相同。第六个参数useHarrisDetector,如果是true则使用Harris角点检测方法,如果是False则使用Shi-Tomasi检测方法,默认为false。第七个参数k是在第六个参数为true时启用的默认为0.04
import numpy as np
import cv2
img = cv2.imread('2.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
qualityLevel = 0.01#相当于之前的img[dst>0.01*dst.max()]
#Shi-Tomasi角点检测。获取的角点存在corners中,输出的corners是一个三维数组且有小数点
corners = cv2.goodFeaturesToTrack(gray,1000,qualityLevel,5)
#将corners数组中的浮点型数据转换为整型
corners = np.int64(corners)
#Shi-Tomasi绘制角点
for i in corners:
x,y = i.ravel()#corners中的数组为多维数组,这里转换成一维数组
cv2.circle(img,(x,y),3,255,-1)#-1画实心圆
cv2.imshow('img',img)
cv2.waitKey(0)
未进行整型转换的corners是一个三维数组且有小数点
进行整型转换后的corners
corners[1]输出内容为一个二维数组
进行corners[1].ravel()操作后,corners[1]变为一维数组
3,SIFT特征检测
具体原理可以查看文章:SIFT算法原理-CSDN博客
Harris角点具有旋转不变性,但缩放后,原来的角点可能就无法检测,如下图
使用步骤:
- 创建SIFT对象
- 进行检测,kp = sift.detect(img, ....)
- 绘制关键点,drawKeypoints(gray,kp,img)。关键点有信息位置,大小和方向
- 计算描述子。kp,des=sift.compute(img,kp),des为返回的描述子,为一个二维数组,每个数组内都有很多数据记录关键点信息。描述子记录了关键点周围对其有贡献的像素点的一组向量值,其不受仿射变换,光照变换等影响。描述子主要用于特征匹配
- opencv为我们提供了一个可以同时计算关键字和描述子的函数
- kp, des = sift.detectAndCompute(img, mask)。mask可以指明对哪个区域进行计算
import numpy as np
import cv2
img = cv2.imread('2.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#创建SIFT对象
sift = cv2.xfeatures2d.SIFT_create()
#进行检测
kp = sift.detect(gray, None)#第二个参数可以设置一个掩码,对某区域进行检测,设为None表示对整 #张图进行检测
#绘制关键点
cv2.drawKeypoints(gray, kp, img)#在img图像上绘制
#计算描述子
kp, des = sift.compute(gray, kp)#返回两个结果,第一个结果kp还是关键点,第二个参数是每个关键
#点对应的特征向量,des.shape输出结果为(关键点数量,128)即每个关键点特征向量为128维
cv2.imshow('SIFT', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
import numpy as np
import cv2
img = cv2.imread('2.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#创建SIFT对象
sift = cv2.xfeatures2d.SIFT_create()
#进行检测
kp, des = sift.detectAndCompute(gray, None)
#绘制关键点
cv2.drawKeypoints(gray, kp, img)#在img图像上绘制
cv2.imshow('SIFT', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
4,SURF特征检测
SIFT最大的问题就是速度慢,而SURF速度更快,并且保留了SIFT的优点。
- 创建surf对象。surf = cv2.xfeatures2d.SURF_create()
- 检测关键点,计算描述子。kp, des = surf.detectAndCompute(img, mask)
import numpy as np
import cv2
img = cv2.imread('2.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#创建SURF对象
surf = cv2.xfeatures2d.SURF_create()
#进行检测并计算描述子
kp, des = surf.detectAndCompute(gray, None)#第二个参数可以设置一个掩码,对某区域进行检测,设为None表示对整张图进行检测
#绘制关键点
cv2.drawKeypoints(gray, kp, img)#在img图像上绘制
cv2.imshow('SURF', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
5,ORB特征检测
ORB可以做到实时监测,也就是它的计算速度很快。ORB是FAST和BRIEF两种技术的结合,他的计算速度均高于两者,但准确度却不如两者。而且ORB是没有版权的,放在Opencv主库
FAST可以做到特征点的实时监测。BRIEF是对已检测到的特征点进行描述,它加快了特征描述建立的速度,同时也极大降低了特征匹配的时间
使用步骤:
- 创建ORB对象。orb = cv2.ORB_create()
- 检测关键点,计算描述子。kp, des = orb.detectAndCompute(img, mask)
import numpy as np
import cv2
img = cv2.imread('2.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#创建ORB对象
orb = cv2.ORB_create()
#进行检测并计算描述子
kp, des = orb.detectAndCompute(gray, None)#第二个参数可以设置一个掩码,对某区域进行检测,设为None表示对整张图进行检测
#绘制关键点
cv2.drawKeypoints(gray, kp, img)#在img图像上绘制
cv2.imshow('SURF', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
6,BF(暴力)特征匹配
通过枚举方式进行特征匹配
原理:特征匹配要有两张图,首先计算第一张图的关键点和描述子。然后使用第一张图的每个特征的描述子与第二组中的所有特征描述子进行匹配。计算两者之间的差距,然后将匹配度最接近的一个匹配返回
特征匹配步骤:
- 创建匹配器。bf = BFMatcher(normType, crossCheck)。第一个参数指匹配类型,也就是我们要用哪种方法进行相似度的计算,包括NORM_L1,NORM_L2,NORM_HAMMING1,NORM_HAMMING2,其中NORM_L1,NORM_L2用于对SIFT和SURF描述子进行计算,NORM_HAMMING1是对ORB得到的描述子进行计算,默认为NORM_L2。第二个参数是是否进行交叉匹配(用第一张图描述子去匹配第二张图,得到相似度较高的再用第二张图描述子去匹配第一张图,起检查的作用),默认为false,开启此功能计算时间会变长
- 进行特征匹配。bf.match(des1, des2)。des1是获取到的第一张图的描述子,des2是获取到的第二张图的描述子
- 绘制匹配点(将匹配点通过线连接到一起)。cv2.drawMatches(img1, kp1, img2, kp2.....),img1和kp1是指原图和关键点,img2和kp2是指匹配的图和其关键点。最后一个参数就是match()方法返回的匹配结果
import numpy as np
import cv2
img1 = cv2.imread('1.jpg')
img2 = cv2.imread('2.jpg')
g1 = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
#创建SIFT对象
sift = cv2.xfeatures2d.SIFT_create()
#进行检测,并计算描述子
kp1, des1 = sift.detectAndCompute(g1,None)
kp2, des2 = sift.detectAndCompute(g2,None)
bf = cv2.BFMatcher(cv2.NORM_L1)
match = bf.match(des1,des2)
img3 = cv2.drawMatches(img1,kp1,img2,kp2,match,None)
cv2.imshow('SIFT', img3)
cv2.waitKey(0)
cv2.destroyAllWindows()
7,FLANN特征匹配
在进行批量特征匹配时,FLANN速度更快。但是由于它使用的是邻近近似值,所以精度较差
使用步骤:
- 创建FLANN匹配器。FlannBasedMatcher(index_params)。第一个参数是index_params字典,主要填入的是匹配算法KDTREE或LSH.如果是SIFT和SURF计算的描述子则使用KDTREE算法,如果是ORB计算的描述子则使用LSH算法。如果使用了KDTREE算法则要设置第二个参数search_params字典,指定KDTREE算法中遍历树的次数。 由于要填入的参数是字典,所以KDTREE通常设置为index_params = dict(algorithm = FLANN_INDEX_KDTREE, tree = 5):FLANN_INDEX_KDTREE实际上代表1,填成1即可,填成0表示使用的是LSH算法,tree是经验值填5。serch_params = dict(checks = 50)
- 进行特征匹配。flann.match(...)或者是knnMatch(...)。knnMatch():第一二个参数为两张图象计算的描述子,第三个参数为k,表示取欧式距离最近的前k个关键点,即第一张图像中的任何一个描述子与第二张图像中的所有描述子匹配之后最接近的前k个。返回的是DMatch对象,DMatch中包括distance(描述子之间的距离,值越小说明近似度越高),queryIdx(第一个图像的描述子索引值),trainIdx(第二个图像的描述子索引值),imgIdx(第二幅图索引值)
- 绘制匹配点。cv2.drawMatches(...)或者是drawMatchesKnn(...)【如果特征匹配选择KnnMatch则用此方法】。drawMatchesKnn(...):参数与drawMatches(...)相同
import numpy as np
import cv2
img1 = cv2.imread('img1.jpg')
img2 = cv2.imread('img2.jpg')
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)
#创建匹配器
index_params = dict(algorithm = 1, trees = 5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
#对所有匹配点进行过滤优化
good = []
for i,(m,n) in enumerate(matches):
if m.distance < 0.7*n.distance:
good.append(m)
ret = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good], None)
cv2.imshow('ret', ret)
cv2.waitKey(0)
cv2.destroyAllWindows()
8,实例练习:图像查找
图像查找用到了 特征匹配+单应性矩阵。特征匹配得到结果后可以把它当作输入来计算单应性矩阵,拿到单应性矩阵再经过透视变换就可以获取到最终的图像了。当我们需要通过一张图去找另一张图时,通过以上步骤就可以找到
单应性矩阵:通过单应性矩阵可以让图像由一个视角转换到另一个视角
在上述FLANN文件下,只需要利用获取的匹配点matches计算单应性矩阵,然后利用单应性矩阵通过透视变换最后找到想找的图像
import numpy as np
import cv2
img1 = cv2.imread('img1.jpg')
img2 = cv2.imread('img2.jpg')
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)
#创建匹配器
index_params = dict(algorithm = 1, trees = 5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
#对所有匹配点进行过滤优化
good = []
for i,(m,n) in enumerate(matches):
if m.distance < 0.7*n.distance:
good.append(m)
#计算单应性矩阵H时,输入特征点必须大于等于4
if len(good) >= 4:
srcPts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)
dstPts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)
#找到单应性矩阵,cv2.RANSAC是对匹配点的过滤,过滤到错误匹配的点。
#该函数有两个返回值,第一个返回值H是需要的单应性矩阵,第二个参数是掩码,我们不需要所以用_代替即可
H, _ =cv2.findHomography(srcPts, dstPts, cv2.RANSAC, 5.0)
#透视变换
#pts是原图的四个角点
h, w = img1.shape[:2]
pts = np.float32([[0,0], [0, h-1], [w-1, h-1], [w-1, 0]]).reshape(-1,1,2)
dst = cv2.perspectiveTransform(pts, H)
#将找到的图像框起来
cv2.polylines(img2, [np.int32(dst)], True, (255,0,0))
else:
print('there are no matches')
exit()
ret = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good], None)
cv2.imshow('ret', ret)
cv2.waitKey(0)
cv2.destroyAllWindows()