網上的文章千篇一律
有用的代碼萬裡挑一
畢設做人臉識别,eigenface作為人臉識别基礎的算法,一定不能落下。但是我仔細找了許多文章,發現他們都是出自同一篇文章,用的字母沒有辨別沒有注解,語言也是前後不搭,是以很難根據文章複現代碼。各種找有用的文章,一周才完成這個eigenface的算法,這裡不談論深入的東西,隻看實作的過程。
-
資料庫的選取與讀入。
我選取的是Yaleface資料庫,看檔案的樣子比較老了
我們看到檔案的類型也未知,看了許多文章,他們都将這些圖檔認為是 . p g m .pgm .pgm的格式,我打不開這些檔案,索性就把它們轉換一下格式,變成 . p n g .png .png格式的,這樣比較好處理。新手入門eigenface以及python實作 介紹一下圖庫,這個圖庫是用15個人的不同表情光照的照片組成,每個人有11張圖檔,共165張圖。每個人的第一張圖檔是正常圖檔。我們要對這11個人的正常臉做訓練,然後取出165張圖檔中的任意一張進行判斷是否是圖庫中的臉(我的判斷結果很不理想,判斷結果誤差非常大,我的推斷是圖庫中人臉位置不同,是以訓練出的資料也有問題)新手入門eigenface以及python實作
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
-
資料拉平
注意: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} ⎣⎡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 77760∗1
-
計算平均臉
所謂平均臉,就是每個人臉相同位置的像素點的平均值。就是在這裡,我看了看我訓練的圖檔的平均臉,有很大的重影,是以我的判斷是人臉位置沒有對齊(即有的臉在圖像的左邊,有的臉在圖像的右邊),是以最後得到的訓練結果很差勁。
def mean_matrix(flattened_images):
return np.mean(flattened_images, axis=1).astype(np.uint8)
注意: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)
- 計算每張臉減去平均臉的值,得到內插補點矩陣
# 每張人臉減去平均臉,得到列矩陣 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
-
計算特征值、協方差矩陣、協方差矩陣的特征向量
得到的 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)
這一步包含了大量的矩陣運算,我以後有機會會更新矩陣的運算,現在太晚了事情還有很多,待到有緣再相見吧。
- 計算訓練集的權重
# 計算訓練集權重
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的矩陣,我得到的矩陣如下:
這裡隻截取了一部分。
- 計算目标圖檔的權重,原理和第 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
-
計算歐幾裡得距離,也就是 ∣ ∣ Φ − Φ 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)
-
最後,輸出計算出的距離,也可以對他們排序輸出。我們要的是歐氏距離最小的那個。
比如我的訓練集圖像是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)