理解PCA的基本思路,掌握基本的使用PCA进行人脸分类的方法。
1、了解基本的PCA原理;
2、掌握基本的读取数据的方法,能基本上完成pgm文件的读取;
3、能够利用PCA进行数据压缩,并利用基本的决策方法实现人脸分类。
2022年6月27日-2022年6月29日
1、CSDN技术博客一篇;
2、基于PCA的人脸识别Python程序一个。
主成分分析(Principal Component Analysis, PCA)方法是K. Pearson在1901年提出的一种数据分析方法,其出发点是:从一组特征中计算出一组按重要性从大到小排列的新特征,它们是原有特征的线性组合,并且相互之间是不相关的。
K-L变换是推广的PCA方法,其思想与PCA较为类似,只是在构造产生矩阵的时候略有不同,因为考虑到了类别信息。
我们使用的样本集一共有20个类别,每个类别有8个,前7个被作为训练样本,已知其类别,每类的最后一个样本作为待识别样本。
首先需要读取文件,训练所用的数据集为.pgm文件,是一种灰度图像的存储格式,可以利用函数Image.open打开这类文件。
首先我们要将数据读取为一个数组的形式,每个数组的元素为一个图片对应的一维化处理之后的形式。
为了增加表现力,可以查阅相关资料实现相应的图片展示出来的函数。
img=np.array(img)
print(img.shape)
将原本的列表转换为一个np数组,然后看它对应的维度即可。问题在于如果将它转换为一个np数组之后就不再能使用append函数来添加数组,所以在它遍历完所有的一次之后再进行相应的转换。利用这种方法可以得到将所有数组添加之后的数组维度如下:
(8, 112, 92)
(8, 112, 92)
(8, 112, 92)
(8, 112, 92)
(8, 112, 92)
(8, 112, 92)
(8, 112, 92)
(8, 112, 92)
(8, 112, 92)
(8, 112, 92)
(20, 8, 112, 92)
读取样本时需要对于每个图片读取的像素进行降维处理,将其变为一个1维的数组,具体的思路是:
for i in range(N):
# 读取第i列里面所有元素,将这些元素都放到一个数组里。
C1=X[:,i]
for j in range(M):
# 读取第i列里面的元素,依次把它们添加到数组里面。
D[i*M+j,0]=C1[j]
利用的matplotlib里面的imshow函数来显示
plt.imshow(img)
ple.show()
不过由于利用的Image.open读取的图片,所以颜色上有点奇怪。
可以通过添加参数cmap='gray’之后变为正常的灰度图片。
plt.imshow(im,cmap='gray')
plt.show()
可以利用直方图均衡化来实现图片对比度增强,增强的过程中还利用到了线性插值方法。
def histeq(im,nbr_bins=256):
imhist,bins=np.histogram(im.flatten(),nbr_bins)
cdf=imhist.cumsum()
cdf=255.0*cdf/cdf[-1]
im2=interp(im.flatten(),bins[:-1],cdf)
return im2.reshape(im.shape)
def read_X_to_col(X,M,N,n):
D=np.zeros((M*N,1))
D1=np.zeros((1,M*N))
if n==0:
# 顺次从原矩阵中取出每一列的元素,放到一维列矩阵里。
for i in range(N):
C1=X[:,i]
for j in range(M):
D[i*M+j,0]=C1[j]
return D
else:
for i in range(M):
C1=X[i,:]
for j in range(N):
D1[0,i*N+j]=C1[j]
return D1
利用for循环将所有的类别的人脸作为训练数据集,求解出来其均值作为均值向量。
XM=np.ndarray((M*N,1))
for i in range(num_class):
for j in range(0,num_inclass-1):
XM=XM+X0[i,j]
XM = XM/num_all
利用训练集的前七个样本作为协方差矩阵的计算组成部分,用前七个样本的特征向量减去均值向利用这个向量与其转置进行点积,即可得到对应的协方差矩阵,在这里利用了一个新的矩阵来存放样本特征,即:
X=np.ndarray((N*M,num_class*(num_inclass-1),1))
for i in range(num_class):
for j in range(0,(num_inclass-1)):
X[:,i*(num_inclass-1)+j]=X0[i,j]-XM
X=X.reshape(N*M,num_class*(num_inclass-1))
R=np.dot(X.T,X)
# 求R的特征值和特征向量
# 其中D为特征值,按降序排列,V为对应的特征向量
D,V=np.linalg.eig(R)
U=np.ndarray((N*M,num_class*(num_inclass-1)))
for i in range(V.shape[1]):
U[:,i]=np.dot(X,V[:,i])/np.sqrt(D[i])
在提取本征脸的时候我们需要先把图像都标准化为 N × N N\times N N×N的人脸图像,为了方便后续的运算,需要将该 N × N N\times N N×N的矩阵转化成 N 2 × 1 N^2\times 1 N2×1的列向量(这里就要用到我们写的将X矩阵转化成列向量的函数 read_X_to_col(X,M,N,n))
def read_X_to_col(X,M,N,n):
D=np.zeros((M*N,1))
D1=np.zeros((1,M*N))
if n==0:
for i in range(N):
C1=X[:,i]
for j in range(M):
D[i*M+j,0]=C1[j]
return D
else:
for i in range(M):
C1=X[i,:]
for j in range(N):
D1[0,i*N+j]=C1[j]
return D1
之后再进行样本总协方差矩阵的计算,通过公式:
∑ = 1 m ∑ i = 1 m ( x i − μ ) ( x i − μ ) T = 1 m X X T \quad\quad \sum =\frac{1}{m} \sum^m_{i=1}(x_i-\mu)(x_i-\mu)^T =\frac{1}{m}XX^T ∑=m1∑i=1m(xi−μ)(xi−μ)T=m1XXT
X : N 2 × M ∑ : N 2 × N 2 \quad\quad X:N^2\times M\quad\quad\quad\quad\quad \sum:N^2\times N^2 X:N2×M∑:N2×N2
可以看出如果直接对 ∑ \sum ∑求取其特征值和特征向量,运算量将十分庞大,由于 M × M M\times M M×M 而 M < < N 2 M<
推导过程如下:
X T X v i = λ i v i \quad X^TXv_i=\lambda_iv_i XTXvi=λivi
→ X X T X v i = λ i X v i XX^TXv_i=\lambda_iXv_i XXTXvi=λiXvi
→ ∑ X v i = λ i X v i \sum Xv_i=\lambda_iXv_i ∑Xvi=λiXvi 记: u i = X v i u_i=Xv_i ui=Xvi
→ ∑ u i = λ i u i \sum u_i=\lambda_iu_i ∑ui=λiui
从上述推导可以看出: M × M M\times M M×M 维矩阵 X T X X^TX XTX和 N 2 × N 2 N^2\times N^2 N2×N2维矩阵 X X T XX^T XXT 具有相同的特征值,而特征向量具有以下的关系:
u i = X v i u_i=Xv_i ui=Xvi
∑ \sum ∑ 的正交归一的特征向量是:
u i = 1 λ i X v i i = 1 , . . . , m u_i=\frac{1}{\sqrt{\lambda_i}}Xv_i\quad\quad\quad i=1,...,m ui=λi1Xvii=1,...,m
∑ \sum ∑ 最多有 M M M 个非零特征向量值。
通过以上的推导和相关公式,我们可以求得正交归一化的特征向量,进而对原始数据(转化成列向量的人脸数据)进行降维处理得到本征脸并将其显示出来。
kk1=kk2=0
for i in range(num_class):
Y=read_col_to_X(U[:,i],M,N)
if kk1<5:
kk1=kk1+1
elif kk1==5:
kk1=1
kk2=kk2+1
plt.figure(20)
plt.subplot(4,5,kk1+kk2*5)
plt.imshow(Y)
plt.show()
# 降维然后分类
y=np.ndarray((num_class,num_inclass,num_class*(num_inclass-1),1))
for i in range(num_class):
for j in range(num_inclass):
y[i,j]=np.dot(U.T,X0[i,j])
right_num=0
pred=list()
for k in range(num_class):
Dist=np.ndarray((num_class,num_inclass-1))
for i in range(num_class):
for j in range(num_inclass-1):
Dist[i,j]=np.linalg.norm(y[k,num_inclass-1]-y[i,j])
#存放每一类的预测出来的类别
#Dist[i,j]每个东西对应的最小值
# np.argmin()
pred_tmp=np.argmin(Dist)//7
pred.append(pred_tmp)
for i in range(num_class):
if pred[i]==i:
right_num=right_num+1
print('第{0: <2}个样本被分为第{1: <2}类,实际类别为{2: <2}'.format(i+1,pred[i]+1,i+1))
right_rate=right_num/num_class
print(f'利用该种方法获得的人脸识别准确率为{right_rate:.2%}')
这篇博客是我们学校模式识别课程最后的一个实验作业,老师让我们通过上课所学的PCA算法实现一个小小的人脸识别系统,就是上面这篇博客的内容。她说虽然这个方法很早之前就提出来了,但是至今都应用在很多领域,我们也不能小看很早之前提出的算法,这些都是前人智慧的结晶,至于为什么还在用这么“古老”的方法,那是因为还没提出更好的方法,或许未来就是看博客的你提出了更好的方法呢 ~
或许博客里的代码并不很好Ctrl+C Ctrl+V,但是却是我们改了许久的学习成果,也算是写给自己看的,图一乐啦~
至于这里的“我们”是什么意思?那就是“我们”的意思啊哈哈哈~