在计算机视觉领域,特征匹配是实现图像认证、目标识别、指纹 / 人脸验证等功能的核心技术,而 SIFT 结合 FLANN 的匹配方案,因具备尺度不变、旋转不变的鲁棒性和高效的匹配速度,成为此类项目的经典选择

一、核心逻辑

提取待认证图像与模板图像的 SIFT 特征点和描述符,通过 FLANN 算法进行 K 近邻特征匹配,再利用 Lowe 比率测试筛选有效匹配点,最终根据有效匹配点的数量是否达到阈值(500),判断图像认证是否通过,可同时对多张待认证图像进行批量验证。

本文使用 SIFT 特征点检测 和 FLANN 匹配器 实现简单的指纹认证和指纹识别

二、核心概念

1. SIFT 特征提取

SIFT(尺度不变特征变换)能在图像中检测出关键点(如角点、边缘点,不受图像缩放、旋转、轻微光照变化影响),并为每个关键点计算128 维特征描述符(用向量描述关键点周围像素分布,向量越相似,特征点匹配度越高)。

2. FLANN 特征匹配

FLANN(快速近似最近邻库)是高效的特征匹配算法,相比暴力匹配,在特征点数量较多时匹配速度更快,适合实际场景的高效匹配需求,代码中用其实现 K 近邻匹配(K=2)。

3. Lowe 比率测试

用于剔除特征匹配中的虚假匹配(如相似纹理导致的错误匹配):对每个待匹配特征点,取其在模板图像中最近邻次近邻的匹配距离,若最近邻距离 < 0.8× 次近邻距离,则判定为有效匹配点,否则剔除。

三、指纹认证

1.基础指纹认证

import cv2

def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)

def verification(src, model):
    # 创建SIFT特征提取器
    sift = cv2.SIFT_create()
    # 检测关键点和计算描述符(特征向量) 源图像
    kp1, des1 = sift.detectAndCompute(src, None)  # 第二个参数:掩膜
    # 检测关键点和计算描述符 模板图像
    kp2, des2 = sift.detectAndCompute(model, None)

    # 创建FLANN匹配器
    flann = cv2.FlannBasedMatcher()
    # 使用k近邻匹配(des1中的每个描述符与des2中的最近两个描述符进行匹配)
    matches = flann.knnMatch(des1, des2, k=2)

    # distance:匹配的特征点描述符的欧式距离,数值越小也就说明两个特征点越相近。
    # queryIdx:测试图像的特征点描述符的下标(第几个特征点描述符),同时也是描述符对应特征点的下标。
    # trainIdx:样本图像的特征点描述符下标,同时也是描述符对应特征点的下标。

    # 进行比较筛选
    ok = []
    for m, n in matches:
        # 根据Lowe's比率测试,选择最佳匹配
        if m.distance < 0.8 * n.distance:
            ok.append((m, n))

    # 统计通过筛选的匹配数量
    num = len(ok)
    if num >= 500:
        result = "认证通过"
    else:
        result = "认证失败"
    return result

if __name__ == "__main__":
    src1 = cv2.imread(r"C:\Users\LEGION\Desktop\2a72c3384c9818424f5b46dea6bdb835.bmp")
    cv_show(name='src1', img=src1)
    src2 = cv2.imread(r"C:\Users\LEGION\Desktop\7621f27ada4aa4f3f3c3027587f01f2a.bmp")
    cv_show(name='src2', img=src2)
    model = cv2.imread(r"C:\Users\LEGION\Desktop\5cdb73f36e8f3d1d256bb6a5d9dc19a7.bmp")
    cv_show(name='model', img=model)

    result1 = verification(src1, model)
    result2 = verification(src2, model)


    print("src1验证结果为:", result1)
    print("src2验证结果为:", result2)

代码注释:

sift.detectAndCompute(src, None)

  • detectAndCompute:一站式实现关键点检测特征描述符计算

  • 参数None:表示掩膜,即对整幅图像进行特征提取,若设置掩膜则仅提取掩膜区域的特征;

  • 返回值:kp1/kp2为图像的关键点集合,des1/des2为对应的 128 维特征描述符数组

matches = flann.knnMatch(des1, des2, k=2)

  • 创建无参数配置的 FLANN 匹配器,直接调用knnMatch实现 K 近邻匹配;

  • k=2:为每个待认证图像的特征描述符,匹配模板图像中最相似的 2 个描述符,为后续 Lowe 比率测试做准备;

  • 返回值matches:所有匹配结果的集合,每个元素包含最近邻次近邻两个匹配结果

m.distance/n.distance:匹配结果的欧式距离,数值越小表示特征点越相似;

筛选规则:仅保留最近邻距离小于 0.8 倍次近邻距离的匹配结果,存入ok列表,作为有效匹配点。

运行结果:

2.匹配点可视化的指纹认证

相比基础的图像认证代码,本次实现的系统新增 / 优化了五大实用功能,兼顾实用性、鲁棒性和直观性

  1. 匹配点可视化:在待验证图像和模板图像上用红色实心圆标注所有有效匹配点,直观查看匹配位置;

  2. 匹配信息输出:控制台打印每个匹配点的坐标(待验证图 + 模板图)和匹配距离,便于分析匹配质量;

  3. 鲁棒性提升:增加特征描述符判空、图像读取失败检查,避免程序因异常场景崩溃;

  4. 匹配效率优化:配置 FLANN 官方推荐的 KD 树参数,平衡匹配速度与精准度;

  5. 阈值合理化:结合实际匹配场景降低有效匹配阈值和 Lowe 比率阈值,解决原阈值过高导致的匹配失败问题。

代码如下:

import cv2
import numpy as np


def cv_show(name, img):
    """
    显示图像,窗口持续保留(不自动关闭)
    参数:
        name: 窗口名称(需唯一)
        img: 要显示的图像
    """
    cv2.imshow(name, img)  # 仅显示图像,不调用waitKey和destroyWindow


def verification(src, model):
    """
    特征匹配并标注匹配点
    参数:
        src: 待验证图像
        model: 模板图像
    返回:
        result: 验证结果(认证通过/失败)
        marked_src: 标注了匹配点的待验证图像
        marked_model: 标注了匹配点的模板图像
    """
    # 创建SIFT特征提取器
    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 "认证失败", src.copy(), model.copy()

    # 创建FLANN匹配器(优化匹配参数)
    FLANN_INDEX_KDTREE = 1
    index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
    search_params = dict(checks=50)  # 搜索次数,越高越准确但越慢
    flann = cv2.FlannBasedMatcher(index_params, search_params)

    # k近邻匹配(k=2:每个特征点找最近的2个匹配)
    matches = flann.knnMatch(des1, des2, k=2)

    # 筛选满足条件的匹配点(distance < 0.4 * 次近distance)
    good_matches = []
    for m, n in matches:
        if m.distance < 0.4 * n.distance:
            good_matches.append(m)

    # 统计匹配数量,判断验证结果
    num = len(good_matches)
    if num >= 50:  # 降低阈值(原500过高,实际匹配很难达到)
        result = "认证通过"
    else:
        result = "认证失败"

    # 复制图像用于标注(避免修改原图像)
    marked_src = src.copy()
    marked_model = model.copy()

    # 标注匹配点(红色实心圆,半径3)
    for match in good_matches:
        # 获取待验证图像的匹配点坐标
        src_idx = match.queryIdx
        src_point = kp1[src_idx].pt  # (x, y) 浮点型坐标
        x1, y1 = int(src_point[0]), int(src_point[1])
        # 绘制待验证图像的匹配点
        cv2.circle(marked_src, (x1, y1), 3, (0, 0, 255), -1)

        # 获取模板图像的匹配点坐标
        model_idx = match.trainIdx
        model_point = kp2[model_idx].pt
        x2, y2 = int(model_point[0]), int(model_point[1])
        # 绘制模板图像的匹配点
        cv2.circle(marked_model, (x2, y2), 3, (0, 0, 255), -1)

        # 打印匹配点坐标(可选)
        print(f"匹配点 - 待验证图: ({x1}, {y1}) | 模板图: ({x2}, {y2}) | 距离: {m.distance:.2f}")

    return result, marked_src, marked_model


if __name__ == "__main__":
    # 读取图像(确保路径正确)
    src1 = cv2.imread(r"C:\Users\LEGION\Desktop\2a72c3384c9818424f5b46dea6bdb835.bmp")
    src2 = cv2.imread(r"C:\Users\LEGION\Desktop\7621f27ada4aa4f3f3c3027587f01f2a.bmp")
    model = cv2.imread(r"C:\Users\LEGION\Desktop\5cdb73f36e8f3d1d256bb6a5d9dc19a7.bmp")

    # 检查图像是否读取成功
    if src1 is None or src2 is None or model is None:
        print("错误:图像文件读取失败,请检查文件路径!")
    else:
        # 验证src1并标注匹配点
        result1, marked_src1, marked_model1 = verification(src1, model)
        # 验证src2并标注匹配点
        result2, marked_src2, marked_model2 = verification(src2, model)

        # 显示所有结果图像(所有窗口同时保留)
        print("src1验证结果为:", result1)
        cv_show(name='src1_matched', img=marked_src1)
        cv_show(name='model_matched_with_src1', img=marked_model1)

        print("src2验证结果为:", result2)
        cv_show(name='src2_matched', img=marked_src2)
        cv_show(name='model_matched_with_src2', img=marked_model2)

        # 等待用户按任意键,然后关闭所有窗口
        print("\n所有图像已显示,按任意键关闭所有窗口...")
        cv2.waitKey(0)  # 阻塞等待按键
        cv2.destroyAllWindows()  # 关闭所有打开的窗口

运行结果:

  • 弹出 4 个窗口:src1 标注图、src1 对应的模板标注图、src2 标注图、src2 对应的模板标注图;

  • 窗口中红色实心圆为有效匹配点,匹配点越密集,说明图像相似度越高。

3.匹配点连线的指纹认证

import cv2

def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)

# 读取图像
src1 = cv2.imread(r"C:\Users\LEGION\Desktop\2a72c3384c9818424f5b46dea6bdb835.bmp")
cv_show("src1", src1)

model = cv2.imread(r"C:\Users\LEGION\Desktop\5cdb73f36e8f3d1d256bb6a5d9dc19a7.bmp")
cv_show("model", model)

# 创建SIFT特征提取器
sift = cv2.SIFT_create()

# 检测关键点和计算描述符
kp1, des1 = sift.detectAndCompute(src1, None)
kp2, des2 = sift.detectAndCompute(model, None)

# 创建FLANN匹配器
flann = cv2.FlannBasedMatcher()
matches = flann.knnMatch(des1, des2, k=2)

# 筛选匹配点
good = []
alist = []
for m, n in matches:
    if m.distance < 0.4 * n.distance:  # Lowe's比率测试
        alist.append((m.queryIdx, m.trainIdx))  # 匹配成功的(指src1中的索引,指model中的索引)
        good.append((m, n))

# 绘制关键点
for i, j in alist:
    x, y = kp1[i].pt
    m, n = kp2[j].pt
    cv2.circle(src1, center=(int(x), int(y)), radius=3, color=(0, 0, 255), thickness=-1)
    cv2.circle(model, center=(int(m), int(n)), radius=3, color=(0, 0, 255), thickness=-1)

# 显示标记后的图像
cv_show("Marked src1", src1)
cv_show("Marked model", model)

# 绘制匹配点连线
# drawMatchesKnn(img1, keypoints1, img2, keypoints2, matches1to2, outImg, matchColor=None, singlePointColor=None, matchesMask=None, flags=None)
# 参数:
# img1: 第一张原始图像的关键点。
# img2: 第二张原始图像。
# keypoints1: 第一张原始图像的关键点。
# keypoints2: 第二张原始图像的关键点。
# matches1to2: 从第一个图像到第二个图像的匹配,这意味着keypoints1[i]在keypoints2[Matches[i]]中有一个对应的点。
# outImg: 绘制结果图像。
# matchColor: 匹配连线与关键点的颜色,当matchColor == Scalar::all(-1)时,代表随机颜色。
# singlePointColor: 没有匹配项的关键点的颜色,当singlePointColor == Scalar::all(-1)时,代表随机颜色。
# matchesMask: 确定绘制哪些匹配项的掩码,如果掩码为空,则绘制所有匹配项。
# flags: 绘制功能的一些标志,具体有:
# cv.DRAW_MATCHES_FLAGS_DEFAULT
# cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
# cv.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG
# cv.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS
matched_image = cv2.drawMatchesKnn(src1, kp1, model, kp2, good, outImg=None, flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)
cv_show("Matched Points", matched_image)

cv2.drawMatchesKnn:专门用于 k 近邻匹配结果的连线绘制,核心参数说明:

  • src1, kp1:待验证图及其关键点;

  • model, kp2:模板图及其关键点;

  • good:筛选后的有效匹配结果;

  • outImg=None:自动创建结果图像;

  • flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS:屏蔽无匹配的单点,只显示有对应匹配的点和连线,让结果更清晰;

运行结果:

四、指纹识别

流程如下:

  1. 计算待验证指纹与库中单个指纹的有效匹配点数量;

  2. 遍历本地指纹库,找到匹配点数量最多的指纹文件;

  3. 从文件名提取人员编号,通过字典映射为姓名;

  4. 设定匹配点阈值(200),若最大匹配数低于阈值,判定为 “库中无匹配指纹”

import os
import cv2

"""===============计算两个指纹间匹配点的个数==============="""
def getNum(src, model):
    img1 = cv2.imread(r"C:\Users\LEGION\Desktop\2a72c3384c9818424f5b46dea6bdb835.bmp")
    img2 = cv2.imread(r"C:\Users\LEGION\Desktop\5cdb73f36e8f3d1d256bb6a5d9dc19a7.bmp")
    sift = cv2.SIFT_create()  # orb_create()
    kp1, des1 = sift.detectAndCompute(img1, None)
    kp2, des2 = sift.detectAndCompute(img2, None)
    flann = cv2.FlannBasedMatcher()
    matches = flann.knnMatch(des1, des2, k=2)
    ok = []
    for m, n in matches:
        if m.distance < 0.8 * n.distance:
            ok.append(m)
    num = len(ok)
    return num

"""===============获取指纹编号==============="""
def getID(src, database):
    max = 0
    for file in os.listdir(r"E:\xwechat_files\wxid_qi43v1w2nqcb12_e432\msg\file\2026-01\database\database"):
        model = os.path.join(r"E:\xwechat_files\wxid_qi43v1w2nqcb12_e432\msg\file\2026-01\database\database", file)
        num = getNum(src, model)
        print("文件名:", file, "匹配点个数:", num)
        if num > max:
            max = num
            name = file
    ID = name[0]
    if max < 200:  # src图片不一定是库里面人的指纹
        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"
    ID = getID(src, database)
    name = getName(ID)
    print("识别结果为:", name)

运行结果:

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐