草庐IT

一个简单的人脸识别系统

Guoge66 2023-03-28 原文

一个简单的人脸识别系统

本系统简介

本系统是模式识别课程中要求完成的一个课设,结合上课所学知识,我是在自己之前实现的 PCA 算法基础上进行了封装和改进细化,其他部分则调用sklearn 机器函数函数库进行实现,实现的一个包括数据采集、模型训练和数据测试等完整的模式识别系统。

配置描述

解释器:Python3.8
要在线下载lfw_data数据集文件(直接运行FaceRecognition.py文件即可)
并把它放在C:\Users\86180\scikit_learn_data路径下

人脸识别系统流程

首先要对人脸数据进行采集和表示,然后在去噪、去模糊等预处理之后得到数据集,然后由于原始数据的特征向量维度非常高,也就意味着训练模型的复杂度非常高,所以必须进行图像特征的选择与提取得到了特征向量空间(我在本次试验中使用了 PCA 主成分分析法),然后在得到的特征向量空间中,对原始数据集进行映射,得到原始数据的一个降维表示,大大地减小了模型的复杂度和运算量。然后用降维后的训练集数据,训练分类器模型(我在本次实验中选用了支持向量机 SVM 模型(参数为 C=1000.0,class_weight='balanced', gamma=0.01)),然后用测试集数据来对训练好的模型进行一个评价,用准确率、召回率、F1 值来衡量训练出来的模型的好坏。

实验过程

本次实验选择的是 sklearn 库中自带的 fetch_lfw_people 数据集,该数据集是互联网上收集的名人.jpeg 格式图片的集合,每张图片都以一张脸为中心。每个通道的每个像素(RGB 中的颜色)由 0.0 - 1.0 范围内的浮点数编码。图像尺寸为 50*37。

我并没有选择全部的数据集进行训练,我是筛选出了大于 50 张图片的类别(即多少个人物),用这些图片来训练,避免一些类别因为训练样本太少造成的误差。共筛选出了 1560 条数据,共 12 类,其中特征个数定为 1850(即 50*37)。然后,根据五折交叉检验的原则,利用 train_test_split 函数随机将测试集与测试集以 4:1 的比例分开来。

然后,按照 PCA 算法的流程,将其封装为 GsnPCA 类与函数,定义的属性有n_components(希望抽取的主成分个数),components_为特征向量(即抽取出的主成分),还有降维变换矩阵。仿照 sklearn 中 PCA 类中方法的命名,分别定义了 fit 方法(训练方法),按照主成分分析方法的流程,依次进行中心化操作,求解各个维度之间的协方差矩阵,然后求解协方差矩阵的特征值和特征向量,然后将特征值从大到小降序排序,抽取前 n_components 个特征值作为主成分,将主成分向量组成降维变换矩阵,方便后面定义的 transform 方法使用。

transform方法是用于将数据集降维映射到特征空间上,而 inverse_transform 方法则用于重建图像。定义完 GsnPCA 类之后,就开始用训练集数据训练 PCA 的降维转换矩阵并且实现对主成分的抽取,再分别对训练集和测试集数据进行降维操作,我在本次实验中选取的 n_components 值为 150(即把数据集从 1850 维降到 150 维),对数据进行降维处理之后,准备进行分类器的训练。由于 SVM 模型需要进行参数的选择,所以我使用 sklearn 库中的 GridSearchCV函数对 SVM 模型进行一个寻找最优参数,c 是一个对错误部分的惩罚,gamma 表示使用多少比例的特征点,使用不同的 c 和不同值的 gamma,进行多个量的尝试,然后进行搜索,选出调用 SVM 进行分类搜索归类精确度最高的模型。我这里选用的 kernel 是高斯径向基核函数。下列是输出的效果最好的参数选择:接下来就是用降维后的训练集数据训练 SVM 模型,然后用训练好的模型据来对测试集数进行了预测,得到了一组预测标签,然后用 classification_report 方法对预测结果进行了输出,并且我还生成了混淆矩阵,通过观察对角线数字大小来判断准确率高低。

然后我通过调用自己封装好的函数对特征脸以及真实标签和预测标签的对应进行输出。

源代码

from __future__ import print_function # 便于测试新版本函数
from time import time # 从time模块导入time,因为有些步骤需要计时
import logging # 打印出一些程序进展信息
import matplotlib.pyplot as plt # 绘图的包,即最后将我们预测出来的人脸打印出来
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_lfw_people
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.decomposition import PCA
import numpy as np
from sklearn.svm import SVC
from sklearn import svm

# 打印输出日志信息
logging.basicConfig(level=logging.INFO, format='%(asctime)s%(message)s')

# 使用户外脸部数据集lfw(Labeled Faces in the Wild)
# minfaces_per_person:int,可选默认无,提取的数据集仅保留包含min_faces_per_person不同图片的人的照片
# resize调整每张人脸图片的比例,默认是0.5 ,min_faces_per_person=70
lfw_people = fetch_lfw_people(min_faces_per_person=50, resize=0.4)
# 返回数据集有多少个实例,h是多少,w是多少
n_samples, h, w = lfw_people.images.shape
print('h=',h)
print('w=',w)
X = lfw_people.data # X矩阵用来装特征向量,得到数据集的所有实例,每一行是一个实例,每一列是个特征值
# X矩阵调用shape返回矩阵的行数和列数,
n_features = X.shape[1] # X.shape[1]返回矩阵的列数,对应的特征向量的维度或者特征点多少

# 获取特征结果集,提取每个实例对应的每个人脸
Y = lfw_people.target # y为classlabel目标分类标记,即不同人的身份
target_names = lfw_people.target_names # 数据集中有多少个人,以人名组成列表返回
n_classes = target_names.shape[0] # shape[0]就是多少行,多少个人,多少类

print("Total dataset size:") # 数据集中信息
print("n_samples:%d" % n_samples) # 数据个数1288
print("n_features:%d" % n_features) # 特征个数,维度1850
print("n_classes:%d" % n_classes) # 结果集类别个数,即多少个人

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.20) # test_size=0.20表示随机抽取20%的测试集 五折交叉检验

# 自己实现的PCA类及方法
class GsnPCA:
def __init__(self, n_components):
self.n_components = n_components # 主成分的个数n
self.components_ = None # 具体主成分
self.dimensionReductionMatrix=None

def fit(self, X):

def demean(X): # 均值归零
return X - np.mean(X, axis=0)

def Cov(X):
return np.cov(X.transpose())

print("step1:对样本去均值,进行中心化")
X_demean=demean(X)
print("step2:求特征协方差矩阵")
sigma=Cov(X_demean)
print("step3:求协方差矩阵的 特征值 和 特征向量")
eigenValues, eigenVectors = np.linalg.eig(sigma)
print("step4:将特征值从大到小排序,选择前n_components个")
eigenFeatureList = []
for i in range(len(eigenValues)):
eigenFeatureList.append((eigenValues[i], eigenVectors[i]))
# key=lambda tuple: tuple[0]表示按第1个元素(特征值)为排序标准 reverse=True表示降序排序
eigenFeatureList = sorted(eigenFeatureList, key=lambda tuple: tuple[0], reverse=True)
print("step5:合并前n_components个特征向量得到降维变换矩阵")
self.dimensionReductionMatrix = np.array([eigenFeatureList[i][1] for i in range(n_components)])
print(self.dimensionReductionMatrix)

return self

# 将X数据集映射到各个主成分分量中
def transform(self, X):
# print(self.components_.shape[1])
# assert X.shape[1] == self.components_.shape[1]
# return X.dot(self.components_.T)
lower_X = np.matmul(X, self.dimensionReductionMatrix.transpose()).astype(np.float) # 避免出现复数
return lower_X

def inverse_transform(self, X):
return X.dot(self.components_)


# 采用PCA降维,原始数据的特征向量维度非常高,意味着训练模型的复杂度非常高
# 保存的组件数目,也即保留下来的特征个数n
n_components = 150

print("Expecting the top %d EigenFaces from %faces" % (n_components, X_train.shape[0]))

# 降维
pca = PCA(n_components=n_components, whiten=True).fit(X_train)

t0 = time()# 初始时间
gsnpca = GsnPCA(n_components)
gsnpca.fit(X_train)
print("gsnpca done in %0.3fs" % (time() - t0))

# 从人脸中提取特征点,对于人脸的一张照片提取的特征值名为eigenFaces
eigenFaces = pca.components_.reshape((n_components, h, w))

print("projecting the input data on the EigenFaces orthonormal basis")
# t0 = time()
# # 把训练集特征向量转化为更低维的矩阵
# X_train_pca = pca.transform(X_train)
# print(X_train_pca)
X_train_pca = gsnpca.transform(X_train)
print(X_train_pca)
# 把测试集的特征向量转化为更低维的矩阵
# X_test_pca = pca.transform(X_test)
X_test_pca = gsnpca.transform(X_test)
print("done in %0.3fs" % (time() - t0))

# 后续

# 训练一个支持向量机的分类model——构造分类器
print("Fitting the classifier to the training set")
t0 = time()

# c是一个对错误部分的惩罚
# gamma的参数对不同核函数有不同的表现,gamma表示使用多少比例的特征点
# 使用不同的c和不同值的gamma,进行多个量的尝试,然后进行搜索,选出准确率最高模型
param_grid = {
'C': [1e3, 5e3, 1e4, 5e4, 1e5],
'gamma': [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.1]
}
# 调用SVM进行分类搜索哪对组合产生最好的归类精确度
# kernel:rbf高斯径向基核函数 class_weight权重
# 把所有我们所列参数的组合都放在SVC里面进行计算,最后看出哪一组函数的表现度最好
clf = GridSearchCV(svm.SVC(kernel='rbf', class_weight='balanced'), param_grid=param_grid)
clf = clf.fit(X_train_pca, Y_train)
print("fit done in %0.3fs" % (time() - t0))
print("Best estimator found by grid search:")
print(clf.best_estimator_)

##################进行评估准确率计算######################
print("Predicting people's names on the test set")
t0 = time()
# 预测新的分类
Y_pred = clf.predict(X_test_pca)
print("done in %0.3fs" % (time() - t0))
# 通过classification_report方法进行查看,可以得到预测结果中哪些是正确
print(classification_report(Y_test, Y_pred, target_names=target_names))
# confusion_matrix是建一个n*n的方格,横行和纵行分别表示真实的每一组测试的标记和测试集标记的差别
# 混淆矩阵对角线表示的是正确的值,对角线数字越多表示准确率越高
print(confusion_matrix(Y_test, Y_pred, labels=range(n_classes)))


# 将测试标记过进行展示,即先弄一个通用的图片可视化函数:
def plot_gallery(images, titles, h, w, n_row=3, n_col=4):
"""Helper function to plot a gallery of portraits"""
# 建立图作为背景
# 自定义画布大小
plt.figure(figsize=(1.8 * n_col, 2.4 * n_row))
# 位置调整
plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.35)
for i in range(n_row * n_col):
# 设置画布划分以及图像在画布上输出的位置
plt.subplot(n_row, n_col, i + 1)
# 在轴上显示图片
plt.imshow(images[i].reshape((h, w)), cmap=plt.cm.gray)
# 整个画板的标题
plt.title(titles[i], size=12)
# 获取或设置x、y轴的当前刻度位置和标签
plt.xticks(())
plt.yticks(())


# 预测函数归类标签和实际归类标签打印
# 返回预测人脸姓和测试人脸姓的对比title
def title(y_pred, y_test, target_names, i):
# rsplit(' ',1)从右边开始以右边第一个空格为界,分成两个字符
# 组成一个list
# 此处代表把'姓'和'名'分开,然后把后面的姓提出来
# 末尾加[-1]代表引用分割后的列表最后一个元素
pred_name = target_names[y_pred[i]].rsplit(' ', 1)[-1]
true_name = target_names[y_test[i]].rsplit(' ', 1)[-1]
return 'predicted:%s\ntrue: %s' % (pred_name, true_name)


# 预测出的人名
prediction_titles = [title(Y_pred, Y_test, target_names, i) for i in range(Y_pred.shape[0])]
# 测试集的特征向量矩阵和要预测的人名打印
plot_gallery(X_test, prediction_titles, h, w) # 调用plot_gallery函数打印出实际是谁,预测的谁
# 打印原图和预测的信息
eigenFaces_titles = ["EigenFace %d" % i for i in range(eigenFaces.shape[0])]
plot_gallery(eigenFaces, eigenFaces_titles, h, w) # 以及提取过特征的脸

plt.show()

有关一个简单的人脸识别系统的更多相关文章

  1. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  4. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  5. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  6. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  7. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  8. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  9. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

  10. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

随机推荐