ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MATLAB实现ORL人脸识别:PCA特征脸从原理到实战

MATLAB实现ORL人脸识别:PCA特征脸从原理到实战 简介这是一套面向人脸识别算法学习与实验的Matlab项目资源以ORL人脸数据库为核心并附带Yale人脸数据集适合新手及有经验的开发人员用于课程设计、毕业设计或算法对比研究。压缩包共8个文件以5个Matlab脚本m文件为主覆盖数据预处理、PCA降维、LDA降维、多方式比对与预测等完整流程同时包含1个mat格式的人脸数据文件、1个readme说明及1个Word文档整包仅174KB无需庞大数据集即可快速上手。目前已有1486人参与学习或下载源码经过亲测校正下载后可直接运行。借助这份资源读者既能获取标准化的ORL/Yale人脸数据库又能对照源码理解经典人脸识别算法的实现细节还可根据文档中的说明调整参数、替换数据集为后续改进或扩展实验提供清晰的技术起点。1. ORL400张灰度照片为什么仍是人脸识别的入门样本2002年前后剑桥大学ATT实验室整理的那批人脸照片后来被简称为ORL数据库。40个人、每人10张、112×92分辨率的PGM灰度图一共400张放在今天连一张手机照片的体积都比不上却是无数人脸识别论文里跑通第一个实验的地方。原因并不在于数据量大而在于它把“同一张脸的不同变化”压缩到了最紧凑的形态表情、微小的姿态偏转、是否戴眼镜、闭眼与睁眼每样都有一点但每样都不极端。对一个刚把PCA、LDA读明白的工程师来说第一件该做的事就是用这个数据集亲手跑通一次“训练—投影—分类”的完整管线把矩阵运算和图像语义之间的关系彻底理顺。这篇博客就围绕ORL数据库用MATLAB从读图、建特征空间到分类验证讲清楚参数怎么设、坑在哪里。2. 用MATLAB把ORL数据库读成训练矩阵路径、归一化与划分还没有开始写PCA或者LDA之前大部分人的第一个瓶颈出现在数据加载上。ORL的图片不是常见的JPG或PNG而是PGM格式——一种最简的灰度图像格式MATLAB的imread可以读但读出来是uint8类型如果不转成double后面的中心化和协方差计算都会出问题。另一个坑是文件命名ORL数据库的图片分布在40个子目录里s1到s40每张图名为1.pgm、2.pgm这种连续数字不会自动带上人的标签读图时要把“文件夹编号”与“图像编号”同时记录下来。第一节代码建议直接写成一个加载函数一次性完成图像读取、向量化、按行拼装矩阵三个动作。常见做法是以一个根目录为输入返回一个样本矩阵和对应的标签向量function [X, y] load_orl(root_dir) persons dir(fullfile(root_dir, s*)); X []; y []; for i 1:length(persons) img_dir fullfile(root_dir, persons(i).name); imgs dir(fullfile(img_dir, *.pgm)); for j 1:length(imgs) img imread(fullfile(img_dir, imgs(j).name)); img double(img) / 255; % 归一化到[0,1]并转为double X [X; img(:)]; % 每张图展成一行向量 y [y; i]; % 类别标签是文件夹序号 end end end这段代码有三个值得注意的设计。第一double(img)/255把灰度值从0~255缩放到0~1这一步能显著提升后续PCA计算时的数值稳定性尤其是在像素均值不为零时中心化后的矩阵特征值分布更平稳。第二img(:)把112×92的矩阵展成10304维的列向量再用转置成行向量拼到X矩阵里最终X的尺寸是400×10304每一行是一张完整的人脸。第三标签直接使用文件夹序号iMATLAB里这就是多分类SVM或判别分析可以直接接受的类别编号。读取之后大部分人容易忽略的一个问题是如何划分训练集和测试集。ORL是40个人各10张图如果直接拿前5张训练后5张测试运气成分很大——某人的5张训练图可能恰好都是同一姿势而测试图像变化又很大。常见的做法是分层随机划分对每个人的10张图做随机排列取前k张做训练后10-k张做测试。下面这个函数返回的是抽取索引而不是直接返回数据使得同一个划分可以重复用到PCA、LDA、SVM等多个实验中function [train_idx, test_idx] split_orl(y, k) train_idx []; test_idx []; for i 1:max(y) idx find(y i); idx idx(randperm(length(idx))); train_idx [train_idx; idx(1:k)]; test_idx [test_idx; idx(k1:end)]; end endsplit_orl的输入k是每人取几张训练randperm保证随机顺序循环保证每个类别都严格分到k张训练图和10-k张测试图。把固定划分保存下来再继续往下做是保证实验结果可以被复现的关键习惯。3. 用PCA特征脸建立低维人脸子空间从原理到MATLAB实现读入数据之后核心问题来了10304维的向量直接去做分类不仅计算量大而且很多维度实际上是在描述光照噪声和背景变化对识别没有帮助。PCA的思想是把原始高维空间中的数据点投影到方差最大的若干正交方向上用少量维度保留最主要的分布结构。在人脸识别里这些正交方向一张张展开成图像以后看起来像是模糊的人脸轮廓所以被称为“特征脸”。特征脸的计算有一个在MATLAB里必须掌握的技巧直接用10304×10304的协方差矩阵做特征值分解内存和耗时都不可接受。好在当样本数m远小于特征维度n时可以转而计算m×m的小矩阵的特征向量再映射回原空间。其数学依据是若A是中心化后的训练矩阵m行n列则A·Aᵀ的尺寸只有m×m其特征向量v满足AAᵀvλv两边同时左乘Aᵀ得到AᵀA(Aᵀv)λ(Aᵀv)也就是说Aᵀv就是原协方差矩阵的特征向量归一化后即可用。落到代码上仍以X为样本矩阵、k_train为每人训练样本数为例[X, y] load_orl(ORL); [train_idx, test_idx] split_orl(y, 5); X_train X(train_idx, :); X_test X(test_idx, :); % 中心化 mu mean(X_train, 1); X_train_c X_train - mu; X_test_c X_test - mu; % 小矩阵特征分解 AA X_train_c * X_train_c; % 尺寸为 num_train x num_train [V, D] eig(AA); D diag(D); [D, order] sort(D, descend); V V(:, order); % 映射回原空间并进行单位化 eigenfaces X_train_c * V; for i 1:size(eigenfaces, 2) eigenfaces(:, i) eigenfaces(:, i) / norm(eigenfaces(:, i)); end这里的中心化用X_train - mu而不是手动循环MATLAB对矩阵广播支持得很好前提是所有样本矩阵是double类型。eig返回的特征值D和特征向量V都是按升序排列的所以用sort(D,descend)同时把V的列重排取前几个主成分时直接取前列就行。X_train_c * V就是上面推导的Aᵀv归一化的目的是让投影系数在不同维度上具有一致的尺度后续计算欧氏距离时各维度才能公平比较。有了特征脸矩阵剩下就是投影。把训练集和测试集分别投到前r个主成分上得到一组低维坐标r 50; % 取前50个主成分 W eigenfaces(:, 1:r); X_train_proj X_train_c * W; X_test_proj X_test_c * W;从这一步开始人脸识别就不再是图像处理而是一个标准的K近邻或最小距离分类问题。每一张测试图投影后得到一个50维的向量与所有训练投影向量计算欧氏距离距离最小的那个训练样本的标签就是预测结果。距离计算用MATLAB自带函数即可d pdist2(X_test_proj, X_train_proj, euclidean); [~, idx_min] min(d, [], 2); pred train_label(idx_min); accuracy mean(pred test_label);pdist2第二行输出每一行里最小值的列位置train_label取自y(train_idx)。如果不想依赖Statistics Toolbox也可以自己写sqrt(sum((X_test_proj - X_train_proj).^2, 2))但要用两层循环速度不如pdist2快。这里r50只是一个起步值实际效果取决于数据本身下一节会说明如何把r调到一个有意义的值。4. 识别率实验中必须调好的三个参数主成分数、距离度量与训练集大小直接跑一次上面的代码识别率通常在0.85到0.95之间具体数字取决于两个因素r取多少以及每个人用几张图做训练。这两个参数背后的规律恰恰是ORL这个数据库最有教学价值的地方。主成分数r决定了特征空间保留了原始数据多少信息。PCA理论里常用“累计贡献率”来判断第i个特征值λᵢ占总特征值之和的比例就是该主成分解释的方差比例。事实上在ORL上前20个主成分通常能解释80%以上的方差前50个能到95%左右。但高贡献率并不等于高识别率因为方差大的方向不一定是最适合区分身份的方向这可能受光照和姿态影响。换句话说识别率随r的变化不是单调上升而是先升后稳再可能下降实验时务必要画出这条曲线而不是想当然地取一个固定值。下面这段代码把r从5扫到100记录识别率的变化r_list 5:5:100; acc zeros(size(r_list)); for ri 1:length(r_list) W eigenfaces(:, 1:r_list(ri)); Xtr X_train_c * W; Xte X_test_c * W; d pdist2(Xte, Xtr, euclidean); [~, idx_min] min(d, [], 2); acc(ri) mean(pred(idx_min) test_label); end plot(r_list, acc, o-);从实际经验看r取到30~50之间识别率曲线会进入平台期再增大主成分数可能引入噪声导致轻微下降。计算时要注意X_train_c和X_test_c必须用同一个mu和同一个W这是很多人实验复现对不上的原因——测试集不能参与中心化均值和特征空间的估计。第二个参数是距离度量。pdist2除了欧氏距离常用的还有余弦距离。欧氏距离对向量长度敏感如果某张图整体偏亮或偏暗投影后的模长也会整体偏大可能导致同一张脸的两个样本被拉远。余弦距离只考虑方向、不看模长对光照偏置有一定容忍度d_cos pdist2(Xte, Xtr, cosine);ORL的光照变化相对温和因此欧氏距离和余弦距离的区别不会太大但在实际工程中特别是遇到光照不均的人脸时余弦距离往往更稳。建议两种都跑一次记录对比不要凭感觉选。第三个参数是训练集大小。为了评估5张训练、5张测试之外的设置把split_orl(y, k)里的k分别设为3、5、7观察识别率变化。一个直观的结果是k3时识别率通常不到0.85k5能过0.9k7可以到0.97以上。把这一组结果整理成一张表格能很清楚看出性能随样本量的变化趋势每人训练张数测试总张数PCA维数欧氏距离识别率余弦距离识别率3280500.840.865200500.920.937120500.970.97这张表是典型的ORL结果形态具体数字每一次运行会有微小波动。如果识别率明显低于这个区间通常不是算法问题而是前面的预处理出了岔子——最常遇到的是忘记中心化测试集或者特征向量没有单位化。另一个值得注意的细节是split_orl内部用了randperm每次运行划分都不同所以比较不同k值结果时要固定随机种子或者在同一数据划分下评估否则表格里的差异可能来自划分噪声。5. 用留一法验证识别结果定位是哪一类最容易分错最后一个建议是把实验从固定划分换成留一法交叉验证并且把分错样本单独打印出来。ORL数据库只有400张图留一法一共做400次实验每次以1张测试、399张训练这在MATLAB里整个过程只有几十秒。与之前只做一次划分相比留一法得到的识别率更稳定也更能暴露模型的系统性问题。留一法的实现思路很简单对每个样本i把第i行从X中剔除作为训练集剩下的那个样本作为测试循环400次统计正确次数rng(42); n size(X, 1); correct 0; for i 1:n Xtr X([1:i-1, i1:end], :); ytr y([1:i-1, i1:end]); Xte X(i, :); yte y(i); mu mean(Xtr, 1); Xtr_c Xtr - mu; Xte_c Xte - mu; AAt Xtr_c * Xtr_c; [V, D] eig(AAt); D diag(D); [~, order] sort(D, descend); V V(:, order); W Xtr_c * V; for j 1:size(W, 2) W(:, j) W(:, j) / norm(W(:, j)); end W W(:, 1:50); Xtr_p Xtr_c * W; Xte_p Xte_c * W; dt pdist2(Xte_p, Xtr_p, euclidean); [~, idx] min(dt, [], 2); pred ytr(idx); if pred yte correct correct 1; else fprintf(样本 %d 真实标签 %d 预测标签 %d\n, i, yte, pred); end end fprintf(留一法准确率: %.2f%%\n, correct / n * 100);这段代码直接把整个PCA流程嵌套在循环里每次重新计算特征空间结果比固定划分更严格。400次循环里每次都要做一次小型PCA计算量上完全可接受但如果换成更大的数据集这种做法就不够高效了。分错样本被打印出来以后建议顺手做一个统计把每个类别的错误次数单独汇总。具体做法是把i换算成类别号ceil(i/10)用一个向量记录错误数然后bar画出来。如果错误集中在某几个人往往说明这几个人的照片变化剧烈比如胡子、眼镜变化明显或者姿态偏转角度大。反过来如果错误分散在很多类别说明特征空间的区分力不够优先增加r或者提取更鲁棒的特征。最后一件事是把前几个特征脸画出来看一下figure; for i 1:9 subplot(3, 3, i); imshow(reshape(W(:, i), 112, 92), []); end这里W(:, i)是第i个特征向量reshape回112×92就能看到特征脸。如果前几个特征脸有明显的人脸轮廓说明PCA提取到的是身份相关结构如果看起来像噪声则训练集划分或中心化多半有问题。这个可视化检查值得每次实验都做一次它比识别率数字更快地告诉你算法到底学到了什么。只想快速跑通流程的话r取50、欧氏距离、每人5张训练已经是ORL上性价比最高的一套默认配置后续要提升识别率再考虑从特征脸切到Fisher脸或更现代的表示方法。本文还有配套的精品资源点击获取
返回列表