计算机视觉----opencv实战----指纹识别的案例
一、数据准备
src2.BMP
src1.BMP
src.bmp
model.BMP
二、识别原理讲解(sift特征提取)
SIFT(Scale-Invariant Feature Transform,尺度不变特征变换)是一种经典的图像特征提取算法,核心优势是不受图像尺度缩放、旋转、光照变化的影响,能稳定提取图像中的关键特征点,广泛用于图像匹配、目标检测、图像拼接等场景。以下从核心原理、OpenCV 实现步骤、关键特性三方面展开介绍,全程不涉及公式。
核心原理(4 个关键步骤)
SIFT 的本质是通过 “模拟人眼对不同尺度物体的感知”,找到图像中 “无论放大 / 缩小、旋转都不变” 的特征点,并为每个特征点生成唯一的 “特征描述符”(用于后续匹配)。整个过程可拆解为 4 步:
1. 尺度空间极值检测:找 “不受尺度影响” 的候选特征点
人眼观察物体时,近距离看细节、远距离看整体 ——SIFT 通过 “高斯模糊 + 图像缩放” 构建 “尺度空间”,模拟这种感知过程:
- 对原始图像做不同程度的高斯模糊(模糊程度逐渐增加),再对模糊后的图像做下采样(缩小尺寸),得到一系列 “不同尺度” 的图像集合(称为 “高斯金字塔”)。
- 在相邻尺度的图像间做差值(得到 “差分高斯金字塔”),然后在每个像素点的 “上下左右相邻像素 + 相邻尺度对应位置像素” 中比较,找到 “局部极值点”—— 这些点就是 “在不同尺度下都突出” 的候选特征点(比如小尺度下的角点、大尺度下的轮廓顶点)。
2. 特征点精确定位:剔除 “不稳定” 的候选点
第一步找到的候选点中,可能包含因噪声、边缘干扰产生的 “假特征点”,需要进一步筛选:
- 对每个候选极值点,分析其周围像素的灰度变化,判断该点是否是 “真正的特征点”(比如边缘上的点会被剔除,因为边缘在垂直方向的灰度变化不显著,稳定性差)。
- 最终保留 “灰度变化显著、在尺度上稳定” 的点,作为最终的 SIFT 特征点。
3. 特征点方向赋值:实现 “旋转不变性”
为了让特征点不受图像旋转影响,需要给每个特征点分配一个 “主方向”:
- 以特征点为中心,取一个小区域(比如半径 16 像素的圆),统计该区域内所有像素的 “梯度方向”(即像素灰度变化的方向,比如从暗到亮的方向)和 “梯度大小”(灰度变化的强度)。
- 用 “直方图” 统计这些梯度方向的分布,找到出现次数最多的方向(主方向),将该方向作为特征点的 “基准方向”—— 后续生成描述符时,会以这个主方向为参考,从而抵消旋转的影响。
4. 生成特征描述符:让特征 “可匹配”
每个特征点需要一个 “唯一标识”(描述符),用于和其他图像中的特征点对比匹配:
- 以特征点为中心,取一个 16×16 的像素块(按主方向对齐,避免旋转干扰),将这个块分成 4×4 的 16 个小格子(每个小格子 4×4 像素)。
- 对每个小格子,统计其中像素的梯度方向分布(用 8 个方向的直方图表示),得到 8 个数值。
- 16 个小格子共生成 16×8=128 个数值,将这 128 个数值组成一个向量,就是该特征点的 “128 维 SIFT 描述符”。
- 最后会对描述符做 “归一化” 处理(比如消除光照变化的影响:让描述符向量的长度为 1),确保其在不同光照下仍能稳定匹配
三、对比检测指纹(简单)
这里我们需要用到(src1.BMP,src2.BMP,model.BMP)三张图片,在(src1.BMP,src2.BMP)找出和model.BMP匹配的图片
代码示例:
1. 导入依赖库
import cv2
导入 OpenCV 库,它提供了强大的计算机视觉处理功能,包括 SIFT 特征提取和 FLANN 匹配器。
2. 核心认证函数 verification
该函数接收三个参数:
src
:待验证的源图像model
:作为标准的模型图像threshold
:判断认证通过的匹配点数量阈值,默认值为 500
函数执行流程:
(1)初始化 SIFT 特征提取器
sift = cv2.SIFT_create()
SIFT(尺度不变特征变换)是一种对尺度、旋转、光照变化都具有稳健性的特征提取算法,非常适合用于图像匹配。
(2)提取图像特征点和描述符
kp1, des1 = sift.detectAndCompute(src, None)
kp2, des2 = sift.detectAndCompute(model, None)
kp1
/kp2
:分别是源图像和模型图像的特征点(KeyPoint)集合des1
/des2
:分别是对应特征点的描述符(Descriptor),是特征点的数字表示
(3)特征点检查
if des1 is None or des2 is None:return "认证失败" # 无特征点
如果任何一幅图像无法提取到特征点,直接返回认证失败。
(4)FLANN 特征匹配
flann = cv2.FlannBasedMatcher()
matches = flann.knnMatch(des1, des2, k=2)
- 使用 FLANN(快速最近邻搜索库)匹配器进行特征匹配,比暴力匹配更高效
knnMatch
方法返回每个特征点的前 k 个最近邻匹配(这里 k=2)
(5)筛选优质匹配
good_matches = [m for m, n in matches if m.distance < 0.8 * n.distance]
应用 Lowe's 比率测试筛选优质匹配:如果最佳匹配距离小于次佳匹配距离的 80%,则认为是一个好的匹配点,这能有效剔除误匹配。
(6)返回认证结果
return "认证通过" if len(good_matches) >= threshold else "认证失败"
如果优质匹配点数量达到或超过阈值,则认证通过,否则失败。
3. 主程序
if __name__ == "__main__":src1 = cv2.imread("src1.BMP")src2 = cv2.imread("src2.BMP")model = cv2.imread("model.BMP")
读取待验证图像(src1.BMP、src2.BMP)和模型图像(model.BMP)。
if src1 is None or src2 is None or model is None:print("❌ 图像读取失败,请检查文件路径")
检查图像是否成功读取,如果有任何图像读取失败,提示检查文件路径。
else:print("src1验证结果:", verification(src1, model))print("src2验证结果:", verification(src2, model))
如果所有图像都成功读取,则分别对 src1 和 src2 进行认证,并打印结果。
完整代码:
import cv2def verification(src, model, threshold=500):"""使用SIFT + FLANN匹配,返回认证结果"""sift = cv2.SIFT_create()kp1, des1 = sift.detectAndCompute(src, None)kp2, des2 = sift.detectAndCompute(model, None)if des1 is None or des2 is None:return "认证失败" # 无特征点flann = cv2.FlannBasedMatcher()matches = flann.knnMatch(des1, des2, k=2)good_matches = [m for m, n in matches if m.distance < 0.8 * n.distance]return "认证通过" if len(good_matches) >= threshold else "认证失败"# ========== 主程序 ==========
if __name__ == "__main__":src1 = cv2.imread("src1.BMP")src2 = cv2.imread("src2.BMP")model = cv2.imread("model.BMP")if src1 is None or src2 is None or model is None:print("❌ 图像读取失败,请检查文件路径")else:print("src1验证结果:", verification(src1, model))print("src2验证结果:", verification(src2, model))
运行结果:
四、进阶任务(多图片匹配)
现在需要对(src.bmp)在指纹库里进行匹配并绘制出匹配上的点(指纹库:database,已经上传,可以自行下载)
代码详解:
1. 导入依赖库
import os # 用于文件和目录操作
import cv2 # OpenCV库,用于图像处理和特征提取
import numpy as np # 用于数值计算和数组操作
2. 核心函数:获取匹配点
def get_good_matches(src, model):# 读取源图像和模板图像img1 = cv2.imread(src)img2 = cv2.imread(model)if img1 is None or img2 is None:return [], [], []# 创建SIFT特征提取器并计算特征点和描述符sift = cv2.SIFT_create()kp1, des1 = sift.detectAndCompute(img1, None) # kp: 关键点, des: 描述符kp2, des2 = sift.detectAndCompute(img2, None)if des1 is None or des2 is None:return kp1 or [], kp2 or [], []# 使用FLANN匹配器进行特征匹配flann = cv2.FlannBasedMatcher()matches = flann.knnMatch(des1, des2, k=2) # k=2表示返回两个最佳匹配# 应用Lowe's比例测试筛选良好的匹配点good = []for m, n in matches:# 如果最佳匹配距离小于次佳匹配的80%,则认为是好的匹配if m.distance < 0.8 * n.distance:good.append(m)return kp1, kp2, good
3. 计算匹配点个数
def getNum(src, model):_, _, good = get_good_matches(src, model)return len(good)
这个函数简化了匹配点获取过程,只返回良好匹配点的数量,用于比较不同模板的匹配程度。
4. 获取指纹编号
def getID(src, database):max_num = 0best_name = "0.bmp" # 默认值,防止未匹配时报错# 遍历数据库中的所有文件for file in os.listdir(database):model = os.path.join(database, file)num = getNum(src, model)print(f"文件名:{file},匹配点个数:{num}")# 记录匹配点最多的文件if num > max_num:max_num = numbest_name = file# 如果匹配点数量大于等于100,则认为匹配有效ID = int(best_name[0]) if max_num >= 100 else 9999return ID
该函数通过比较输入图像与数据库中所有图像的匹配点数量,找到最相似的图像,并返回其对应的 ID。
5. 根据 ID 获取姓名
def getName(ID):nameID = {0: '张三', 1: '李四', 2: '王五', 3: '赵六', 4: '朱老七',5: '钱八', 6: '曹九', 7: '王二麻子', 8: 'andy', 9: 'Anna',9999: "没找到"}return nameID.get(int(ID), "未知")
这是一个简单的 ID 与姓名映射表,根据识别出的 ID 返回对应的姓名。
6. 绘制匹配点
def drawMatchesWithCircles(src, model, show=True):img1 = cv2.imread(src)img2 = cv2.imread(model)kp1, kp2, good_matches = get_good_matches(src, model)# 在两张图像上绘制匹配点(红色实心圆)for match in good_matches:pt1 = tuple(map(int, kp1[match.queryIdx].pt)) # 源图像上的匹配点pt2 = tuple(map(int, kp2[match.trainIdx].pt)) # 模板图像上的匹配点cv2.circle(img1, pt1, 3, (0, 0, 255), -1) # -1表示填充圆cv2.circle(img2, pt2, 3, (0, 0, 255), -1)# 拼接两张图像以便对比显示h1, w1 = img1.shape[:2]h2, w2 = img2.shape[:2]h = max(h1, h2) # 取最大高度w = w1 + w2 # 宽度相加combined = np.zeros((h, w, 3), dtype=np.uint8)combined[:h1, :w1] = img1combined[:h2, w1:w1+w2] = img2# 显示结果if show:cv2.imshow("Matches with Circles", combined)cv2.waitKey(0)cv2.destroyAllWindows()return len(good_matches)
7. 主程序
if __name__ == "__main__":src = "src.bmp" # 待识别的源图像database = "database" # 模板数据库目录# 执行识别流程ID = getID(src, database)name = getName(ID)print("识别结果是:", name)# 绘制最佳匹配的匹配点if ID != 9999:# 找到最佳匹配的模板文件best_model = os.path.join(database, [f for f in os.listdir(database) if f.startswith(str(ID))][0])print(f"\n正在绘制 {best_model} 与 {src} 的匹配点...")drawMatchesWithCircles(src, best_model)else:print("未找到有效匹配,不进行绘制。")
工作流程总结
- 读取待识别图像 (
src.bmp
) 和数据库中的所有模板图像 - 对每对图像使用 SIFT 算法提取特征点并进行匹配
- 统计匹配点数量,找到匹配度最高的模板
- 根据模板的 ID 查找对应的姓名并输出
- 可视化显示最佳匹配的特征点对应关系
完整代码:
import os
import cv2
import numpy as np############## 获取匹配点(核心函数) #####################
def get_good_matches(src, model):img1 = cv2.imread(src)img2 = cv2.imread(model)if img1 is None or img2 is None:return [], [], []sift = cv2.SIFT_create()kp1, des1 = sift.detectAndCompute(img1, None)kp2, des2 = sift.detectAndCompute(img2, None)if des1 is None or des2 is None:return kp1 or [], kp2 or [], []flann = cv2.FlannBasedMatcher()matches = flann.knnMatch(des1, des2, k=2)good = []for m, n in matches:if m.distance < 0.8 * n.distance:good.append(m)return kp1, kp2, good############## 计算匹配个数 #####################
def getNum(src, model):_, _, good = get_good_matches(src, model)return len(good)############# 获取指纹编号 ################
def getID(src, database):max_num = 0best_name = "0.bmp" # 默认值,防止未匹配时报错for file in os.listdir(database):model = os.path.join(database, file)num = getNum(src, model)print(f"文件名:{file},匹配点个数:{num}")if num > max_num:max_num = numbest_name = fileID = int(best_name[0]) if max_num >= 100 else 9999return ID############# 获取姓名 ################
def getName(ID):nameID = {0: '张三', 1: '李四', 2: '王五', 3: '赵六', 4: '朱老七',5: '钱八', 6: '曹九', 7: '王二麻子', 8: 'andy', 9: 'Anna',9999: "没找到"}return nameID.get(int(ID), "未知")############## 绘制匹配点(实心小圆圈) #####################
def drawMatchesWithCircles(src, model, show=True):img1 = cv2.imread(src)img2 = cv2.imread(model)kp1, kp2, good_matches = get_good_matches(src, model)# 绘制匹配点for match in good_matches:pt1 = tuple(map(int, kp1[match.queryIdx].pt))pt2 = tuple(map(int, kp2[match.trainIdx].pt))cv2.circle(img1, pt1, 3, (0, 0, 255), -1)cv2.circle(img2, pt2, 3, (0, 0, 255), -1)# 拼接显示h1, w1 = img1.shape[:2]h2, w2 = img2.shape[:2]h = max(h1, h2)w = w1 + w2combined = np.zeros((h, w, 3), dtype=np.uint8)combined[:h1, :w1] = img1combined[:h2, w1:w1+w2] = img2if show:cv2.imshow("Matches with Circles", combined)cv2.waitKey(0)cv2.destroyAllWindows()return len(good_matches)############# 主程序 ################
if __name__ == "__main__":src = "src.bmp"database = "database"ID = getID(src, database)name = getName(ID)print("识别结果是:", name)# 绘制最佳匹配if ID != 9999:best_model = os.path.join(database, [f for f in os.listdir(database) if f.startswith(str(ID))][0])print(f"\n正在绘制 {best_model} 与 {src} 的匹配点...")drawMatchesWithCircles(src, best_model)else:print("未找到有效匹配,不进行绘制。")详细介绍代码