特征提取
特征提取是从原始数据中提取出能够代表数据本质特征和关键信息的过程,在很多领域都有广泛应用。原始数据往往包含大量的冗余信息,特征提取的目的是去除这些冗余,提取出最具代表性、最能区分不同类别或模式的特征,从而降低数据维度,提高数据处理的效率和效果,同时也有助于后续的数据分析、建模和决策。
- 类别
- SIFT(尺度不变特征变换):虽然 SIFT 算法存在专利限制,但在非商业用途中仍可在 OpenCV 中使用。通过构建高斯金字塔寻找尺度空间极值点,为关键点分配方向,生成 128 维描述子。
- SURF(加速稳健特征):SURF 是 SIFT 的加速版本,同样具有尺度和旋转不变性。它利用积分图像快速计算特征点和描述子,计算效率比 SIFT 更高。
- ORB(加速分割测试的定向 FAST 和旋转 BRIEF):ORB 是一种快速的特征提取和描述算法,它结合了 FAST(Features from Accelerated Segment Test)关键点检测和 BRIEF(Binary Robust Independent Elementary Features)描述子,并对其进行了改进。ORB 具有计算速度快、实时性好的优点,同时在一定程度上也具有旋转和尺度不变性。
- BRISK(二进制稳健不变可伸缩关键点):BRISK 也是一种二进制描述子算法,它通过在不同尺度上采样点对来计算描述子,具有较好的尺度和旋转不变性,同时对噪声也有一定的鲁棒性。
这些特征提取算法在不同的应用场景中各有优劣,本文主要介绍sift算法。
特征提取sift算法
SIFT(Scale-Invariant Feature Transform,尺度不变特征变换)是一种计算机视觉领域中非常重要的特征提取算法,由加拿大教授 David Lowe 于 1999 年提出,并在 2004 年加以完善总结。以下是关于 SIFT 算法的详细介绍:
- 算法原理
-
尺度空间极值检测:SIFT 算法首先使用不同尺度的高斯滤波器对图像进行卷积操作,生成高斯金字塔。然后在高斯金字塔的不同层之间进行比较,寻找图像中的极值点(极大值或极小值),这些极值点就是可能的特征点位置。通过这种方式,使得算法能够检测到不同尺度下的特征,从而实现尺度不变性。
-
关键点定位:在找到极值点后,需要进一步确定这些点是否为真正的关键点。通过拟合三维二次函数来精确确定关键点的位置和尺度,同时去除低对比度的点和位于边缘上的点,以提高关键点的稳定性和可靠性。
-
方向赋值:为每个关键点指定一个方向,使得算法具有旋转不变性。通过计算关键点邻域内的梯度方向直方图,将直方图中峰值对应的方向作为关键点的主方向,同时也可以根据需要确定几个辅方向。
-
关键点描述:在确定了关键点的位置、尺度和方向后,以关键点为中心取一个邻域窗口,将该窗口内的像素点的梯度方向和幅值进行统计,生成一个描述子。通常使用一个 128 维的向量来描述每个关键点,这个描述子包含了关键点周围区域的丰富信息,具有很强的区分性。
-
- 优点
- 尺度和旋转不变性:SIFT 特征对图像的尺度变化和旋转变换具有很好的不变性,能够在不同尺度和角度下准确地检测到相同的特征。
- 光照不变性:通过对梯度信息的利用,SIFT 特征对光照变化也具有一定的鲁棒性,在不同的光照条件下仍然能够保持较好的性能。
- 独特性高:生成的 128 维描述子具有很高的独特性,能够有效地区分不同的特征点,适用于各种复杂的图像匹配和识别任务。
- 缺点
- 计算复杂度高:SIFT 算法涉及到高斯金字塔的构建、极值点检测、方向赋值和描述子生成等多个步骤,计算量较大,运行速度相对较慢,不适合对实时性要求较高的应用场景。
- 专利问题:由于 SIFT 算法受专利保护,在商业应用中需要获得相应的授权,这在一定程度上限制了它的广泛应用。
- 应用领域
- 图像匹配:SIFT 特征常用于图像之间的匹配,通过找到两幅图像中特征点的对应关系,可以实现图像的对齐、拼接等操作,例如全景图像拼接。
- 目标识别:在目标识别任务中,SIFT 特征可以用于提取目标物体的特征,与数据库中的模板进行匹配,从而识别出目标物体,如在安防监控中识别特定的目标物体。
- 三维重建:通过对多幅图像中特征点的匹配和分析,可以计算出相机的位置和姿态,进而实现场景的三维重建,如在文物数字化保护中对文物进行三维建模。
匹配方法
在 OpenCV 中,提供了多种特征匹配方法,主要可分为基于传统特征的匹配和基于深度学习的匹配:
基于传统特征的匹配方法
-
暴力匹配(Brute-Force Matcher,BFMatcher)
- 原理:对一个图像中的每个特征描述符,在另一个图像的所有特征描述符中进行遍历,计算它们之间的距离(对于 SIFT、SURF 等浮点型描述符常用欧氏距离,对于 ORB、BRIEF 等二进制描述符常用汉明距离),选择距离最小的作为匹配项。
- 原理:对一个图像中的每个特征描述符,在另一个图像的所有特征描述符中进行遍历,计算它们之间的距离(对于 SIFT、SURF 等浮点型描述符常用欧氏距离,对于 ORB、BRIEF 等二进制描述符常用汉明距离),选择距离最小的作为匹配项。
-
FLANN 匹配(Fast Library for Approximate Nearest Neighbors)
- 原理:FLANN 是一个用于快速近似最近邻搜索的库。它采用了诸如 KD 树、层次聚类树等高效的数据结构和算法,在处理大规模特征点时,比暴力匹配速度更快。
- 原理:FLANN 是一个用于快速近似最近邻搜索的库。它采用了诸如 KD 树、层次聚类树等高效的数据结构和算法,在处理大规模特征点时,比暴力匹配速度更快。
本文主要讲解FLANN 匹配
FLANN 匹配
FLANN(Fast Library for Approximate Nearest Neighbors)是一个用于快速近似最近邻搜索的开源库,在 OpenCV 中被广泛用于特征匹配任务。在处理大规模特征点时,传统的暴力匹配方法(Brute - Force Matching)计算量极大、效率低下,而 FLANN 通过使用高效的数据结构和算法,能够在可接受的精度损失下,显著提高匹配速度。
- 原理
- FLANN 采用了多种近似最近邻搜索算法,常见的有 KD 树(KD - Tree)和层次聚类树(Hierarchical Clustering Tree)等:
- KD 树(KD - Tree):是一种对 k 维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。它通过不断地在不同维度上进行划分,将高维空间分割成多个区域,从而在搜索最近邻时可以减少不必要的比较,提高搜索效率。
- 层次聚类树(Hierarchical Clustering Tree):它将数据点进行层次聚类,构建成一棵树形结构。在搜索时,从树的根节点开始,根据数据点与聚类中心的距离逐步向下搜索,快速缩小搜索范围。
- FLANN 采用了多种近似最近邻搜索算法,常见的有 KD 树(KD - Tree)和层次聚类树(Hierarchical Clustering Tree)等:
实例
对指纹进行识别:
对十张指纹图片分别和模版指纹图片进行匹配,打印两个指纹间匹配点的个数。、
模版图片:
十张指纹图片:
实例过程
设置计算两个指纹间匹配点的个数的函数
"""====================计算两个指纹间匹配点的个数==============="""
def getNum(src, model):
# 读取源指纹图像
img1 = cv2.imread(src)
# 读取模板指纹图像
img2 = cv2.imread(model)
# 创建SIFT(尺度不变特征变换)特征检测器对象
sift = cv2.SIFT_create()
# 检测并计算源图像的关键点和描述符
# kp1 是关键点列表,des1 是对应的描述符矩阵
kp1, des1 = sift.detectAndCompute(img1, None)
# 检测并计算模板图像的关键点和描述符
# kp2 是关键点列表,des2 是对应的描述符矩阵
kp2, des2 = sift.detectAndCompute(img2, None)
# 创建基于FLANN(快速最近邻搜索包)的匹配器对象
flann = cv2.FlannBasedMatcher()
# 使用knnMatch方法进行特征匹配,返回每个查询描述符的前k个最近邻匹配结果
# k=2 表示为每个查询描述符找到两个最近邻匹配
matches = flann.knnMatch(des1, des2, k=2)
# 初始化一个空列表,用于存储满足筛选条件的匹配点
ok = []
# 遍历所有匹配结果
for m, n in matches:
# 根据比值测试筛选匹配点
# 如果第一个匹配的距离小于第二个匹配距离的0.8倍,则认为是好的匹配
if m.distance < 0.8 * n.distance:
ok.append(m)
# 统计满足条件的匹配点的数量
num = len(ok)
return num
获取指纹编号
'''===================获取指纹编号==============='''
def getID(src, database):
# 初始化最大匹配点数为0
max = 0
# 遍历数据库文件夹中的所有文件
for file in os.listdir(database):
# 拼接数据库中文件的完整路径
model = os.path.join(database, file)
# 调用getNum函数计算源指纹图像与当前模板指纹图像的匹配点数
num = getNum(src, model)
# 打印文件名和对应的匹配点数
print('文件名:', file, '匹配数字:', num)
# 如果当前匹配点数大于之前记录的最大匹配点数
if num > max:
# 更新最大匹配点数
max = num
# 记录当前文件名
name = file
# 从文件名中提取指纹编号,取文件名的第一个字符
ID = name[0]
# 如果最大匹配点数小于100,认为没有找到匹配的指纹,将编号设为9999
if max < 100:
ID = 9999
return ID
根据指纹编号,获取对应名字
def getName(ID):
# 定义一个字典,将指纹编号映射到对应的人名
nameID = {0: '张三', 1: '李四', 2: '王五', 3: '赵六', 4: '朱老七', 5: '钱八',
6: '曹九', 7: '王二麻子', 8: 'andy', 9: 'Anna', 9999: '没找到'}
# 根据指纹编号从字典中获取对应的人名
name = nameID.get(int(ID))
return name
调用函数进行指纹匹配
if __name__ == "__main__":
# 定义源指纹图像的文件名
src = 'src.bmp'
# 定义存储指纹模板的数据库文件夹名
database = 'database'
# 调用getID函数获取指纹编号
ID = getID(src, database)
# 调用getName函数根据指纹编号获取对应的人名
name = getName(ID)
# 打印识别结果
print('识别结果为:', name)
结果
完整代码
import os
import cv2
"""====================计算两个指纹间匹配点的个数==============="""
def getNum(src, model):
# 读取源指纹图像
img1 = cv2.imread(src)
# 读取模板指纹图像
img2 = cv2.imread(model)
# 创建SIFT(尺度不变特征变换)特征检测器对象
sift = cv2.SIFT_create()
# 检测并计算源图像的关键点和描述符
# kp1 是关键点列表,des1 是对应的描述符矩阵
kp1, des1 = sift.detectAndCompute(img1, None)
# 检测并计算模板图像的关键点和描述符
# kp2 是关键点列表,des2 是对应的描述符矩阵
kp2, des2 = sift.detectAndCompute(img2, None)
# 创建基于FLANN(快速最近邻搜索包)的匹配器对象
flann = cv2.FlannBasedMatcher()
# 使用knnMatch方法进行特征匹配,返回每个查询描述符的前k个最近邻匹配结果
# k=2 表示为每个查询描述符找到两个最近邻匹配
matches = flann.knnMatch(des1, des2, k=2)
# 初始化一个空列表,用于存储满足筛选条件的匹配点
ok = []
# 遍历所有匹配结果
for m, n in matches:
# 根据比值测试筛选匹配点
# 如果第一个匹配的距离小于第二个匹配距离的0.8倍,则认为是好的匹配
if m.distance < 0.8 * n.distance:
ok.append(m)
# 统计满足条件的匹配点的数量
num = len(ok)
return num
'''===================获取指纹编号==============='''
def getID(src, database):
# 初始化最大匹配点数为0
max = 0
# 遍历数据库文件夹中的所有文件
for file in os.listdir(database):
# 拼接数据库中文件的完整路径
model = os.path.join(database, file)
# 调用getNum函数计算源指纹图像与当前模板指纹图像的匹配点数
num = getNum(src, model)
# 打印文件名和对应的匹配点数
print('文件名:', file, '匹配数字:', num)
# 如果当前匹配点数大于之前记录的最大匹配点数
if num > max:
# 更新最大匹配点数
max = num
# 记录当前文件名
name = file
# 从文件名中提取指纹编号,取文件名的第一个字符
ID = name[0]
# 如果最大匹配点数小于100,认为没有找到匹配的指纹,将编号设为9999
if max < 100:
ID = 9999
return ID
'''======================根据指纹编号,获取对应名字==================='''
def getName(ID):
# 定义一个字典,将指纹编号映射到对应的人名
nameID = {0: '张三', 1: '李四', 2: '王五', 3: '赵六', 4: '朱老七', 5: '钱八',
6: '曹九', 7: '王二麻子', 8: 'andy', 9: 'Anna', 9999: '没找到'}
# 根据指纹编号从字典中获取对应的人名
name = nameID.get(int(ID))
return name
'''==================主函数=========================='''
if __name__ == "__main__":
# 定义源指纹图像的文件名
src = 'src.bmp'
# 定义存储指纹模板的数据库文件夹名
database = 'database'
# 调用getID函数获取指纹编号
ID = getID(src, database)
# 调用getName函数根据指纹编号获取对应的人名
name = getName(ID)
# 打印识别结果
print('识别结果为:', name)