人臉識別之特徵臉方法(Eigenface)

老司機的詩和遠方發表於2020-04-06

人臉識別之特徵臉方法(Eigenface)

zouxy09@qq.com

http://blog.csdn.net/zouxy09

 

      因為需要,花了一點時間寫了下經典的基於特徵臉(EigenFace)的人臉識別方法的Matlab程式碼。這裡僅把該程式碼分享出來。其實,在較新版本的OpenCV中已經提供了FaceRecognizer這一個類,裡面不僅包含了特徵臉EigenFace,還有FisherFace和LBPHFace這三種人臉識別方法,有興趣的可以參考OpenCV的API手冊,裡面都有很詳細的使用例程了。

 

一、特徵臉

      特徵臉EigenFace從思想上其實挺簡單。就相當於把人臉從畫素空間變換到另一個空間,在另一個空間中做相似性的計算。這麼說,其實影像識別的基本思想都是一樣的,首先選擇一個合適的子空間,將所有的影像變換到這個子空間上,然後再在這個子空間上衡量相似性或者進行分類學習。那為什麼要變換到另一個空間呢?當然是為了更好的做識別或者分類了。那為什麼變換到一個空間就好識別或者分類了呢?因為變換到另一個空間,同一個類別的影像會聚到一起,不同類別的影像會距離比較遠,或者在原畫素空間中不同類別的影像在分佈上很難用個簡單的線或者面把他們切分開,然後如果變換到另一個空間,就可以很好的把他們分開了。有時候,線性(分類器)就可以很容易的把他們分開了。那既然人類看起來同類的影像本來就是相似的,不同類的影像就不太相似,那為什麼在原始的畫素空間他們同類不會很近,不同類不會很遠,或者他們為什麼不好分開呢?因為影像各種因素的影響,包括光照、視角、背景和形狀等等不同,會造成同一個目標的影像都存在很大的視覺資訊上的不同。如下圖所示。

       世界上沒有存在任何兩片完全相同的葉子,雖然他們都是葉子。萬千世界,同一類事物都存在共性,也存在個性,這就是這個世界多彩的原因。那怎麼辦呢?很自然,只要在我們想要的粒度上把同一類目標的共性找出來就好了,而且這個共性最好和我們要區分的類是不一樣的。什麼叫我們想要的粒度?我理解和我們的任務相關的。例如我們要區分人和車,那人的共性就是有臉、有手、有腳等等。但如果我們要區分亞洲人和非洲人,那麼亞洲人的共性就是黃色皮膚等等。可以試著想象,上帝把世界萬物組織成一個樹狀結構,樹的根就是萬物之源,下一層可以分成生物和非生物,再下一層將生物分為……(囧,想象不到),直到最底層,萬物,你我,為樹的一片普通得再普通的葉子。樹越往下,粒度越小,分類越細(哈哈,自己亂扯的)。停!廢話多了點,跑題了,回到剛才的問題,重頭戲來了,要變換到什麼空間,才具備上述這種良好類內相似、類間區分的效果?到這,我就只能say sorry了。計算機視覺領域發展了幾十年,就為了這一個問題傾注了無數研究者的智慧與心血。當然了,也誕生和孕育了很多經典和有效的解答。(個人理解,上述說的實際上就是特徵提取)。從一開始的顏色特徵(顏色直方圖)、紋理特徵(Harr、LBP、HOG、SIFT等)、形狀特徵等到視覺表達Bag of Words,再到特徵學習Deep Learning,技術的發展總能帶給人希望,曙光也越來越清晰,但路還很遠,是不?      

       扯太多了,嚴重離題了。上面說到,特徵臉EigenFace的思想是把人臉從畫素空間變換到另一個空間,在另一個空間中做相似性的計算。EigenFace選擇的空間變換方法是PCA,也就是大名鼎鼎的主成分分析。它廣泛的被用於預處理中以消去樣本特徵維度之間的相關性。當然了,這裡不是說這個。EigenFace方法利用PCA得到人臉分佈的主要成分,具體實現是對訓練集中所有人臉影像的協方差矩陣進行本徵值分解,得對對應的本徵向量,這些本徵向量(特徵向量)就是“特徵臉”。每個特徵向量或者特徵臉相當於捕捉或者描述人臉之間的一種變化或者特性。這就意味著每個人臉都可以表示為這些特徵臉的線性組合。實際上,空間變換就等同於“搞基”,原始畫素空間的基就是單位“基”,經過PCA後空間就是以每一個特徵臉或者特徵向量為基,在這個空間(或者座標軸)下,每個人臉就是一個點,這個點的座標就是這個人臉在每個特徵基下的投影座標。哦噢,說得有點繞。

      下面就直接給出基於特徵臉的人臉識別實現過程:

1)將訓練集的每一個人臉影像都拉長一列,將他們組合在一起形成一個大矩陣A。假設每個人臉影像是MxM大小,那麼拉成一列後每個人臉樣本的維度就是d=MxM大小了。假設有N個人臉影像,那麼樣本矩陣A的維度就是dxN了。

2)將所有的N個人臉在對應維度上加起來,然後求個平均,就得到了一個“平均臉”。你把這個臉顯示出來的話,還挺帥的哦。

3)將N個影像都減去那個平均臉影像,得到差值影像的資料矩陣Φ。

4)計算協方差矩陣C=ΦΦT。再對其進行特徵值分解。就可以得到想要的特徵向量(特徵臉)了。

5)將訓練集影像和測試集的影像都投影到這些特徵向量上了,再對測試集的每個影像找到訓練集中的最近鄰或者k近鄰啥的,進行分類即可。

      演算法說明白了都是不明白的,所以還是得去看具體實現。因此,可以對照下面的程式碼來弄清楚這些步驟。

      另外,對於步驟4),涉及到求特徵值分解。如果人臉的特徵維度d很大,例如256x256的人臉影像,d就是65536了。那麼協方差矩陣C的維度就是dxd=65536x65536。對這個大矩陣求解特徵值分解是很費力的。那怎麼辦呢?如果人臉的樣本不多,也就是N不大的話,我們可以通過求解C’=ΦTΦ矩陣來獲得同樣的特徵向量。可以看到這個C’=ΦTΦ只有NxN的大小哦。如果N遠遠小於d的話,那麼這個力氣就省得很值了。那為什麼求解C’=ΦTΦ矩陣的特徵向量可以獲得C=ΦΦT的特徵向量?萬眾矚目時刻,數學以完美舞姿登上舞臺。證明如下:

      其中,ei是C’=ΦTΦ的第i個特徵向量,vi是C=ΦΦT的第i個特徵向量,由證明可以看到,vi=Φei。所以通過求解C’=ΦTΦ的特徵值分解得到ei,再左乘Φ就得到C=ΦΦT的特徵向量vi了。也就是我們想要的特徵臉。

 

二、Matlab實現

      下面的程式碼主要是在著名的人臉識別資料庫YaleB中進行實現。用的是裁切後的人臉資料庫,可以點選CroppedYale下載。共有38個人的人臉,人臉是在不同的光照下采集的,每個人臉影像是32x32個畫素。實驗在每一個的人臉影像中隨機取5個作為訓練影像,剩下的作為測試影像。當然了,實際過程中這個過程需要重複多次,然後得到多次準確率的均值和方差才有參考意義,但下面的demo就不做這個處理了。計算相似性用的是歐氏距離,但程式設計實現的時候為了加速,用的是簡化版,至於如何簡化的,考驗你的時候到了。

% Face recognition using eigenfaces

close all, clear, clc;

%% 20 random splits
num_trainImg = 5;
showEigenfaces = true;

%% load data
disp('loading data...');
dataDir = './CroppedYale';
datafile = 'Yale.mat';
if ~exist(datafile, 'file')
	readYaleDataset(dataDir, datafile);
end
load(datafile);

%% Five images per class are randomly chosen as the training
%% dataset and remaining images are used as the test dataset
disp('get training and testing data...');
num_class = size(unique(labels), 2);
trainIdx = [];
testIdx = [];
for i=1:num_class
	label = find(labels == i);
	indice = randperm(numel(label));
	trainIdx = [trainIdx label(indice(1:num_trainImg))];
	testIdx = [testIdx label(indice(num_trainImg+1:end))];
end

%% get train and test data
train_x = double(data(:, trainIdx));
train_y = labels(trainIdx);
test_x = double(data(:, testIdx));
test_y = labels(testIdx);

%% computing eigenfaces using PCA
disp('computing eigenfaces...');
tic;
[num_dim, num_imgs] = size(train_x);   %% A: #dim x #images
avg_face = mean(train_x, 2); 			 %% computing the average face
X = bsxfun(@minus, train_x, avg_face); %% computing the difference images

%% PCA
if num_dim <= num_imgs 
	C = X * X';
	[V, D] = eig(C);
else
	C = X' * X; 
	[U, D] = eig(C);
	V = X * U;
end
eigenfaces = V;
eigenfaces = eigenfaces ./ (ones(size(eigenfaces,1),1) * sqrt(sum(eigenfaces.*eigenfaces)));
toc;

%% visualize the average face
P = sqrt(numel(avg_face));
Q = numel(avg_face) / P;
imagesc(reshape(avg_face, P, Q)); title('Mean face');
colormap('gray');

%% visualize some eigenfaces
figure;
num_eigenfaces_show = 9;
for i = 1:num_eigenfaces_show
	subplot(3, 3, i)
	imagesc(reshape(eigenfaces(:, end-i+1), P, Q));
	title(['Eigenfaces ' num2str(i)]);
end
colormap('gray');

%% transform all training images to eigen space (each column for each image)
disp('transform data to eigen space...');
X = bsxfun(@minus, train_x, avg_face);
T = eigenfaces' * X;

%% transform the test image to eigen space
X_t = bsxfun(@minus, test_x, avg_face);
T_t = eigenfaces' * X_t;

%% find the best match using Euclidean distance
disp('find the best match...');
AB = -2 * T_t' * T;       % N x M
BB = sum(T .* T);         % 1 x M
distance = bsxfun(@plus, AB, BB);        % N x M
[score, index] = min(distance, [], 2);   % N x 1

%% compute accuracy
matchCount = 0;
for i=1:numel(index)
	predict = train_y(index(i));
	if predict == test_y(i)
		matchCount = matchCount + 1;
	end
end

fprintf('**************************************\n');
fprintf('accuracy: %0.3f%% \n', 100 * matchCount / numel(index));
fprintf('**************************************\n');

      下面是將CroppedYale的影像讀入matlab的程式碼。

function readYaleDataset(dataDir, saveName)
	dirs = dir(dataDir);
	data = [];
	labels = [];
	for i = 3:numel(dirs)
		imgDir = dirs(i).name;
		imgDir = fullfile(dataDir, imgDir);
		imgList = dir(fullfile(imgDir, '*.pgm'));
		for j = 1:numel(imgList)
			imgName = imgList(j).name;
			if strcmp('Ambient.pgm',  imgName(end-10:end))
				continue;
			end
			im = imread(fullfile(imgDir, imgName));
			if size(im, 3) ==3
				im = rgb2gray(im);
			end
			im = imresize(im, [32 32]);
			im = reshape(im, 32*32, 1);
			data = [data im];
		end
		labels = [labels ones(1, numel(imgList)-1) * (i-2)];
	end
	save(saveName, 'data', 'labels');
end

 

三、實驗結果

      首先來個帥帥的平均臉:

      然後來9個帥帥的特徵臉:

      在本實驗中,實驗結果是30.126%左右。如果加上了某些預處理,這個結果就可以跑到62%左右。只是這個預處理我有點解析不通,所以就沒放在demo上了。

      本文如果有什麼不對的地方,還望大家指正。

 

相關文章