计算机视觉----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("未找到有效匹配,不进行绘制。")
工作流程总结
  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 = 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("未找到有效匹配,不进行绘制。")详细介绍代码
运行结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/96606.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/96606.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

npm 发布流程——从创建组件到发布到 npm 仓库

1. 准备组件 1.1 创建一个 Vue 组件 假设我们要创建一个简单的按钮组件&#xff1a; src/MyButton.vue <template><button class"my-btn" click"$emit(click)"><slot /></button> </template><script setup lang"ts…

MySQL入门基础指南

目录 一、什么是数据库&#xff1f; 仅依靠文件存储数据存在以下几个明显缺点&#xff1a; 数据库的存储介质通常包括&#xff1a; 二、主流数据库介绍 三、客户端 VS 服务器 四、推荐看的MySQL安装技术博客 五、数据库的存储介质 数据库的存储介质主要分为以下两类&am…

【实战中提升自己完结篇】分支篇之分支之无线、内网安全与QOS部署(完结)

1 1拓扑 「模拟器、工具合集」复制整段内容 链接&#xff1a;https://docs.qq.com/sheet/DV0xxTmFDRFVoY1dQ?tab7ulgil1 分支无线部署 说明&#xff1a;分支无线用瘦AP部署&#xff0c;通过VPN直接注册到总部的AC上面&#xff0c;实现无线的业务提供&…

带你了解STM32:GPIO通用输入输出口

目录 3.1 GPIO简介 3.2 GPIO基本结构 3.3 GPIO位结构 输入部分&#xff1a; 二极管的保护作用&#xff1a; 施密特触发器&#xff1a; 片上外设端口 输出部分&#xff1a; MOS管 3.4 GPIO模式 3.4.1 浮空/上拉/下拉输入 3.4.2 模拟输入 3.4.3 开漏/推挽输出 3.4.…

Http(自写)

作为一个程序员&#xff0c;假设我们要在a电脑的进程里发一段数据到b电脑&#xff0c;一般使用socket编程&#xff0c;可选项也就tcp&#xff0c;udp二选一socket本质上就是一个代码库tcp有粘包问题&#xff08;字节流&#xff09;&#xff0c;纯裸tcp不能之际拿来使用所以我们…

C#使用OpenVinoSharp和PP-Human进行行人检测

效果 项目依赖 OpenCvSharp 4.11.0.20250507 OpenVINO.CSharp.Windows 2024.0.0.1 主要代码 using OpenCvSharp; using OpenVinoSharp; using System; using System.Windows.Forms;namespace HelloPPHuman {public partial class Form1 : Form{public Form1(){InitializeCo…

四、Scala深入面向对象:类、对象与伴生关系

在前几节中&#xff0c;我们学习了 Scala 的基础语法和流程控制。现在&#xff0c;我们将深入探索 Scala 作为一门纯粹的面向对象语言的核心。在 Scala 中&#xff0c;万物皆对象&#xff0c;没有像 Java 那样的原始类型和静态成员的区分。本节将重点介绍如何定义对象的蓝图&am…

【大语言模型 58】分布式文件系统:训练数据高效存储

分布式文件系统&#xff1a;训练数据高效存储 关键词&#xff1a;分布式文件系统、HDFS、Lustre、GlusterFS、数据本地性、I/O优化、存储架构、大数据存储、训练数据管理、存储性能调优 摘要&#xff1a;本文深入探讨大语言模型训练中的分布式文件系统技术&#xff0c;从存储架…

【科研绘图系列】R语言绘制散点图以及线性回归拟合曲线图

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍 加载R包 数据下载 函数 导入数据 数据预处理 画图 总结 系统信息 介绍 数据导入 代码的开始部分涉及多个数据集的导入,这些数据集涵盖了不同类型的生态学数据,包括实验室培养…

SQL 数据库操作语言详解

1. SQL 语言概述SQL&#xff08;Structured Query Language&#xff09;是用于管理关系型数据库的标准语言&#xff0c;主要分为以下几个子语言&#xff1a;- DQL&#xff08;数据查询语言&#xff09;&#xff1a;SELECT - 用于数据查询 - DML&#xff08;数据操作语言&#x…

积分变换的前世今生

积分变换常应用于解微分方程微分方程的解法&#xff1a;时域经典法&#xff1b;频域变换法&#xff1b;“积分变换”最初并不是为了解微分方程&#xff0c;而是出于更“纯粹”的数学动机——理解函数的结构、求解代数或几何问题&#xff0c;以及简化复杂的积分运算。微分方程的…

《Linux——gflags》

一、什么是gflags&#xff1f; gflags 是一个由 Google 开发的命令行参数解析库&#xff0c;主要用于在 C&#xff08;也支持其他语言&#xff09;程序中便捷地处理命令行参数。它的核心作用是帮助开发者快速定义、解析和使用命令行选项&#xff0c;避免手动编写繁琐的参数解析…

编译器的前端中端和后端

前面说的词法分析和语法分析&#xff0c;确实是编译器前端 (Front End) 最核心的两个部分。但前端的工作还没有结束。编译器各阶段划分 一个完整的编译器通常可以分为三个部分&#xff1a;前端、中端 (Middle End)、后端 (Back End)。 前端 (Front End) 核心职责: 理解源代码。…

黑马Java进阶教程,全面剖析Java多线程编程,并发和并行,笔记02

黑马Java进阶教程&#xff0c;全面剖析Java多线程编程&#xff0c;并发和并行&#xff0c;笔记02 一、并发和并行 并发&#xff1a;在同一时刻&#xff0c;有多个指令在单个CPU上交替执行 并行&#xff1a;在同一时刻&#xff0c;有多个指令在多个CPU上同时执行 二、为什么有…

20250908 背包DP总结

引子 ~ 我们都有一个家&#xff0c;名字叫背包 ~ 背包DP 顾名思义&#xff0c;背包DP是用来解决背包最值问题的。题目会给出背包的容量&#xff0c;以及几个物品的属性&#xff0c;比如重量&#xff0c;价值&#xff0c;限额等等&#xff0c;具体是什么看题目。 01背包 01…

Redis持久化之RDB:快照机制原理、配置与最佳实践

Redis持久化之RDB&#xff1a;快照机制原理、配置与最佳实践 1. RDB持久化概述 1.1 什么是RDB RDB&#xff08;Redis Database&#xff09;是Redis的默认持久化方式&#xff0c;它在指定的时间间隔内生成数据集的快照&#xff08;snapshot&#xff09;&#xff0c;并将快照保…

daily notes[44]

文章目录基础references基础 hello,world是几乎所有编程语言的第一例子&#xff0c;rust也不例外。但和其它语言不一样&#xff0c;Rust的源码最好拥有自己的项目目录。 $ mkdir ~/pro $ cd ~/pro $ mkdir helloWorld $ cd helloWorld源代码文件名为main.rs&#xff0c;内容如…

JavaScript对象创建方式完全指南:从原始到现代的演进之路

前言 作为一名前端开发者&#xff0c;JavaScript中对象创建是很重要。在JavaScript这门基于原型的语言中&#xff0c;对象几乎无处不在。今天&#xff0c;我将带领大家回顾JavaScript对象创建的7种方式&#xff0c;从最原始的字面量到现代的ES6 class&#xff0c;每一步演进都解…

基于单片机的无线水塔监控系统设计(论文+源码)

本设计为基于单片机的无线水塔监控系统设计&#xff0c;主要由以下几部分组成&#xff1a;均采用STC89C52RC单片机为主控&#xff1b;主机&#xff1a;NRF24L01无线通讯模块&#xff0c;1602LCD液晶显示屏。从机&#xff1a;NRF24L01无线通讯模块&#xff0c;水位传感器&#x…

凌晨0-3点不睡,你熬的不是夜,是人生!

“熬夜”这个词&#xff0c;早已成为现代生活的常态。有人为了工作加班到深夜&#xff0c;有人为了娱乐刷剧到天明&#xff0c;但你知道吗&#xff1f;熬夜最“要命”的时间段&#xff0c;其实是凌晨0点到凌晨3点。别以为只是少睡几个小时而已&#xff0c;这个时间段不睡&#…