
简介这份资源面向人脸识别算法学习者与MATLAB仿真入门者聚焦基于LDA线性判别分析特征提取的人脸识别方法通过对比不同维度特征下的识别率帮助读者直观理解降维对分类性能的影响。压缩包共包含403个文件以400个PGM人脸图像样本为主另有2个MATLAB脚本LDA.m与Runme.m和1个AVI仿真操作录像整体大小仅4.67MB轻量易用。资源提供MATLAB 2022A环境下的完整仿真流程代码涵盖ORL人脸库读取、LDA特征投影、欧氏距离分类等关键环节并附有操作录像可逐步对照实现避免环境配置与路径设置的常见坑。目前已有250人学习适合作为课程设计、毕业设计或算法对比实验的参考实现尤其适合想快速跑通LDA人脸识别流程的初学者。1. 从ORL库到低维判别子空间为什么LDA比PCA更适合作人脸分类人脸识别这类任务难点从来不在“读图”而在“降维后还能分得开”。一张112×92的ORL人脸图展开就是10304维直接算距离不仅慢而且维数灾难会让欧氏距离失去区分度。PCA找的是方差最大的重构方向保留的是“像不像一张脸”的信息而LDALinear Discriminant Analysis找的是让类间散度最大、类内散度最小的投影方向保留的是“这是谁的脸”的判别信息。所以当目标是识别而非重建时LDA通常是比PCA更合适的起点。本仿真工程基于Matlab 2022a实现了一套完整的LDA人脸识别流程核心文件是LDA.m和Runme.m数据取自ORL人脸库并附带了0009.avi操作录像。它做的事情很聚焦对不同维度特征下的识别率做对比观察投影维度从低到高时识别率先升后平甚至下降的拐点。适合正在做模式识别课程设计、毕业论文或者想快速验证LDA在小型人脸库上效果的研究生和工程师。整套代码逻辑不复杂但有几个关键细节——样本均值中心化、类内散度矩阵的奇异性处理、投影矩阵的截断方式——直接决定最终识别率能到多少。2. LDA的数学原理与四步降维流程2.1 从Fisher准则到特征值分解LDA的核心目标是找到一个投影矩阵(W)使得投影后类间散度(S_B)与类内散度(S_W)的比值最大化。数学上写成Fisher准则[ J(W) \frac{W^T S_B W}{W^T S_W W} ]其中(S_W)是各类样本协方差矩阵的加权和反映类内离散程度(S_B)是各类均值与总体均值之差的加权外积和反映类间分离程度。对这个准则求极值最终归结为求解广义特征值问题[ S_B w \lambda S_W w ]由于(S_B)的秩最多是类别数减1所以LDA最多只能得到(c-1)个有效判别方向。ORL库有40个人意味着有效判别维度上限是39。这一点直接决定了后面对比维度时要怎么设置上限不是拍脑袋选50还是100就能行的。2.2 训练阶段中心化、散度计算、投影矩阵求解打开Runme.m前几行基本就是ORL数据集的读取和预处理。每类取前5张训练、后5张测试是ORL实验最常见的划分方式。实际代码逻辑分四步% 假设 train_data 是 nSample x nFeature 矩阵每行一张人脸图 % nClass 40, nTrainPerClass 5 % 第一步总体均值与各类均值 global_mean mean(train_data, 1); class_mean zeros(nClass, size(train_data, 2)); for i 1:nClass idx (i-1)*nTrainPerClass 1 : i*nTrainPerClass; class_mean(i, :) mean(train_data(idx, :), 1); end % 第二步计算类内散度矩阵 Sw 和类间散度矩阵 Sb Sw zeros(size(train_data, 2)); Sb zeros(size(train_data, 2)); for i 1:nClass idx (i-1)*nTrainPerClass 1 : i*nTrainPerClass; class_samples train_data(idx, :); centered class_samples - repmat(class_mean(i, :), nTrainPerClass, 1); Sw Sw centered * centered; diff_mean class_mean(i, :) - global_mean; Sb Sb nTrainPerClass * (diff_mean * diff_mean); end % 第三步求解广义特征值问题取前 k 个特征向量 % 注意Sw 通常是奇异的需要先做 PCA 降维或加正则项 [V, D] eig(Sb, Sw 1e-6 * eye(size(Sw))); eigenvalues diag(D); [~, sorted_idx] sort(eigenvalues, descend); vector V(:, sorted_idx(1:k)); % k 为要保留的判别维度数这段代码中eig(Sb, Sw 1e-6 * eye(size(Sw)))是关键细节。因为人脸图像的像素维度远大于训练样本数Sw几乎必然是奇异矩阵直接求逆会得到NaN或者极端不稳定的结果。加一个小的单位阵扰动是工程上最常见的处理方式等效于给类内散度加了一个小的正则项。另一种常见做法是先PCA降到nClass * nTrainPerClass - nClass维再算LDA但那种做法多一步PCA的方差阈值判断代码量会大一些。2.3 测试阶段投影与最近邻分类测试部分在摘要描述里已经给出了核心三行代码的脉络test double(imread(strcat(ORL\s, int2str(j), \, int2str(number(e,n)), .pgm))); test_data reshape(test, 1, L); test_Y test_data * vector; % 将测试样本投影到判别子空间这里有一个非常容易踩的坑reshape的第二个参数写的是1, L其中L是单张图像的像素总数即112*92。也就是说test_data是一个行向量所以后面投影用的是test_data * vector而不是vector * test_data。如果在自己的代码里习惯用列向量存样本这个乘法顺序必须对应调整否则矩阵维度直接报错。投影之后计算欧氏距离并找最近邻% 计算测试样本与所有训练样本投影后的欧氏距离 distances zeros(size(train_projected, 1), 1); for m 1:size(train_projected, 1) diff test_Y - train_projected(m, :); distances(m) sqrt(diff * diff); end [~, min_idx] min(distances); % 通过 min_idx 反推所属类别判断是否与真实标签一致 predicted_class ceil(min_idx / nTrainPerClass); if predicted_class j correct_count correct_count 1; end距离度量选欧氏距离是LDA人脸识别最常用的做法因为LDA投影后各维度的量纲已经被Fisher准则统一。如果你换成余弦距离或者马氏距离识别率会略有浮动但不会产生质的改变。真正影响识别率的是投影维度的选择这也是这个仿真项目对比不同维度识别率的出发点。2.4 维度对比实验的设计思路Runme.m里通常是一个循环从k 1到k 39依次计算识别率每轮重复多次取平均以消除训练集随机划分的波动。最终输出一个二维曲线横轴是LDA判别维度数纵轴是识别率。这个实验设计之所以有意义是因为LDA的判别能力并不随维度单调上升——维度太低时判别信息不足维度太高时容易过拟合训练集中的噪声反而伤害泛化能力。通过这条曲线你能直观看到针对ORL库的最优维度区间通常落在20到35之间。3. Runme.m 运行细节与常见报错排查3.1 文件结构与运行前置条件拿到压缩包后解压出的文件包括LDA.m、Runme.m、若干个.pgm样本图以及0009.avi操作录像。强调一点这些.pgm只是演示样本完整ORL库需要从网上下载并放到ORL目录下目录结构必须是ORL\s1\1.pgm到ORL\s40\10.pgm这种形式。运行前必须确认MATLAB左侧当前文件夹路径指向程序所在目录。录像里反复强调这一点原因是imread使用的是相对路径ORL\s1\1.pgm如果当前路径不对第一张图就读取失败直接报错。我的建议是建立如下的目录结构D:\LDA_FaceRecognition\ ├── LDA.m ├── Runme.m ├── ORL\ │ ├── s1\ │ │ ├── 1.pgm │ │ └── ... (共10张) │ ├── s2\ │ └── ... (直到 s40) └── 0009.avi3.2 录像中演示的操作步骤还原0009.avi是Windows Media Player可直接播放的仿真操作录像记录了从打开Matlab 2022a到最终得到识别率对比曲线的全过程。根据录像里点击的菜单顺序操作流程是先运行Runme.m主脚本观察命令行输出每轮训练的识别率最后等待对比曲线图窗口弹出。如果只想单独看LDA投影矩阵的构造过程可以打开LDA.m单步执行。录像中值得注意的一个调试细节是Runme.m脚本开头往往会加一行clear all; close all; clc;。如果上一轮运行中遗留了同名变量比如vector或者train_projected有可能导致本轮计算使用了旧的变量值而不报错最终结果却完全错误。所以自己在修改代码时变量清理这行别删也别改成clear variables——clear all还会清除断点和全局变量排错时更干净。3.3 报错场景与对策最常见的一种报错是错误使用 imread (line ...) 无法打开文件 ORL\s3\7.pgm。原因要么是ORL目录没有放置在程序所在路径下要么是代码中类别索引i是从1开始而目录名是s1开头。另一种高频问题出在eig函数调用上错误使用 eig 矩阵必须为方阵且大小相同。出现这个错误说明Sw或Sb的尺寸不一致根源在于构造散度矩阵时使用了错误的转置方向。centered * centered得到的是nFeature x nFeature而centered * centered得到的是nSamplePerClass x nSamplePerClass。LDA要求散度矩阵定义在特征空间所以必须是前者。如果你的训练样本矩阵是列向量堆叠的转置方向需要反过来这点最容易搞混。还有一类隐蔽错误不报错但结果异常识别率一直在3%以下波动近似随机猜测。这通常是因为图像像素值没有转换为double类型。.pgm读进来是uint8直接做矩阵乘法时Matlab会截断中间结果散度矩阵的值全部溢出为0或255的倍数算出来的投影方向完全失去判别意义。代码里的double(imread(...))转换行不能省略。4. 从39维判别空间到识别率曲线参数对结果的影响4.1 投影维度k的遍历策略维度对比实验的核心代码框架如下accuracy_list zeros(1, 39); for k 1:39 acc_sum 0; repeat_times 5; for r 1:repeat_times % 随机划分训练集和测试集比如每人前5张训练后5张测试 acc_sum acc_sum run_single_lda(k); % 一次训练测试的识别率 end accuracy_list(k) acc_sum / repeat_times; end plot(1:39, accuracy_list * 100, b-o, LineWidth, 1.5); xlabel(LDA 判别维度数 (k)); ylabel(识别率 (%)); grid on;repeat_times取5是为了让曲线更平滑。ORL库的训练/测试划分方式有多种最常见的是每人5张训练5张测试。如果你只跑一次划分就画曲线曲线会有明显的锯齿因为某些随机划分恰好把难样本全分到了测试集。我在实际跑的时候习惯把repeat_times提到10耗时增加不多曲线稳定性提升明显尤其在维度接近39的上界区域。4.2 不同维度下的识别率表现与解读在ORL库上典型的LDA识别率曲线呈现以下特征判别维度k识别率范围现象说明1540%70%判别信息严重不足仅能区分少数差异极大的人脸102088%94%进入可用区间主要判别特征已被捕获253592%96%最高识别率区间类间/类内散度比达到最优平衡363990%94%开始出现过拟合迹象识别率波动增大维度达到类别数-139时LDA的判别子空间已经被完全利用。继续增加特征数量是做不到的因为Sb的秩上限就是39。这也是LDA与PCA的一个本质区别PCA可以取任意多个主成分而LDA的有效维度天然受限。如果你的实验出现所有维度下识别率都低于85%的情况优先检查训练集划分时是否发生了同一个人的图像同时出现在训练集和测试集——ORL库中同一个人的表情变化很大但光照条件几乎一致信息泄漏会虚高识别率数据划分错误则会虚低两种假象都要排除。4.3 类内散度矩阵奇异时的处理策略Sw奇异是LDA在人脸识别中的经典问题因为训练样本数通常远小于图像像素数。这个仿真工程用的是加正则项Sw lambda * eye的方式lambda取1e-6。这个值不是随意定的太大会把判别方向往PCA方向拉失去LDA的判别特性太小则数值不稳定eig计算出的特征向量会出现震荡。更稳妥的做法有两步降维方案先PCA降到nClass * nTrainPerClass - nClass维再在降维后的空间做LDA。这个方案的优点是理论上更严谨避免了人为选择正则参数的困扰缺点是代码多十几行而且要额外处理PCA的方差贡献率阈值。对于这个仿真项目来说正则化方式已经够用如果纯粹为了验证LDA算法本身没必要上两阶段方案。5. 用混淆矩阵和随机划分验证LDA实现是否可靠代码跑通后不能只盯着一张识别率曲线看还需要进一步验证投影矩阵的质量和实验结果的可信度。先画混淆矩阵这一步能快速定位模型在哪些人之间容易混淆% 假设 test_labels 是真实标签向量pred_labels 是预测标签向量 confusion zeros(40, 40); for i 1:length(test_labels) confusion(test_labels(i), pred_labels(i)) ... confusion(test_labels(i), pred_labels(i)) 1; end imagesc(confusion); colormap(gray); colorbar; xlabel(预测类别); ylabel(真实类别);理想情况下混淆矩阵的非零元素应当集中在主对角线上。如果某个类别的行出现了明显的非对角分布说明这个人的人脸特征在LDA子空间下与其他人重叠严重具体原因可能是他的照片存在较大的姿态或表情变化也可能是训练样本中这个类别的图像质量异常。混淆矩阵比识别率数字更能暴露问题。分类器的选择对最终识别率影响也不可忽视。在上述LDA特征提取框架下最近邻分类器效果已经很稳定了。如果换成SVM识别率会有小幅波动因为SVM对特征尺度和核函数参数敏感需要额外的交叉验证。用最近邻分类器时我一般会做一个小的交叉验证测试比如将每个类别的训练集从5张改成6张观察识别率是否显著上升。如果上升幅度超过两个百分点说明当前训练样本量偏少模型的方差较大你在论文里汇报识别率时就要注明训练样本规模这一前提。从工程角度来看LDA.m中最值得复盘的是vector的构造逻辑和维度排序。eig返回的特征向量列的顺序并不保证特征值从大到小排列代码中必须做sort排序。我还见过有人在多轮循环中不小心复用了上一次的vector变量导致不同维度下的识别率全部相同。这个问题通过观察维度k1和k5的识别率是否不同就能快速定位。LDA的原理并不复杂但每个细节——转置方向、数据类型转换、特征值排序、维度截断——都可能成为识别率异常的来源这个仿真项目正好把这些问题挨个暴露了一遍这才是它作为学习素材的真正价值。本文还有配套的精品资源点击获取