ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

掌纹识别大作业:从预处理到分类器融合的完整实战指南

掌纹识别大作业:从预处理到分类器融合的完整实战指南 简介一份面向机器学习课程设计与期末大作业的掌纹识别项目源码基于Python实现已获导师指导并被评为97分高分项目。资源共17个文件其中11个ipynb过程笔记和5个py脚本构成整体仅189KB压缩包结构清晰适合直接运行。内容覆盖掌纹图像预处理、特征提取、PCA降维、滤波器设计、CNN分类模型构建、预训练模型微调以及分类器融合与海明距离对比等关键环节任务包括分类与认证两个方向的完整实现。已有401人学习可用作期末大作业或课程设计的完整参考模板无需修改即可复现结果适合需要高质量机器学习项目源码的在校学生。1. 掌纹识别大作业97 分项目怎么把“识别”拆成四条技术线把掌纹识别做成机器学习大作业最容易踩的坑是把它当成猫狗分类的翻版直接丢一个 CNN 上去调参。掌纹识别里真正决定分数的不是网络有多深而是特征怎么提、PCA 怎么降维、分类和认证怎么区分以及最后能不能用一组干净的实验数据把方法说明白。这份 97 分高分项目源码把掌纹数据的处理流程做成了完整闭环图像预处理、特征提取、PCA 降维、CNN 分类、海明距离认证、分类器融合还配了多进程加速和时间对比脚本。下文按文件组织顺序拆开讲每个模块写清楚参数怎么设、坑在哪。适合机器学习课程设计与期末大作业复现也适合想做生物特征识别基线的同学参考。2. 图像预处理与数据组织先把一张掌纹图变成模型爱吃的样本预处理决定了后面所有实验的上限。项目里图像预处理.ipynb和classify.py前半段做的就是这个事。掌纹图不是通用图像直接用原图喂模型学到的往往是背景、手指缝和手腕边缘而不是掌纹纹路。这一节把预处理流程和数据组织规则拆开讲。2.1 分类任务和认证任务对数据组织的要求完全不同写代码之前得先想清楚这份大作业做的是分类还是认证。项目里的分类与认证.ipynb和两个任务的对比.ipynb把两条线都做了。分类任务对应 1:N每一类是一个人输出一个 person_id认证任务对应 1:1判断两张图是否来自同一个人。分类要按人分 ID认证要构造正负样本对同一人的两张图是正样本对不同人的两张图是负样本对。如果数据表里只有图像路径没有 person_id后面所有实验都做不了所以数据组织是整个项目的地基。公开掌纹库每个类通常只有 5~10 张图像这个样本量直接决定网络能不能做深。处理这类项目时我一般先把每个人当成一个类别索引建一个 CSV 记录 sample_id、person_id、img_path、split再写 Dataset 按行读取。好处是后面分类和认证可以复用同一张表不需要为两个任务各写一套目录遍历代码。sample_idperson_idimg_pathsplitP001_011data/palm/P001/train_01.pngtrainP001_021data/palm/P001/train_02.pngtrainP002_012data/palm/P002/test_01.pngtestP002_022data/palm/P002/test_02.pngtest注意看这张表split 的划分单位是 person_id 而不是 image。同一个人的两张图必须只出现在 train 或只出现在 test 里如果同一个人前后两张图一边训练一边测试测试准确率会虚高答辩时被问到很难说清。压缩包里没有显式列出 data 目录复现时通常自己按上面的方式组织再用 pandas 或 glob 读进来。2.2 图像预处理.ipynb 的流程拆解原始图经常是固定背景的整只手图片预处理的目标只有四个去掉背景、转灰度、拉平光照、统一尺寸。下面这段逻辑对应图像预处理.ipynb的核心部分classify.py里也复用了同一套处理# image_preprocess 核心逻辑 import cv2 import numpy as np def preprocess_palm(img_path, out_size(128, 128), roi_ratio0.6): img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) h, w img.shape # 只保留中心区域去掉手指、手腕和背景 y0 int(h * (1 - roi_ratio) / 2) y1 int(h * (1 roi_ratio) / 2) x0 int(w * (1 - roi_ratio) / 2) x1 int(w * (1 roi_ratio) / 2) roi img[y0:y1, x0:x1] roi cv2.resize(roi, out_size) # 直方图均衡化弱光照下纹路对比度更好 roi cv2.equalizeHist(roi) # 转 float32 并归一化到 [0, 1] roi roi.astype(np.float32) / 255.0 return roi这段代码里两个参数要重点说明。roi_ratio 决定 ROI 占原图的比例0.6 表示只留中心 60% 区域太小会切掉靠近指尖和腕部的掌纹太大则把手指边缘卷进来。out_size 是最终输入尺寸手工特征流程里 64x64 就够提取统计特征CNN 流程建议 128 往上因为掌纹细纹需要空间分辨率。转 float32 再除以 255 是必要操作否则后续 PCA 和距离计算会遇到类型警告和量纲问题。提示如果原始图像掌纹区域不是居中分布中心裁剪不可靠需要按手掌轮廓或指缝点做仿射变换。公开掌纹数据集基本都是标准姿态中心裁剪够用。2.3 数据增强与训练/验证/测试划分的边界项目里同时跑分类和认证数据集划分必须提前固定否则两个 notebook 各切一次结果对比就失效。我习惯写一个固定随机种子的划分函数按 person_id 分组确保同一个人的图像只出现在一个集合里。划分最小单位是“人”而不是“图”这一点在报告里要写清楚。数据增强只放训练集随机水平翻转、小角度旋转、轻微平移。验证集和测试集必须保持原图。掌纹是有向纹理旋转增强范围要克制±10 度以内足够旋转太大会让掌纹拓扑失真。增强在 batch 读取时实时做不要提前落盘否则磁盘占用成倍增长后面跑多进程时间对比时还会把磁盘 IO 混进总时间实验数据不好看。3. 特征提取与 PCA手工特征在小样本掌纹任务上为什么是刚需这一章对应feature_extraction.py、滤波器PCA.ipynb、PCA.py和海明距离.ipynb。在每类样本极少的情况下手工特征加分类器比直接上深度网络要稳得多这也是这份项目能拿 97 分的重要原因。3.1 先做特征提取而不是直接端到端开放题大作业里常见矛盾是每类只有几张图CNN 很快就过拟合。掌纹识别有经典方法论主流路线就是“特征提取 分类器”。Gabor 滤波器组在不同方向、不同尺度下对掌纹图做卷积得到响应图压缩成统计量再交给 SVM 或做距离匹配。项目里把这条路作为 CNN 的对比基线在两个任务的对比.ipynb里用同一份数据做横向比较这个设计本身就很有说服力。Gabor 滤波器本质上是带方向选择性的带通滤波器对特定方向的条纹非常敏感天然匹配掌纹主线和皱纹的纹理方向。裸 CNN 要学到类似的方向选择性需要大量卷积核拟合每类 5~10 张图不够它吃。所以大作业里保留手工特征基线既是方法线也是防过拟合的手段。3.2 feature_extraction.pyGabor 滤波器组与特征向量拼接feature_extraction.py的核心是构建 Gabor 核对预处理图滤波再提取统计量。常见配置是 4 个方向、1~2 个尺度每个响应图取均值和标准差。下面这段是典型实现# feature_extraction.py 核心逻辑 import cv2 import numpy as np def build_gabor_bank(ksize31, sigma4.0, lambd10.0, gamma0.5): filters [] # 四个方向0、45、90、135 度覆盖掌纹主线走向 for theta in (0, np.pi / 4, np.pi / 2, 3 * np.pi / 4): kernel cv2.getGaborKernel( (ksize, ksize), sigma, theta, lambd, gamma, 0, ktypecv2.CV_32F ) filters.append(kernel) return filters def extract_gabor_features(img, filters): # img: 预处理后的灰度图float32范围 [0,1] feats [] for kernel in filters: filtered cv2.filter2D(img, cv2.CV_32F, kernel) feats.append(filtered.mean()) # 整体响应强度 feats.append(filtered.std()) # 纹理起伏程度 return np.asarray(feats, dtypenp.float32)Gabor 参数之间会互相影响调试时按下面这张表逐个调参数取值作用ksize31核尺寸决定感受野范围sigma4.0高斯包络标准差控制频带宽度theta0~135 度滤波器方向对应掌纹线走向lambd10.0波长控制条纹疏密gamma0.5纵横比让核在垂直方向被拉扁变量名写 lambd 是因为 lambda 是 Python 关键字。sigma 太大会平滑掉细纹太小又变成噪声响应lambd 越小条纹越密适合掌纹里密集的褶皱。如果特征在验证集上分不开先把每个方向的响应图打印出来看一眼比盲目调分类器有用。另外 filter2D 有边界效应图像边缘响应偏低最好在 ROI 基础上再内缩 10 个像素否则手指轮廓的响应会被混进特征。3.3 PCA.py 与滤波器 PCA 的降维逻辑Gabor 特征拼接后维度不高为什么还要 PCA因为不同方向响应之间有相关性直接喂 SVM 会受共线性影响而且同一张图在不同光照下特征浮动大。PCA 把高相关特征旋转到主成分空间去掉冗余分量。PCA.py里的流程分三步标准化、求解主成分、投影。# PCA.py 核心流程先标准化再做主成分投影 from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA # X: (n_samples, n_feature_dims)来自 extract_gabor_features scaler StandardScaler() X_scaled scaler.fit_transform(X) pca PCA(n_components0.95) # 保留 95% 方差 X_pca pca.fit_transform(X_scaled) print(原始维度:, X.shape[1]) print(降维后维度:, X_pca.shape[1]) print(累计方差贡献率:, pca.explained_variance_ratio_.cumsum())两个参数最容易出错。第一StandardScaler 必须在 PCA 之前否则量纲大的特征主导协方差矩阵第二n_components 用 0.95 表示保留 95% 方差比固定写成 50 更稳因为不同数据集上 Gabor 维度不同固定维数要么丢信息要么降维不足。这里还有一个隐蔽的数据泄漏问题scaler 和 pca 都只能在训练集上 fit验证集和测试集只做 transform。正确写法是这样# 正确做法数据划分必须在 fit 之前 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) scaler.fit(X_train) X_train_scaled scaler.transform(X_train) X_test_scaled scaler.transform(X_test) pca.fit(X_train_scaled) X_train_pca pca.transform(X_train_scaled) X_test_pca pca.transform(X_test_scaled)如果把全量数据拿去 fit测试集信息提前进入降维矩阵测试指标会虚高这是答辩最容易翻车的地方。3.4 用 PCA 特征算海明距离分类和认证的最后一公里在认证任务里模型输出的不是 ID而是两张图是否匹配。海明距离.ipynb的做法是先把特征二值化再逐位比较差异比例。PCA 特征可以按维度中位数转成 0/1然后算海明距离def hamming_distance(a, b): # a, b 是两个二值化后的 0/1 向量 diff np.bitwise_xor(a, b) return diff.sum() / a.size这个 trick 在报告里很好写特征从连续值变成二进制占用空间小匹配只靠 XOR 和 popcount。注意海明距离和 CNN 分类概率不是一回事前者面向认证后者面向分类。在两个任务的对比.ipynb里两条线的评价指标要分开分类用准确率认证用 FAR/FRR 和 EER。4. CNN 与分类器融合自建网络、迁移学习与投票策略怎么组合这份项目不是只用手工特征CNN 是重要加分点。构建CNN分类掌纹.ipynb、预训练模型CNN.ipynb、分类器融合.ipynb三份文件合起来是一条完整的深度学习路线。4.1 构建 CNN 分类掌纹.ipynb轻量 CNN 的结构PalmCNN 是轻量结构两个卷积块加自适应池化加全连接头。为什么轻量每类只有几张图网络参数过多会先记住训练集而不是学习泛化特征。两个卷积块已经足够学到掌纹局部方向纹理分类头把 64 维特征映射到类别数。# 构建CNN分类掌纹.ipynb 中的 PalmCNN import torch.nn as nn class PalmCNN(nn.Module): def __init__(self, num_classes, in_channels1): super().__init__() self.features nn.Sequential( nn.Conv2d(in_channels, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.AdaptiveAvgPool2d(1) ) self.classifier nn.Linear(64, num_classes) def forward(self, x): x self.features(x) return self.classifier(x.view(x.size(0), -1))in_channels1 对应灰度图输入如果预处理时复制成三通道就改成 3。AdaptiveAvgPool2d(1) 是懒人设计不管输入 64 还是 128池化后维度都是 64分类头不用改。训练参数建议 batch size 16 到 32学习率 1e-3Adam交叉熵损失。样本少时我只训 30~50 个 epoch用验证集早停。训练震荡就把学习率降到 5e-4再看增强强度是否过大。4.2 预训练模型 CNN迁移学习怎么用才不出错预训练模型CNN.ipynb用 ImageNet 预训练 ResNet18 做迁移。最大的坑是输入通道和输入尺寸ImageNet 是三通道 224 或 256掌纹是灰度图。把灰度图直接喂进去是错的。推荐做法是把灰度图复制成三通道保留 ImageNet 预训练权重而不是替换第一层卷积。替换 conv1 会让预训练权重完全失效反而更慢收敛。# 预训练模型CNN.ipynb 核心思路 import torchvision.models as models import torch.nn as nn model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 预处理阶段把灰度图复制成三通道 (B, 1, 128, 128) - (B, 3, 128, 128) # 修改最后的全连接层输出类别数 model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(model.fc.in_features, num_classes) )预训练模型要尊重它的输入尺寸preprocess_palm的 out_size 改成 224 再放进这段流程。显存有限时冻结前 4 个 block只训练最后 1 个 block 和 fc学习率用 1e-4 到 3e-4比从头训练小一些。Pytorch 在切换训练和 eval 模式时要注意 BatchNorm 行为batch size 也别给太小的值否则 BN 统计量不稳定。4.3 分类器融合加权投票为什么能反超单一强模型分类器融合.ipynb的思路很直接手工特征那条线用 SVM 出概率CNN 那条线出 softmax 概率在验证集上加权融合。两个模型错误模式互补SVM 对精细纹理敏感CNN 对整体结构敏感互补性越大投票收益越大。# 分类器融合.ipynb 中的加权投票 import numpy as np def weighted_vote(prob_svm, prob_cnn, w0.6): # w 是 SVM 分支权重1-w 是 CNN 分支权重 blended w * prob_svm (1 - w) * prob_cnn return np.argmax(blended) # 在验证集上搜索最优 w best_w, best_acc 0.0, 0.0 for w in np.arange(0.0, 1.05, 0.05): acc np.mean([ weighted_vote(svm_p[i], cnn_p[i], w) y_val[i] for i in range(len(y_val)) ]) if acc best_acc: best_w, best_acc w, acc print(fbest w{best_w:.2f}, val_acc{best_acc:.4f})酒精藏在细节里SVM 的 decision_function 分数和 CNN softmax 概率量纲完全不一样融合前要先各自归一化。我一般把 svm_score 和 cnn_prob 都做 min-max 归一化否则 w 的语义会被破坏。报告里写融合结果时要分别列出 SVM、CNN、融合三个准确率只写融合后的数字会被追问提升来源。5. 掌纹识别避坑手册五个典型翻车现场与排查路径这一章把复现过程中最容易踩的五个坑单独列出来。每一条都是真实翻车场景按现象、原因、解决三步排查。5.1 训练 loss 不降准确率恒等于多数类占比现象CNN 训练十几轮loss 几乎不动输出概率集中在某一个类准确率恰好像多数类的占比。原因标签和特征错位。常见于自己写 DataLoader 时对图片列表用 os.listdir 排序同时又手动 shuffle图片和标签没有同步也可能是数据集中混入了背景图模型学的是背景而不是掌纹。解决先做数据完整性校验随机抽 10 个 batch把图像缩略图和 label 一起打印出来人工核对。给每个样本生成唯一 IDDataLoader 里只按 ID 读取不要一边排序一边打乱。5.2 PCA 后准确率反而比原始特征更低现象原始特征喂 SVM 能到 80% 以上PCA 降维到 50 维后面识别率掉到 70%。原因没做标准化或者 n_components 定得太低丢掉了有区分度的低频信息。Gabor 特征维度间量纲差异不大但分布不同直接 PCA 会被大方差方向带偏。解决先 StandardScaler 再 PCAn_components 在 0.90 到 0.99 之间做扫描对比不要把 PCA 后的特征当成唯一答案配合 SVM 的 C 参数一起调。5.3 海明距离认证的阈值怎么调都不准现象类内距离和类间距离分布重叠严重无论阈值取多少FAR 和 FRR 都居高不下。原因二值特征太短滤波器方向太少特征区分度不足或者训练样本和测试样本采集时间跨度大光照差异被反映到距离上。解决先画类内和类间距离直方图阈值取两个分布的交叉点附近增加 Gabor 方向和尺度让特征维度足够报告里用 EER 作为单一指标别只给一个手调阈值。5.4 多进程.py 在 Jupyter 里跑起来就报错或直接卡死现象在 notebook 里调用 multiprocessing.Pool 没反应Windows 下反复弹错误或直接杀掉内核。原因multiprocessing 在 Windows 下用 spawn 启动新进程会重新导入主模块notebook 顶层没有 ifname main 保护时子进程递归执行 worker 函数之前的代码。解决把多进程入口放到独立 .py 文件用 ifname main: 包住 Pool 逻辑任务函数放模块顶层。也可以换成 joblib.Parallel和 notebook 的兼容性好很多。5.5 预训练模型加载后训练更慢显存还容易爆现象加上预训练 ResNet18 后原来能跑完的实验 OOM或一个 epoch 慢得离谱。原因ResNet18 参数比轻量 CNN 大一个量级学习率没调前几轮 loss 跳动剧烈batch size 沿用原来的 32显存直接爆。解决输入 resize 到 224冻结前 4 个 blockbatch size 从 8 或 16 开始学习率降到 1e-4有条件就开自动混合精度。报告里写明冻结和微调的比例面试时这是一个有含金量的工程细节。6. 多进程加速与结果评估把报告里的实验数据做扎实最后这一章处理两个容易被忽略但很拉分的点多进程加速怎么测评估指标怎么写。多进程.py和多进程的时间对比.py负责前者结果评估.ipynb负责后者。6.1 多进程加速怎么测出可信的加速比多进程加速适合放在预处理和特征提取阶段因为每张图的处理相互独立。测速时要保证输入相同、核数一致先跑单进程再跑多进程每秒取平均。建议按下面这段写# 多进程的时间对比.py 测速思路 import time t0 time.time() # 单进程处理所有图 single_time time.time() - t0 t1 time.time() # 多进程 Pool.map 处理同样一批图 multi_time time.time() - t1 print(f单进程 {single_time:.4f}s, 多进程 {multi_time:.4f}s, 加速比 {single_time / multi_time:.2f}x)注意图像数量太少时多进程开销反而大报告里写清楚“加速比随图像数量增大而上升”最好给一组不同图像数量的折线图。6.2 结果评估混淆矩阵、分类报告与 FAR/FRR结果评估.ipynb里至少要做三件事分类报告、混淆矩阵、认证任务的 ROC 曲线。分类报告直接调 sklearn 输出即可重点是把每个类别的 precision、recall、f1-score 都列出来答辩时一眼看出哪些人容易被混淆。from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_true, y_pred)) print(confusion_matrix(y_true, y_pred))认证任务要用 FAR 和 FRR不要只算一个准确率。FAR 是把别人认成自己的比例FRR 是把自己认成别人的比例两者随阈值变动画出 ROC 曲线EER 取 FAR 与 FRR 交点。这个指标是掌纹认证任务的标准写法报告里放一张 ROC 曲线能直接拉高实验深度。从那以后我每次做大作业都会先把单进程跑通、把指标脚本写全再开多进程优化和调参否则根本分不清是业务 bug 还是并行引入的问题。这份源码更适合先按原始逻辑复现一遍把数据表和评估脚本固定好再动融合和多进程希望帮到你。本文还有配套的精品资源点击获取
返回列表