先导文章:SIFT特征提取
图像匹配
获得两张图像的关键点之后,下一步就是找到它们之间的对应关系,找到那些相匹配的点,从而基于这些点,实现图像拼接。在OpenCV中,我们可以用于Feature Matching的方法有两种:Brute-Force Matcher 和 FLANN Matcher。
Brute-Force Matcher
这一方法即暴力搜索法,它选择第一个集合中一个特征的描述符,计算与第二个集合中的所有其他特征描述符之间的距离,返回最接近的一个。
cv.BFMathcer()
对于BF Matcher,我们首先使用cv.BFMatcher()创建BFMatcher对象,该方法需要两个可选参数:
- normType:指定距离测量方法,默认为cv.NORM_L2,适合SIFT、SURF。cv.NORM_HAMMING则是更适合ORB(WTA_K == 3 or 4)、BRIEF、BRISK等二进制串描述符。下面列举一些距离方法,参考链接为:OpenCV: Operations on arrays
- cv.NORM_INF
- cv.NORM_L1
- cv.NORM_L2
- cv.NORM_L2SQR
- cv.NORM_HAMMING
- cv.NORM_HAMMING2
- crossCheck:这是一个布尔型的参数,默认为False。当crossCheck为True时,该方法仅返回那些互为最佳匹配的描述符下标(i,j)。
BFMatcher.match()
创建BFMatcher对象之后,使用BFMatcher.match()方法获得最佳匹配。更进一步地,可以使用BFMatcher.knnMatch()方法返回 k 个最佳匹配,其中 k 由用户指定。
该方法返回的结果是DMatch对象的列表。一个DMatch对象具有如下属性:
- DMatch.distance - 描述符之间的距离。越低越好。
- DMatch.trainIdx - 训练描述符描述符的索引。
- DMatch.queryIdx - 查询描述符中描述符的索引。
- DMatch.imgIdx - 训练图像的索引。
cv.drawMatches()
该方法被用于绘制关键点的匹配情况。我们看到的许多匹配结果都是使用这一方法绘制的——一左一右两张图像,匹配的关键点之间用线条链接。
函数原型:
cv.drawMatches( img1, keypoints1, img2, keypoints2, matches1to2, outImg[, matchColor[, singlePointColor[, matchesMask[, flags]]]]) -> outImg
cv.drawMatches( img1, keypoints1, img2, keypoints2, matches1to2, outImg, matchesThickness[, matchColor[, singlePointColor[, matchesMask[, flags]]]] ) -> outImg
cv.drawMatchesKnn( img1, keypoints1, img2, keypoints2, matches1to2, outImg[, matchColor[, singlePointColor[, matchesMask[, flags]]]]) -> outImg
参数:
- img1:第一张原始图像。
- keypoints1:第一张原始图像的关键点。
- img2:第二张原始图像。
- keypoints2:第二张原始图像的关键点。
- matches1to2:从第一个图像到第二个图像的匹配,这意味着keypoints1[i]在keypoints2[Matches[i]中有一个对应的点。
- outImg:绘制结果图像。
- matchColor:匹配连线与关键点点的颜色,当matchColor==Scalar::all(-1) 时,代表取随机颜色。
- singlePointColor:没有匹配项的关键点的颜色,当singlePointColor==Scalar::all(-1) 时,代表取随机颜色。
- matchesMask:确定绘制哪些匹配项的掩码。如果掩码为空,则绘制所有匹配项。
- flags:绘图功能的一些标志。具体有:
- cv.DRAW_MATCHES_FLAGS_DEFAULT
- cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
- cv.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG
- cv.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS
使用SIFT描述符的具体实现
# Brute-Force Matching
def bf_match(img_path1, img_path2):
# 读取两张图像
img1 = cv2.imread(img_path1, cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread(img_path2, cv2.IMREAD_GRAYSCALE)
# 计算两张图像的SIFT描述符
kp1, des1, _ = sift_algorithm(img_path1)
kp2, des2, _ = sift_algorithm(img_path2)
# 创建BFMatcher实例
bf = cv2.BFMatcher()
# 获得最佳匹配
# matches = bf.match(des1, des2)
matches = bf.knnMatch(des1, des2, k=2)
# 使用比率检测,筛选出好的匹配
good = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good.append([m])
# 绘制匹配结果
# matches = sorted(matches, key = lambda x:x.distance)
# match_result = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
match_result = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 显示绘制结果
plt.imshow(match_result)
plt.show()
return match_result
bf_match(images_list[0], images_list[1])
匹配结果
FLANN based Matcher
FLANN,即近似最近邻的快速库(Fast Library for Approximate Nearest Neighbors)。
它包含一系列算法,这些算法针对大型数据集中的快速最近邻搜索(KD树)和高维特征进行了优化。对于大型数据集,它比BFMatcher工作得更快。
cv2.FlannBasedMatcher()
对于基于 FLANN 的匹配器,我们使用cv2.FlannBasedMatcher()创建FLANN匹配器对象,该方法需要传递两个字典来指定要使用的算法及其相关参数等。
- 第一个是 IndexParams,它指定最近邻搜索中的KD树算法。
- 对于SIFT,SURF,该信息可以是:
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
-
- 对于ORB,该信息可以是:
FLANN_INDEX_LSH = 6
index_params= dict(algorithm = FLANN_INDEX_LSH,
table_number = 6,
key_size = 12,
multi_probe_level = 1)
- 第二个是 SearchParams,它指定递归遍历索引中的树的次数。值越高,精度越高,但也需要更多时间。
search_params = dict(checks=100)
使用SIFT描述符的具体实现
# FLANN Matching
def flann_match(img_path1, img_path2):
# 读取两张图像
img1 = cv2.imread(img_path1, cv2.IMREAD_GRAYSCALE) # queryImage
img2 = cv2.imread(img_path2, cv2.IMREAD_GRAYSCALE) # trainImage
# 计算两张图像的SIFT描述符
kp1, des1, _ = sift_algorithm(img_path1)
kp2, des2, _ = sift_algorithm(img_path2)
# 设置FLANN所需要的两个字典
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
# 创建FlannBasedMatcher对象
flann = cv2.FlannBasedMatcher(index_params, search_params)
# 获得匹配
matches = flann.knnMatch(des1, des2, k=2)
# 为了只绘制好的匹配,这里使用掩码
matches_mask = [[0,0] for i in range(len(matches))]
# 使用比率检测,筛选出好的匹配
for i, (m, n) in enumerate(matches):
if m.distance < 0.7 * n.distance:
matches_mask[i] = [1, 0]
# 绘制匹配结果,这里使用一个字典传递参数
draw_params = dict(matchColor=(0, 255, 0),
singlePointColor=(255, 0, 0),
matchesMask=matches_mask,
flags=cv2.DrawMatchesFlags_DEFAULT)
match_result = cv2.drawMatchesKnn(img1, kp1, img2, kp2, matches, None, **draw_params)
# 显示绘制结果
plt.imshow(match_result)
plt.show()
return match_result
flann_match(images_list[0], images_list[1])
匹配结果