This is a Python rendition of principal component analysis in the context of facial recognition using the Extended Yale Faces Database B which you can download here. Originally done in R, this was written in order to experiment with the sklearn
library. If you have any questions about this notebook, please do not hesitate to contact me at ryan.quan08@gmail.com.
%matplotlib inline
import numpy as np
import os
import matplotlib.pyplot as plt
import math
from PIL import Image
!pwd
data_dir = "/Users/Quan/GitHub/sklearn-practice/CroppedYale"
os.chdir(data_dir)
!ls
/Users/Quan/github/sklearn-practice yaleB01 yaleB05 yaleB09 yaleB13 yaleB18 yaleB22 yaleB26 yaleB30 yaleB34 yaleB38 yaleB02 yaleB06 yaleB10 yaleB15 yaleB19 yaleB23 yaleB27 yaleB31 yaleB35 yaleB39 yaleB03 yaleB07 yaleB11 yaleB16 yaleB20 yaleB24 yaleB28 yaleB32 yaleB36 yaleB04 yaleB08 yaleB12 yaleB17 yaleB21 yaleB25 yaleB29 yaleB33 yaleB37
If your files are not already in the .png format, you can use this block of code to convert the file using the UNIX shell. Credits to UCSD_Big_Data for this block of code.
# converting from svg to png
# from glob import glob
# %cd $data_dir
# files=glob('yaleB*/*.pgm')
# print 'number of files is',len(files)
# count=0
# for f in files:
# new_f=f[:-3]+'png'
# !convert $f $new_f
# count += 1
# if count % 100==0:
# print count,f,new_f
This image_grid_
function reshapes the array into its original dimensions and plots the image in a grid. The col
parameter allows you to specify the number of images in each row.
def image_grid(D,H,W,cols=10,scale=1):
""" display a grid of images
H,W: Height and width of the images
cols: number of columns = number of images in each row
scale: 1 to fill screen
"""
n = np.shape(D)[0]
rows = int(math.ceil((n+0.0)/cols))
fig = plt.figure(1,figsize=[scale*20.0/H*W,scale*20.0/cols*rows],dpi=300)
for i in range(n):
plt.subplot(rows,cols,i+1)
fig=plt.imshow(np.reshape(D[i,:],[H,W]), cmap = plt.get_cmap("gray"))
plt.axis('off')
create_filenames
allows the users to specify the current working directory where the CroppedYale
folder resides and the image view for the subjects.
def create_filenames(data_dir, view_list):
# loads the pictures into a list
# data_dir: the CroppedYale folder
# view_list: the views you wish to grab
dir_list = os.listdir(data_dir)
file_list = []
for dir in dir_list:
for view in view_list:
filename = "%s/%s_%s.png" % (dir, dir, view)
file_list.append(filename)
return(file_list)
view_list = ['P00A+000E+00', 'P00A+005E+10' , 'P00A+005E-10' , 'P00A+010E+00']
file_list = create_filenames(data_dir, view_list)
len(file_list)
152
# open image
im = Image.open(file_list[0]).convert("L")
# get original dimensions
H,W = np.shape(im)
print 'shape=',(H,W)
im_number = len(file_list)
# fill array with rows as image
# and columns as pixels
arr = np.zeros([im_number,H*W])
for i in range(im_number):
im = Image.open(file_list[i]).convert("L")
arr[i,:] = np.reshape(np.asarray(im),[1,H*W])
image_grid(arr,H,W)
shape= (192, 168)
# let's find the mean_image
mean_image = np.mean(arr, axis=0)
plt.imshow(np.reshape(mean_image,[H,W]), cmap = plt.get_cmap("gray"))
plt.figure()
plt.hist(mean_image,bins=100);
# centering the data (subtract mean face)
arr_norm = np.zeros([im_number, H*W])
arr_norm = arr - mean_image
# plot the first 10 normalized faces
image_grid(arr_norm[:10,:],H,W)
from sklearn.decomposition.pca import PCA
pca = PCA()
pca.fit(arr_norm)
PCA(copy=True, n_components=None, whiten=False)
# Let's make a scree plot
pve = pca.explained_variance_ratio_
pve.shape
plt.plot(range(len(pve)), pve)
plt.title("Scree Plot")
plt.ylabel("Proportion of Variance Explained")
plt.xlabel("Principal Component Number")
<matplotlib.text.Text at 0x10844bc50>
The eigenvectors of the variance-covariance matrix of our "face" data represent the so-called "eigenfaces". They represent the direction of greatest variability in our "face space". We plot the first 9 eigenfaces here.
# eigenfaces
eigenfaces = pca.components_
image_grid(eigenfaces[:9,:], H, W, cols=3)
img_idx = file_list.index('yaleB01/yaleB01_P00A+010E+00.png')
loadings = pca.components_
n_components = loadings.shape[0]
scores = np.dot(arr_norm[:,:], loadings[:,:].T)
img_proj = []
for n in range(n_components):
proj = np.dot(scores[img_idx, n], loadings[n,:])
img_proj.append(proj)
len(img_proj)
152
faces = mean_image
face_list = []
face_list.append(mean_image)
for i in range(len(img_proj)):
faces = np.add(faces, img_proj[i])
face_list.append(faces)
len(face_list)
153
face_arr = np.asarray(face_list)
face_arr.shape
(153, 32256)
image_grid(face_arr[:25], H, W, cols=5)
image_grid(face_arr[range(0, 121, 5)], H, W, cols=5)
In this scenario, we remove a subject from the "face data" run another PCA without that subject in the training set. We then reconstruct the subject's face using the new principal components to see how similar the reconstructed face looks to the original image.
# getting the index of the subject
sub_idx = [i for i, s in enumerate(file_list) if "yaleB05" in s]
print sub_idx
[16, 17, 18, 19]
face_idx = file_list.index("yaleB05/yaleB05_P00A+010E+00.png")
print face_idx
19
# plot target face
image_grid(arr[19:20], H, W)
# remove subject from array
arr_new = np.zeros([len(file_list), H*W])
for i in range(len(file_list)):
im = Image.open(file_list[i]).convert("L")
arr_new[i,:] = np.reshape(np.asarray(im),[1, H*W])
target_face = arr_new[19,]
arr_new = np.delete(arr_new, sub_idx, axis = 0)
arr_new.shape
(148, 32256)
target_face.shape
(32256,)
mean_face = np.mean(arr_new, axis = 0)
centered_face = target_face - mean_face
plt.imshow(np.reshape(centered_face,[H,W]), cmap = plt.get_cmap("gray"))
plt.figure()
plt.hist(centered_face,bins=100);
arr_norm = arr_new - mean_face
pca.fit(arr_new)
PCA(copy=True, n_components=None, whiten=False)
loadings = pca.components_
n_components = loadings.shape[0]
scores = np.dot(centered_face, loadings.T)
reconstruct = np.dot(scores, loadings)
reconstruct.shape
(32256,)
plt.imshow(np.reshape(reconstruct, [H,W]), cmap = plt.get_cmap("gray"))
plt.figure()
<matplotlib.figure.Figure at 0x115cc9990>
<matplotlib.figure.Figure at 0x115cc9990>