参考:
人脸识别之特征脸方法(Eigenface):http://blog.csdn.net/zouxy09/article/details/45276053
Eigenface算法,PCA数学理论,协方差:http://blog.csdn.net/gdut2015go/article/details/46271523
人脸识别算法-特征脸方法(Eigenface)及python实现:http://blog.csdn.net/u010006643/article/details/46417127
人脸识别经典算法之一:特征脸方法(Eigenface):http://blog.csdn.net/feirose/article/details/39552887
PCA数学原理:http://blog.csdn.net/u012005313/article/details/50877366
#######################################################################
关于PCA数学方面,可以看上面的PCA数学原理
EigenFace实现步骤:
1.获取M张图片集合。本实验中,使用4个人共20张图片,每张图片大小为256x256。将获取的20张图片按列排序,其中每张图片数据均拉平至一列(即256x256图像拉平为65536x1矩阵)。此时得到图像矩阵
2.计算平均图像。图像矩阵T中每行表示一个字段,累加各个字段并平均得到平均图像M,此时M的大小为65536x1,可以通过操作转换为256x256格式,就是平均图像:
3.计算差值矩阵。将图像矩阵T每一列减去平均图像M就是差值矩阵
4.求解协方差矩阵。计算差值矩阵D的协方差矩阵C,求得特征向量和特征值。标准的PCA计算公式是:,不过此时我们的差值矩阵维数是65536x20,所以求得协方差矩阵维数达到65536x65536,计算量过于大,不易于处理,并且存储也很困难。根据上面的博文,有一种简化的方法:
经过上述变换,我们只需求得的特征值和特征向量U,然后用差值矩阵D乘以特征向量U就是协方差矩阵C的特征向量V。
而计算结果矩阵维数为20x20,大大减少了计算量。最后得到的特征向量矩阵V维数为65536x20,其中每一列代表一个特征脸,同样可以转换为图像
5.计算得到的特征向量矩阵并不是每一个有用(这里没有太弄懂)。因为一般训练图像的数量小于图像的维数,比如M<N^2,那么起作用的特征向量只有M-1个而不是N^2个(因为其他的特征向量对应的特征值为0)。所以我仅使用最大几个特征值对应的特征向量
6.计算权重。假定我使用3个特征向量构成基向量,即可求得训练图像在新的子空间的表示,称之为权重。计算公式为。W为权重向量矩阵,表示训练图像在新子空间的表示,结果维数是20x3;D表示差值图像矩阵,维数为65536x20;B表示基向量矩阵,维数为65536x3
7.此时可以进行图像识别。读取一张新的人脸图像,将图像拉平成一列得到矩阵,使用上面得到的基向量矩阵B计算得到权重
8.利用欧式距离方法用权重计算。当距离小于阈值时说明要判别的脸和训练集内的第k个脸是同一个人。当遍历所有训练集都大于阈值时,根据距离值的大小又可分为是新的人脸或者不是人脸的两种情况。
######################################################
我写的一个算法(python):
#!/usr/bin/env python #-*- coding: utf-8 -*- ''' eigenface的python实现 ''' __author__='zj' import cv2 import os import sys import numpy as np from numpy import * suffix_list=['.jpg', '.png', '.jpeg', '.JPG', '.PNG', '.JPEG'] ''' 提取所有图片以及对应的标签 src_dir格式如下: . ├── PEOPLE_GRAY_1 │ ├── 10.jpg │ ├── 12.jpg │ ├── 3.jpg │ ├── 5.jpg │ └── 6.jpg ├── PEOPLE_GRAY_2 │ ├── 12.jpg │ ├── 14.jpg │ ├── 21.jpg │ ├── 6.jpg │ └── 9.jpg ├── PEOPLE_GRAY_3 │ ├── 38.jpg │ ├── 46.jpg │ ├── 49.jpg │ ├── 54.jpg │ └── 57.jpg └── PEOPLE_GRAY_4 ├── 14.jpg ├── 18.jpg ├── 20.jpg ├── 7.jpg └── 9.jpg ''' def getImgAndLabel(src_dir): #初始化返回结果 imgs=[] #存放图像 labels=[] #存放类别 #获取子文件夹名 catelist=os.listdir(src_dir) #遍历子文件夹 for catename in catelist: #设置子文件夹路径 cate_dir=os.path.join(src_dir, catename) #获取子文件名 filelist=os.listdir(cate_dir) #遍历所有文件名 for filename in filelist: #设置文件路径 file_dir=os.path.join(cate_dir, filename) #判断文件名是否为图片格式 if not os.path.splitext(filename)[1] in suffix_list: print file_dir,"is not an image" continue #endif #读取灰度图 imgs.append(cv2.imread(file_dir, cv2.IMREAD_GRAYSCALE)) #读取相应类别 labels.append(catename) #endfor #endfor return imgs,labels #end of getImgAndLabel #将图像数据变为一列 def convertImageToArray(img): img_arr=[] height,width=img.shape[:2] #遍历图像 for i in range(height): img_arr.extend(img[i,:]) #endfor return img_arr #end of convertImageToArray #将每个图像变为一列 def convertImageToArrays(imgs): #初始化数组 arr=[] #遍历每个图像 for img in imgs: arr.append(convertImageToArray(img)) #endfor return array(arr).T #end of convertImageToArrays #计算均值数组 def compute_mean_array(arr): #获取维数(行数),图像数(列数) dimens,nums=arr.shape[:2] #新建列表 mean_arr=[] #遍历维数 for i in range(dimens): aver=0 #求和每个图像在该字段的值并平均 aver=int(sum(arr[i,:])/float(nums)) mean_arr.append(aver) #endfor return array(mean_arr) #end of compute_mean_array #将数组转换为对应图像 def convert_array_to_image(arr, height=256, width=256): img=[] for i in range(height): img.append(arr[i*width:i*width+width]) #endfor return array(img) #end of convert_array_to_image #计算图像和平均图像之间的差值 def compute_diff(arr, mean_arr): return arr-mean_arr #end of compute_diff #计算每张图像和平均图像之间的差值 def compute_diffs(arr, mean_arr): diffs=[] dimens,nums=arr.shape[:2] for i in range(nums): diffs.append(compute_diff(arr[:,i], mean_arr)) #endfor return array(diffs).T #end of compute_diffs #计算协方差矩阵的特征值和特征向量,按从大到小顺序排列 #arr是预处理图像的矩阵,每一列对应一个减去均值图像之后的图像 def compute_eigenValues_eigenVectors(arr): arr=array(arr) #计算arr'T * arr temp=dot(arr.T, arr) eigenValues,eigenVectors=linalg.eig(temp) #将数值从大到小排序 idx=np.argsort(-eigenValues) eigenValues=eigenValues[idx] #特征向量按列排 eigenVectors=eigenVectors[:,idx] return eigenValues,dot(arr,eigenVectors) #end of compute_eigenValues_eigenVectors #计算图像在基变换后的坐标(权重) def compute_weight(img, vec): return dot(img, vec) #end of compute_weight #计算图像权重 def compute_weights(imgs, vec): dimens,nums=imgs.shape[:2] weights=[] for i in range(nums): weights.append(compute_weight(imgs[:, i], vec)) return array(weights) #end of compute_weights #计算两个权重之间的欧式距离 def compute_euclidean_distance(wei1, wei2): #判断两个向量的长度是否相等 if not len(wei1) == len(wei2): print '长度不相等' os._exit(1) #endif sqDiffVector=wei1-wei2 sqDiffVector=sqDiffVector**2 sqDistances=sqDiffVector.sum() distance=sqDistances**0.5 return distance #end of compute_euclidean_distance #计算待测图像与图像库中各图像权重的欧式距离 def compute_euclidean_distances(wei, wei_test): weightValues=[] nums=wei.shape print nums for i in range(nums[0]): weightValues.append(compute_euclidean_distance(wei[i], wei_test)) #endfor return array(weightValues) #end of compute_euclidean_distances if __name__ == '__main__': #获取图片库路径 src_dir=os.path.join(os.getcwd(), "gray") #获取图片以及对应类别 imgs,labels=getImgAndLabel(src_dir) ''' for i in range(len(labels)): print labels[i] ''' #将图片转换为数组 arr=convertImageToArrays(imgs) print "arr's shape : {}".format(arr.shape) #计算均值图像 mean_arr=compute_mean_array(arr) print mean_arr print "mean_arr's shape: {}".format(mean_arr.shape) #mean_img=convert_array_to_image(mean_arr) #print mean_img #print "type(mean_img) : {}".format(type(mean_img)) #cv2.imwrite("mean.png", mean_img) #获取差值图像 arr_diff=compute_diffs(arr, mean_arr) #计算特征值以及特征向量 eigenValues,eigenVectors=compute_eigenValues_eigenVectors(arr) print "eigenValues'shape : {}".format(eigenValues.shape) print "eigenVectors'shape : {}".format(eigenVectors.shape) #print eigenValues #计算权重向量,此处假定使用特征值最大的前3个对应的特征向量作为基 weights=compute_weights(arr_diff, eigenVectors[:,:3]) print "weights.shape : {}".format(weights.shape) #print weights #读取测试图像,此处使用训练库中的一张 img_test=cv2.imread("gray/PEOPLE_GRAY_1/10.jpg", cv2.IMREAD_GRAYSCALE) arr_test=convertImageToArray(img_test) diff_test=compute_diff(arr_test, mean_arr) wei=compute_weight(diff_test, eigenVectors[:,:3]) print "test's weight : {}".format(wei) #计算欧式距离 weightValues=compute_euclidean_distances(weights, wei) print "weightValues.shape : {}".format(weightValues.shape) #打印结果 for i in range(len(weightValues)): print weightValues[i], labels[i] ''' for i in range(len(eigenValues)): img=convert_array_to_image(eigenVectors[:,i]) cv2.imwrite(str(i)+".jpg" ,img) #break #print eigenValues[i] #endfor cv2.waitKey() print "endl..."''' #endif
结果由图可知,由于使用的是训练集中的一张图片,所以得到的结果存在欧式距离刚好为0的情况
######################################################################################################
其中的一篇博文里记载的python代码,修改了一些,没办法运行,不过其中一些python函数值得学习以下:
#coding:utf-8 from numpy import * from numpy import linalg as la import cv2 import os def loadImageSet(add): FaceMat=mat(zeros(20, 256*256)) j=0 #获取子文件夹名 catelist=os.listdir(src_dir) #遍历子文件夹 for catename in catelist: #设置子文件夹路径 cate_dir=os.path.join(src_dir, catename) #获取子文件名 filelist=os.listdir(cate_dir) #遍历所有文件名 for filename in filelist: #设置文件路径 file_dir=os.path.join(cate_dir, filename) #判断文件名是否为图片格式 if not os.path.splitext(filename)[1] in suffix_list: print file_dir,"is not an image" continue #endif #读取灰度图 img=cv2.imread(file_dir, cv2.IMREAD_GRAYSCALE) FaceMat[j,:]=mat(img).flatten() j+=1 #读取相应类别 labels.append(catename) #endfor #endfor return FaceMat ''' FaceMat = mat(zeros((15,98*116))) j =0 for i in os.listdir(add): if i.split('.')[1] == 'normal': try: img = cv2.imread(add+i,0) except: print 'load %s failed'%i FaceMat[j,:] = mat(img).flatten() j += 1 return FaceMat ''' def ReconginitionVector(selecthr = 0.8): # step1: load the face image data ,get the matrix consists of all image FaceMat = loadImageSet(os.path.join(os.getcwd(), "gray")).T # step2: average the FaceMat avgImg = mean(FaceMat,1) # step3: calculate the difference of avgimg and all image data(FaceMat) diffTrain = FaceMat-avgImg #step4: calculate eigenvector of covariance matrix (because covariance matrix will cause memory error) eigvals,eigVects = linalg.eig(mat(diffTrain.T*diffTrain)) #从大到小排序 eigSortIndex = argsort(-eigvals) #此处利用阈值设置新空间的基向量个数,特征值按从大到小排列,从头相加,其值刚刚大于阈值则其对应特征向量为基向量 for i in xrange(shape(FaceMat)[1]): if (eigvals[eigSortIndex[:i]]/eigvals.sum()).sum() >= selecthr: eigSortIndex = eigSortIndex[:i] break covVects = diffTrain * eigVects[:,eigSortIndex] # covVects is the eigenvector of covariance matrix # avgImg 是均值图像,covVects是协方差矩阵的特征向量,diffTrain是偏差矩阵 return avgImg,covVects,diffTrain #judgeImg - 待处理图像 #FaceVector - 特征向量矩阵 #avgImg - 平均图像矩阵 #diffTrain - 差值图像矩阵 def judgeFace(judgeImg,FaceVector,avgImg,diffTrain): #待处理图像减去平均图像 diff = judgeImg.T - avgImg #计算权重向量 weiVec = FaceVector.T* diff res = 0 resVal = inf for i in range(15): TrainVec = FaceVector.T*diffTrain[:,i] if (array(weiVec-TrainVec)**2).sum() < resVal: res = i resVal = (array(weiVec-TrainVec)**2).sum() return res+1 if __name__ == '__main__': avgImg,FaceVector,diffTrain = ReconginitionVector(selecthr = 0.9) nameList = ['01','02','03','04','05','06','07','08','09','10','11','12','13','14','15'] characteristic = ['centerlight','glasses','happy','leftlight','noglasses','rightlight','sad','sleepy','surprised','wink'] for c in characteristic: count = 0 for i in range(len(nameList)): # 这里的loadname就是我们要识别的未知人脸图,我们通过15张未知人脸找出的对应训练人脸进行对比来求出正确率 loadname = 'gray/PEOPLE_GRAY_1/10.jpg'#+nameList[i]+'.'+c+'.pgm' judgeImg = cv2.imread(loadname,0) if judgeFace(mat(judgeImg).flatten(),FaceVector,avgImg,diffTrain) == int(nameList[i]): count += 1 print 'accuracy of %s is %f'%(c, float(count)/len(nameList)) # 求出正确率
mean函数:
功能:沿着指定的轴计算算术平均值。默认求和数组中每个元素并按元素数求和。可以指定某个轴进行计算,axis=0表示沿着x轴计算,axis=1表示沿着y轴计算。在计算过程中使用数据类型"float64"进行计算,不过返回时会自动转换为integer格式
参数 a:数组或矩阵,不为空
参数axis:int类型,默认为空,表示整个数组进行计算,axis=0时求和每列并求平均值;axis=1时求和每行并求平均值
flatten
功能:将数组折叠成一个维度
参数order:可选,默认为'c',表示按行排列;如果设为'F',表示按列排列
numpy.linalg.eig:
功能:计算正方形数组或矩阵的特征值以及对应的特征向量
返回的特征值和特征向量都是数组类型,并且特征向量是按行排列的