基於PCA和SVM的人臉識別

東城青年發表於2018-08-17

原始碼上傳到了我的github上,大家可以去免費下載https://github.com/tongxiaobin?tab=repositories

本文所用的是ORL人臉庫,由英國劍橋實驗室拍攝,共有40人,每人不同角度不同表情拍攝了10張,所以共有400個樣本資料,圖片尺寸為112*92,格式為pgm。本文將每人的前5張作為訓練集,後5張作為測試集。ORL人臉庫可在該網址下載https://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html

一、讀取人臉資料(向量化人臉容器)

function [faceContainer,label]=ReadFace(n_persons,flag)
%   當flag為0時,表示讀取訓練集,flag為1時,表示讀取測試集
%   n_persons為多少人,label是人臉的標籤  
%   faceContainer是一個向量化人臉容器,即將每一張圖片轉成一行向量放入其每行中

%先隨便讀入一張圖片,得到其大小
I = imread('ORL\orl_faces\s1\1.pgm');
[M,N] = size(I);

label=zeros(n_persons*5,1);
faceContainer=zeros(n_persons*5,M*N);
for i=1:n_persons
    %函式num2str(i)說明:將數字轉化為字元
    facepath=strcat('ORL\orl_faces\s',num2str(i),'\');  %路徑因不同情況而定
    temppath=facepath;
    for j=1:5
        facepath=temppath;
        if flag==0      
            facepath=strcat(facepath,num2str(j));
        else
            facepath=strcat(facepath,num2str(j+5));
        end
        label((i-1)*5+j)=i;
        facepath=strcat(facepath,'.pgm');    
        img=imread(facepath);
        faceContainer((i-1)*5+j,:)=img(:)';
    end
end
save('ORL\faceContainer','faceContainer');%儲存讀取的資料

二、PCA降維(該演算法在我的上一篇部落格主成分分析(PCA)中有所講解)

        讀取的資料是1\times10304的向量,把每一個畫素點當做一維特徵,故每張圖片有10304維,現對其進行降維。

%A為樣本矩陣,將其降至k維後的矩陣為pcaA,V為主成分分量
function [pcaA ,V]=fastPCA(A,k)

[m,n]=size(A);
%樣本均值,計算各列的均值
meanVec=mean(A); 
 
%計算協方差矩陣的轉置 covMatT
%樣本矩陣中心化,每一維度減去該維度的均值,使得每一維度的均值為0
%repmat:Replicate Matrix複製和平鋪矩陣
Z= ( A-repmat(meanVec,m,1)  );  
                                
covMatT =Z*Z'; %快速PCA
%計算covMatT的前k個特徵值和特徵向量
[V, ~]=eigs(covMatT,k);  %V為m*k, k個特徵向量
 
%得到協方差矩陣(covMatT')的特徵向量
V=Z'*V;
 
%特徵向量歸一化為得到單位特徵向量
for i=1:k
    V(:,i)=V(:,i)/norm(V(:,i));  %norm 為範數,預設為2範數(各分量的平方和 再開根號)
end
 
%投影降維至k維
pcaA=Z*V;
 
%儲存變換矩陣V和平均矩陣meanVec
save('ORL/PCA.mat','V','meanVec');

三、資料歸一化

     特徵資料歸一化 ,因為對於不同的特徵,如果不歸一化是不具有比較性的,兩者不在一個量級上,比如說A體重70kg,身高1.75,B體重69kg,身高1.50,C體重65kg,身高1.74,SVM是基於距離算的,所以會把A和B看成相似的,而實際上A和C比較相似。

    一般歸一化到[-1,1],即lTarget = -1,uTarget = 1。

function [ scaledface] = scaling( faceMat,lTarget,uTarget )  
%faceMat需要進行規範化的影象資料,  
[m,n]=size(faceMat);  
scaledface=zeros(m,n); 

upVec=zeros(1,n); %所有資料中每個特徵的最大值
lowVec=zeros(1,n);%所有資料中每個特徵的最小值

 for i=1:n
     lowVec(i)=min( faceMat(:,i) );
     upVec(i)=max( faceMat(:,i) );   
     scaledface(:,i)=(faceMat(:,i) - lowVec(i) )/( upVec(i)- lowVec(i))*(uTarget-lTarget)+lTarget;   
 end
 save('ORL/scaling.mat','upVec','lowVec');


四、對樣本進行訓練(SVM模型)

     我是利用libsvm工具箱,特別簡單方便(由臺灣林智仁開發),首先下載libsvm工具箱,下載地址https://www.csie.ntu.edu.tw/~cjlin/libsvm/,進入頁面後,點選下圖圈的zip檔案下載。

 

下載解壓後,會有c++,python,matlab不同的資料夾即libsvm針對不同語言封裝的不同介面。我用的是matlab,首先開啟matlab軟體,依次點選主頁->設定路徑->新增幷包含子資料夾,將剛才解壓的matlab資料夾新增進來,最後一步複製解壓後的matlab資料夾的路徑到matlab編輯器路徑,在命令列視窗輸入make然後回車等待編譯完成即大功告成。

%第一個引數為標籤,第二個為訓練資料,第三個是個命令集合,-t表示核函式,-c為懲罰係數,-v為交叉驗證數
%-t為0時線性核,1多項式核,2徑向基函式(高斯),3sigmod核函式
model = svmtrain(label,scaledface,'-t 0 -c 1');

五、對測試集進行預測

%輸出的三個引數分別為預測的標籤,準確率,評估值(非分類問題用著),
%輸入為測試資料的標籤(這個可與可無,如果沒有,那麼預測的準確率accuracy就沒有意義了,
%如果有,那麼就可以通過這個值與預測出來的那個型別值相比較得出準確率accuracy,
%但是要說明一點的是,無論這個值有沒有,在使用的時候都得加上,即使沒有,也要隨便加上一個型別值,
%反正你也不管它對不對,這是函式使用所規定的的

[predict_label,accuracy,prob_estimates]=svmpredict(label,scaledface,model);

注意測試資料降維是在訓練集的特徵向量中降維,即testDataPca = (testData - meanVec)*V,測試資料歸一化也是在訓練集中進行的。

測試資料歸一化:

function [ testscaledface] = testscaling( faceMat,lTargB,uTargB )   
% lowvec原來影象資料中的最小值  
% upvec原來影象資料中的最大值  
[m,n] = size(faceMat);
testscaledface = zeros(m,n);
load ORL/scaling.mat
 for i=1:n
     testscaledface(:,i)=(faceMat(:,i) - lowVec(i) )/( upVec(i)- lowVec(i))*(uTargB-lTargB)+lTargB;   
 end

主函式:


disp('讀取訓練資料...')
disp('......') 
[train_faceContainer,train_label] = ReadFace(41,0);

disp('訓練資料PCA降維...') 
disp('......') 
[pcaA ,V]=fastPCA(train_faceContainer,20);

disp('訓練資料歸一化...');
disp('.........') 
[ scaledface] = scaling( pcaA,-1,1 );

disp('SVM樣本訓練...')  
model = svmtrain(train_label,scaledface,'-t 0 ');

disp('讀取測試資料...')  
[test_faceContainer,test_label]=ReadFace(40,1);

disp('測試資料pca降維...') 
disp('.......') 
load 'ORL/PCA.mat'
testData = (test_faceContainer - repmat(meanVec,200,1)) * V;

disp('測試資料歸一化...')  
disp('.......')  
scaled_testData = testscaling( testData,-1,1);

disp('SVM樣本分類預測...')
disp('......')  
[predict_label,accuracy,prob_estimates]=svmpredict(test_label,scaled_testData,model);

補充一:顯示主成分臉

function visualize(V)  
%顯示主成分臉(變換空間中的投影向量,即單位特徵向量),這裡顯示前20個主成分臉,即將原始資料降至20維
figure  
img=zeros(112,92);  
for i=1:20  
    img(:)=V(:,i);  
    subplot(4,5,i);  
    imshow(img,[])  
end  

 

補充二:基於主成分分量的人臉重建

%k為重建至多少維
function rebuid(y,k)

%匯入平均矩陣meanVec和主成分向量V
load ORL/PCA.mat

temp = meanVec;
for i = 1:k    
    xi = (y - meanVec) * V(:,i);%某人臉y在第i維的投影值
    yi =  xi * V(:,i)';%某人臉y在第i維的向量值
    temp = temp + yi ;%對該人臉投影到所有維的向量進行一個向量相加,得到該人臉向量的一個近似值
end

%顯示重建人臉
I = zeros(112,92);
I(:) = temp';
imshow(I,[]);

以第一個人臉為例,分別基於前50,100,150,200維重建後的人臉如下,可見原來的10304維人臉用150維左右就可重建出來:

補充三:實時識別

首先自拍10張自己的臉,轉換成pgm格式,尺寸歸一化為112*92,在ORL人臉庫中新建資料夾s41,將這10張圖片放裡面,現在對這41個人臉重新進行訓練,得到訓練好的模型model。

%改變影象尺寸和格式並儲存

%將自己的人臉照片先放在桌面
temp = 'C:\Users\Administrator\Desktop\';

for i = 1:10
    path1 = strcat(temp,num2str(i),'.jpg');
    I = imread(path1);
    I = rgb2gray(I);
    I = imresize(I,[112,92]);%改變影象尺寸

    %先儲存在D盤下,然後去D盤全部剪下到ORL人臉庫中
    path2 = strcat('D:\',num2str(i),'.pgm');
    imwrite(I,path2);  %儲存成pgm格式
end

開啟matlab應用程式裡的imageAcquision,可以預覽開始捕捉停止捕捉,設定幀數等,其右下角會對應生成程式碼,複製出來直接用即可。

%初始化這部分程式碼可用imageAcquision應用程式生成:

%建立視訊物件
vid = videoinput('winvideo', 1, 'YUY2_160x120');
%設定屬性值,持續不斷獲取影象
vid.FramesPerTrigger = Inf;
%開啟攝像頭
start(vid);

[faceContainer,label]=ReadFace(41,0);
[pcaA ,V]=fastPCA(faceContainer,20);
[ scaledface] = scaling( pcaA,-1,1 ); 
model = svmtrain(label,scaledface,'-t 0 ');
load ORL\PCA.mat
container = zeros(1,112*92);%一定要和訓練時一樣先初始化定義一個人臉容器,使其資料型別一樣,而不能直接轉化成double型

while 1
    frame = getsnapshot(vid);%抓取影象
    I = ycbcr2rgb(frame);%ycbcr是色彩空間的一種,由於我的計算機獲取影象是這種格式所以先轉換為rgb再轉換為gray
    I = rgb2gray(I);
    I = imresize(I,[112,92]);%尺寸歸一化
    imshow(I);
    container(1,:) = I(:)';
    faceData = (container - meanVec)*V;%在訓練資料的特徵向量中降維
    faceData = testscaling( faceData,-1,1 );%測試資料歸一化
    [predict_label,accuracy,prob_estimates]=svmpredict(41,faceData,model);
    if predict_label == 41
        disp('識別正確:童小彬')
    else
        disp('識別錯誤')
    end
    
    if strcmp(get(gcf,'SelectionType'),'alt')%右鍵滑鼠事件
        break;
    end
    
end
stop(vid);%關閉攝像頭

實時識別還是挺準的,現擷取某一時刻的結果如下圖所示:

 

 

相關文章