目录
2.1 Harris角点检测器
Harris 角点检测算法(也称 Harris & Stephens 角点检测器)是一个极为简单的角点 检测算法。该算法的主要思想是,如果像素周围显示存在多于一个方向的边,我们 认为该点为兴趣点。该点就称为角点。
arris 算法主要通过以下步骤工作:
-
计算梯度:首先计算图像的梯度(x方向和y方向)。
-
构建自相关矩阵:利用梯度信息构建自相关矩阵 MM,该矩阵用于描述图像中每个点的局部结构。
计算响应函数:使用自相关矩阵计算响应函数 RR,这是一种衡量每个点是否为角点的指标。Harris 角点响应函数的计算公式为:-
其中 det(M)是矩阵 M的行列式,trace(M)是矩阵的迹,k 是一个经验常数,通常取值为 0.04 到 0.06。
-
-
非极大值抑制:对响应函数进行非极大值抑制,确定最终的角点位置。
-
应用阈值:根据阈值过滤响应函数,得到最终的角点。
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 读取图像并转换为灰度图像
image = cv2.imread('empire.jpg', cv2.IMREAD_GRAYSCALE)
if image is None:
raise FileNotFoundError("Image file 'empire.jpg' not found.")
# Harris 角点检测函数
def harris_corner_detection(image, threshold):
# 使用 OpenCV 的 cornerHarris 方法
dst = cv2.cornerHarris(image, 2, 3, 0.04)
dst = cv2.dilate(dst, None)
# 创建一个空白图像以标记角点
corners = np.zeros_like(image)
corners[dst > threshold * dst.max()] = 255
return corners
# 设置不同的阈值
thresholds = [0.01, 0.05, 0.1]
# 生成结果图像
fig, axs = plt.subplots(1, len(thresholds) + 1, figsize=(15, 5))
axs[0].imshow(image, cmap='gray')
axs[0].set_title('Original Image')
axs[0].axis('off')
for i, threshold in enumerate(thresholds):
corners = harris_corner_detection(image, threshold)
axs[i + 1].imshow(corners, cmap='gray')
axs[i + 1].set_title(f'Threshold = {threshold}')
axs[i + 1].axis('off')
plt.show()
分析:
- 图像读取和转换:加载并转换图像为灰度图像,以便进行角点检测。
- Harris 角点检测函数:使用
cv2.cornerHarris
函数计算 Harris 角点响应,应用阈值来检测角点。 - 阈值设置:测试不同的阈值,生成不同的角点检测结果图像。
- 结果显示:使用
matplotlib
显示原始图像及不同阈值下的角点检测结果。
结果:
- 阈值 0.01:较低的阈值会检测到更多的角点,包括许多可能是噪声的点。图像上角点分布可能较为密集。
- 阈值 0.05:中等阈值能更好地平衡角点的检测数量和准确性。图像上的角点数量适中,主要集中在显著的角落位置。
- 阈值 0.1:较高的阈值会减少检测到的角点数量,过滤掉一些弱的角点,只保留更显著和稳定的角点。
2.2 SIFT(尺度不变特征变换)
SIFT 特征包括兴趣点检测器和描述 子。SIFT 描述子具有非常强的稳健性,这在很大程度上也是 SIFT 特征能够成功和流行的主要原因。
2.2.1 兴趣点
SIFT 特征使用高斯差分函数来定位兴趣点:
2.2.2 描述子
为了实 现旋转不变性,基于每个点周围图像梯度的方向和大小,SIFT 描述子又引入了参考方向。SIFT 描述子使用主方向描述参考方向。主方向使用方向直方图(以大小为权重)来度量。
下面我们基于位置、尺度和方向信息来计算描述子。SIFT 描述子在每个像素点附近选取子区域网格,在每个子区域内计算图像 梯度方向直方图。每个子区域的直方图拼接起来组成描述子向量。SIFT 描述子的标 准设置使用 4×4 的子区域,每个子区域使用 8 个小区间的方向直方图,会产生共 128 个小区间的直方图(4×4×8=128)。图 2-3 所示为描述子的构造过程:
2.2.3 检测兴趣点
我们使用开源工具包 VLFeat 提供的二进制文件来计算图像的 SIFT 特征 :
import numpy as np
import cv2
import ctypes
from ctypes import POINTER, c_int, c_double, c_void_p
# 加载 VLFeat 的共享库
vlfeat = ctypes.CDLL('/path/to/vlfeat/bin/generic/libvl.so') # 根据实际路径修改
# 定义 SIFT 相关函数的 ctypes 接口
vl_sift_new = vlfeat.vl_sift_new
vl_sift_new.argtypes = [c_int, c_int, c_int, c_int, c_int]
vl_sift_new.restype = c_void_p
vl_sift_process = vlfeat.vl_sift_process
vl_sift_process.argtypes = [c_void_p, POINTER(c_double)]
vl_sift_process.restype = None
vl_sift_get_keypoints = vlfeat.vl_sift_get_keypoints
vl_sift_get_keypoints.argtypes = [c_void_p, POINTER(c_int)]
vl_sift_get_keypoints.restype = None
vl_sift_get_descriptors = vlfeat.vl_sift_get_descriptors
vl_sift_get_descriptors.argtypes = [c_void_p, POINTER(c_double)]
vl_sift_get_descriptors.restype = None
vl_sift_delete = vlfeat.vl_sift_delete
vl_sift_delete.argtypes = [c_void_p]
vl_sift_delete.restype = None
# 读取图像并转换为灰度图像
def load_image(image_path):
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if img is None:
raise FileNotFoundError("Image not found.")
return img
# 计算 SIFT 特征
def compute_sift(image):
# 转换图像为浮点数类型
image_float = image.astype(np.float64)
# 获取图像尺寸
height, width = image.shape
# 创建 SIFT 计算器
sift = vl_sift_new(width, height, 3, 4, 2)
if not sift:
raise MemoryError("Failed to create SIFT object.")
# 处理图像
image_data = image_float.flatten()
vl_sift_process(sift, image_data.ctypes.data_as(POINTER(c_double)))
# 获取特征点和描述子
num_keypoints = c_int()
keypoints = np.zeros((0, 4)) # Initialize empty array to hold keypoints
descriptors = np.zeros((0, 128)) # Initialize empty array to hold descriptors
vl_sift_get_keypoints(sift, keypoints.ctypes.data_as(POINTER(c_int)))
vl_sift_get_descriptors(sift, descriptors.ctypes.data_as(POINTER(c_double)))
# 清理
vl_sift_delete(sift)
return keypoints, descriptors
# 主程序
if __name__ == "__main__":
image_path = 'path_to_your_image.jpg'
image = load_image(image_path)
keypoints, descriptors = compute_sift(image)
print(f"Number of keypoints: {len(keypoints)}")
print(f"Descriptors shape: {descriptors.shape}")
结果:
特征点数量:可以用于了解图像中检测到的特征点数量。特征点数量较多通常意味着图像中有更多的可区分的特征。
描述符形状:每个特征点的描述符通常是一个 128 维的向量。描述符的形状为 (num_keypoints, 128)
,其中 num_keypoints
是特征点的数量。
2.2.4 匹配描述子
对于将一幅图像中的特征匹配到另一幅图像的特征,一种稳健的准则(同样是由 Lowe 提出的)是使用这两个特征距离和两个最匹配特征距离的比率。
import cv2
import numpy as np
# 加载图像
img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)
# 创建SIFT特征检测器
sift = cv2.SIFT_create()
# 检测关键点和计算描述子
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 使用BFMatcher进行特征匹配
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)
# 进行KNN匹配,k=2意味着每个描述子会找到两个最接近的匹配
matches = bf.knnMatch(des1, des2, k=2)
# 应用比率测试进行过滤
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good_matches.append(m)
# 绘制匹配结果
img_matches = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 显示结果
cv2.imshow('Matches', img_matches)
cv2.waitKey(0)
cv2.destroyAllWindows()
分析:
-
特征检测与描述:使用SIFT检测图像中的特征点并计算其描述子,SIFT能够提供旋转和尺度不变性。
-
特征匹配:使用暴力匹配器(BFMatcher)进行描述子的匹配,
knnMatch
方法找出每个描述子最近的两个匹配。 -
比率测试:根据Lowe的比率测试法,只保留那些第一个匹配距离明显小于第二个匹配距离的特征点对。此方法通过设置比率阈值(通常为0.75),降低了错误匹配的数量,确保了匹配的准确性。
-
结果可视化:通过
cv2.drawMatches
函数显示匹配结果,帮助直观评估匹配质量。
结果: