图像特征匹配在计算机视觉领域有广泛的应用,其中包括目标跟踪、三维重建、图像拼接、图像检索以及基于内容的图像推荐等。在目标跟踪中,特征匹配可以用于在连续帧中定位目标;在三维重建中,可以通过匹配不同视角的图像特征来恢复相机的姿态和移动轨迹,进而重建目标或场景的三维结构;在图像拼接中,特征匹配可以用于将多张有关联的图像拼接在一起;在图像检索和基于内容的图像推荐中,特征匹配可以用于寻找与给定图像特征相似的图像。
匹配方法有多种,其中包括基于灰度的匹配、基于特征的匹配和基于模型的匹配。
这里我们主要介绍归一化平方差匹配法。
归一化平方差匹配法是一种图像处理中的匹配方法。它基于图像和模板之间的像素差的平方和进行匹配,但在计算过程中会进行归一化处理。
其核心思想是,当模板与图像的某个区域匹配时,它们之间的差的平方和应该接近0。而与模板匹配程度较差的区域,其差的平方和会相对较大。为了使得匹配过程对亮度和对比度变化具有鲁棒性,该方法进行了归一化处理。
下面是该代码的实现:
import cv2
# 读取图像和模板
img = cv2.imread('cat.jpeg', 0)
template = cv2.imread('template.jpeg', 0)
# 计算SAD值
result = cv2.matchTemplate(img, template, cv2.TM_SQDIFF_NORMED)
# 获取最小值和最大值的位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
# 在原始图像中绘制矩形
w, h = template.shape[::-1]
top_left = min_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(img, top_left, bottom_right, 255, 2)
# 显示结果
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果如下:
基于特征的匹配算法是计算机视觉领域中的一种重要算法。
这种算法首先从图像中提取出具有显著性和不变性的特征点和描述子,然后根据描述子之间的相似性来寻找最佳匹配对。
常用的基于特征的匹配算法包括SIFT、SURF和ORB等。
这些算法通过设计一种旋转不变和尺度不变(或近似不变)的描述子来表示特征点周围区域的信息,并采用暴力搜索或近似搜索(如FLANN)来加速查找过程。
在处理过程中,这类算法对于光照变化、形变和遮挡等因素具有一定的鲁棒性。
基于特征的匹配方法的主要优点是,它们能够处理图像之间的旋转、缩放和其他几何变换,因此在许多计算机视觉任务中都有广泛应用。
然而,它们也可能在存在噪声、遮挡或视角变化较大的情况下失效。
以下是我们实现的,基于ORB特征的特征匹配算法:
import cv2
import numpy as np
# 读取目标图像和模板图像
img = cv2.imread('cat.jpeg', cv2.IMREAD_GRAYSCALE)
template = cv2.imread('template.jpeg', cv2.IMREAD_GRAYSCALE)
# 特征描述子
orb = cv2.ORB_create()
keypoints_img, descriptors_img = orb.detectAndCompute(img, None)
keypoints_template, descriptors_template = orb.detectAndCompute(template, None)
# 特征匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(descriptors_img, descriptors_template)
matches = sorted(matches, key=lambda x: x.distance) # 按照距离排序
# 绘制匹配的特征点
result = cv2.drawMatches(img, keypoints_img, template, keypoints_template, matches[:10], None, flags=2)
# 显示匹配结果
cv2.imshow('Feature Matching', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果如下:
RANSAC(Random Sample Consensus,随机采样一致性)算法是一种用于从一组观察数据集中估计数学模型参数的迭代方法。它假设观察数据包含异常值,所以目标是找到可以拟合大部分数据点的参数,即该模型可能只适应很小的一部分观察值。在图像处理和计算机视觉中,RANSAC算法经常用于解决诸如点对应、单应性估计等问题。
在找到模板在图像中的位置这个问题上,RANSAC的算法原理如下:
因此,通过RANSAC算法,我们可以找到一个最佳的单应性矩阵,这个矩阵可以最大化满足变换的点对数量。然后,这个矩阵就可以用来确定模板在图像中的位置。
import cv2
import numpy as np
# 读取图像并提取特征,并以灰度模式加载。
img = cv2.imread("cat.jpeg", cv2.IMREAD_GRAYSCALE)
template = cv2.imread("template.jpeg", cv2.IMREAD_GRAYSCALE)
# 预览显示
cv2.imshow("cat image", img)
cv2.imshow("template image", template)
cv2.waitKey(0)
# 特征描述子,创建了一个 ORB(Oriented FAST and Rotated BRIEF)特征提取器。
orb = cv2.ORB_create()
# 使用 ORB 提取器检测图像中的特征点和特征描述子。
keypoints_img, descriptors_img = orb.detectAndCompute(img, None)
keypoints_template, descriptors_template = orb.detectAndCompute(template, None)
# 创建BFMatcher对象,基于暴力匹配的匹配器。
bf = cv2.BFMatcher()
# 使用匹配器在两个图像的特征描述子之间进行了 K 近邻匹配,得到了每个特征点的最佳两个匹配。
# 这个参数定义了我们希望得到的最佳匹配项的数量。通常情况下,设置为2,这样每个特征点会有两个最佳匹配项,然后可以通过 Lowe's ratio 测试来筛选这些匹配,只保留最可靠的匹配。
matches = bf.knnMatch(descriptors_img, descriptors_template, k=2)
# 过滤
matches = [match for match in matches if match]
# 排序
matches = sorted(matches, key=lambda x: x[0].distance) if matches else []
# 画出距离最短的前15个点
result = cv2.drawMatchesKnn(img, keypoints_img, template, keypoints_template, matches[0:15], None,
matchColor=(0, 255, 0), singlePointColor=(255, 0, 255))
cv2.imshow("orb-match", result)
cv2.imwrite("orb-match-1.jpg", result)
cv2.waitKey(0)
# 应用Lowe's ratio测试剔除不良匹配
good = []
for m, n in matches:
if m.distance < 0.99 * n.distance:
good.append([m])
if len(good) >= 4: # 至少需要4个匹配点来计算单应性矩阵
# keypoints_img[m[0].queryIdx].pt for m in good:这是一个列表推导式,它遍历了列表 good 中的每个元素 m,然后使用索引 m[0].queryIdx 来获取关键点 keypoints_img 中对应索引的位置信息.pt。这样就创建了一个包含关键点位置信息的列表。
# np.float32(...):这将列表中的数据转换为 NumPy 的浮点数类型。
# .reshape(-1, 1, 2):这个操作用于调整数组的形状。在这里,它将数组重新组织为一个三维数组,第一维度是自动确定的(根据数组的长度),第二维度是1(每个点有1个坐标),第三维度是2(x 和 y 坐标)。
src_pts = np.float32([keypoints_img[m[0].queryIdx].pt for m in good]).reshape(-1, 1, 2)
dst_pts = np.float32([keypoints_template[m[0].trainIdx].pt for m in good]).reshape(-1, 1, 2)
# 使用 RANSAC 算法计算了图像之间的单应性矩阵。这个矩阵可以用来对图像进行透视变换,使它们更好地对齐。
H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
# 使用得到的单应性矩阵H将原图像做透视变换
height, width = img.shape
warped_img = cv2.warpPerspective(img, H, (width, height))
# 将匹配结果绘制在图像上
# cv2.addWeighted(img, 0.5, warped_img, 0.5, 0): 这行代码创建了一个融合图像,通过将原图像 img 与经过透视变换后的图像 warped_img 进行融合。
# 参数 0.5 表示两个图像的权重相同,最后一个参数 0 表示融合时没有gamma值的加权。融合后的图像展示了原图像和变换后的图像的叠加,以便直观地比较它们之间的对应关系。
result = cv2.addWeighted(img, 0.5, warped_img, 0.5, 0)
cv2.imshow("warped image", warped_img)
cv2.imshow("addWeighted", result)
cv2.waitKey(0)
# for i in range(len(good)):
# pt1 = (int(good[i][0].queryIdx * width), int(good[i][0].queryIdx * height))
# pt2 = (int(good[i][0].trainIdx * width), int(good[i][0].trainIdx * height))
# cv2.line(result, pt1, pt2, (0, 255, 0), 1)
for i in range(len(good[0: 15])):
pt1 = tuple(map(int, keypoints_img[good[i][0].queryIdx].pt)) # 获取第一个图像的匹配点坐标
pt2 = tuple(map(int, keypoints_template[good[i][0].trainIdx].pt)) # 获取第二个图像的匹配点坐标
cv2.line(result, pt1, pt2, (0, 255, 0), 1) # 绘制匹配点之间的连线
cv2.imshow("Matching Result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print("匹配点数不足,无法计算单应性矩阵。")
运行结果如下: