(一)基于MatLab的PCA降维人脸识别系统
本次博客内容将详细介绍如何使用MatLab,进行PCA降维来识别人脸。内容参考张铮《精通MatLab数字图像处理与识别》。书中有些内容应该是出现了打印错误,再次对部分算法进行改正,补充了书中没有的函数,此次博客内容仅适用于新手入门。部分背景,介绍描述性语言都省略,想看详细的内容,请看书籍内容。
1. 实验使用器材
MatLab、ORL人脸库
其中ORL人脸库来源:http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html :
2. ORL人脸库简单介绍
数据库中一共有400张人脸图(40人,每人10张,大小 112*92)
数据库中光照姿势等变化都不大
4.理论介绍
(1)PCA降维
如果将一个图像的每一个像素点看成一个特征,我们将得到一幅图像会具有 112*92 = 10304个特征点,在这里可以看成是10304维。自然而然,如此庞大的维度计算量是相当的大,而且在如此高维度下会出现维度灾难,也就是高维度会使得识别率逐渐降低。所以我们要将图像进行降维到一定的维度。接下来将说到如何进行降维。
上一段中提到了一幅图像将拥有‘10304个特征点‘,也就是我们可以换成是1 * 10304 的矩阵,1表示一张图,10304表示112 * 92 。例如2 * 10304 表示 两张 112 * 92的图像。此时将用到函数: ReadFaces
function [imgRow,imgCol,FaceContainer,faceLabel] = ReadFaces(nFacePerPerson,nPerson,bTest)
%读入ORL人脸库的指定数目的人脸前五张(训练)
%输入:nFaceperperson:每个人需要读入的样本数
% nPerson:需要读入的人数,默认为全部40人
% bTest:bool型参数。默认为0(读入训练样本前五张),1表示后五张
%输出:FaceContainer:人脸容器,nperson * 10304 的2维矩阵,每行对应一个人脸向量
if nargin == 0
nFacePerPerson = 5;
nPerson = 40;
bTest = 0;
elseif nargin < 3
bTest = 0;
end
img = imread(‘DATA/ORL/S1/1.pgm’);
[imgRow,imgCol] = size(img);
FaceContainer = zeros(nFacePerPerson * nPerson,imgRow*imgCol);
faceLabel = zeros(nFacePerPerson*nPerson,1);
%读入训练样本
for i = 1:nPerson
i1 = mod(i,10);
i0 = char(i/10);
strPath = ‘Data/ORL/S’;
if (i0 ~= 0)
strPath = strcat(strPath,’0’+i0);
end
strPath = strcat(strPath,’0’+i1);
strPath = strcat(strPath,’/’);
tempStrPath = strPath;
for j = 1:nFacePerPerson
strPath = tempStrPath;
if bTest == 0 %读入训练数据
strPath = strcat(strPath,’0’+j);
else
strPath = strcat(strPath,num2str(5+j));
end
strPath = strcat(strPath,'.pgm');
img = imread(strPath);
FaceContainer((i-1)*nFacePerPerson + j,:) = img(:);
faceLabel((i-1)*nFacePerPerson+j) = i;
end %j
end %i
save(‘Mat/FaceMate.mat’,’FaceContainer’)
通过该函数能够得到一个人脸的容器:其中规定了 当bTest = 0 时,将训练样本。当bTest = 1 时开始测试样本。
为什么要训练样本?得到的这个人脸的容器有什么用?
接下来利用得到的人脸容器FaceContainer进行PCA降维。
人脸容器中 具有 200 * 10304 矩阵,每一行为人脸信息。其中降维,我在线性代数中理解为得到相应的单位特征向量,这些单位特征向量就是维度。具体可以学习线性代数中关于空间变换的知识。
将空间中的大量的点,在一定维度下进行投影(可以理解为在一定维度在进行表示):可以得到一个推广公式
x = m + a1*e1+a2*e2+a3*e3……. x为样本,m为均值,e表示的就是单位特征向量。
为什么会得到这样的一个推广公式? (第一次写博客,公式推导省略一下,其实原理很简单)
虽然多维度的点能在一定维度下表示了,但是怎么确定直线e的方向来使得平方误差E最小呢。这里抛出了直线e的方向和平方误差。何为直线e的方向?当在1维时,直线e就一根,它可以指着任何方向。当在2维时,直线e维两个互相垂直,也可以朝着任意方向,所以推广以后,直线e我们只知道了它是相互垂直,但是具体的方向我们是可以任意确定了。平方误差E和直线e方向有什么关系呢?e的方向将会决定e向量的值,E中会使用到e的运算。使用平方误差的意义,E = ( ||(m+a1 * e)-x1 || +||(m+a2 * e)-x2 || +||(m+a3 * e)-x3 || +….. )^2就是将降维(投影)后的点与实际样本点的差值平方。如果差值越小,说明这些方向上的e能够尽量更好的表示这些点。于是打开平方(这里说的打开并不是直接这样打开,E表示的时候使用累加符号表示的)在运算过程中出现了 S 散布矩阵。并且求解这个最小值变为了带有约束条件的求解问题,使用拉格朗日乘数法。就得到了S * e = namda * e ,namda为特征值,e为特征向量,S取最大,E将最小,则在n维下,依次取最大的特征值即可。所以,降维至n维,就是取n个特征向量运算得到a = e*(x-m)
在这里要说一下,可能会有读者看不明白,S矩阵如何计算得出,S矩阵的得出为什么与取最大特征值对应的特征向量有关?请在网上搜索,主成分分析的理论基础,再结合本文,应该能解决您的疑惑。
解决了一般的PCA降维理论以后可以进行实验了,但是,再运算时发现,维度太大,内存耗尽,为什么呢。原因出在了S矩阵上,所以这里引出了快速PCA降维,原理很简单,就是对S * e = namda * e进行简单的一些变换。下面使用函数 fastPCA
function [pcaA,V] = fastPCA(A,k)
%快速PCA
%输入: A:样本矩阵
% K:降维至k维
%输出: pcaA:降维后,由特征向量组成的特征矩阵,每行一个样本,列数为维度k
% V:主成分向量
if nargin == 0
display(‘参数不足’)
else
[r,c] = size(A);
meanVec = mean(A);
%计算协方差矩阵的转置
Z = (A - repmat(meanVec,r,1));
covMatT = Z* Z’;
%计算convMatT的前K个特征值和特征向量
[V,D]=eigs(covMatT,k);
%得到协方差矩阵的特征向量
V = Z’ * V;
%特征向量转为单位向量
for i = 1:k
V(:,i) = V(:,i) / norm(V(:,i));
end
pcaA = Z*V;
%保存变换矩阵V和变换原点meanVec
save(‘Mat/PCA.mat’,’V’,’meanVec’);
end
其中A,就是人脸容器
留下问题,为什么要训练样本?
开始准备操作
接下来就主成分脸进行可视化。
其中visualize_pc,再PCA_main函数后面,注意一下,别复制错了
function PCA_main(k)
%ORL 人脸采集的主成分分析
%输入k : 降维至k
global imgRow;
global imgCol;
nPerson = 40;
nFacePerPerson = 5;
disp(‘读入人脸数据。。。。’);
[imgRow,imgCol,FaceContainer,faceLabel] = ReadFaces(nFacePerPerson,nPerson);
disp(‘………………’)
nFaces = size(FaceContainer,1);%样本人脸数目
disp(‘PCA降维’);
% LowDimFace nPerson*nFacePerPerson行,k列,每行代表一张主成分脸,每个脸k个维特征
% W是分离变换矩阵
[LowDimFace,W] = fastPCA(FaceContainer,k);
visualize_pc(W);%显示主成分脸
save(‘Mat/LowDimFace.mat’,’LowDimFace’);
disp(‘计算结束’);
function visualize_pc(E)
% 显示主成分分量(主成分脸,即变换空间中的基向量)
%输入E 每一列是主成分分量
[size1,size2] = size(E);
global imgRow;
global imgCol;
row = imgRow;
col = imgCol;
if size2 ~=20
error(‘只用于显示20个主成分’);
end
figure
img = zeros(row,col);
for ii = 1:20
img(:) = E(:,ii);
subplot(4,5,ii);
imshow(img,[]);
end
function [SVFM,lowVec,upVec] = scaling(VecFeaMat,bTest,lRealBVec,uRealBVec)
%训练样本时,输入上下限无意义,将得到上下限,并且得到训练样本的规格化向量 ;测试样本时提供上下限可以得到测试样本规格化向量
% 输入 VecFeaMat – 需要规格化的m*n矩阵,其中每行一个特征向量,列数为维度
% bTest – 1:说明此时是对测试样本进行规格化,此时必须提供l,u,此2值是在训练样本规格化时得到
% – 0:默认值,对训练样本进行规格化
% lRealBVec – n维向量,对训练样本规格化时得到的各维的实际下限lowVec
% uRealBVec – n维向量,对训练样本规格化时得到的各维的实际上限upVec
%输出 SVFM – VecFeaMat的规格化版本
% lowVec – 各维特征的下限(只在对训练样本规格化是有意义,bTest = 0)
% upVec – 各维特征的下限(只在对训练样本规格化是有意义,bTest = 0)
if nargin < 2
bTest = 0;
lRealBVec=-1;
uRealBVec= 1;
end
%缩放目标范围[-1,1]
lTargB = -1;
uTargB = 1;
[m,n] = size(VecFeaMat);
SVFM = zeros(m,n);
if bTest
if nargin < 4
error(‘测试样本时需要提供uRealB,lRealB’);
end
if nargout > 1
error(‘当测试样本时,只提供一个输出,SVFM’)
end
for iCol = 1:n
if uRealBVec(iCol) == lRealBVec(iCol)
SVFM(:,iCol) = uRealBVec(iCol);
SVFM(:,iCol) = 0;
else
SVFM(:,iCol) = lTargB+(VecFeaMat(:,iCol)-lRealBVec(iCol))/(uRealBVec(iCol)-lRealBVec(iCol))*(uTargB-lTargB);
end
end
else %训练样本
upVec = zeros(1,n);
lowVec = zeros(1,n);
for iCol = 1:n
lowVec(iCol) = min(VecFeaMat(:,iCol));
upVec(iCol) = max(VecFeaMat(:,iCol));
if lowVec(iCol)==upVec(iCol)
SVMF(:,iCol) = upVec(iCol);
SVMF(:,iCol) = 0;
else
SVFM(:,iCol) = lTargB+(VecFeaMat(:,iCol)-lowVec(iCol))/(upVec(iCol)-lowVec(iCol))*(uTargB-lTargB);
end
end
end
for ii = 1:m1
for jj = 1:m2
K(ii,jj) = exp(-gamma*norm(U(ii,:)-V(jj,:))^2);
end
end