☰
基于Python的掌纹识别大作业:从数据采集到模型部署全流程
2026/10/12 16:46:51 网站建设 项目流程

简介:这份资源是面向计算机、人工智能及相关专业学生与教师的机器学习大作业完整项目,围绕基于Python的掌纹识别任务展开,可用于课程设计、毕业设计、作业提交或项目初期立项演示。压缩包共18个文件,约201KB,以11个ipynb实验笔记和5个py脚本为主,辅以license与gitignore,覆盖图像预处理、特征提取、PCA降维、CNN分类、分类器融合、海明距离匹配、分类与认证及结果评估等完整流程,并附预训练模型与多进程加速对比实验。已有184人学习下载,说明其具备一定参考价值。读者可据此获得一套可运行的掌纹识别方案,理解从数据预处理到模型评估的工程链路,并在此基础上修改扩展功能,适合小白进阶与有基础者二次开发。下载后建议先阅读README.md,仅供学习参考,切勿用于商业用途。

1. 掌纹识别大作业:从一张手机照片到可复现的 Python 工程

很多机器学习大作业的题目看着唬人,真正动手才发现卡在第一步:数据从哪来。掌纹识别就是典型——它不像 MNIST 那样开箱即用,也不像人脸识别有一堆预训练权重可以蹭。你手里可能只有一部手机、一个宿舍桌面、一盏台灯,却要交出一份带源码、文档说明和模型文件的高分项目。这篇笔记就按这个真实约束来拆:用 Python 从零搭一套掌纹识别流程,覆盖数据采集、ROI 提取、特征建模、训练评估到模型导出,每一步都给可抄的命令和参数。适合正在做机器学习大作业的学生,也适合想补一个完整 CV 小项目的 Python 入门者。核心不是刷多高的准确率,而是让整条链路能跑通、能解释、能复现。

2. 掌纹 ROI 提取:为什么直接拿原图训练会翻车

掌纹识别的第一个反直觉结论是:把手机拍的整张手掌图直接丢进 CNN,准确率往往惨不忍睹。原因不在模型,而在数据。手掌在画面里的位置、旋转角度、光照强度每次都不一样,背景里还有桌面、键盘、水杯。网络会偷懒去学背景纹理,而不是掌纹主线。所以工业界和学术界的通用做法是先做 ROI(Region of Interest)提取,把掌心区域裁出来、转正、归一化,再送进模型。这一步做扎实,后面哪怕用最简单的分类器都能出结果。

2.1 掌纹 ROI 的几何依据与常见做法

掌纹的关键区域由几条主线围成:生命线、智慧线、感情线,以及它们之间的掌心区。ROI 提取的目标就是找到手掌的稳定关键点,通常是食指与中指之间的指蹼点、无名指与小指之间的指蹼点,以及手掌边缘的两个参考点。用这四个点可以构建一个仿射变换,把掌心区域旋转到统一方向并裁剪成固定尺寸。

常见做法分两条路。一条是基于传统图像处理:用肤色分割或边缘检测找到手掌轮廓,再通过凸包和缺陷点定位指蹼。另一条是直接上关键点检测模型,比如 MediaPipe Hands 或自己标注训练一个轻量回归网络。大作业场景下,我一般推荐 MediaPipe Hands,因为它开箱即用、CPU 就能跑,省去大量调参时间。下面是一个最小可用的 ROI 提取脚本。

import cv2 import mediapipe as mp import numpy as np mp_hands = mp.solutions.hands hands = mp_hands.Hands( static_image_mode=True, max_num_hands=1, min_detection_confidence=0.5 ) def extract_roi(image_path, output_size=(128, 128)): img = cv2.imread(image_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results = hands.process(img_rgb) if not results.multi_hand_landmarks: return None h, w = img.shape[:2] lm = results.multi_hand_landmarks[0] # 取食指与中指指蹼点(landmark 5 和 9 之间)与无名指小指指蹼点(13 和 17 之间) def to_px(idx): return np.array([lm.landmark[idx].x * w, lm.landmark[idx].y * h]) p1 = (to_px(5) + to_px(9)) / 2 p2 = (to_px(13) + to_px(17)) / 2 # 以两点连线为水平轴,构建旋转矩阵 angle = np.degrees(np.arctan2(p2[1] - p1[1], p2[0] - p1[0])) center = tuple(((p1 + p2) / 2).astype(int)) M = cv2.getRotationMatrix2D(center, angle, 1.0) rotated = cv2.warpAffine(img, M, (w, h)) # 以中心为基准裁剪固定大小区域 cx, cy = center half = 100 roi = rotated[max(0, cy-half):cy+half, max(0, cx-half):cx+half] roi = cv2.resize(roi, output_size) return roi

这段代码的逻辑是:先用 MediaPipe 拿到 21 个手部关键点,取指蹼点计算手掌朝向角度,再旋转图像使掌纹主线水平,最后以掌心为中心裁剪 200×200 区域并缩放到 128×128。参数上,min_detection_confidence设 0.5 是平衡漏检和误检的起点,如果照片质量差可以降到 0.3;output_size建议不低于 128,因为掌纹纹理较细,太小会丢失高频信息。裁剪的half值决定 ROI 覆盖范围,100 像素在 1080P 照片上大约对应掌心区域,太小会切掉主线,太大会引入手指边缘噪声。

2.2 批量处理与数据增强的工程细节

单张能跑通之后,下一步是把整个数据集过一遍。这里有个血泪经验:不要边训练边做 ROI 提取,因为 MediaPipe 在 CPU 上处理一张图大约 30 到 50 毫秒,几千张图就是几分钟,每次 epoch 都重算会拖垮训练速度。正确做法是先离线把所有图片转成 ROI 缓存,训练时直接读缓存。

import os import numpy as np from tqdm import tqdm def build_roi_cache(raw_dir, cache_dir, size=(128, 128)): os.makedirs(cache_dir, exist_ok=True) for person in os.listdir(raw_dir): person_dir = os.path.join(raw_dir, person) if not os.path.isdir(person_dir): continue save_dir = os.path.join(cache_dir, person) os.makedirs(save_dir, exist_ok=True) for fname in tqdm(os.listdir(person_dir)): roi = extract_roi(os.path.join(person_dir, fname), size) if roi is None: continue np.save(os.path.join(save_dir, fname + '.npy'), roi)

数据增强方面,掌纹识别和普通图像分类不太一样。左右翻转会破坏掌纹的生物学对称性,不建议用。旋转正负 10 度、亮度微调、高斯噪声是安全且有效的。注意增强只在训练时做,验证和测试必须用原始 ROI,否则评估结果会虚高。

3. 模型选型与训练:从 HOG+SVM 到轻量 CNN 的取舍

大作业的评分标准通常看三点:方法是否合理、结果是否可解释、代码是否完整。不是非得用 ResNet 才能拿高分。我见过太多人直接套一个 50 层残差网络,结果数据集只有几百张,过拟合到验证集准确率像过山车。选模型要匹配数据量。

3.1 小样本场景下的 HOG + SVM 基线

如果每个人只有 5 到 10 张掌纹图,总类别在 20 到 50 人之间,传统方法反而更稳。HOG(方向梯度直方图)能捕捉掌纹主线的边缘方向分布,配合 SVM 在小样本上表现扎实,而且训练时间以秒计。

from skimage.feature import hog from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline import numpy as np def extract_hog_features(roi): # 掌纹主线以低频边缘为主,orientations 设 9 足够 return hog(roi, orientations=9, pixels_per_cell=(16, 16), cells_per_block=(2, 2), channel_axis=None) def train_svm(X_train, y_train): clf = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC(kernel='rbf', C=10, gamma='scale', probability=True)) ]) clf.fit(X_train, y_train) return clf

参数上,pixels_per_cell设 16×16 是掌纹任务的常用值,再小会引入噪声,再大会丢失主线细节。C=10比默认的 1 更严格,适合特征维度高但样本少的情况。这个基线在 30 人、每人 8 张的数据集上,通常能做到 90% 以上的验证准确率,而且训练不到一秒。

3.2 轻量 CNN 的搭建与训练参数

如果数据量上到每人 20 张以上,或者你想在报告里展示深度学习部分,可以上一个 4 层卷积的小网络。不要用预训练权重,因为 ImageNet 学的是自然图像纹理,和掌纹差异太大,微调反而容易过拟合。

import torch import torch.nn as nn class PalmCNN(nn.Module): def __init__(self, num_classes): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(), nn.AdaptiveAvgPool2d(1) ) self.classifier = nn.Linear(256, num_classes) def forward(self, x): x = self.features(x) x = x.flatten(1) return self.classifier(x)

训练参数建议:优化器用 Adam,学习率 1e-3,权重衰减 1e-4,batch size 设 32。如果显存不够就降到 16。epoch 数不要固定,用早停策略,验证集准确率连续 10 个 epoch 不提升就停。数据量少的时候,Dropout 加在 classifier 前面,比例 0.5。注意 BatchNorm 在小 batch 下不稳定,如果 batch size 小于 16,建议换成 GroupNorm。

3.3 训练循环与模型保存

from torch.utils.data import DataLoader, Dataset import torch.optim as optim def train_model(model, train_loader, val_loader, epochs=50, lr=1e-3): device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=1e-4) best_acc = 0.0 for epoch in range(epochs): model.train() for imgs, labels in train_loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() loss = criterion(model(imgs), labels) loss.backward() optimizer.step() # 验证 model.eval() correct, total = 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels = imgs.to(device), labels.to(device) pred = model(imgs).argmax(dim=1) correct += (pred == labels).sum().item() total += labels.size(0) acc = correct / total if acc > best_acc: best_acc = acc torch.save(model.state_dict(), 'best_palm_model.pth') return best_acc

保存模型时只存state_dict,不要存整个模型对象,否则换环境加载会报类找不到的错误。文档说明里要写清楚输入尺寸、归一化参数和类别映射,不然别人拿到模型文件也没法用。

4. 避坑与排查:掌纹识别大作业里最容易翻车的 5 个点

这一章按「现象 → 原因 → 解决」来写,都是我在帮人看代码时反复遇到的。

现象一:训练准确率 99%,测试准确率 40%。原因通常是数据泄漏——同一个人同一张图经过不同增强后同时出现在训练集和验证集。解决方法是按人划分数据集,而不是按图片随机划分。用GroupShuffleSplit或者手动按类别分。

现象二:ROI 提取后图片全是黑的。原因是 MediaPipe 没检测到手,extract_roi返回 None,但后续代码没做判断直接保存了空数组。解决是在批量处理时加if roi is None: continue,并统计失败率,如果超过 10% 要检查拍摄背景是否太杂。

现象三:模型训练 loss 不下降。常见原因是输入没有归一化。ROI 是 0 到 255 的 uint8,直接送进网络会导致梯度爆炸或消失。解决是转成 float32 后除以 255,再用均值 0.5、标准差 0.5 做标准化。

现象四:换一台电脑加载模型报错。原因是保存时用了torch.save(model)而不是torch.save(model.state_dict())。解决是统一保存 state_dict,加载时先实例化模型结构再load_state_dict。

现象五:SVM 训练报内存不足。HOG 特征维度可能上万,样本多的时候内存吃紧。解决是先用 PCA 降到 200 到 300 维,再送 SVM。PCA 的whiten=True对 SVM 有额外帮助。

5. 把大作业做成可展示项目:文档、模型导出与一个提分技巧

最后一章说点让项目从「能跑」变成「能看」的事。大作业评分里,文档和代码规范占的比重往往被低估。一份好的 README 应该包含:环境依赖、数据目录结构、训练命令、评估结果、模型文件说明。不要写成长篇论文,用表格和命令块最清晰。

模型导出方面,如果老师要求演示,可以把 PyTorch 模型转成 ONNX,然后用 ONNX Runtime 做推理,速度比原生 PyTorch 快不少,而且不依赖 CUDA。

import torch.onnx model = PalmCNN(num_classes=30) model.load_state_dict(torch.load('best_palm_model.pth')) model.eval() dummy = torch.randn(1, 3, 128, 128) torch.onnx.export(model, dummy, 'palm_model.onnx', input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch'}})

导出后可以用onnxruntime验证输出是否一致。注意dynamic_axes让 batch 维度可变,演示时可以一次识别多张。

一个提分技巧:在文档里加一张混淆矩阵图和一个错误案例分析。老师看的不只是准确率数字,而是你有没有分析模型在哪些类别上容易混。掌纹识别里,同一个人左右手如果都采集了,模型很容易混淆,因为主线结构相似。把这类样本单独拎出来讨论,比多跑几个 epoch 更能体现理解深度。

我自己做这类项目的习惯是:先跑通最小闭环,再逐步加东西。不要一上来就搭复杂网络,先用 HOG+SVM 出一个基线结果,心里有底了再换 CNN 对比。这样即使 CNN 效果不好,报告里也有完整的对比实验,不会翻车。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询