【计算机视觉】特征提取与匹配
图像特征包含了图像的某种标志性星系,从图像上可以直接观察到角点、边缘、轮廓、纹理、颜色等特征。
图像特征包含了图像的某种标志性星系,从图像上可以直接观察到角点、边缘、轮廓、纹理、颜色等特征。
1.特征提取
1.1SIFT
SIFT是一种局部图像特征,它对旋转、尺度缩放、亮度变化具有不变形,并且一定程度上对噪声、遮挡等也保持稳定。
主要有以下几步:
- 尺度空间的极值点检测
- 定位关键点
- 关键点方向分配
- 关键点描述
SIFT 特征提取步骤
- 构建高斯金字塔
- 通过对图像进行高斯模糊和降采样,生成不同尺度的图像。
- 这些图像在不同分辨率下描述图像结构。
- 计算差分高斯金字塔 (DoG)
- 通过相邻高斯模糊图像做差,检测可能的特征点位置。
- 关键点定位
- 精确确定特征点的位置和尺度,滤除低对比度点和边缘响应。
- 方向分配
- 为每个关键点分配一个主方向,使特征点具有旋转不变性。
- 生成特征描述子
- 根据关键点的局部区域生成一组描述子,用于匹配。
代码示例:SIFT特征提取
以下示例展示如何使用 OpenCV 的 SIFT 提取关键点并绘制结果。
代码实现
import cv2 as cv
# 加载图像
img = cv.imread('example.jpg', cv.IMREAD_GRAYSCALE)
# 创建 SIFT 对象
sift = cv.SIFT_create()
# 检测关键点并计算描述子
keypoints, descriptors = sift.detectAndCompute(img, None)
# 在图像上绘制关键点
img_with_keypoints = cv.drawKeypoints(
img, keypoints, None, flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
# 显示结果
cv.imshow('Original Image', img)
cv.imshow('SIFT Keypoints', img_with_keypoints)
cv.waitKey(0)
cv.destroyAllWindows()
代码详解
-
cv.SIFT_create():- 创建一个 SIFT 对象,用于特征提取。
-
detectAndCompute():- 检测关键点并计算特征描述子。
- 输出两个结果:
keypoints:检测到的关键点列表。descriptors:每个关键点对应的描述子,形状为 (N \times 128),其中 (N) 是关键点的数量。
-
cv.drawKeypoints():- 将关键点绘制到图像上。
- 参数
flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS表示绘制关键点的位置、方向和尺度。
关键点与描述子
-
关键点 (Keypoints):
每个关键点包含以下属性:- 位置 (x, y):关键点在图像中的坐标。
- 大小 (size):关键点的尺度。
- 方向 (angle):关键点的主方向,用于实现旋转不变性。
-
描述子 (Descriptors):
- 描述关键点周围的局部区域。
- 长度为 128 的向量,表示该区域的梯度方向分布。
关键点匹配
通常,SIFT 提取的描述子会用于图像匹配。以下是使用 BFMatcher 进行匹配的示例:
代码实现:SIFT特征匹配
import cv2 as cv
# 加载两幅图像
img1 = cv.imread('example1.jpg', cv.IMREAD_GRAYSCALE)
img2 = cv.imread('example2.jpg', cv.IMREAD_GRAYSCALE)
# 创建 SIFT 对象并检测特征
sift = cv.SIFT_create()
keypoints1, descriptors1 = sift.detectAndCompute(img1, None)
keypoints2, descriptors2 = sift.detectAndCompute(img2, None)
# 创建暴力匹配器 (Brute-Force Matcher)
bf = cv.BFMatcher(cv.NORM_L2, crossCheck=True)
# 进行特征匹配
matches = bf.match(descriptors1, descriptors2)
# 按匹配质量排序
matches = sorted(matches, key=lambda x: x.distance)
# 绘制匹配结果
img_matches = cv.drawMatches(img1, keypoints1, img2, keypoints2, matches[:50], None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 显示结果
cv.imshow('Matches', img_matches)
cv.waitKey(0)
cv.destroyAllWindows()
代码详解
-
BFMatcher:- 暴力匹配器,比较两个描述子集合的欧几里得距离。
- 参数:
cv.NORM_L2:使用 L2 范数(欧几里得距离)。crossCheck=True:开启双向匹配检查,提高匹配精度。
-
match():- 逐一比较两个描述子集合,返回匹配的结果。
-
drawMatches():- 绘制匹配结果,显示关键点的连接线。

- 绘制匹配结果,显示关键点的连接线。
1.2ORB
ORB采用了改进的具有方向的FAST特征提取,使用具有旋转不变形的BRIEF特征描述子,所以ORB的效率很高。
ORB (Oriented FAST and Rotated BRIEF) 是一种高效的特征点检测与描述子算法。它结合了 FAST 特征检测和 BRIEF 特征描述的优点,并引入旋转不变性和多尺度支持,是 SIFT 和 SURF 的快速替代方案,且不受专利限制。
ORB特征提取的步骤
- 特征点检测:
- 使用 FAST (Features from Accelerated Segment Test) 检测图像中的关键点。
- 按响应值对关键点进行排序,并通过金字塔法支持多尺度。
- 方向分配:
- 计算关键点局部区域的灰度质心方向,为关键点赋予主方向,实现旋转不变性。
- 特征描述:
- 使用旋转后的 BRIEF (Binary Robust Independent Elementary Features) 描述子,对关键点周围的局部区域进行二值编码。
- 特征点匹配:
- 使用汉明距离比较描述子,进行快速匹配。
代码示例:ORB特征提取
以下是使用 OpenCV 的 ORB 提取图像关键点和描述子的实现。
代码实现
import cv2 as cv
# 加载图像
img = cv.imread('example.jpg', cv.IMREAD_GRAYSCALE)
# 创建 ORB 对象
orb = cv.ORB_create()
# 检测关键点并计算描述子
keypoints, descriptors = orb.detectAndCompute(img, None)
# 在图像上绘制关键点
img_with_keypoints = cv.drawKeypoints(
img, keypoints, None, color=(0, 255, 0), flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
# 显示结果
cv.imshow('Original Image', img)
cv.imshow('ORB Keypoints', img_with_keypoints)
cv.waitKey(0)
cv.destroyAllWindows()
代码详解
-
cv.ORB_create():- 创建 ORB 对象。
- 可传递参数(如特征点数量、金字塔层数等)进行自定义。
-
detectAndCompute():- 检测图像中的关键点并生成描述子。
- 返回值:
keypoints:关键点列表,包含位置、大小、方向等信息。descriptors:每个关键点对应的二进制描述子(形状为 (N \times 32))。
-
cv.drawKeypoints():- 在图像上绘制关键点,设置颜色和绘制模式。
ORB参数调整
ORB 提供了许多参数供用户自定义,以下是常用参数:
nfeatures:最大特征点数量(默认 500)。scaleFactor:图像金字塔的缩放比例(默认 1.2)。nlevels:金字塔层数(默认 8)。edgeThreshold:边界阈值,用于过滤靠近图像边界的特征点(默认 31)。patchSize:每个关键点的邻域大小,用于计算描述子(默认 31)。WTA_K:每次选择的位元个数(默认 2,用于 BRIEF 的哈希函数)。
示例:调整参数
orb = cv.ORB_create(nfeatures=1000, scaleFactor=1.2, nlevels=10)
关键点匹配
ORB 特征描述子是二进制的,因此使用 Hamming 距离匹配效率更高。
代码实现:ORB特征匹配
import cv2 as cv
# 加载两幅图像
img1 = cv.imread('example1.jpg', cv.IMREAD_GRAYSCALE)
img2 = cv.imread('example2.jpg', cv.IMREAD_GRAYSCALE)
# 创建 ORB 对象并检测特征
orb = cv.ORB_create()
keypoints1, descriptors1 = orb.detectAndCompute(img1, None)
keypoints2, descriptors2 = orb.detectAndCompute(img2, None)
# 创建暴力匹配器(Hamming 距离)
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
# 进行特征匹配
matches = bf.match(descriptors1, descriptors2)
# 按匹配距离排序
matches = sorted(matches, key=lambda x: x.distance)
# 绘制匹配结果
img_matches = cv.drawMatches(img1, keypoints1, img2, keypoints2, matches[:50], None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 显示结果
cv.imshow('Matches', img_matches)
cv.waitKey(0)
cv.destroyAllWindows()
代码详解
-
cv.BFMatcher:- 使用暴力匹配器进行特征点匹配。
- 参数:
cv.NORM_HAMMING:使用汉明距离比较二进制描述子。crossCheck=True:双向匹配验证,确保匹配的质量。
-
drawMatches():- 将匹配的特征点连接起来,展示匹配结果。
2.特征匹配
特征匹配是在两幅或者多幅图像中找出相同的图像特征,这些图像特征是通过前面介绍的特征提取得到的。
2.1暴力匹配
计算第一幅图像中的每个特征与第二幅图像中所有特征的距离,然后返回距离最近的那个特征作为与第一幅图像的匹配特征。
BFMatcher 的原理
- 描述子距离度量:
- 根据不同的描述子类型使用相应的度量方法:
- L2 范数:欧几里得距离,适用于 SIFT、SURF 等浮点描述子。
- 汉明距离:适用于 ORB、BRIEF 等二进制描述子。
- 根据不同的描述子类型使用相应的度量方法:
- 暴力比较:
- 对于每个关键点的描述子,遍历另一幅图像中所有描述子,计算距离,找到距离最近的匹配。
创建 BFMatcher 对象
cv.BFMatcher(normType, crossCheck)
参数解释:
-
normType:- 指定描述子之间的距离度量方法:
cv.NORM_L2:欧几里得距离(默认值)。cv.NORM_HAMMING:汉明距离,用于二进制描述子。cv.NORM_HAMMING2:用于带有两位 Hamming 权重的描述子。
- 指定描述子之间的距离度量方法:
-
crossCheck:- 布尔值,默认为
False。 - 如果为
True,仅保留通过双向验证的匹配对,即 ( A ) 的描述子匹配到 ( B ),而 ( B ) 的描述子也匹配回 ( A ) 才视为有效。
- 布尔值,默认为
常用方法
1. match()
- 对两组描述子进行逐一比较,返回最佳匹配。
- 返回值是一个列表,其中每个元素是一个
DMatch对象。
2.knnMatch() - 对两组描述子进行 K 近邻 匹配,返回每个关键点的 ( K ) 个最佳匹配。
3.radiusMatch() - 返回在指定距离范围内的所有匹配点。
代码示例
1. 暴力匹配 (match)
以下代码展示如何使用 BFMatcher 和 SIFT 特征描述子进行暴力匹配。
import cv2 as cv
# 加载两幅图像
img1 = cv.imread('example1.jpg', cv.IMREAD_GRAYSCALE)
img2 = cv.imread('example2.jpg', cv.IMREAD_GRAYSCALE)
# 创建 SIFT 检测器
sift = cv.SIFT_create()
# 检测关键点和描述子
keypoints1, descriptors1 = sift.detectAndCompute(img1, None)
keypoints2, descriptors2 = sift.detectAndCompute(img2, None)
# 创建 BFMatcher 对象(使用 L2 范数)
bf = cv.BFMatcher(cv.NORM_L2, crossCheck=True)
# 进行特征匹配
matches = bf.match(descriptors1, descriptors2)
# 按匹配距离排序
matches = sorted(matches, key=lambda x: x.distance)
# 绘制匹配结果
img_matches = cv.drawMatches(img1, keypoints1, img2, keypoints2, matches[:50], None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 显示结果
cv.imshow('Matches', img_matches)
cv.waitKey(0)
cv.destroyAllWindows()
2. K近邻匹配 (knnMatch)
K近邻匹配可以同时返回每个描述子的多个匹配结果,常用于过滤误匹配点(如使用比例测试)。
# 创建 BFMatcher 对象
bf = cv.BFMatcher(cv.NORM_L2)
# 使用 knnMatch 进行 K 近邻匹配
knn_matches = bf.knnMatch(descriptors1, descriptors2, k=2)
# 过滤匹配对:使用 Lowe's ratio test
good_matches = []
for m, n in knn_matches:
if m.distance < 0.75 * n.distance: # 比例测试
good_matches.append(m)
# 绘制匹配结果
img_matches = cv.drawMatches(img1, keypoints1, img2, keypoints2, good_matches, None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 显示结果
cv.imshow('KNN Matches', img_matches)
cv.waitKey(0)
cv.destroyAllWindows()
2.2快速最近邻
这是一种快速近似最近邻开元算法,使用了最近邻搜索算法进行查找,大数据量的背景下,将会比暴力匹配更快。
使用 cv.FlannBasedMatcher 创建 FLANN 匹配器时,需要提供两个参数:
cv.FlannBasedMatcher(indexParams, searchParams)
参数解释
-
indexParams:- 指定用于加速搜索的索引算法及其参数。
- 常用索引方法:
-
KD 树:适合 SIFT、SURF 等浮点型描述子。
indexParams = dict(algorithm=1, trees=5)algorithm=1:表示 KD 树。trees:构建的 KD 树数量,树越多精度越高,但速度可能略慢。
-
K-Means 聚类:适合大数据场景。
indexParams = dict(algorithm=0, branching=32, iterations=10, centers_init=2)algorithm=0:表示 K-Means 聚类。branching:每个聚类的分支数。iterations:聚类迭代次数。centers_init:聚类中心的初始化方式。
-
-
searchParams:- 用于控制搜索过程的参数。
- 常用设置:
searchParams = dict(checks=50)checks:表示算法在树中搜索的次数,值越高精度越高,但速度变慢。
1. 使用 FLANN 匹配 SIFT 描述子
以下示例展示如何使用 FLANN 进行特征匹配。
import cv2 as cv
# 加载图像
img1 = cv.imread('example1.jpg', cv.IMREAD_GRAYSCALE)
img2 = cv.imread('example2.jpg', cv.IMREAD_GRAYSCALE)
# 创建 SIFT 检测器
sift = cv.SIFT_create()
# 检测关键点和描述子
keypoints1, descriptors1 = sift.detectAndCompute(img1, None)
keypoints2, descriptors2 = sift.detectAndCompute(img2, None)
# 设置 FLANN 的参数
index_params = dict(algorithm=1, trees=5) # 使用 KD 树
search_params = dict(checks=50) # 检查次数
# 创建 FLANN 匹配器
flann = cv.FlannBasedMatcher(index_params, search_params)
# 进行 K 近邻匹配
knn_matches = flann.knnMatch(descriptors1, descriptors2, k=2)
# 使用 Lowe's ratio test 筛选匹配点
good_matches = []
for m, n in knn_matches:
if m.distance < 0.75 * n.distance:
good_matches.append(m)
# 绘制匹配结果
img_matches = cv.drawMatches(img1, keypoints1, img2, keypoints2, good_matches, None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 显示结果
cv.imshow('FLANN Matches', img_matches)
cv.waitKey(0)
cv.destroyAllWindows()
代码详解
-
cv.FlannBasedMatcher:- 创建 FLANN 匹配器,提供索引参数和搜索参数。
-
knnMatch:- 每个描述子返回 (k) 个最近邻匹配。
-
Lowe’s ratio test:
- 比较前两个匹配的距离,保留距离比小于阈值(如 0.75)的匹配点,以过滤误匹配。
-
drawMatches:- 绘制筛选后的匹配结果。
2. FLANN 匹配 ORB 描述子
对于 ORB 等二进制描述子,FLANN 不直接支持,需要做一些调整。
# 设置 FLANN 的参数(使用 LSH 索引方法)
index_params = dict(algorithm=6, table_number=6, key_size=12, multi_probe_level=1)
search_params = dict(checks=50)
# 创建 FLANN 匹配器
flann = cv.FlannBasedMatcher(index_params, search_params)
# 进行 K 近邻匹配
knn_matches = flann.knnMatch(descriptors1, descriptors2, k=2)
# 使用比例测试筛选匹配点
good_matches = []
for m, n in knn_matches:
if m.distance < 0.75 * n.distance:
good_matches.append(m)
# 绘制匹配结果
img_matches = cv.drawMatches(img1, keypoints1, img2, keypoints2, good_matches, None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 显示结果
cv.imshow('FLANN Matches (ORB)', img_matches)
cv.waitKey(0)
cv.destroyAllWindows()
参数解释:
algorithm=6:- 使用 LSH(局部敏感哈希)索引方法,专为二进制描述子设计。
table_number:- 哈希表数量,值越大精度越高。
key_size:- 每个哈希键的位数,控制哈希表的分布。
multi_probe_level:- 增加多次探测,提升匹配率。
FLANN 与 BFMatcher 的对比
| 特性 | FLANN | BFMatcher |
|---|---|---|
| 匹配效率 | 高效,适合大数据量或高维特征点匹配。 | 对小数据集或低维描述子效率更高。 |
| 匹配精度 | 提供近似匹配,可通过参数调整速度与精度平衡。 | 精确匹配,但计算复杂度较高。 |
| 支持描述子 | 浮点型和二进制描述子(需要调整参数)。 | 浮点型和二进制描述子。 |
| 使用场景 | 适合实时应用或大规模特征匹配。 | 小规模特征匹配或实验分析。 |
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)