关键点匹配与单应性变换

所谓关键点匹配是指寻找两幅图像之间的特征像素点的对应关系,从而确定两幅图像的位置关系。

关键点匹配

OpenCV提供了匹配函数在两个图像之间进行关键点匹配。最常用的匹配函数是Brute-Force。

1
2
cv.BFMatcher(normType = cv.NORM_L2,
crossCheck = false)

参数:

  • normType
    确定用于确定匹配质量的度量标准。默认情况下,normType = cv.NORM_L2,它用于测量两个描述子之间的距离。但是,对于像ORB创建的二进制描述子一样,汉明度量更合适。汉明度量通过计算二进制描述子之间的不相似位的数量来确定距离。当使用WTA_K = 2创建ORB描述子时,要选择两个随机像素并在亮度上进行比较。最亮像素的索引返回为0或1。此类输出仅占用1位,因此应使用cv.NORM_HAMMING 度量。另一方面,如果使用WTA_K = 3创建ORB描述子,则选择三个随机像素并在亮度上进行比较。最亮像素的索引返回0、1或2。这样的输出将占用2位,因此应该使用汉明距离的特殊变体,即cv.NORM_HAMMING2(2代表2位)。然后,对于所选择的任何度量,当比较训练和查询图像中的关键点时,具有较小度量(它们之间的距离)的对被认为是最佳匹配。
  • crossCheck
    布尔变量,可以设置为TrueFalse。交叉验证对于消除错误匹配非常有用。交叉验证通过执行两次匹配过程来完成。第一次匹配中,将训练图像中的关键点与查询图像中的关键点进行比较;第二次匹配中,将查询图像中的关键点与训练图像中的关键点进行比较(即,反向进行比较)。启用交叉验证时,只有当训练图像中的关键点A是查询图像中关键点B的最佳匹配时,该匹配才被视为有效,反之亦然(即,如果查询图像中的关键点B是训练图像中的关键点A,该匹配则是最佳匹配)。

设置了BFMatcher的参数之后,我们就可以使用.match(descriptors1, descriptors2)方法,使用它们的ORB描述子找到训练和查询图像之间的匹配关键点。最后,我们将使用cv.drawMatches ()函数来可视化Brute-Force匹配程序找到的匹配关键点。此函数会水平堆叠训练和查询图像,并将训练图像中关键点的线条绘制到查询图像中对应的最佳匹配关键点。
选择ORB描述子作为关键点是因为它具有如下几个特征:

1、尺度不变性

这意味着无论图像大小如何,都能够检测图像中的对象。

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
# coding = utf-8
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from mahotas.features import surf

# 导入资源
image = cv.imread('123.jpg')
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)

img_resize = cv.resize(image, (0, 0), fx=0.5, fy=0.5, interpolation=cv.INTER_NEAREST)
img_resize = cv.cvtColor(img_resize, cv.COLOR_BGR2RGB)
gray_resize = cv.cvtColor(img_resize, cv.COLOR_RGB2GRAY)

# 检测角点
orb = cv.ORB_create(200, 2.0)
keypoints1, desc1 = orb.detectAndCompute(gray, None)
keypoints2, desc2 = orb.detectAndCompute(gray_resize, None)

# 角点配对
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck = True)
matches = bf.match(desc1, desc2)
matches = sorted(matches, key = lambda x : x.distance)
result = cv.drawMatches(gray, keypoints1, gray_resize, keypoints2, matches[:int(len(matches)/2)], gray_resize, flags = 2)

plt.imshow(result)
plt.show()

2、旋转不变性

这意味着无论方向如何,都能够检测到图像中的对象。

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
# coding = utf-8
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from mahotas.features import surf

def rotate_bound(image, angle):
'''
图像旋转
INPUT -> 图像数组, 旋转角度(顺时针)
'''
(h, w) = image.shape[:2]

# 旋转中心点
(cX, cY) = (w // 2, h // 2)

# 获得旋转矩阵
M = cv.getRotationMatrix2D((cX, cY), -angle, 1.0)

cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])

# 计算图像新外接框
nW = int((h * sin) + (w * cos))
nH = int((h * cos) + (w * sin))

M[0, 2] += (nW / 2) - cX
M[1, 2] += (nH / 2) - cY

return cv.warpAffine(image, M, (nW, nH))

# 导入资源
image = cv.imread('123.jpg')
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)

img_rotate = rotate_bound(image, 45)
img_rotate = cv.cvtColor(img_rotate, cv.COLOR_BGR2RGB)
gray_rotate = cv.cvtColor(img_rotate, cv.COLOR_RGB2GRAY)

# 检测角点
orb = cv.ORB_create(200, 2.0)
keypoints1, desc1 = orb.detectAndCompute(gray, None)
keypoints2, desc2 = orb.detectAndCompute(gray_rotate, None)

# 角点配对
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck = True)
matches = bf.match(desc1, desc2)
matches = sorted(matches, key = lambda x : x.distance)
result = cv.drawMatches(gray, keypoints1, gray_rotate, keypoints2, matches[:int(len(matches)/2)], gray_rotate, flags = 2)

plt.imshow(result)
plt.show()

3、光照不变性

这意味着无论光照如何,都能够检测到图像中的物体。

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
# coding = utf-8
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from mahotas.features import surf

# 导入资源
image = cv.imread('123.jpg')
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)

image2 = cv.imread('1232.jpg')
image2 = cv.cvtColor(image2, cv.COLOR_BGR2RGB)
gray2 = cv.cvtColor(image2, cv.COLOR_RGB2GRAY)

# 检测角点
orb = cv.ORB_create(200, 2.0)
keypoints1, desc1 = orb.detectAndCompute(gray, None)
keypoints2, desc2 = orb.detectAndCompute(gray2, None)

# 角点配对
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck = True)
matches = bf.match(desc1, desc2)
matches = sorted(matches, key = lambda x : x.distance)
result = cv.drawMatches(gray, keypoints1, gray2, keypoints2, matches[:int(len(matches)/2)], gray2, flags = 2)

plt.imshow(result)
plt.show()

4、抗噪性

这意味着即使图像具有一定程度的噪声,也能够检测到图像中的对象。

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
# coding = utf-8
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from mahotas.features import surf

# 导入资源
image = cv.imread('123.jpg')
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)

image2 = cv.imread('1233.jpg')
image2 = cv.cvtColor(image2, cv.COLOR_BGR2RGB)
gray2 = cv.cvtColor(image2, cv.COLOR_RGB2GRAY)

# 检测角点
orb = cv.ORB_create(200, 2.0)
keypoints1, desc1 = orb.detectAndCompute(gray, None)
keypoints2, desc2 = orb.detectAndCompute(gray2, None)

# 角点配对
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck = True)
matches = bf.match(desc1, desc2)
matches = sorted(matches, key = lambda x : x.distance)
result = cv.drawMatches(gray, keypoints1, gray2, keypoints2, matches[:int(len(matches)/2)], gray2, flags = 2)

plt.imshow(result)
plt.show()

单应性变换

关键点匹配是实现单应性变换的基础。
所谓单应性变换,指的是将一个平面内的点映射到另一个平面(这里的平面是指图像或者三维中的平面表面)内的二维投影变换,对应的变换矩阵被称为单应性矩阵(Homography)

单应性变换具有很强的实用性,它在图像矫正、视角变换、图像拼接、相机位姿估计、视觉SLAM等领域有非常重要的作用。

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
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from mahotas.features import surf

# 导入资源
image = cv.imread('1.jpg')
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)

image2 = cv.imread('2.jpg')
image2 = cv.cvtColor(image2, cv.COLOR_BGR2RGB)
gray2 = cv.cvtColor(image2, cv.COLOR_RGB2GRAY)

# 检测角点
orb = cv.ORB_create(200, 2.0)
keypoints1, desc1 = orb.detectAndCompute(gray, None)
keypoints2, desc2 = orb.detectAndCompute(gray2, None)

# 角点配对
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck = True)
matches = bf.match(desc1, desc2)
# 按照相近程度进行排序
matches = sorted(matches, key = lambda x : x.distance)
goodmatches = matches[:int(len(matches)/2)]

MIN_MATCH_COUNT = 10
if len(goodmatches) > MIN_MATCH_COUNT:
src_pts = np.float32([keypoints1[m.queryIdx].pt for m in goodmatches]).reshape(-1, 1, 2)
dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in goodmatches]).reshape(-1, 1, 2)
H, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)

print(H)
0%