你好,我是禅墨!好久,不见!
在忙了好久的各科考试之后,我终于闲下来了。
自从小凯上次输给我K210之后,一心想搞我,想要报仇。
小凯:阿墨,我给你说啊,我XX不服,给你三个小时,你给我做出来一个人脸识别,识别咱两个班一共57人,正确率不能低于96%,我赌100块钱!!!你得在我的监视下完成!
禅墨:确定?我怕你后悔!
小凯:确定!后悔,不存在的!你搞吧!!!小样!
思考了片刻决定用PCA实现!
PCA全名为主成分分析,其主要目的就是寻找一个矩阵,然后把原来的一组带有相关性的矩阵映射到寻找到的那个矩阵中,达到降维的目的。一般的,如果我们有M个N维向量,想将其变换为由R个N维向量表示的新空间中,那么首先将R个基按行组成矩阵A,然后将向量按列组成矩阵B,那么两矩阵的乘积AB就是变换结果,其中AB的第m列为A中第m列变换后的结果。 这句话就相当于找到了一个R行N列矩阵,然后乘一个N行M列矩阵,这样就得到了一个R行M列矩阵(其中R<=N),达到降维的目的。其中M和N的含义为,M可以代表样本个数,而N代表每个样本的特征个数,所以最终结果就是把原来N个特征变为了R个特征,达到降维目的。
1、构建一个样本集合 S = { T 1 , T 2 , . . . , T M } S =\{T_1,T_2,...,T_M\} S={ T1,T2,...,TM}, S S S 可以看做是一个N行M列的矩阵,也就是有M个样本,每个样本有N个特征。其中 T i T_i Ti是一个向量。
2、0均值化,为了便于计算方差的时候需要减去均值,所以如果本身样本是零均值的,就方便计算。
m = 1 M ∑ i = 1 M T i m = \frac{1}{M}\sum_{i=1}^{M}T_i m=M1∑i=1MTi ,这个是计算均值在python中可以使用
m = T.mean(axis = 1)
进行计算,其中axis = 1代表按行求均值。
然后 A = T − m A = T -m A=T−m 这个相当于把每个样本都减去均值,这样之后就相当于做了0均值化。
3、计算投影矩阵(就是相当于上面的那个R行M列矩阵)
这个投影矩阵其实就是由 A ∗ A T A*A^T A∗AT矩阵的特征向量构成,但是由于大多数情况 A ∗ A T A*A^T A∗AT的维度太大( A ∗ A T A*A^T A∗AT是N行N列矩阵,如果是一张图片的话N就代表像素点个数,所以是相当大的),所以这个时候就利用数学的小技巧转化为先求 A T ∗ A A^T*A AT∗A的特征向量矩阵V,其中V的每一列是一个特征向量,那么V是一个M行M列的矩阵,然后我们再从V中取出前R个最大特征值对应的特征向量,所以V就变成了M行R列矩阵,然后 C = A V C = AV C=AV,那么这个C矩阵就是计算出的投影矩阵,C为一个N行R列的矩阵。
4、把原来样本进行投影
第三步我们得到了一个N行R列的矩阵C,其中每一列是一个特征向量,但是我们在讲PCA原理的时候我们需要一个R行N列的矩阵,每一行是一个特征向量,所以我们可以使用 C T C^T CT,所以我们投影后的样本变为 P = C T A P = C^T A P=CTA 其中P就是一个R行M列的矩阵,可以看出已经达到了降维的目的。
特征脸就是我们上面求得的C矩阵,所谓的基于特征脸进行的人脸识别,就是先把人脸映射到一个低纬空间,然后再计算映射后的脸之间的距离,把距离最近的两个特征脸归为同一个人的脸。
所以特征脸的步骤为:
创建一个存放所有图片的数据库,具体的步骤看程序注释
def createDatabase(path):
# 查看路径下所有文件
TrainFiles = os.listdir(path)
# 计算有几个文件(图片命名都是以 序号.jpg方式)
Train_Number = len(TrainFiles)
T = []
# 把所有图片转为1-D并存入T中
for i in range(0,Train_Number):
ip = path+'/'+str(i)+'.jpg'
image = cv.imread(ip,cv.IMREAD_GRAYSCALE)
image=cv.resize(image,img_size)
# 转为1-D
image = image.reshape(image.size,1)
T.append(image)
T = np.array(T)
# 不能直接T.reshape(T.shape[1],T.shape[0]) 这样会打乱顺序,
T = T.reshape(T.shape[0],T.shape[1])
return np.mat(T).T
特征脸核心处理函数,对T进行数据处理操作,包括进行均值化,计算特征向量和特征值。
def eigenfaceCore(T):
# 把均值变为0 axis = 1代表对各行求均值
m = T.mean(axis = 1)
A = T-m
L = (A.T)*(A)
# 计算AT *A的 特征向量和特征值V是特征值,D是特征向量
# L = np.cov(A,rowvar = 0)
V, D = np.linalg.eig(L)
L_eig = []
for i in range(A.shape[1]):
L_eig.append(D[:,i])
L_eig = np.mat(np.reshape(np.array(L_eig),(-1,len(L_eig))))
# 计算 A *AT的特征向量
eigenface = A * L_eig
return eigenface,m,A
识别器函数:找到投影矩阵C,计算投影后的矩阵样本P,加载一个测试图片投影为test-p,然后在总样本P中进行比对,找到与test-p距离最近的样本,即为比对结果
def recognize(testImage, eigenface,m,A):
_,trainNumber = np.shape(eigenface)
# 投影到特征脸后的
projectedImage = eigenface.T*(A)
# 可解决中文路径不能打开问题(相当于英文路径下imread)
testImageArray = cv.imdecode(np.fromfile(testImage,dtype=np.uint8),cv.IMREAD_GRAYSCALE)
# 转为1-D
testImageArray=cv.resize(testImageArray,img_size)
testImageArray = testImageArray.reshape(testImageArray.size,1)
testImageArray = np.mat(np.array(testImageArray))
differenceTestImage = testImageArray - m
projectedTestImage = eigenface.T*(differenceTestImage)
distance = []
for i in range(0, trainNumber):
q = projectedImage[:,i]
temp = np.linalg.norm(projectedTestImage - q)
distance.append(temp)
minDistance = min(distance)
index = distance.index(minDistance)
cv.imshow("recognize result",cv.imread('./TrainDatabase'+'/'+str(index+1 )+'.jpg',cv.IMREAD_GRAYSCALE))
cv.waitKey()
return index+1
制作一个简单的UI界面,通过简单的按钮选择需要识别人脸的图片,点击开始识别,输出结果,检索列表输出人名
def gui():
root = tk.Tk()
root.title("pca face")
#点击选择图片时调用
def select():
filename = tkinter.filedialog.askopenfilename()
if filename != '':
s=filename
# jpg图片文件名 和 路径。
im=Image.open(s)
tkimg=ImageTk.PhotoImage(im)
# 执行此函数之前, Tk() 必须已经实例化。
l.config(image=tkimg)
btn1.config(command=lambda : example(filename))
btn1.config(text = "开始识别")
btn1.pack()
# 重新绘制
root.mainloop()
# 显示图片的位置
l = tk.Label(root)
l.pack()
btn = tk.Button(root,text="选择识别的图片",command=select)
btn.pack()
btn1 = tk.Button(root) # 开始识别按钮,刚开始不显示
root.mainloop()
if __name__ == "__main__":
gui()
我觉得有必要吐槽一下给的570张图片:jpg , png, jpeg, pmg各种格式各种分辨率应有尽有,真的是搞死我了!(一寸人脸照一般为92*112) 不过还好,只有七八十张,其他的就是正常的jpg格式,怎么办呢,我不可能说一张张进行裁剪,所以 就写个程序,批量裁剪吧。我直接给出核心代码,比较简单,就不废话说明了
for (path, dirnames, filenames) in os.walk(input_dir):
for filename in filenames:
if filename.endswith('.jpg'):
print('正在处理第 %s 张图片' % index)
img_path = path + '/' + filename
print(img_path)
img = cv.imdecode(np.fromfile(img_path,dtype=np.uint8),-1)
new_img = cv.resize(img, (width, height))
imwritedir = output_dir + '/' + str(499+index) + '.jpg'
print(imwritedir)
cv.imwrite(imwritedir, new_img)
index += 1
key = cv.waitKey(30) & 0xff
if key == 27:
sys.exit(0)
用过opencv的应该知道,读取中文路径会报错,怎么解决呢,也很简单
cv.imdecode(np.fromfile(img_path,dtype=np.uint8),-1)
用这个代替imread 完美!
由于部分照片的问题,背景太花,所以做些调整,就是先把人脸裁剪出来。
这里就是用到上篇博客基于OpenCV的人脸及笑脸检测
就是检测出来,保存人脸尺寸照片
计时结束:2:58:56!
哈哈哈,小凯又输了,收钱去!
凭良心说,这一篇博文干货满满。
1.OpenCV中文路径解决方法
2.批量进行图片的简单裁剪
3.解析PCA算法原理
4.Python程序实现
欢迎关注:禅墨云
公众号: