☰
MATLAB人脸识别实战:att_faces数据集PCA降维与SVM分类全流程
2026/10/14 15:23:03 网站建设 项目流程

简介:这份资源面向人脸识别入门者与MATLAB算法实践者,围绕PCA主成分分析与降维这一经典主题,提供可直接运行的实验素材。包内共802个文件,以400个bmp与400个pgm人脸图像为主,另含1个m脚本与1个txt说明,压缩包约6.24MB,图像用于构建训练与测试集,脚本负责中心化、协方差矩阵计算、特征分解及特征脸提取,txt则记录数据集组织方式。资源源自31att.com提供的att_faces数据集,涵盖不同角度、表情与光照条件下的面部样本,便于验证模型泛化能力。已有598人学习下载,适合希望理解PCA降维流程、复现特征脸方法并衔接SVM等分类器训练的读者,可据此搭建完整的人脸识别实验链路,掌握从高维图像到低维特征空间的转换思路与实现细节。

1. 从一份人脸数据集说起:att_faces 在 MATLAB 里到底能跑出什么

如果你手里正好有一份 att_faces 人脸数据集,又习惯用 MATLAB 做图像实验,那这套组合其实能撑起一个完整的小型人脸识别验证流程。att_faces 是那种典型的「小而规整」数据集:灰度图、固定尺寸、每人多张、姿态和表情有轻微变化,特别适合拿来验证预处理、特征降维、分类器这条链路是否跑得通。标题里那串31att、www.31attcom看着像来源标记,实际不影响使用,真正要关心的是目录结构和图像规格。我见过太多人卡在第一步——数据读进来是乱的,后面 PCA、SVM 全白搭。这篇就按「读数据 → 预处理 → 特征提取 → 分类评估」的顺序,把 MATLAB 下的可复现路径和参数坑一次讲清,适合刚接触人脸识别、想用 MATLAB 快速搭 baseline 的开发者。

2. 把 att_faces 读进 MATLAB:目录遍历与标签对齐

2.1 先看清 att_faces 的目录长什么样

att_faces 常见组织方式是每人一个子文件夹,文件夹名就是类别标识,里面放若干张 pgm 或 jpg 灰度图。比如s1到s40,每个文件夹 10 张左右,尺寸多为 92×112。这个结构决定了读取逻辑:先列子目录,再列每个子目录下的图像文件,类别标签从文件夹名解析。很多人直接dir('*.pgm')一把梭,结果所有图混在一起,标签全丢,后面分类器只能瞎猜。正确做法是两层遍历,把路径和标签成对存下来。

% 读取 att_faces 数据集,返回图像路径、标签、图像矩阵 dataRoot = 'att_faces'; % 数据集根目录 subDirs = dir(dataRoot); subDirs = subDirs([subDirs.isdir]); % 只保留文件夹 subDirs = subDirs(~ismember({subDirs.name}, {'.', '..'})); % 去掉 . 和 .. imgPaths = {}; labels = []; for i = 1:numel(subDirs) personDir = fullfile(dataRoot, subDirs(i).name); imgFiles = dir(fullfile(personDir, '*.pgm')); % 按实际格式改后缀 for j = 1:numel(imgFiles) imgPaths{end+1} = fullfile(personDir, imgFiles(j).name); labels(end+1) = i; % 用文件夹序号作为类别标签 end end fprintf('共读取 %d 张图像,%d 个类别\n', numel(imgPaths), numel(subDirs));

这段代码的关键点有三个:一是isdir过滤保证只处理文件夹;二是ismember去掉.和..,否则会多出两个无效类别;三是标签用循环序号i,保证和文件夹一一对应。如果你的数据集文件名里带有人名或编号,也可以从文件名解析,但用文件夹序号最稳,不会因为命名规则变化翻车。

2.2 图像读取与向量化:尺寸必须统一

读进来之后要转成 double 并向量化。att_faces 尺寸固定,但如果你混入了其他来源的图,必须先 resize。常见做法是imread后imresize到 92×112,再double归一化到 [0,1]。向量化时用(:)拉成列向量,所有图按列拼成矩阵,每列一个样本。这样后续 PCA 的输入就是D×N的矩阵,D 是像素数,N 是样本数。

imgSize = [112, 92]; % 高×宽,注意 MATLAB 是行优先 N = numel(imgPaths); X = zeros(prod(imgSize), N); for k = 1:N img = imread(imgPaths{k}); if size(img,3) == 3 img = rgb2gray(img); % 防止混入彩色图 end img = imresize(img, imgSize); X(:,k) = double(img(:)) / 255; % 归一化到 [0,1] end

参数说明:imgSize的顺序容易搞反,MATLAB 里size(img)返回[行, 列],即[高, 宽],所以写[112, 92]。归一化除以 255 是习惯做法,也可以不做,但做了之后 PCA 的数值稳定性更好。如果内存吃紧,可以改用single,但 att_faces 规模小,double完全够用。

2.3 划分训练集与测试集:别让同一张脸同时出现在两边

划分方式直接影响评估结果。常见做法是每人随机取 7 张训练、3 张测试,或者按固定顺序前 7 后 3。随机划分要固定随机种子,否则每次跑出来不一样,调参时根本分不清是参数起作用还是划分变了。用rng(42)锁住,再用randperm打乱每人内部的索引。

rng(42); % 固定随机种子,保证可复现 trainIdx = []; testIdx = []; for i = 1:numel(subDirs) idx = find(labels == i); perm = randperm(numel(idx)); trainIdx = [trainIdx, idx(perm(1:7))]; testIdx = [testIdx, idx(perm(8:end))]; end Xtrain = X(:, trainIdx); ytrain = labels(trainIdx); Xtest = X(:, testIdx); ytest = labels(testIdx);

这里每人 10 张,取 7 训练 3 测试。如果某人有 8 张,就取 5 或 6,保证测试集不为空。标签向量ytrain、ytest要和列对应,后面分类器直接拿来用。

3. PCA 降维与特征脸:MATLAB 里怎么算才不翻车

3.1 为什么先做 PCA:维度灾难与冗余像素

92×112 的图拉直后是 10304 维,而样本可能只有几百。直接送分类器,计算量大不说,像素间高度相关,分类器容易被噪声带偏。PCA 的作用是把数据投影到方差最大的方向,用少量主成分保留大部分信息。在人脸识别里,这些主成分可视化出来就是「特征脸」,所以这套方法也叫 Eigenfaces。选 PCA 不是因为它最先进,而是因为它足够稳、可解释、在 att_faces 这种小数据上不容易过拟合。

3.2 用 MATLAB 内置函数做 PCA:中心化不能忘

MATLAB 的pca函数默认会对列做中心化,但前提是你把样本按列排。输入Xtrain是D×N,pca要求行是观测、列是变量,所以要先转置。另一个坑是pca默认可能用 SVD 或 ALS,数据量大时选Economy省内存。下面代码取累计方差贡献 95% 的主成分。

% Xtrain 是 D×N,转置成 N×D 再送 pca [coeff, score, latent, tsquared, explained] = pca(Xtrain', 'Economy', true); cumVar = cumsum(explained); k = find(cumVar >= 95, 1); % 取到 95% 累计方差 fprintf('保留 %d 个主成分,累计方差 %.2f%%\n', k, cumVar(k)); % 训练集和测试集投影 XtrainPCA = score(:, 1:k); % N_train × k XtestPCA = (Xtest' - mean(Xtrain', 1)) * coeff(:, 1:k); % 用训练均值中心化

逻辑说明:pca返回的coeff是特征向量,score是投影后的得分。测试集投影时必须用训练集的均值做中心化,不能用自己的均值,否则信息泄露,评估结果虚高。k取 95% 是经验值,也可以取 90% 或 99%,后面分类器效果会变,建议在验证集上试。

3.3 特征脸可视化:确认降维没把脸降没了

投影完最好把前几个主成分还原成图像看一眼,确认不是噪声。coeff的每一列就是特征脸,reshape 回 112×92 再显示。

figure; for i = 1:6 subplot(2,3,i); eigenface = reshape(coeff(:,i), imgSize); imshow(eigenface, []); title(sprintf('Eigenface %d', i)); end

如果显示出来是一片雪花或全黑,多半是中心化没做对,或者coeff取错了维度。正常特征脸能看到明暗交替的面部轮廓。这一步不参与分类,但能帮你快速判断前面流程有没有硬伤。

4. 分类器选型与评估:最近邻、SVM 在 MATLAB 里的落地

4.1 最近邻分类器:最简单也最容易被低估

PCA 降维后,最直接的分类器是最近邻:算测试样本到每个训练样本的欧氏距离,取最近的训练标签。MATLAB 里用pdist2一行搞定。别小看它,在 att_faces 上,只要预处理和 PCA 没问题,最近邻的准确率往往能到 90% 以上,而且没有超参数要调,适合当 baseline。

% 最近邻分类 distMat = pdist2(XtestPCA, XtrainPCA, 'euclidean'); % N_test × N_train [~, minIdx] = min(distMat, [], 2); ypredNN = ytrain(minIdx); accNN = mean(ypredNN == ytest); fprintf('最近邻准确率: %.2f%%\n', accNN * 100);

参数说明:pdist2的第二个参数是训练集,返回每行测试样本到每列训练样本的距离。min沿第二维取最小,minIdx是对应训练样本索引。如果准确率异常低,先检查XtestPCA和XtrainPCA是否在同一投影空间,常见错误是测试集用了自己的均值中心化。

4.2 SVM 分类器:多分类用 fitcecoc

想要更高准确率可以上 SVM。MATLAB 里多分类用fitcecoc,底层是纠错输出码。核函数选线性还是 RBF 要看数据,PCA 后的特征通常线性可分性已经不错,先试线性核,不行再换 RBF。注意 SVM 对特征尺度敏感,PCA 得分最好再做一次标准化。

% 特征标准化 mu = mean(XtrainPCA, 1); sigma = std(XtrainPCA, 0, 1); sigma(sigma == 0) = 1; % 防止除零 XtrainNorm = (XtrainPCA - mu) ./ sigma; XtestNorm = (XtestPCA - mu) ./ sigma; % 训练多分类 SVM t = templateSVM('KernelFunction', 'linear', 'Standardize', false); svmModel = fitcecoc(XtrainNorm, ytrain, 'Learners', t); ypredSVM = predict(svmModel, XtestNorm); accSVM = mean(ypredSVM == ytest); fprintf('SVM 准确率: %.2f%%\n', accSVM * 100);

逻辑说明:templateSVM里Standardize设为 false,因为前面已经手动标准化,避免重复。fitcecoc会自动处理多分类。如果类别数多、样本少,SVM 可能过拟合,这时最近邻反而更稳。建议两个都跑,对比着看。

4.3 评估指标:准确率之外还要看混淆矩阵

准确率只给一个数,看不出哪类容易错。用confusionmat看混淆矩阵,能发现是不是某几个人的脸被反复认错。att_faces 里光照和表情变化小的类别通常分得开,变化大的容易混。

C = confusionmat(ytest, ypredSVM); figure; confusionchart(C); title('SVM 混淆矩阵');

如果某两类互相错得厉害,可以回去看这两类的图像,多半是姿态或光照差异大,考虑加直方图均衡化或换更鲁棒的特征。评估不是为了刷分,是为了定位问题。

5. 避坑与排查:att_faces + MATLAB 最常见的 5 个翻车点

5.1 现象:准确率只有 10% 左右,跟随机猜差不多

原因:标签和图像没对齐,或者训练集测试集划分时标签错位。常见于用dir遍历时顺序和标签生成顺序不一致。解决:在读取阶段就打印每个类别的图像数和标签,确认labels长度和imgPaths一致;划分后用unique(ytrain)检查类别是否齐全。

5.2 现象:PCA 投影后测试集准确率远低于训练集

原因:测试集投影时用了自己的均值中心化,造成信息泄露或分布偏移。解决:统一用训练集的均值,即XtestPCA = (Xtest' - mean(Xtrain',1)) * coeff(:,1:k)。这个坑很隐蔽,因为代码不报错,只是结果虚高或虚低。

5.3 现象:特征脸显示为噪声,分类效果差

原因:图像向量化时没有归一化,或者imresize把图像转成了 uint8 后直接double没除 255,导致数值范围差异大。解决:读取后统一double(img)/255,并在pca前确认Xtrain的数值范围在 [0,1]。

5.4 现象:SVM 训练报错「类别必须有两个以上」

原因:某个类别在训练集中只有 1 个样本,fitcecoc无法构建二分类器。解决:划分时保证每类训练样本不少于 2 个;如果数据集中有类别样本极少,考虑合并或剔除。

5.5 现象:每次运行结果都不一样

原因:没有固定随机种子,randperm每次划分不同。解决:在划分前加rng(42),并确保所有随机操作都在固定种子之后。调参阶段尤其重要,否则无法判断参数是否真的有效。

6. 进阶技巧:把准确率再往上推一推的实操习惯

想让 att_faces 上的结果更稳,我一般会做三件事。第一,预处理加一步直方图均衡化,用histeq对每张图做,能缓解光照差异,代价是可能放大噪声,所以做完要对比准确率再决定是否保留。第二,PCA 的主成分数不要死守 95%,在 80% 到 99% 之间扫一遍,用交叉验证选,往往 90% 左右比 95% 更好,因为后面几个主成分可能是噪声。第三,分类器融合:最近邻和 SVM 的预测结果做投票,如果两者一致就采纳,不一致时看距离或概率,简单投票有时能涨一两个点。

% 简单投票融合 ypredFusion = ypredNN; for i = 1:numel(ytest) if ypredNN(i) ~= ypredSVM(i) % 不一致时选距离更近的 [~, idxNN] = min(distMat(i,:)); if distMat(i, idxNN) < 0.5 % 阈值按数据调 ypredFusion(i) = ypredNN(i); else ypredFusion(i) = ypredSVM(i); end end end accFusion = mean(ypredFusion == ytest); fprintf('融合准确率: %.2f%%\n', accFusion * 100);

阈值 0.5 不是固定的,要在验证集上试。融合不是万能,如果两个分类器错得一样,投票也没用,所以先看混淆矩阵再决定。

还有一个习惯:每次实验记录k、核函数、是否均衡化、随机种子,写成一行日志。我吃过亏,调了半天参数,最后发现是某次忘了固定种子,结果全白跑。MATLAB 的diary可以自动记命令窗口输出,配合save存模型,复现时省很多事。

最后说个验证方法:留一法。att_faces 样本少,留一法(每次留一张测试,其余训练)能更充分利用数据,虽然慢,但评估更稳。写个循环,把每人每张都当一次测试,最后算平均准确率。如果留一法和固定划分差距大,说明划分方式对结果影响大,报告结果时要说明划分策略。

希望帮到你。

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

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

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

立即咨询