资讯动态

掌纹识别毕设源码:SIFT与ResNet双路线实现与避坑指南

发布时间:2026/10/5 5:18:34 来源:尧图企业网站定制
简介一份基于 Python 的掌纹特征提取与分类任务完整源码及文档说明面向计算机视觉、模式识别相关专业的毕业设计和课程设计适合需要快速搭建可运行项目的中高级学习者。资源共 10 个文件压缩包整体约 13.49MB其中 5 个 ZIP 数据包按训练集、验证集和测试集划分4 个 Python 脚本分别承担 ResNet 模型搭建、SIFT 特征工具函数、模型工具函数及测试入口另有 README.md 说明部署步骤与运行方式。代码注释详细目录结构清晰从数据加载、特征提取到模型训练与评估均有完整实现简单部署即可运行同时ZIP 数据文件已整理完毕免去自行采集样本的环节模型模块彼此解耦便于替换数据集和调整参数。项目为作者手打的 98 分毕业设计导师认可度较高已有 76 人学习下载适合需要借鉴完整掌纹识别流程、减少重复开发的同学参考。1. 掌纹特征提取与分类毕设源码两条路线一次性跑通答辩被追问算法原理的时候卡壳是很多毕设项目的通病——掌纹识别尤甚既要讲清特征提取又要说明分类模型两边都糊就容易被判定为“非原创”。这份基于 python 的掌纹特征提取和分类任务源码把两条技术路线都备齐了SIFT 关键点描述子走传统特征工程ResNet 残差网络走深度学习分类训练脚本、测试脚本、数据集划分压缩包和 README 一应俱全解压后按文档装依赖就能跑。适合急需交付毕业设计或期末大作业的学生也适合 python 入门阶段想完整体验图像分类流程的读者。2. 项目结构与数据组织train / valid / test 三包必须先摆对位置拿到的 zip 里不是只有一套模型脚本而是一组互相配合的文件加三到四组数据包。很多新手第一反应是把训练脚本直接跑起来结果FileNotFoundError一个接一个原因基本都出在数据没按约定摆放。所以这篇笔记先从文件清单和目录结构讲起这部分搞定了后面训练和测试的坑至少少一半。2.1 压缩包里的文件到底负责什么一份清单与功能对照先把 zip 里的内容摆开看每份文件承担一段职责对照关系如下文件 / 目录在项目里的作用main Resnet.pyResNet 路线的训练主入口负责数据加载、模型构建、训练循环Resnet_UTIL.pyResNet 配套工具通常封装了预处理、可视化、评估辅助函数SIFT_UTIL.pySIFT 特征提取与匹配工具走传统特征工程路线test.py测试脚本加载训练好的模型跑测试集并输出分类指标README.md环境依赖、目录结构、运行顺序的说明文档train.zip训练集压缩包valid.zip验证集压缩包test.zip测试集压缩包training.zip/testing.zip大概率是 train / test 的冗余副本或按类别拆分后的分片部署时以 README 的说明为准优先使用 train / valid / test 三套先盯一下main Resnet.py这个文件名中间带一个空格。在命令行里直接python main Resnet.py会被 shell 拆成两个参数十有八九报cant open file。常见做法是先重命名成main_resnet.py或者在命令里给整个文件名加引号。这个问题我放到第 5 章避坑里再展开但它值得你从第一步就留意。2.2 三个 zip 的摆放逻辑为什么必须按“根目录 / 类别 / 图片”组织PyTorch 的torchvision.datasets.ImageFolder默认严格要求目录结构根目录下每个子目录是一个类别子目录里的图片文件属于这个类别目录名就是标签名。也就是说data/train下面的class_a、class_b这样的文件夹名称会被按字母序映射成 0、1、2 这样的标签编号。解压命令很简单但解压到哪很关键mkdir -p data unzip train.zip -d data/train unzip valid.zip -d data/valid unzip test.zip -d data/test-d参数指定解压目标目录。解压完成后应该是这样的结构data/ ├── train/ │ ├── class_a/ │ │ ├── 001.jpg │ │ ├── 002.jpg │ │ └── ... │ └── class_b/ │ ├── 001.jpg │ └── ... ├── valid/ │ ├── class_a/ │ └── class_b/ └── test/ ├── class_a/ └── class_b/为什么训练、验证、测试三套数据要物理隔离训练集负责学习参数验证集用于监控过拟合、调整超参数测试集只在最终评估时跑一次。如果把验证集和测试集混着用最终报出的准确率会虚高答辩时“测试数据是不是参与过调参”这个问题一戳就破。这也是我在处理这类项目时一定会先跟 README 核对的部分别指望脚本自己替你分好。2.3 先做数据检查再训练路径与标签对齐的检查脚本数据摆放完毕在敲训练命令之前我会先跑一段检查脚本确认三件事每个类别目录下确实有图片、类别名没有中文或空格、训练集与测试集的类别集合一致。这段脚本可以直接保存成check_data.py使用import os def inspect_split(root): if not os.path.isdir(root): print(f[缺失] {root} 不存在) return set() classes set() for cls in sorted(os.listdir(root)): cls_path os.path.join(root, cls) if not os.path.isdir(cls_path): continue imgs [f for f in os.listdir(cls_path) if f.lower().endswith((.jpg, .jpeg, .png, .bmp))] print(f{root}/{cls}: {len(imgs)} 张) if len(imgs) 0: print(f [警告] {cls} 目录下没有任何图片文件) if any(ord(c) 127 for c in cls): print(f [警告] 类别名包含非 ASCII 字符: {cls}) classes.add(cls) return classes train_classes inspect_split(data/train) valid_classes inspect_split(data/valid) test_classes inspect_split(data/test) if train_classes ! test_classes: print([错误] 训练集与测试集类别不一致) print( 仅在训练集:, train_classes - test_classes) print( 仅在测试集:, test_classes - train_classes)这个脚本的逻辑分三层第一层判断目录是否存在避免路径写错还傻等报错第二层遍历每个类别目录统计图片数量并用endswith过滤出常见图片扩展名第三层做集合比对训练集和测试集的类别必须完全一致否则模型最后一层全连接的输出维度会跟测试标签对不上加载权重时直接抛错。参数上要注意两点endswith接收的是一个元组因此jpg、jpeg、png、bmp都会匹配ord(c) 127用来检测中文在内的非 ASCII 字符。类目含中文不是不能跑ImageFolder也能生成标签但 zip 在不同操作系统间解压时中文目录名极易出现乱码或编码不一致训练集和测试集只要有一边解压结果不同标签编号就全错位了这属于典型的“训练看着正常、测试一塌糊涂”的隐蔽问题。注意如果笔记本内存小于 8GB这里先别急着开训练往下看第 5 章的 DataLoader 那一条能帮你避开训练到一半进程被杀的问题。3. SIFT 特征提取与匹配手工特征这条线为什么能跑出分类结果深度学习模型确实在掌纹识别上表现更好但毕设项目只放一条路线答辩时“为什么不用另一种方法”这个问题很难答。SIFT_UTIL.py 的存在价值就在这里它是一条完整的手工特征路线能跟 ResNet 的结果做对比也能让项目里的特征提取环节变得肉眼可见、可以画图解释。3.1 选型理由SIFT 为什么适合掌纹图像SIFTScale-Invariant Feature Transform做的事可以压缩成四步构建高斯尺度空间金字塔、在金字塔中检测关键点、为关键点分配主方向、生成 128 维描述子。掌纹图像的特点是纹理密集、线纹走向复杂SIFT 对尺度变化和旋转具有一定不变性对褶皱、乳突纹这类局部纹理的响应比较稳定所以它在掌纹这种“背景单纯、纹理丰富”的图像上比很多手工特征更好用。为什么不用 ORB 或 Surf常见做法是优先 SIFT原因是它的描述子维度高、判别力强匹配结果更适合拿来当作分类依据ORB 更快但误匹配率偏高Surf 有专利限制在毕设这种要讲清楚出处的场景里 SIFT 最稳。注意这里指的是 OpenCV 的SIFT_createopen-source 版本可以直接用不存在授权问题。3.2 SIFT_UTIL 的接口形态与特征提取参数调节SIFT_UTIL.py 里通常会封装特征提取和匹配函数常见的接口形态是extract_sift_features(image_path)和match_two_images(desc1, desc2)这样的调用方式。如果 README 里没细说参数我建议你按下面这套模板去检查和改写import cv2 sift cv2.SIFT_create( nfeatures300, # 每张图保留的关键点数量上限 nOctaveLayers3, # 高斯金字塔每组内的层数 contrastThreshold0.04, # 对比度阈值低于该值的弱关键点被丢弃 edgeThreshold10, # 边缘响应阈值用于过滤细长边缘上的点 ) def extract_sift_features(img_path): # 掌纹识别不需要颜色信息灰度图能显著加快关键点检测 img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: return None, None kps, des sift.detectAndCompute(img, None) return kps, des参数说明nfeatures300表示最多保留 300 个关键点掌纹图像用这个量级足够关键点太多不仅慢还会引入大量背景干扰点contrastThreshold控制低对比度点的过滤强度默认 0.04如果图像偏模糊可以降低到 0.02 试试代价是特征点数量会上升edgeThreshold控制边缘响应的过滤数值越小越严格对掌纹这种纹理密集的图像设在 10 附近比较合适。如果检测出来的特征点大量集中在手背景轮廓而不是掌纹纹路打开nOctaveLayers到 4 或者先做一次 CLAHE 直方图均衡化通常能救回来。3.3 匹配分类比值测试 0.7 与最近邻投票逻辑拿到描述子之后怎么分类常见流程是把测试图与每个类别的模板图逐一匹配匹配点数量最多的类别胜出。模板图可以从训练集每类里选几张多张模板做投票能有效降低单张图质量波动的影响。import cv2 def match_score(desc_query, desc_template, ratio0.7): # 任一描述子为空时直接返回 0避免空指针 if desc_query is None or desc_template is None: return 0 if len(desc_query) 0 or len(desc_template) 0: return 0 matcher cv2.BFMatcher() # k2 表示找最近邻和次近邻用来做比值筛选 matches matcher.knnMatch(desc_query, desc_template, k2) good [] for m, n in matches: # Lowe 论文中的经典比值测试最近邻距离显著小于次近邻才算可靠匹配 if m.distance ratio * n.distance: good.append(m) return len(good)这里最难讲清楚的是ratio0.7。它的原理来自 Lowe 2004 年提出的比值测试对查询点来说如果最近邻距离和次近邻距离非常接近说明这个点在模板里有多个相似的候选位置匹配是模糊的、不可靠的如果最近邻距离明显更小才是“一眼认出”的稳定匹配。比值越小筛选越严格0.7 是论文里的经验值实际项目中可以在 0.6 到 0.8 之间调。返回的good列表长度就是两个描述子集的可靠匹配数拿它作为相似度分数比直接用平均距离稳定得多因为平均距离会被少数噪声点拉偏。3.4 这条线的效果边界与改进方向实验做完你会发现SIFT 路线的准确率通常低于深度学习路线这很正常。掌纹图像如果光照不均、指缝阴影明显SIFT 特征点会大量落在阴影边缘上匹配结果被这些背景点主导。常见改进是加一步 RANSAC 几何校验用基础矩阵过滤掉不符合单应关系的误匹配点或者把描述子做 PCA 降维到 64 维减少冗余同时还能提速。把这几个改进方案写进论文里的“进一步工作”比单纯贴准确率更能体现对这条线的理解。4. ResNet 分类训练与评估从迁移学习到准确率上线的完整流程SIFT 处理的是“怎么把像素变成特征”ResNet 处理的是“怎么让网络自己学特征”。main Resnet.py 这条线是项目的核心从数据加载到模型评估是一整套流程下面按工程习惯拆开讲。4.1 残差结构为什么适合小数据量的掌纹分类ResNet 的核心是残差块输入 x 经过两层卷积得到 F(x)输出不是 F(x) 而是 F(x) x。这条恒等映射让深层网络的梯度可以直接回流到浅层解决了网络加深后梯度消失的问题因此可以堆到很深的层数而不退化。对掌纹这种中小规模数据集来说从零训练深网络很容易过拟合所以常规做法是加载 ImageNet 预训练权重把最后一层全连接替换成自己数据集的类别数只微调后面的层。预训练模型的浅层学到的都是边缘、纹理这类通用特征掌纹的线纹结构恰好也用得上这部分。唯一要改的就是输出维度原来全连接层输出 1000 类现在要变成len(train_ds.classes)。4.2 main Resnet.py 里的训练流程与关键参数怎么定训练入口一般长这样对应的就是main Resnet.py的核心逻辑import torch import torch.nn as nn from torchvision import models, transforms, datasets from torch.utils.data import DataLoader transform_train transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) train_ds datasets.ImageFolder(data/train, transformtransform_train) train_dl DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) model models.resnet18(pretrainedTrue) model.fc nn.Linear(model.fc.in_features, len(train_ds.classes))逻辑说明输入必须缩放到 224×224因为预训练权重是在 ImageNet 224×224 图像上训练出来的归一化用的均值[0.485, 0.456, 0.406]和方差[0.229, 0.224, 0.225]也是 ImageNet 的统计值换数据集后不需要改改了反而会让预训练权重首层输入失真。model.fc.in_features是把原全连接层输入维度取出来再拼一个新的输出层维度等于类别数这一步是整个迁移学习的按钮。训练循环部分常见的配置如下criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lr0.001, momentum0.9, weight_decay5e-4) for epoch in range(50): model.train() running_loss 0.0 for inputs, labels in train_dl: optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * inputs.size(0) epoch_loss running_loss / len(train_ds) print(fepoch {epoch 1}, loss: {epoch_loss:.4f})参数说明lr0.001是迁移学习的常见起点比从零训练小一个量级因为预训练权重已经比较接近最优解步子太大容易把学好的特征破坏掉momentum0.9让梯度更新更平稳weight_decay5e-4做 L2 正则对掌纹这种样本量有限的任务能压住过拟合。50 个 epoch 对中小数据集通常是够用的如果 loss 在最后十几个 epoch 基本不动早停比硬跑完更划算。如果跑 10 个 epoch 后训练集 loss 还在 1.0 以上不降先核对学习率再核对数据增强是不是太重了。4.3 test.py 的推理流程与分类评估指标训练完成后test.py 加载保存的模型权重跑测试集输出指标。推理阶段的核心代码就几行model.eval() correct 0 total 0 with torch.no_grad(): for inputs, labels in test_dl: outputs model(inputs) _, preds torch.max(outputs, dim1) total labels.size(0) correct (preds labels).sum().item() accuracy correct / total print(fTest Accuracy: {accuracy * 100:.2f}%)model.eval()会切换 BatchNorm 和 Dropout 的行为BatchNorm 用全局统计量而非当前 batch 统计量Dropout 直接不生效这一步忘了做测试指标会比训练时的验证指标明显偏低。torch.no_grad()关闭自动求导图的构建推理显存占用更小速度也更快。只看准确率是不够的尤其是掌纹数据集类别不均衡时准确率会被多数类带偏。常见的做法是补一张分类评估报告from sklearn.metrics import classification_report print(classification_report(y_true, y_pred, target_namesclass_names))y_true和y_pred需要按样本顺序收集到一个列表里不要跨 batch 乱序。classification_report会同时给出每个类别的精确率、召回率、F1 和支持样本数答辩时这张表比单个准确率数字有说服力得多。类目少的那个类如果召回率明显低说明该类样本不足或者特征与其他类重叠这也是论文里可以展开讨论的点。5. 避坑指南数据、路径、显存与标签的五处翻车现场这部分是拆项目过程中最容易浪费时间的部分每一条都来自真实运行中会撞上的问题。按“现象 → 原因 → 解决”写你可以直接把解决代码抄走。5.1 DataLoader worker 进程被杀报错却指向路径不存在现象训练刚启动终端打印一条DataLoader worker (pid 12345) is killed by signal紧接着报FileNotFoundError但你检查路径发现文件明明存在。原因num_workers4或更大时PyTorch 会 fork 出多个子进程加载数据。每个子进程都会复制一部分数据到内存机器物理内存不够时系统直接把 worker 进程杀死随后主进程再访问一个没能正常加载的数据项时就报路径错误。路径异常只是表象内存不足才是根因。解决内存小的机器先把num_workers改成 0让主进程直接加载数据再不行把batch_size从 32 降到 16。train_dl DataLoader(train_ds, batch_size16, shuffleTrue, num_workers0, pin_memoryFalse)num_workers0意味着不用子进程内存占用直线下降代价是数据加载变慢如果数据量不大这点差距完全可以接受。pin_memoryTrue在大多数场景能提一点速度但内存本来就紧张时先关掉。5.2main Resnet.py这个文件名在命令行里就是跑不起来现象执行python main Resnet.py提示cant open file或直接报No such file or directory有的 shell 会把它当成两个参数Python 只会尝试打开main。原因文件名里有个空格shell 按空白字符切分参数main Resnet.py被拆成了main和Resnet.py两段。解决重命名是最省事的方式。mv main Resnet.py main_resnet.py如果不想动原文件名命令行里用引号包住整个文件名也行python main Resnet.py但后续在代码里import或subprocess调用这个文件时同样会被空格坑所以统一改成下划线命名是长期省心的方案。5.3 SIFT 提取不到特征点des直接返回 None现象调用sift.detectAndCompute之后des为None或者len(kps)只有个位数匹配分数永远是 0。原因掌纹图像如果不是标准的灰度格式读取时颜色通道会干扰关键点检测更常见的是contrastThreshold设得偏高掌纹纹路和背景的对比度本来就低弱关键点全被过滤掉。图像太模糊或者背景复杂也是同款表现。解决先确认用cv2.IMREAD_GRAYSCALE读图再做一次 CLAHE 增强最后把contrastThreshold降下去。img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) img clahe.apply(img) sift cv2.SIFT_create(nfeatures300, contrastThreshold0.02, edgeThreshold15) kps, des sift.detectAndCompute(img, None)clipLimit2.0控制对比度增强强度太高会把噪声也放大tileGridSize是分块大小掌纹这种纹路细密的图像用默认 8×8 就好。调参到这一步如果关键点还是稀疏再看一眼原图是不是有大量背景区域抢占了检测器注意力裁剪掉无关背景比调参数更直接。5.4 训练集准确率很高测试集准确率却低得离谱现象训练日志里准确率到了 95% 以上test.py一跑掉到 60% 以下怎么看都像过拟合但换了小模型也没用。原因大概率是训练集和测试集的类别目录名在解压后不一致ImageFolder按字母序生成标签索引两边只要有一个类目名编码不同同一个标签编号对应的类就是错的。中文目录名跨平台解压成乱码是最常见的导火索。解决统一重命名三套数据里的类目录保持名称完全一致然后重跑 2.3 的检查脚本确认集合相等。import os for root in [data/train, data/valid, data/test]: if not os.path.isdir(root): continue for name in os.listdir(root): new_name name.strip().replace( , _) if new_name ! name: os.rename(os.path.join(root, name), os.path.join(root, new_name))strip()去掉首尾空白replace( , _)把空格换成下划线中文目录名在重命名时顺手改成拼音或英文即可。改完务必重新走一遍检查脚本确认train_classes test_classes。5.5 GPU 不可用torch 版本与驱动不匹配现象程序能跑但训练速度比预计慢很多查看日志发现torch.cuda.is_available()返回False模型全程在 CPU 上跑。原因pip 默认安装的 torch 是 CPU 版本或者本机显卡驱动太老torch 要求的 CUDA 版本比驱动支持的新。解决先跑一段探针代码确认环境import torch print(torch.__version__) print(torch.cuda.is_available())如果is_available()为False需要到 PyTorch 官网按本机 CUDA 版本重新生成安装命令。注意如果本机 python 安装版本太新torch 可能还没有对应的预编译包常见做法是把 python 环境降到 3.83.10 区间再装。装完重跑上面两行确认输出True再开始训练。注意保模型权重时不要只存state_dict把类别名列表class_names一起存成 json下次加载测试时标签顺序才不会对不上。6. 让答辩不再被问倒混淆矩阵与关键点可视化的准备习惯准确率是一个数字但评委真正想看的是你对结果的理解。我在处理这类项目时最后总会补两样东西混淆矩阵和 SIFT 匹配可视化。前者暴露类别间的混淆规律后者把特征提取的过程变成肉眼可见的证据。from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt cm confusion_matrix(y_true, y_pred) disp ConfusionMatrixDisplay(cm, display_labelsclass_names) disp.plot(cmapBlues) plt.savefig(confusion_matrix.png, dpi150)y_true和y_pred需要在 test.py 里按样本顺序各自收集成一个列表不能只留累计正确数。这张图能直接告诉你哪些类互相认错答辩时被问“错误的点集中在哪”可以指着图答这两类掌纹纹理本来就接近误判集中在它们之间。这比笼统地说“整体效果不错”扎实得多。SIFT 路线的可视化更有演示效果。把两张测试图的 SIFT 关键点画出来并排对比同类图匹配点多异类图匹配点稀少这个视觉差异当场就能讲清楚手工特征的分类依据img cv2.imread(sample.jpg) kps, _ extract_sift_features(sample.jpg) vis cv2.drawKeypoints(img, kps, None, color(0, 255, 0)) cv2.imwrite(sift_vis.jpg, vis)drawKeypoints会在原图上把关键点画成彩色圆圈color(0, 255, 0)是 BGR 空间下的绿色保存下来的sift_vis.jpg可以直接贴进论文。Run 项目时把训练日志、验证集准确率曲线截图、混淆矩阵图统一放进 workspace答辩现场按时间线展示比你临时敲命令要稳得多。我从第一次做这类毕设资源复盘时就被拖过进度跑完test.py以为万事大吉结果被问“测试图从哪批数据里选的”当时答不上来。从那以后每次交付类似项目我都强制走一遍完整流程——检查三个 split 的类别一致性、用训练好的模型重跑一次验证集、把混淆矩阵截图归档确认日志里每个数字都能对上再谈“跑通了”。这套习惯救了我好几次。希望这份拆解也能帮你把项目跑得明白答辩站得住。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑