天天看點

新手入門eigenface以及python實作

網上的文章千篇一律

有用的代碼萬裡挑一

畢設做人臉識别,eigenface作為人臉識别基礎的算法,一定不能落下。但是我仔細找了許多文章,發現他們都是出自同一篇文章,用的字母沒有辨別沒有注解,語言也是前後不搭,是以很難根據文章複現代碼。各種找有用的文章,一周才完成這個eigenface的算法,這裡不談論深入的東西,隻看實作的過程。

  1. 資料庫的選取與讀入。

    我選取的是Yaleface資料庫,看檔案的樣子比較老了

    新手入門eigenface以及python實作
    我們看到檔案的類型也未知,看了許多文章,他們都将這些圖檔認為是 . p g m .pgm .pgm的格式,我打不開這些檔案,索性就把它們轉換一下格式,變成 . p n g .png .png格式的,這樣比較好處理。
    新手入門eigenface以及python實作
    介紹一下圖庫,這個圖庫是用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 243∗320像素的

    所謂資料拉平,就是把二維矩陣變成一維數組的過程,這個數組不是真正的數組,而是一個類似于數組的一維矩陣。

    比如:

    [ 1 2 3 4 5 6 7 8 9 ] \begin{bmatrix}1&2&3\\4&5&6\\7&8&9\end{bmatrix} ⎣⎡​147​258​369​⎦⎤​

    這樣的矩陣,将它拉平,可以有兩種方式,一種是橫着,一種是豎着:

    [ 1 2 3 4 5 6 7 8 9 ] \begin{bmatrix}1&2&3&4&5&6&7&8&9\end{bmatrix} [1​2​3​4​5​6​7​8​9​]

    [ 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 77760∗1

  1. 計算平均臉

    所謂平均臉,就是每個人臉相同位置的像素點的平均值。就是在這裡,我看了看我訓練的圖檔的平均臉,有很大的重影,是以我的判斷是人臉位置沒有對齊(即有的臉在圖像的左邊,有的臉在圖像的右邊),是以最後得到的訓練結果很差勁。

def mean_matrix(flattened_images):
    return np.mean(flattened_images, axis=1).astype(np.uint8)
           
新手入門eigenface以及python實作

注意: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 77760∗15

  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 77760∗15的矩陣

    這一步參考了: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實作

上圖是我随便拿了一張特征臉。看看别人訓練出來的:人臉識别經典算法一:特征臉方法(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 15∗77760, a f t e r _ s u b after\_sub after_sub的次元是 77760 ∗ 15 77760*15 77760∗15,是以最後得到的權重是 15 ∗ 15 15*15 15∗15的矩陣,我得到的矩陣如下:

新手入門eigenface以及python實作

這裡隻截取了一部分。

  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 ∣∣​∣Φ−Φi​∣​∣∣​2

    這裡的 Φ \Phi Φ是目标圖像的權重, Φ i \Phi_i Φi​是每個訓練集圖檔的權重。

    Φ \Phi Φ的次元是 15 ∗ 15 15*15 15∗15

    Φ i \Phi_i Φi​的次元是 1 ∗ 15 1*15 1∗15

# 檢測人臉與特征臉的歐幾裡得距離
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)

繼續閱讀