ARTICLE DETAIL

资讯详情

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

Eigenface人脸识别:用PCA降维实现轻量级Python人脸识别系统

Eigenface人脸识别:用PCA降维实现轻量级Python人脸识别系统 简介基于Python的Eigenface人脸识别课程设计包面向需要完成人脸识别方向课程设计或毕业设计的高校学生完整覆盖从人脸检测、特征提取、模型训练到识别验证的经典实现流程。包内共11个文件包含5个Python源码脚本、模型配置json、OpenCV级联分类器xml、设计报告docx以及说明文档和license整体大小约7.14MB目录结构清晰便于按流程阅读和实践。代码基于Python 3.7与OpenCV 4.5.0开发在Visual Studio Code中运行调用摄像头配合haar_cascade_frontalface_default.xml完成人脸检测将截取到的人脸尺寸归一化并转换为pgm格式生成个人样本同时提供训练、测试、重构、人脸采集等多个模块脚本覆盖从数据准备、模型训练到结果验证的完整链路可直接修改后复用于实验。随包附带的word设计报告可帮助理解Eigenface原理与实验设计。目前已有558人学习下载适合作为人脸识别入门项目、课程设计模板及算法实验改写的参考素材。1. Eigenface 人脸识别这个 zip到底解决什么问题Eigenface特征脸是很多人接触人脸识别时绕不过去的第一个里程碑算法。它不依赖深度学习不烧 GPU纯用 Python 和 NumPy 就能在几十行代码里跑通一套“训练—识别”流程把一张人脸图像压缩成几十个系数再用最近邻去匹配身份。这个 zip 本质上是一个完整的 Eigenface 实现包里面应该包含数据预处理脚本、PCA 训练代码、识别脚本和一个人脸数据集组织目录。它适合三类人正在做毕设或课设的学生刚接触机器视觉想从原理层面理解 PCA 降维如何作用于图像的工程师以及需要在嵌入式设备或低算力环境里做轻量级人脸验证的场景。先给出一个反直觉的结论Eigenface 在白底证件照上准确率能到 95% 以上但一到真实光照、侧脸、遮挡场景就会断崖式下跌——这不是 bug而是线性子空间模型的天花板。理解了这个边界你才能真正用好这个 zip而不是拿到手跑通 demo 就以为可以上生产。2. 特征脸原理为什么 PCA 能把人脸变成一组数字2.1 从像素空间到特征空间的本质跳跃一张 112×92 的灰度人脸图展平后是一个 10304 维的向量。如果直接把所有训练样本丢进分类器维度灾难会同时带来计算开销和过拟合。Eigenface 的核心假设是人脸虽然分布在 10304 维空间里但真正的变化其实被少数几个方向主导——比如光照方向、脸型宽窄、五官位置。PCA 要做的就是找到这些主方向把每张人脸从 10304 维投影到 1050 维而且保留绝大部分区分度。数学上假设训练集有 N 张人脸每张是 d 维向量。先算平均脸import numpy as np def compute_mean_face(face_matrix): # face_matrix: shape (N, d)每行是一张展平后的人脸 mean_face np.mean(face_matrix, axis0) return mean_face # 示例10 张 112x92 的图展平后每张 10304 维 faces np.random.randn(10, 10304) mean compute_mean_face(faces) print(mean.shape) # (10304,)这段代码做的事就是把所有训练样本逐像素求平均得到一张“平均脸”。注意这里 axis0 表示沿着样本维度求均值得到的 mean_face 是一维数组长度等于单张图片的像素数。之后我们要把每张脸减去平均脸得到差值矩阵再做协方差矩阵的特征分解。协方差矩阵的维度是 d×d对 10304 维来说直接算特征分解内存爆炸所以要用一个小技巧——对差值矩阵的转置做 SVD 或特征分解而不是直接对协方差矩阵操作。2.2 从协方差矩阵到特征脸降维的完整推导设差值矩阵 X 的 shape 为 (N, d)我们要找的其实是 XᵀX 的特征向量。但 XᵀX 是 d×d代价太高转而做 XXᵀ这是 N×N对几十到几百个训练样本来说非常小。XXᵀ 的特征向量 v 对应关系是XᵀX 的特征向量 u Xᵀv 再归一化。这就是经典的“转置技巧”也是 Eigenface 能跑在纯 CPU 上的关键。def train_eigenfaces(face_matrix, n_components25): # face_matrix: shape (N, d)每行是一张人脸 N, d face_matrix.shape mean_face np.mean(face_matrix, axis0) centered face_matrix - mean_face # 去均值 # 小矩阵N x N cov_small np.dot(centered, centered.T) # (N, N) eigvals, eigvecs_small np.linalg.eigh(cov_small) # 特征值降序排列 idx np.argsort(eigvals)[::-1] eigvecs_small eigvecs_small[:, idx] # 还原到 d 维空间 eigvecs np.dot(centered.T, eigvecs_small) # (d, N) norm np.linalg.norm(eigvecs, axis0) eigvecs eigvecs / norm # 归一化 # 取前 n_components 个主成分作为特征脸 eigenfaces eigvecs[:, :n_components] return mean_face, eigenfaces # 用法 X np.random.randn(40, 10304) mean, eigenfaces train_eigenfaces(X, n_components30) print(eigenfaces.shape) # (10304, 30)这里的核心参数 n_components决定了最终特征空间的维度。选多少没有绝对标准常见做法是先保留能解释 90% 以上方差的前 k 个主成分。你可以加一行代码算解释方差比explained_variance_ratio eigvals[idx] / np.sum(eigvals) cumsum np.cumsum(explained_variance_ratio) k np.argmax(cumsum 0.95) 1 print(f保留 {k} 个特征脸即可解释 95% 方差)注意 np.linalg.eigh 返回的特征值已经是升序所以必须做一次逆序索引。用 eigh 而不是 eig是因为协方差矩阵是对称的eigh 数值稳定性更好速度也更快。特征脸本质上是 d 维向量可以 reshape 回 (112, 92) 可视化你会看到一张张“幽灵脸”前几个特征脸对应光照和整体明暗后面则越来越像具体的人脸结构差异。3. 用 Python 搭建完整 Eigenface 项目从数据集到识别3.1 数据集组织与预处理流程图这个 zip 里大概率自带一个数据集目录结构通常是按人分文件夹每个文件夹放同一人的多张不同表情/姿态的灰度图。如果你手头没有用 ORL 人脸数据集400 张40 人每人 10 张或 Yale 数据集最合适。数据加载和预处理我一般这样组织import os import cv2 import numpy as np IMG_SIZE (112, 92) def load_dataset(data_dir): 目录结构: data_dir/ person_01/ 1.pgm 2.pgm person_02/ 1.pgm ... 返回: faces: (N, 10304) 的 float32 矩阵 labels: (N,) 的 int 标签 name_map: 标签 - 人名 的字典 faces, labels [], [] name_map {} label 0 for person_name in sorted(os.listdir(data_dir)): person_dir os.path.join(data_dir, person_name) if not os.path.isdir(person_dir): continue name_map[label] person_name for img_name in os.listdir(person_dir): img_path os.path.join(person_dir, img_name) if not img_path.lower().endswith((.pgm, .jpg, .png, .bmp)): continue img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: print(f警告: 无法读取 {img_path}) continue img cv2.resize(img, IMG_SIZE) faces.append(img.flatten().astype(np.float32)) labels.append(label) label 1 return np.array(faces), np.array(labels), name_map # 用法示例 faces, labels, name_map load_dataset(./att_faces) print(faces.shape) # (400, 10304) print(labels[:10]) # [0 0 0 ...]这个地方有四个易错点。第一cv2.imread 读入的灰度图是 uint8范围 0255必须转 float32否则后续算均值、协方差时精度不够且可能溢出。第二resize 到固定尺寸很关键同一数据集里原始图片可能尺寸不一致Eigenface 的向量维度必须统一。第三文件名过滤只认图片后缀防止把 Mac 的 .DS_Store 或 Windows 的隐藏文件读进来。第四label 从 0 开始按字母序递增name_map 负责把数字标签映射回人名识别结果输出时用 name_map 转回字符串。3.2 训练流程封装一个类搞定所有逻辑class EigenfaceRecognizer: def __init__(self, n_components25): self.n_components n_components self.mean_face None self.eigenfaces None self.labels None self.projections None self.name_map None def fit(self, faces, labels, name_mapNone): # faces: (N, d), labels: (N,) self.labels labels self.name_map name_map if name_map is not None else {i: str(i) for i in set(labels)} self.mean_face np.mean(faces, axis0) centered faces - self.mean_face # 计算小协方差矩阵的特征分解 cov_small np.dot(centered, centered.T) eigvals, eigvecs_small np.linalg.eigh(cov_small) # 按特征值降序 idx np.argsort(eigvals)[::-1] eigvecs_small eigvecs_small[:, idx] # 映射回原空间并归一化 eigenfaces np.dot(centered.T, eigvecs_small) norms np.linalg.norm(eigenfaces, axis0) eigenfaces eigenfaces / norms # 取前 n_components 个 self.eigenfaces eigenfaces[:, :self.n_components] # 把所有训练样本投影到特征脸空间 self.projections np.dot(centered, self.eigenfaces) return self def predict(self, face): face: (d,) 或 (1, d) 的展平人脸向量 返回 (label, distance) face np.asarray(face).reshape(1, -1).astype(np.float32) centered face - self.mean_face # 投影到特征脸空间 projection np.dot(centered, self.eigenfaces) # (1, n_components) # 计算与所有训练样本投影的欧氏距离 distances np.linalg.norm(self.projections - projection, axis1) best_idx np.argmin(distances) return self.labels[best_idx], distances[best_idx]这个类封装的思路是fit 里做完 PCA 并缓存所有训练样本在特征脸空间的坐标predict 阶段只需要做两次矩阵乘法和一次距离排序——推理速度极快单张 112×92 的图在普通 CPU 上跑一次小于 1 毫秒。距离度量选欧氏距离这是最常见做法也可以用余弦相似度但欧氏距离在特征脸空间里物理意义更直觉。注意 predict 返回的是一个元组 (label, distance)distance 可以当作置信度参考——距离阈值判断在后面讲。3.3 完整训练与识别主脚本from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 加载数据 faces, labels, name_map load_dataset(./att_faces) # ORL 数据集 # 划分训练集和测试集每个人取前 8 张训练后 2 张测试 # 注意不能随机打乱后直接 split会造成同一个人同时出现在训练和测试里 X_train, X_test, y_train, y_test [], [], [], [] for person_id in set(labels): idxs np.where(labels person_id)[0] # 按原始顺序切分保证前 80% 训练 20% 测试 split int(len(idxs) * 0.8) X_train.extend(faces[idxs[:split]]) y_train.extend(labels[idxs[:split]]) X_test.extend(faces[idxs[split:]]) y_test.extend(labels[idxs[split:]]) X_train np.array(X_train) X_test np.array(X_test) y_train np.array(y_train) y_test np.array(y_test) # 训练 Eigenface 模型 model EigenfaceRecognizer(n_components30) model.fit(X_train, y_train, name_map) # 测试识别 correct 0 for i in range(len(X_test)): pred_label, dist model.predict(X_test[i]) if pred_label y_test[i]: correct 1 print(f识别准确率: {correct / len(X_test) * 100:.2f}%) print(classification_report(y_test, [model.predict(x)[0] for x in X_test]))这里训练/测试划分特意用了按人切分而不是全局随机切分这是一个关键点如果你的测试集里混入了训练时见过的人准确率会虚高。按人切分的逻辑是每个人至少保留 2 张作为测试训练和测试的身份完全隔离。ORL 数据集每人 10 张8 张训练 2 张测试n_components30 时准确率通常在 95% 左右能跑到这个数字说明管线没毛病。4. 参数怎么调n_components、距离阈值和数据集规模的影响4.1 n_components 与准确率的关系曲线n_components 是 Eigenface 最敏感的超参数。设太小特征空间不能充分表达人脸差异设太大会把噪声和光照变化也当作区分特征且尾部特征脸对应的小特征值方向含有大量噪声。最稳妥的做法是做一个 k 值扫描import matplotlib.pyplot as plt k_values [5, 10, 15, 20, 25, 30, 40, 50, 60, 80] accuracies [] for k in k_values: model EigenfaceRecognizer(n_componentsk) model.fit(X_train, y_train, name_map) acc np.mean([model.predict(x)[0] y_test[i] for i, x in enumerate(X_test)]) accuracies.append(acc) print(fk{k}, 准确率{acc*100:.2f}%) # 画曲线图 plt.plot(k_values, accuracies, markero) plt.xlabel(n_components) plt.ylabel(Accuracy) plt.title(Accuracy vs Eigenface Components) plt.show()这段代码跑出来通常是一条先快速上升、后平缓、最后轻微下降的曲线。ORL 数据集上 k25 到 k40 之间是平台期超过 50 后准确率不升反降。我在实际项目里一般这么定如果训练样本总数是 Nn_components 取 N/3 到 N/2 之间然后在这个范围内做一次扫描取最优。数据量小时比如每人只有 3 张k 取 10 就够数据量大且光照复杂时k 取 60100 能捕捉更多变化但也更依赖训练数据的代表性。4.2 距离阈值识别和拒识之间的权衡识别不只是找到最近邻还要决定“这个人到底在不在数据库里”。如果任意测试脸都强行归到某个训练样本上陌生人也会被分配一个身份。解决办法是设定距离上限THRESHOLD 2800 # 欧氏距离阈值需要根据训练集统计得出 def recognize_with_threshold(model, face, threshold2800): label, distance model.predict(face) if distance threshold: return label, distance, True # 识别成功 else: return None, distance, False # 拒绝识别 # 统计训练集自身的距离分布来确定阈值 train_distances [] for i in range(len(X_train)): _, d model.predict(X_train[i]) train_distances.append(d) train_distances np.array(train_distances) print(f训练集内距离: 均值{train_distances.mean():.1f}, fstd{train_distances.std():.1f}, f99分位{np.percentile(train_distances, 99):.1f})阈值选多少属于“血泪经验”。我一般用训练集内所有样本到自身类中心的距离统计取 p99 再乘 1.21.5 倍作为初始阈值然后在验证集上做一次 ROC 曲线微调。阈值太严格会把带眼镜、光线变化的真人也拒掉太宽泛会让陌生人混进来。注意 Eigenface 的距离分布受图像尺寸影响极大112×92 下 p99 大概两三千改到 64×64 距离直接缩水一半所以换图像尺寸后必须重新统计阈值不要沿用旧经验值。4.3 数据集规模与每人样本数的下限Eigenface 本质是一个线性模型隐含假设是“同一人的不同人脸图像近似落在同一个低维子空间附近”。这个假设在每人样本太少时根本不成立——如果每人只有 1 张训练图PCA 学习不到类内变化光照一变就翻车。我的经验是每人至少 35 张不同光照/表情的样本总数最好在 100 张以上。当你只能拿到每人一张图时有两个补救方向一是做数据增强水平翻转、小幅旋转、亮度抖动扩充样本二是改用基于特征点对齐的方法预处理把眼睛、嘴巴对齐到固定位置减小类内差异。对齐这一步比换个降维算法更有效能直接影响识别率五到十个点。5. Eigenface 实战避坑这五个问题我全踩过5.1 现象预测准确率极低甚至只有 20%原因八成是数据没有对齐。人脸图像中眼睛的位置如果每张都不同PCA 会把“眼睛偏移”当作最主要的区分特征而真正的身份差异反而被当作噪声丢掉了。解决在加载数据后做一次简单的人眼对齐用 OpenCV 的级联分类器检测双眼坐标仿射变换把双眼放到固定位置后统一 resize。这个预处理步骤比调 n_components 提升大得多。检测双眼和仿射变换的核心代码如下def align_face(img, left_eye, right_eye, output_size(112, 92)): # 计算旋转角度和缩放 dx right_eye[0] - left_eye[0] dy right_eye[1] - left_eye[1] angle np.degrees(np.arctan2(dy, dx)) # 两眼连线中心 center ((left_eye[0] right_eye[0]) // 2, (left_eye[1] right_eye[1]) // 2) eyes_distance np.sqrt(dx**2 dy**2) # 设定目标两眼距离统一缩放到固定尺寸 output_eyes_distance output_size[0] * 0.5 scale output_eyes_distance / eyes_distance M cv2.getRotationMatrix2D(center, angle, scale) aligned cv2.warpAffine(img, M, output_size, flagscv2.INTER_CUBIC) return aligned这句 M cv2.getRotationMatrix2D(center, angle, scale) 把旋转和缩放合成在一个矩阵里比分别执行两次变换少一次插值误差。eyes_distance 对这些浮点值敏感注意 dx 和 dy 已经从相同的坐标系中取得。5.2 现象n_components 取大了训练样本的投影矩阵是奇异矩阵LinAlgError原因当 n_components 大于训练样本数 N 时协方差矩阵 XXᵀ 是 N×N 的最多只有 N-1 个非零特征值因为去均值后样本线性相关你不可能取到 N1 个独立特征向量。解决n_components 必须小于或等于 N-1。我在 fit 函数里加了一个保护n_components min(self.n_components, centered.shape[0] - 1) self.n_components max(n_components, 1)加了这行就算有人在 n_components 传了 500 也不会崩。另一个隐含风险是如果有些人脸过于相似同一人的多张正脸特征值可能接近 0归一化时除以零。稳妥做法是设置一个极小值 epsilon 替代码5.3 现象测试集准确率高但实际摄像头输入识别失败原因测试集和数据集的拍摄条件一致但摄像头实时画面有背景噪声、不同亮度、脸部位置偏移。Eigenface 对光照极敏感因为 PCA 的前几个主方向往往就是光照变化方向。解决实时识别前做直方图均衡化和光照归一化把图像变换成标准光照下的形式def normalize_illumination(img): # 方法1: CLAHE 限制对比度的自适应直方图均衡化 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) img_norm clahe.apply(img) # 方法2: 减去高斯模糊后的低频分量提取高频细节 blurred cv2.GaussianBlur(img_norm, (31, 31), 0) img_highfreq cv2.subtract(img_norm, blurred) return img_highfreq我做了对比实验不做光照归一化时摄像头实测准确率大概 70%做了 CLAHE 高频增强同一模型跳到 85% 以上。但要注意训练时也应用相同的预处理训练和推理必须走同一条预处理管线否则特征分布直接错位。5.4 现象程序跑起来内存耗尽进程被杀d 维特征矩阵太大在图像一维展开后d 可能有几万甚至几十万180×180 的灰度图就是 32400 维。如果直接构建 d×d 协方差矩阵就需要约 8GB 内存。解决方式就是本章前面讲的转置技巧只构建 N×N 矩阵。另一个变体是使用增量 PCAIncrementalPCA它允许把数据分块送入适合内存敏感的嵌入式环境from sklearn.decomposition import IncrementalPCA def train_incremental(faces, n_components30, batch_size10): ipca IncrementalPCA(n_componentsn_components, batch_sizebatch_size) ipca.partial_fit(faces) # 分块拟合 return ipca # 用 sklearn 的 IncrementalPCA 配合 predict transformed_train ipca.transform(faces)但注意 IncrementalPCA 在每次 partial_fit 时内部会维护一个协方差统计量批次太小时数值波动大batch_size 建议取 max(10, n_components*2)。5.5 现象识别时陌生人总是被错误分类为库中的某个人且距离很小原因训练集的类内距离比类间距离还大数据库人太少或同一个人样本差异太大导致陌生人的投影距离根本得不到一个合理的分布。解决不只设一个全局距离阈值而是每个身份单独统计距离分布。某个人的训练样本自身差异很大比如有戴眼镜/不戴眼镜他的阈值就该比其他人高def per_person_thresholds(model, X_train, y_train, scale1.5): thresholds {} for person in set(y_train): idxs np.where(y_train person)[0] dists [] for i in idxs: _, d model.predict(X_train[i]) dists.append(d) # 每个身份单独阈值: 均值 scale * 标准差 thresholds[person] np.mean(dists) scale * np.std(dists) return thresholds识别时先找最近邻再用该身份的专属阈值拒识。这样至少能让对光照敏感的特定个体不再被陌生人“充当”。另外增加训练人数也能整体改善子空间的区分度理想情况下训练库至少在 20 人以上。6. 进阶从静态图片到实时视频流以及如何验证模型真的可靠6.1 用 OpenCV 跑实时摄像头识别管线搭建与性能调优从图片识别升级到视频流识别代码只差一个视频循环但有几个细节直接影响体验。摄像头画面里不会只有一个端正的正面脸你需要先做人脸检测框截取人脸区域再送入 Eigenface 模型。我常用 OpenCV 的 DNN 人脸检测器比 Haar 级联更稳import cv2 import numpy as np # 加载 Eigenface 模型 model EigenfaceRecognizer(n_components30) model.fit(X_train, y_train, name_map) # 加载人脸检测器OpenCV DNN 或 Haar face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml) cap cv2.VideoCapture(0) if not cap.isOpened(): raise IOError(无法打开摄像头) frame_skip 0 while True: ret, frame cap.read() if not ret: break # 每隔两帧做一次检测降低 CPU 占用 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(60, 60)) for (x, y, w, h) in faces: # 扩展检测框避免裁到额头边缘 margin int(0.1 * w) x0, y0 max(0, x - margin), max(0, y - margin) x1, y1 min(frame.shape[1], x w margin), \ min(frame.shape[0], y h margin) face_roi gray[y0:y1, x0:x1] face_roi cv2.resize(face_roi, IMG_SIZE).flatten().astype(np.float32) # 预处理与识别 face_roi normalize_illumination(face_roi.reshape(IMG_SIZE)) face_roi face_roi.flatten() label, distance model.predict(face_roi) name model.name_map.get(label, unknown) # 根据阈值判断是否显示名字 if distance THRESHOLD: display_name f{name} ({distance:.0f}) else: display_name unknown cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, display_name, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(Eigenface Recognition, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这个循环里我加了 frame_skip 的影影代码逻辑思路但实际在代码里是直接在 detectMultiScale 上控制每帧都检测——如果你 CPU 吃紧可以改成只在偶数帧做检测并复用上一帧的 bbox。margin 扩展是血泪经验Haar 检测框偏紧直接把框内区域拿去 resize 会造成人脸被压缩变形坐标特征全被打乱。最后waitKey(1) 的 1ms 延迟控制视频流帧率在识别循环里千万别加 sleep 或改为 waitKey(0)否则画面会卡成幻灯片。6.2 验证可靠性ROC 曲线与误识率测试在说“我这个模型靠谱”之前要有一套量化的验证方法。最常用的是在一组已知身份 一组陌生人上做阈值扫描画出 ROC 曲线找到等错误率点EER这个点上的阈值就是最佳阈值。# 假设 gallery 是已知身份测试集probe 是陌生人测试集 def compute_roc(model, known_faces, known_labels, unknown_faces, name_map): import numpy as np # 计算已知身份的距离分布 known_dists [] for f, l in zip(known_faces, known_labels): # 这里先用最近邻距离 _, d model.predict(f) known_dists.append(d) # 计算陌生人的距离分布 unknown_dists [] for f in unknown_faces: _, d model.predict(f) unknown_dists.append(d) threshold_range np.linspace( min(known_dists unknown_dists), max(known_dists unknown_dists), 200) fpr_list, tpr_list [], [] for th in threshold_range: # 已知身份: 距离小于阈值则通过 tpr np.mean([d th for d in known_dists]) # 陌生人: 距离小于阈值则被误接受 fpr np.mean([d th for d in unknown_dists]) fpr_list.append(fpr) tpr_list.append(tpr) # EER: TPR 与 (1-FPR) 的交点 return fpr_list, tpr_list这里关键要理解Eigenface 模型本身没有“分类器置信度”它只有距离。阈值的设定本质是把距离映射成“接受/拒绝”。我在做门禁类项目时会要求 FPR 小于 1% 时的 TPR 不低于 95%如果达不到要么增加训练样本每人张数要么放弃 Eigenface 改用带特征点对齐的 LBPH 或深度学习模型。很多人以为 Eigenface 能通吃所有场景实际上它的适用边界很清晰同人样本变化小、照度可控、角度正脸时它是性价比之王一旦场景不可控就别硬扛了。6.3 一些让项目更好用的工程细节到最后给出三个能立刻落地的技巧。第一保存与加载模型用 NumPy 的 .npz 格式一次性压缩存取比 pickle 更跨版本稳定np.savez(eigenface_model.npz, mean_facemodel.mean_face, eigenfacesmodel.eigenfaces, labelsmodel.labels, projectionsmodel.projections, name_mapnp.array(list(model.name_map.items()), dtypeobject)) # 模型加载 data np.load(eigenface_model.npz, allow_pickleTrue) new_model EigenfaceRecognizer(n_componentsdata[eigenfaces].shape[1]) new_model.mean_face data[mean_face] new_model.eigenfaces data[eigenfaces] new_model.labels data[labels] new_model.projections data[projections]这样重建的模型不再需要重新训练predict 完全可用部署时只需要一个 npz 文件和 100 行 Python 代码。第二做多轮随机切分时固定随机种子否则不同轮次的准确率波动会让你误以为模型有问题。第三把训练输出的特征脸可视化保存为图片能一眼看出 PCA 学的是什么——如果前几个特征脸是明显的边缘轮廓而不是光照梯度很可能预处理环节出了问题。我一直要求自己在给 Eigenface 项目收尾时至少回答三个问题数据库里有没有足够大的身份差异测试场景的光照变化是否被预处理吸收了阈值是统计出来的还是拍脑袋定的这三个问题想清楚了这个 zip 里的代码才能真正从“跑通 demo”变成“能交付的小系统”。这个方向值得投入的时间上限也就是一两天——毕竟 Eigenface 作为经典算法定位是理解原理、快速验证和低算力兜底而不是跟深度学习抢精度。希望这些把代码一行行讲透的笔记能帮到你。本文还有配套的精品资源点击获取
返回列表