新手入门eigenface以及python实现

网上的文章千篇一律
有用的代码万里挑一


毕设做人脸识别,eigenface作为人脸识别基础的算法,一定不能落下。但是我仔细找了许多文章,发现他们都是出自同一篇文章,用的字母没有标识没有注解,语言也是前后不搭,所以很难根据文章复现代码。各种找有用的文章,一周才完成这个eigenface的算法,这里不谈论深入的东西,只看实现的过程。

  1. 数据库的选取与读入。
    我选取的是Yaleface数据库,看文件的样子比较老了
    新手入门eigenface以及python实现_第1张图片
    我们看到文件的类型也未知,看了许多文章,他们都将这些图片认为是 . p g m .pgm .pgm的格式,我打不开这些文件,索性就把它们转换一下格式,变成 . p n g .png .png格式的,这样比较好处理。
    新手入门eigenface以及python实现_第2张图片
    介绍一下图库,这个图库是用15个人的不同表情光照的照片组成,每个人有11张图片,共165张图。每个人的第一张图片是正常图片。我们要对这11个人的正常脸做训练,然后取出165张图片中的任意一张进行判断是否是图库中的脸(我的判断结果很不理想,判断结果误差非常大,我的推断是图库中人脸位置不同,所以训练出的数据也有问题
def readings(img_src):
    init_images = []
    list_of_dir = os.listdir(img_src)
    count = 0
    for i in list_of_dir:
        if "png" in i and count % 11 == 0:
            absolute_path = os.path.join(img_src, i)
            img = cv2.imread(absolute_path, 2)
            init_images.append(img)
        count += 1
    return init_images
  1. 数据拉平
    注意:Yalefaces中的图像都是 243 ∗ 320 243*320 243320像素的
    所谓数据拉平,就是把二维矩阵变成一维数组的过程,这个数组不是真正的数组,而是一个类似于数组的一维矩阵。
    比如:
    [ 1 2 3 4 5 6 7 8 9 ] \begin{bmatrix}1&2&3\\4&5&6\\7&8&9\end{bmatrix} 147258369
    这样的矩阵,将它拉平,可以有两种方式,一种是横着,一种是竖着:
    [ 1 2 3 4 5 6 7 8 9 ] \begin{bmatrix}1&2&3&4&5&6&7&8&9\end{bmatrix} [123456789]
    [ 1 2 3 4 5 6 7 8 9 ] \begin{bmatrix}1\\2\\3\\4\\5\\6\\7\\8\\9\end{bmatrix} 123456789
    这里是无所谓的,不过我看网上教竖着的多一些。
def flattenedimages(images):
    image = np.array(images)
    new_image_arr = []
    for i in range(15):
        new_image_arr.append(image[i].flatten())
    return np.array(new_image_arr).T

我们也取竖着的形式,这一步,矩阵维度是: 77760 ∗ 1 77760*1 777601

  1. 计算平均脸
    所谓平均脸,就是每个人脸相同位置的像素点的平均值。就是在这里,我看了看我训练的图片的平均脸,有很大的重影,因此我的判断是人脸位置没有对齐(即有的脸在图像的左边,有的脸在图像的右边),所以最后得到的训练结果很差劲。
def mean_matrix(flattened_images):
    return np.mean(flattened_images, axis=1).astype(np.uint8)

新手入门eigenface以及python实现_第3张图片
注意:cv2.imshow()函数只能接受uint8类型的数据矩阵,所以我们在读入数据的时候应该把数据转换成uint8的形式

average_face = mean_matrix(flattened_images)  # 3. 计算平均脸
average_face = revive(average_face).astype(np.uint8)
cv2.imshow("asd", average_face)
cv2.waitKey(0)
  1. 计算每张脸减去平均脸的值,得到差值矩阵
# 每张人脸减去平均脸,得到列矩阵 77760*15
def submean(image_of_src, average_img):
    dimension, numbers = image_of_src.shape[:2]
    diffTrain = []
    for i in range(numbers):
        after_sub = image_of_src[:, i] - average_img
        diffTrain.append(after_sub)
    return np.array(diffTrain).T

差值矩阵的维度依然是 77760 ∗ 15 77760*15 7776015

  1. 计算特征值、协方差矩阵、协方差矩阵的特征向量
    得到的 e i g e n V a l u e s eigenValues eigenValues是一堆具体的数据, c o v E i g e n V e c t o r s covEigenVectors covEigenVectors 77760 ∗ 15 77760*15 7776015的矩阵
    这一步参考了:EigenFace的使用 python
# 计算特征值和特征向量和协方差矩阵
def get_eigenValue_covEigenVector(diffTrain):
    # 1. 计算特征向量,设偏差矩阵为a,则特征向量 temp = a^T * a
    temp = np.matmul(diffTrain.T, diffTrain)
    # 2. 得到协方差矩阵的特征值和特征向量
    eigenValues, eigenVectors = np.linalg.eig(np.mat(temp))
    # 3. 得到协方差矩阵的特征向量
    covEigenVector = np.matmul(diffTrain, eigenVectors)
    return eigenValues, covEigenVector

区别是,我没有让其顺序排列,排列的好处是减少噪声,减少计算量。我没有做这一步(我尝试做这一步后,发现对我的结果没有产生正面影响,结果依然不尽如人意,所以我排除了噪声干扰带来的实验结果不准的问题)。且本来特征脸训练出来应该是很像人脸的,我训练出来却不像。
新手入门eigenface以及python实现_第4张图片
上图是我随便拿了一张特征脸。看看别人训练出来的:人脸识别经典算法一:特征脸方法(Eigenface)

这一步包含了大量的矩阵运算,我以后有机会会更新矩阵的运算,现在太晚了事情还有很多,待到有缘再相见吧。

  1. 计算训练集的权重
# 计算训练集权重
def get_weights_of_train_image(covEigenVectors, after_sub):
    # Weight = diffTrain^T * covEigenVectors
    weight = np.matmul(after_sub.T, covEigenVectors)
    return weight

其实这一步很简单,就是用减去平均脸后的 d i f f T r a i n diffTrain diffTrain,这里是 a f t e r _ s u b after\_sub after_sub的转置乘上面求得的特征脸。 d i f f T r a i n T diffTrain^T diffTrainT的维度是: 15 ∗ 77760 15*77760 1577760 a f t e r _ s u b after\_sub after_sub的维度是 77760 ∗ 15 77760*15 7776015,因此最后得到的权重是 15 ∗ 15 15*15 1515的矩阵,我得到的矩阵如下:
新手入门eigenface以及python实现_第5张图片
这里只截取了一部分。

  1. 计算目标图片的权重,原理和第 6 6 6步一样,只是别忘记减去平均脸
# 计算目标脸权重
def get_weights_of_goal_image(covEigenVectors, testface, averageFace):
    # 目标脸-平均脸
    testface = testface.flatten() - averageFace
    # Weight = testface^T * covEigenVectors
    weight = np.matmul(testface.T, covEigenVectors)
    return weight_train
  1. 计算欧几里得距离,也就是 ∣ ∣ Φ − Φ i ∣ ∣ 2 \begin{vmatrix}|\Phi - \Phi_i|\end{vmatrix}^2 ΦΦi2
    这里的 Φ \Phi Φ是目标图像的权重, Φ i \Phi_i Φi是每个训练集图片的权重。
    Φ \Phi Φ的维度是 15 ∗ 15 15*15 1515
    Φ i \Phi_i Φi的维度是 1 ∗ 15 1*15 115
# 检测人脸与特征脸的欧几里得距离
def detect_face(weight_of_train, weight_of_test):
    dimension, number = weight_of_train.shape
    distances = []
    for i in range(number):
        distance = (weight_of_train[i] - weight_of_test) ** 2
        distance = np.abs(distance.sum())
        distance = distance ** 0.5
        distances.append(distance)
    return np.array(distances)
  1. 最后,输出计算出的距离,也可以对他们排序输出。我们要的是欧氏距离最小的那个。
    比如我的训练集图像是15张正常脸,目标图像是第一个人的戴眼镜的图像,如果计算出来的距离结果中,第一个脸的距离最短,那么检测成功,如果是其他脸的距离最短,毫无疑问是失败的。我的结果就很失败,不知道是我的算法复现的问题还是图片人脸未对其的问题,望高手赐教。
import numpy as np
import cv2
import os


def readings(img_src):
    init_images = []
    list_of_dir = os.listdir(img_src)
    count = 0
    for i in list_of_dir:
        if "png" in i and count % 11 == 0:
            absolute_path = os.path.join(img_src, i)
            img = cv2.imread(absolute_path, 2)
            init_images.append(img)
        count += 1
    return init_images


# 数据拉平(图像是320*243=77760)
def flattenedimages(images):
    image = np.array(images)
    new_image_arr = []
    for i in range(15):
        new_image_arr.append(image[i].flatten())
    return np.array(new_image_arr).T


# 计算每一行平均值,形成一个新的行矩阵
def mean_matrix(flattened_images):
    return np.mean(flattened_images, axis=1).astype(np.uint8)


# 每张人脸减去平均脸,得到列矩阵 77760*15
def submean(image_of_src, average_img):
    dimension, numbers = image_of_src.shape[:2]
    diffTrain = []
    for i in range(numbers):
        after_sub = image_of_src[:, i] - average_img
        diffTrain.append(after_sub)
    return np.array(diffTrain).T


# 计算特征值和特征向量和协方差矩阵
def get_eigenValue_covEigenVector(diffTrain):
    # 1. 计算特征向量,设偏差矩阵为a,则特征向量 temp = a^T * a
    temp = np.matmul(diffTrain.T, diffTrain)
    # 2. 得到协方差矩阵的特征值和特征向量
    eigenValues, eigenVectors = np.linalg.eig(np.mat(temp))
    # 3. 得到协方差矩阵的特征向量
    covEigenVector = np.matmul(diffTrain, eigenVectors)
    return eigenValues, covEigenVector


# 计算训练集权重
def get_weights_of_train_image(covEigenVectors, after_sub):
    # Weight = diffTrain^T * covEigenVectors
    weight = np.matmul(after_sub.T, covEigenVectors)
    return weight


# 计算目标脸权重
def get_weights_of_goal_image(covEigenVectors, testface, averageFace):
    # 目标脸-平均脸
    testface = testface.flatten() - averageFace
    # Weight = testface^T * covEigenVectors
    weight = np.matmul(testface.T, covEigenVectors)
    return weight_train


# 检测人脸与特征脸的欧几里得距离
def detect_face(weight_of_train, weight_of_test):
    dimension, number = weight_of_train.shape
    distances = []
    for i in range(number):
        distance = (weight_of_train[i] - weight_of_test) ** 2
        distance = np.abs(distance.sum())
        distance = distance ** 0.5
        distances.append(distance)
    return np.array(distances)


# 将图像还原,得到人脸
def revive(array):
    array_ = np.array(array)
    img = array_.reshape(243, 320)
    return img


img_src = "G:\\face_regconition\\opencv_3\\Eigenface\\yalefaces"
images = readings(img_src)  # 1. 读取人脸数据,这里取正常表情的脸

flattened_images = flattenedimages(images)  # 2. 数据拉平,维度是77760*15

average_face = mean_matrix(flattened_images)  # 3. 计算平均脸

after_sub = submean(flattened_images, average_face)  # 4. 训练的每张脸 - 平均脸

eigenValue, covEigenVector = get_eigenValue_covEigenVector(after_sub)  # 5. 计算特征值和协方差矩阵

weight_train = get_weights_of_train_image(covEigenVector, after_sub)  # 6.计算训练集权重

intended_img_src = "G:\\face_regconition\\opencv_3\\Eigenface\\yalefaces\\subject04_sad_gif.png"
testface = cv2.imread(intended_img_src, 0)

weight_test = get_weights_of_goal_image(covEigenVector, testface, average_face)  # 7. 计算目的图片权重

detect = detect_face(weight_train, weight_test)
print(detect)
idx = detect.argsort(-1)
print(idx)

参考:
EigenFace的使用 python
人脸识别经典算法一:特征脸方法(Eigenface)
特征脸(Eigenface)理论基础-PCA(主成分分析法)
人脸识别之特征脸方法(Eigenface)

你可能感兴趣的:(机器学习)