文章目录
十、特征提取和特征匹配
10.1 特征提取
特征提取指的是使用计算机提取图像信息,决定每个图像的点是否属于一个图像特征。特征提取的结果是把图像上的点分为不同的子集,这些子集往往属于孤立的点、连续的曲线或者连续的区域。特征的好坏对泛化性能有至关重要的影响。
10.1.1 Harris角点检测
角点:抽象的说,角点就是极值点,即在某方面属性特别突出的点。在图像特征提取中,角点可以是两条线的交叉处,也可以是位于相邻的两个主要方向不同的事物上的点。
理解Harris角点检测的概念:
该算法基本思想是使用一个固定窗口在图像上进行任意方向上的滑动,比较滑动前与滑动后两种情况,窗口中的像素灰度变化程度,如果存在任意方向上的滑动,都有着较大灰度变化,那么我们可以认为该窗口中存在角点。结论公式:
import cv2
import numpy as np
img = cv2.imread("32.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)# 拿到图,转成灰度图,再将int变成float32的类型
dst = cv2.cornerHarris(gray, 2, 3, 0.04)
r'''
解释一下Harris算法的参数:
src :传入的灰度图
blockSize :角点检测中要考虑的邻域的大小
ksize :Sobel求导中使用的窗口大小
k :Harris角点检测方程中的自由参数,取值参数为[0,04,0.06]。
-----------------------------------------------------------------------
返回的其实就是R值构成的灰度图像。灰度图像坐标会与原图像对应。R值就是角点分数,
当R值很大的时候,就可以认为这个点是一个角点。
'''
dst = cv2.dilate(dst, cv2.getStructuringElement(cv2.MORPH_RECT, (8, 8)))
img[dst > 0.01 * dst.max()] = [0, 0, 255]
#这是利用布尔类型索引直接将大于某阈值的像素点标记为红色(找到的角点)。
cv2.imshow('dst', img)
cv2.waitKey(0)
此外还有亚像素级 的角点检测,也可以基于Harris检测,还需要用到cv2.cornerSubPix()函数 。有兴趣的可以深入了解。
10.1.2 Shi-Tomasi角点检测
可以理解成对上文算法的改进,即过滤掉一些不太好的角点,留下的肯定都是精华。
import cv2
import numpy as np
img = cv2.imread('32.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
corners = cv2.goodFeaturesToTrack(gray, 100, 0.01, 10)
r'''
第一个参数是灰度图,第二个参数是你想要检测到的角点数目。
第三个参数是设置角点的质量水平,0到1之间,它代表了角点的最低质量,
低于这个数的所有角点都会被忽略。第四个参数用于设置两个角点之间的最短欧式距离。
返回角点,个数=第二个参数
'''
corners = np.int0(corners)# float变成int,其shape=(第二个参数,1,2)
for i in corners:
x, y = i.ravel()# 类似flatten,拉直到1维,和flatten区别是尽量不会创建副本
cv2.circle(img, (x, y), 3, 255, 3)#描点
cv2.imshow("img", img)
cv2.waitKey(0)
10.1.3 SIFT算法和SURF算法
SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT),这是一个付费的算法! 慢
现在可以免费使用了,但 使用起来也比较费事
openCV降到3.4.2
pip install opencv-python==3.4.2.17
pip install opencv-contrib-python==3.4.2.17
import cv2
img = cv2.imread('33.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
kp = sift.detect(gray, None)
img = cv2.drawKeypoints(img, kp, None, color=(0, 0, 255))
cv2.imshow('img', img)
cv2.waitKey(0)
SURF,即加速稳健特征(Speeded Up Robust Features),这也是一个付费的算法!
import cv2
img = cv2.imread('32.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SURT_create()
kp = sift.detect(gray, None)
img = cv2.drawKeypoints(img, kp, None, color=(0, 0, 255))
cv2.imshow('img', img)
cv2.waitKey(0)
10.1.4 角点检测的FAST算法
FAST,就是《Features From Accelerated Segment Test》,不要问,问就是快,而且还没有专利,可以随便用。
核心:若某像素与其周围邻域内足够多的像素点相差较大,则该像素可能是角点。
在筛选出的候选角点中有很多是挨在一起的,需要通过非极大值抑制来消除这种影响
缺陷:过度依赖阈值。过大:找不到点。过小:找的点过多
具体来说,可以分3步走:
1、对固定半径圆上的像素进行分割测试,通过逻辑测试可以去处大量的非特征候选点;
2、基于分类的角点特征检测,利用ID3 分类器根据16个特征判决候选点是否为角点特征,每个特征的状态为-1,0,1。
3、利用非极大值抑制进行角点特征的验证。
虽然FAST算法很快,但是噪声对该算子的影响比较大,而且阈值 t 对算子的影响比较大。
import cv2
src = cv2.imread("33.jpg")
grayImg = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
fast = cv2.FastFeatureDetector_create(threshold=35)
# fast.setNonmaxSuppression(False)
r'''
该方法有三个参数:阈值,是否使用非极大值抑制(默认True),使用邻域大小。
其中邻域选项如下:
-----------------------------------------------------------------------------
cv2.FAST_FEATURE_DETECTOR_TYPE_5_8
cv2.FAST_FEATURE_DETECTOR_TYPE_7_12
cv2.FAST_FEATURE_DETECTOR_TYPE_9_16(默认这个)
-----------------------------------------------------------------------------
该方法返回的是一个对象,可以进行如下操作:
retval = fast.getNonmaxSuppression() #返回布尔型:是否使用非极大值抑制
None = fast.setNonmaxSuppression(bool) #设定非极大值抑制bool型
retval = fast.getThreshold() #返回阈值
None = fast.setThreshold(threshold) #设定阈值
'''
kp = fast.detect(grayImg, None)
img2 = cv2.drawKeypoints(src, kp, None, (0, 0, 255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
print('Threshold: ', fast.getThreshold())
print('nonmaxSuppression: ', fast.getNonmaxSuppression())
print('neighborhood: ', fast.getType())
print('Total Keypoints with nonmaxSuppression: ', len(kp))
#
cv2.imshow('fast_true', img2)
#
# fast.setNonmaxSuppression(False)
# kp = fast.detect(grayImg, None)
#
# print('Total Keypoints without nonmaxSuppression: ', len(kp))
#
# img3 = cv2.drawKeypoints(src, kp, None, (0, 0, 255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
r'''
cv2.drawKeypoints()参数详解:
第一个参数是原图。第二个参数是上面得到的关键点keypoints。
第三个参数是输出图像,此处是None,后面解释。第四个参数是用什么颜色描点(B,G,R)
最后一个是绘图功能的标识设置,在Python里面标识有这么多:
---------------------------------------------------------------------------------------
cv2.DRAW_MATCHES_FLAGS_DEFAULT=>创建输出图像矩阵,使用现存的输出图像
绘制匹配对和特征点,对每一个关键点只绘制中间点。
cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG=>不创建输出图像矩阵,而是在输出图像上绘制匹配对。
cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS=>单点的特征点不被绘制。
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS=>对每一个特征点绘制带大小和方向的关键点图形。
---------------------------------------------------------------------------------------
该方法返回一个img,就是结果了。
'''
# cv2.imshow('fast_false', img3)
cv2.waitKey()
10.1.5 BRIEF算法
收费
10.1.6 ORB算法
ORB=FAST+BRIEF,没错,强强联手。往细里讲,ORB=(Oriented FAST)+(Rotated BRIEF)。然后,SURF和SIFT都有专利保护,而ORB并没有。ORB 的特点是速度超快,而且在一定程度上不受噪点和图像变换的影响,例如旋转和缩放变换等。
import cv2
img = cv2.imread("33.jpg")
grayImg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
orb = cv2.ORB_create()
r'''
函数参数及其默认值如下:
cv2.ORB_create(nfeatures = 500, => 想要检测到的特征点数目
scaleFactor = 1.2, => 金字塔抽取率,必须大于1,越大特征数量越少
nlevels = 8, => 金字塔层数
edgeThreshold = 31, => 边缘阈值大小,应≥patchSize参数。
firstLevel = 0, => 确定哪一层应该是金字塔的第一层,默认0层
WTA_K = 2, => 表示一次选择多少个随机像素来比较它们的亮度
scoreType = cv2.ORB_HARRIS_SCORE, => HARRIS_SCORE或FAST_SCORE。
patchSize = 31, => 感知图像区域的大小
fastThreshold = 20) =>FAST阈值个数
scoreType此参数可以设置为cv2.ORB_HARRIS_SCORE或cv2.ORB_FAST_SCORE。默认的HARRIS分数表示使用HARRIS角点算法对特征进行排序,分数只用来保留最好的特征。FAST_SCORE生成的关键点稍微不太稳定,但计算起来要快一点。
和FAST一样,返回的是一个对象
'''
# kp = orb.detect(grayImg, None)
# kp, des = orb.compute(grayImg, kp)
# 这两个方法在4.1.1版本中已经合为一体了:
kp = orb.detect(grayImg, None)
kp, des = orb.compute(grayImg, kp)
r'''
该方法用于寻找关键点并计算特征值。第一个参数是灰度图,第二个参数是掩码,没有也要写None。
方法返回关键点和特征值
'''
img2 = cv2.drawKeypoints(img, kp, None, color=(0, 0, 255), flags=0)
cv2.imshow("img", img2)
cv2.waitKey(0)
10.2 特征匹配
上面各种方法寻找特征点,目的就是为了进行特征匹配。特征匹配的目的则是为了识别和侦测。
10.2.1 BF算法
BF算法,即暴风(Brute Force)算法,是普通的模式匹配算法,也是一种蛮力算法。
首先在第一幅图像中选取一个关键点然后依次与第二幅图像的每个关键点进行距离测试,最后返回距离最近的关键点。
import cv2
img1 = cv2.imread('34.jpg') # 需要找特征的图片
grayImg1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2 = cv2.imread('33.jpg')# 有特征的图片
grayImg2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 将图片转成灰度图
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(grayImg1, None)
kp2, des2 = orb.detectAndCompute(grayImg2, None)
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
r'''
这里调用BF算法,有两个参数
------------------------------------------------------------------------------------
normType:[cv2.NORM_L1 | cv2.NORM_L2 | cv2.NORM_HAMMING | cv2.NORM_HAMMING2]
它是用来指定要使用的距离测试类型。默认值为cv2.Norm_L2,这很适合SIFT和SURF等(c2.NORM_L1也可)。对于使用二进制描述符的ORB、BRIEF和BRISK算法等,要使用cv2.NORM_HAMMING,
这样就会返回两个测试对象之间的汉明距离。如果ORB算法的参数设置为WTA_K==3或4,normType就应
该设置成cv2.NORM_HAMMING2。
------------------------------------------------------------------------------------
crossCheck:针对暴力匹配,可以使用交叉匹配的方法来过滤错误的匹配。默认值为False。
如果设置为True,匹配条件就会更加严格,只有到A中的第i个特征点与B中的第j个特征点距离最近,
并且B中的第j个特征点到A中的第i个特征点也是最近时才会返回最佳匹配(i,j),即这两个特征点
要互相匹配才行。
------------------------------------------------------------------------------------
'''
matches = bf.match(des1, des2) # 返回最佳匹配
r'''
matches是DMatch对象,具有以下属性:
DMatch.distance - 描述符之间的距离,越低越好。
DMatch.trainIdx - 训练描述符中描述符的索引
DMatch.queryIdx - 查询描述符中描述符的索引
DMatch.imgIdx - 训练图像的索引。
'''
matches = sorted(matches, key=lambda x: x.distance)# 先(根据距离)进行一个排序
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=2)
r'''
用cv2.drawMatches()来绘制匹配的点,它会将两幅图像先水平排列,然后在最佳匹配的点之间绘制直线。
分析一下这个方法的参数:
前面四个参数[img1, kp1, img2, kp2]分别是特征图和特征图的关键点、原图和原图对应的关键点。
第五个参数是将两幅图对应的关键点匹配起来。本例中先排序了,然后取了前20个特征点进行匹配。
第六个参数是outImg,没有也要写None。
最后一个参数flags在【特征提取】的:角点检测的FAST算法"的代码中有详细介绍...
'''
cv2.imshow("img",img3)
cv2.waitKey(0)
10.2.2 FLANN匹配算法
上面的蛮力算法可想而知是比较慢的,于是有了FLANN匹配算法。把KD-Tree用进匹配算法。
快速最近邻搜索包(Fast_Library_for_Approximate_Nearest_Neighbors)在大数据集上比BF算法优秀。
import cv2
img1 = cv2.imread('34.jpg')
grayImg1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2 = cv2.imread('33.jpg')
grayImg2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
detector = cv2.xfeatures2d.SIFT_create()
kp1, des1 = detector.detectAndCompute(grayImg1, None)
kp2, des2 = detector.detectAndCompute(grayImg2, None)
matcher = cv2.DescriptorMatcher_create(cv2.DescriptorMatcher_FLANNBASED)
matches = matcher.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.7 * n.distance:
matchesMask[i] = [1, 0]
draw_params = dict(matchColor=(0, 255, 0), singlePointColor=(255, 0, 0), matchesMask=matchesMask, flags=0)
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, matches, None, **draw_params)
cv2.imshow("img", img3)
cv2.waitKey(0)
10.3 特征匹配和单应性查找对象
我们把特征都匹配到了,达到了上面说的识别和侦测,那我们可以把它找出来,比如用个矩形框提示出来,这就要用到单应性(Homography)查找方法了。平面的单应性被定义为从一个平面到另一个平面的投影映射。比如,一个二维平面上的点映射到摄像机成像仪上的映射就是平面单应性的例子。
这里只放部分核心代码:
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
r'''
第一个参数是原图上的点,第二个参数是特征图上的点。要float32类型,reshape(-1, 1, 2)。
第三个参数是计算单应性矩阵的选项:
------------------------------------------------------------
0 => 使用所有点的常规方法,即最小二乘法
cv2.RANSAC => 基于RANSAC的稳健方法
cv2.LMEDS => 最小中值稳健方法
cv2.RHO => 基于PROSAC的稳健方法
------------------------------------------------------------
第四个参数取值范围在1到10,是一个阈值。原图像的点经过变换后点与目标图像上对应点的误差
超过参数就不要了。
返回值中M为变换矩阵,mask是掩码
'''
matchesMask = mask.ravel().tolist() # 将数组拉直成1维并转成list
h, w = grayImg1.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) # 透视变换操作
img2 = cv2.polylines(img2, [np.int32(dst)], True, 255, 3, cv2.LINE_AA) # 画多边形
r'''
cv2.LINE_AA则是反锯齿,反锯齿在画曲线时看起来会更平滑。
'''
# 最后展示结果图片...