计算机视觉----opencv实战----指纹识别的案例

发布于:2025-09-14 ⋅ 阅读:(24) ⋅ 点赞:(0)

一、数据准备

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 cv2

def 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 = 0
    best_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 = num
            best_name = file

    # 如果匹配点数量大于等于100,则认为匹配有效
    ID = int(best_name[0]) if max_num >= 100 else 9999
    return 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] = img1
    combined[: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("未找到有效匹配,不进行绘制。")
工作流程总结
  1. 读取待识别图像 (src.bmp) 和数据库中的所有模板图像
  2. 对每对图像使用 SIFT 算法提取特征点并进行匹配
  3. 统计匹配点数量,找到匹配度最高的模板
  4. 根据模板的 ID 查找对应的姓名并输出
  5. 可视化显示最佳匹配的特征点对应关系
完整代码:
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 = 0
    best_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 = num
            best_name = file

    ID = int(best_name[0]) if max_num >= 100 else 9999
    return 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 + w2
    combined = np.zeros((h, w, 3), dtype=np.uint8)
    combined[:h1, :w1] = img1
    combined[:h2, w1:w1+w2] = img2

    if 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("未找到有效匹配,不进行绘制。")详细介绍代码
运行结果: