OpenCV 特征提取与描述

1. 什么是特征

图像中的小区域,向周围小范围移动时变化最大,即特征。寻找到这些特征的过程叫做特征检测(Feature Detection)。
比如一个白色背景的矩形图像,位于四个角落的小区域是特征点,位于边线上的次要特征,位于纯色区域的没有特征

2. Harris 角落检测

寻找在全方向的(u,v)位移的亮度变化,形成函数,使用泰勒展开,推为矩阵M。创建一个分数R,取决于矩阵M的两个特征值之间的相对大小关系,判断为平区域、边界、角点

R = det(M) - k(trace(M))2

其中

  • det(M) = λ1λ2
  • trace(M) = λ1 + λ2
  • λ1和λ2是M的特征值

代码使用cv.cornerHarris()

  • img -输入图像,灰度float32
  • blockSize -角点检测考虑的邻域大小
  • kSize -Sobel 微分使用的Aperture参数
  • k -Harris检测公式中的自由参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy as np
import cv2 as cv

filename = 'calibresult.png'
img = cv.imread(filename)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)

gray = np.float32(gray)
dst = cv.cornerHarris(gray,2,3,0.1)

#result is dilated for marking the corners, not important
dst = cv.dilate(dst,None)

# Threshold for an optimal value, it may vary depending on the image.
img[dst>0.01*dst.max()]=[0,0,255]

cv.imshow('dst',img)
if cv.waitKey(0) & 0xff == 27:
cv.destroyAllWindows()

如果要获得亚像素精度的焦点,使用cv.cornerSubPix()

1
2
3
4
5
6
# find centroids
ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)

# define the criteria to stop and refine the corners
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)

3. Shi-Tomasi 角点检测 Good Features to Track

将分数定义为了

R = min(λ1, λ2)

若R大于某个阈值,则认为是角点。

使用cv.goodFeaturesToTrack()

  • 输入图像
  • 需要寻找的角点数目
  • 0-1之间的质量等级
  • 角点之间的最小欧式距离
    1
    2
    3
    4
    5
    6
    corners = cv.goodFeaturesToTrack(gray,250,0.01,20)
    corners = np.int0(corners)

    for i in corners:
    x,y = i.ravel()
    cv.circle(img,(x,y),3,255,-1)

4. SIFT 尺度不变特征转换

小窗口中的角点图被放大后,用同样大的窗口观察看起来变得平滑了。SIFT中,分别进行尺度空间极值检测、关键点定位、方向赋值、关键点描述、关键点匹配。这个算法在2020年专利已经到期,可放心使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

filename = 'images/home.jpg'
img = cv.imread(filename)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)

sift = cv.SIFT_create()
kp = sift.detect(gray,None)

img=cv.drawKeypoints(gray,kp,img)

cv.imwrite('sift_keypoints.jpg',img)

sift.detect()可以输入mask指定寻找区域
cv.drawKeyPoints()用于绘制关键点的圆圈,如果传入flag=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,会绘制关键点直径的圆圈并显示其方向。

1
img=cv.drawKeypoints(gray,kp,img,flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

计算特征描述(Descriptor)

  1. 已知关键点kp,用sift.compute(),``kp, des = sift.compute(gray, kp)
  2. 一步到位,用sift.detectdAndCompute()
    1
    2
    sift = cv.SIFT_create()
    kp, des = sift.detectAndCompute(gray,None)
    kp是关键点列表,des是numpy数组形状是(关键点数)*128

通常获得了关键点和描述,我们就可以在之后的操作中匹配不同图片中的关键点了。

5. SURF 快速鲁棒特征

SURF在每个步骤增加了很多特征,达到同样效果比SIFT快3倍,适用于带有模糊和旋转的图片,但不适用视角转变和光线变化的情况。

如何在OpenCV中还处于专利保护阶段,要想使用,需要卸载当前高版本,重新安装opencv-contrib-python==3.4.2.17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 寻找SURF关键点和描述符并绘制
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('images/fly.png', 0)

# 创建SURF对象,可以在创建时指定参数也可以稍后设置参数
# 此处设置 Hessian阈值为 400
sift = cv2.xfeatures2d.SIFT_create()
print('sift: ', sift)
surf = cv2.xfeatures2d.SURF_create(400)
print('surf: ', surf,
' \ndefaultParameter\thessianThreshold: ', surf.getHessianThreshold(),
' upright: ', surf.getUpright(),
' extended: ', surf.getExtended(),
' descriptors: ',surf.descriptorSize())

# 寻找SURF关键点和描述符
# kp:返回的关键点列表,des:numpy数组
kp, des = surf.detectAndCompute(img, None)
# 绘制关键点在图片上
img2 = cv2.drawKeypoints(img, kp, None, (255, 0, 0), 4)
plt.imshow(img2), plt.xticks([]), plt.yticks([]), plt.title('more keypoints'), plt.show()

print('keypoints: ', len(kp))

# 检查当前Hessian阈值
# print(surf.getHessianThreshold())

# 调整Hessian阈值,此处设置为50000,但一般最佳设置为300~500
surf.setHessianThreshold(50000)
print(' parameters\thessianThreshold: ', surf.getHessianThreshold(),
' upright: ', surf.getUpright(),
' extended: ', surf.getExtended(),
' descriptors: ',surf.descriptorSize())

# 再一次计算关键点和描述符
kp, des = surf.detectAndCompute(img, None)

print('keypoints: ', len(kp))

# 绘制关键点在图片上
img2 = cv2.drawKeypoints(img, kp, None, (255, 0, 0), 4)

plt.imshow(img2), plt.xticks([]), plt.yticks([]), plt.title('less than 50 keypoints'), plt.show()

# U-SURF不会计算方向
# print(surf.getUpright())
surf.setUpright(True)
print(' parameters\thessianThreshold: ', surf.getHessianThreshold(),
' upright: ', surf.getUpright(),
' extended: ', surf.getExtended(),
' descriptors: ',surf.descriptorSize())

# 重新计算关键点和描述符,并绘制
kp = surf.detect(img, None)
print('keypoints: ', len(kp))
img2 = cv2.drawKeypoints(img, kp, None, (255, 0, 0), 4)

plt.imshow(img2), plt.xticks([]), plt.yticks([]), plt.title('U-SURF'), plt.show()

# 所有方向显示在同一方向,它比以前快多了。如果您正在处理方向不成问题的情况(如全景缝合)等,使用U-SURF会更好。
# 寻找描述符的大小
# print(surf.descriptorSize())
# extended为false,默认为64D
# print(surf.getExtended())

# 设置描述符为128D
surf.setExtended(True)
print(' parameters\thessianThreshold: ', surf.getHessianThreshold(),
' upright: ', surf.getUpright(),
' extended: ', surf.getExtended(),
' descriptors: ',surf.descriptorSize())

kp, des = surf.detectAndCompute(img, None)
print('keypoints: ',len(kp))
img2 = cv2.drawKeypoints(img, kp, None, (255, 0, 0), 4)

plt.imshow(img2), plt.xticks([]), plt.yticks([]), plt.title('128D res'), plt.show()

代码来自这

6. FAST 快速角点检测方法

比前几种方法快几倍,但对高噪音不鲁棒。有一个阈值参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('images/blox.jpg', cv.IMREAD_GRAYSCALE) # `<opencv_root>/samples/data/blox.jpg`

# Initiate FAST object with default values
fast = cv.FastFeatureDetector_create()

# find and draw the keypoints
kp = fast.detect(img,None)
img2 = cv.drawKeypoints(img, kp, None, color=(255,0,0))

# Print all default params
print( "Threshold: {}".format(fast.getThreshold()) )
print( "nonmaxSuppression:{}".format(fast.getNonmaxSuppression()) )
print( "neighborhood: {}".format(fast.getType()) )
print( "Total Keypoints with nonmaxSuppression: {}".format(len(kp)) )

cv.imwrite('fast_true.png', img2)

# Disable nonmaxSuppression
fast.setNonmaxSuppression(0)
kp = fast.detect(img, None)

print( "Total Keypoints without nonmaxSuppression: {}".format(len(kp)) )

img3 = cv.drawKeypoints(img, kp, None, color=(255,0,0))

cv.imwrite('fast_false.png', img3)

7. BRIEF 二元鲁棒独立基本特征

一种更快的特征描述与匹配方法,需要使用其他的方法检测到关键点,适用于CenSurE(STAR)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('images/aero1.jpg', cv.IMREAD_GRAYSCALE)

# Initiate FAST detector
fast = cv.xfeatures2d.StarDetector_create()

# Initiate BRIEF extractor
brief = cv.xfeatures2d.BriefDescriptorExtractor_create()

# find the keypoints with STAR
kp = fast.detect(img,None)


# compute the descriptors with BRIEF
kp, des = brief.compute(img, kp)

print( brief.descriptorSize() )
print( des.shape )

8. ORB(Oriented FAST and Rotated BRIEF)

没有专利,安全使用,更快更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('images/blox.jpg', cv.IMREAD_GRAYSCALE)

# Initiate ORB detector
orb = cv.ORB_create()

# find the keypoints with ORB
kp = orb.detect(img,None)

# compute the descriptors with ORB
kp, des = orb.compute(img, kp)

# draw only keypoints location,not size and orientation
img2 = cv.drawKeypoints(img, kp, None, color=(0,255,0), flags=0)
plt.imshow(img2), plt.show()

具体使用直接看官方教程