[Python] 基於KNN演算法的臉部辨識

最近在讀機器學習classfication跟cluster的演算法,就想找些有趣的例子來實做看看

於是這篇就誕生了:使用KNN(K Nearest Neighbor)近鄰演算法實作的臉部辨識

話不多說,先來張最終辨識的成果圖:

 

KNN(K Nearest Neighbor)簡介

KNN是一種近鄰分類演算法,核心概念就是給定k值,然後要被分類的點x就會尋找最近的k個點,看這k個點中哪個種類佔大多數,則該點x即會被歸為此類

例如這張從維基百科抓的示意圖k=3,在最近的三個點中紅色佔大多數,所以綠色將會被歸類為紅色

但有趣的是,假如k=5,則藍色的數量又比紅色多,所以綠色將會被歸類為藍色

因此k值的挑選也是一門學問

 

圖像中臉部的128個特徵值

回到我們的主題臉部辨識,要怎麼套用KNN幫我們歸類這張臉是屬於誰的呢

答案就是想辦法將我們每個人的臉部特徵量化

由於每個人長得不盡相同,所以理論上每個人量化後的臉部特徵都會有差異

但又因為每個人不同角度的照片臉部特徵又比其他人來得相近

藉由這樣的差異,來讓KNN歸類新看到的一張臉應該是屬於哪個人的

至於上面所說的將臉部特徵量化怎麼做到呢

放心,Python裡面已經有一個很好用的機器學習套件Dlib可以幫我們把圖像中的臉轉成128個特徵值

以下是上面最終辨識成果圖的128個特徵值:

雖然128個值代表他是128維度中的一個點,遠超過人類最大能夠製圖的三維座標系統

但是原理相同,都是讓KNN判斷周圍的k個點的所屬種類嘛~

 

安裝所需的套件

在開始實作前我們會用到以下套件,這是我在寫本篇親測可正確執行的版本組合

MacOS 10.14.6
Python: 3.7.3
argparse: 1.4.0
cv2: 4.1.1.26
dlib: 19.19.0
numpy: 1.16.0
scikit-learn: 0.22

如果有缺少的可以使用pip來安裝

argparse: pip install argparse
OpenCV: pip install opencv-python
Dlib: pip install dlib
NumPy: pip install numpy
scikit-learn: pip install scikit-learn

 

準備資料

由於程式中的KNN是將人臉歸類給已知的人,所以我們需要準備個幾個人的照片各3-5張

並依照不同的人放在對應的人名資料夾中,以下是儲存的結構:

KNN_facial_recognition
    |-- knn.py(等等要寫的主程式)
    |-- shape_predictor_68_face_landmarks.dat(等等會用到的模型)
    |-- dlib_face_recognition_resnet_model_v1.dat(也是等等會用到的模型)
    |-- data
             |-- person1
                      |-- 1.jpg
                      |-- 2.jpg
                      |-- 3.jpg
                      |-- 4.jpg
                      |-- 5.jpg
             |-- person2
                      |-- 1.jpg
                      |-- 2.jpg
                      |-- 3.jpg
                      |-- 4.jpg
                      |-- 5.jpg
             ...

由於肖像權、隱私權等問題,圖片大家就自己去網路上找找吧,相信google一下就會有很多的

然後要額外下載一下這兩個檔案:

shape_predictor_68_face_landmarks.dat
dlib_face_recognition_resnet_model_v1.dat

第一個是已經訓練好用來抓出臉部68個點座標的模型,第二個等等會拿它來將臉轉成128個特徵值用

兩個檔案下載好後解壓縮,和剛剛存圖片的data資料夾與等等的主程式knn.py放在一起

 

開始寫程式嘍

當上面要準備要下載的東西都完成後,開新檔案並命名為knn.py,將要使用的套件import進來

import argparse
import cv2
import dlib
import numpy as np
import os
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import LabelEncoder

 

為了不寫死程式中所需的檔案路徑,我們使用ArgumentParser將檔案路徑當成執行時要傳的參數

ap = argparse.ArgumentParser()
ap.add_argument('-i', '--input-image', type=str, required=True, help='path to the image for prediction')
ap.add_argument('-f', '--faces-folder-path', type=str, default='data/', help='path to the faces folder')
ap.add_argument('-p', '--shape-predictor', type=str, default='shape_predictor_68_face_landmarks.dat', help='path to facial landmark predictor')
ap.add_argument('-r', '--face-recognition-model', type=str, default='dlib_face_recognition_resnet_model_v1.dat', help='path to facial recognition model file')
args = vars(ap.parse_args())

 

載入detector,用於偵測人臉位置

載入predictor,用於從detector得到的結果計算人臉的68點座標

載入face_recognition_model,用於從predictor得到的68點座標計算出128個人臉特徵值

最後建立一個空的list待會拿來存data中的所有已知人臉

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(args['shape_predictor'])
face_recognition_model = dlib.face_recognition_model_v1(args['face_recognition_model'])

faces_list = []

 

data中的所有已知人臉存入faces_list

每個影像檔都是一個dict型態,裡面存了是誰的臉person以及檔案路徑path

for directory in os.listdir(args['faces_folder_path']):
	if os.path.isdir(args['faces_folder_path'] + directory):
		for file in os.listdir(args['faces_folder_path'] + directory):
			if file[-4:] in ['.jpg', 'jpeg', '.png']:
				face = dict()
				face['person'] = directory
				face['path'] = args['faces_folder_path'] + directory + '/' + file
				faces_list.append(face)

 

重新檢視faces_list裡面剛剛儲存的所有資訊

並從檔案路徑path讀入影像,計算出128個人臉特徵值

這邊我們假設每張圖片都只會出現一張人臉,如果出現超過一個,則抓取第一個辨識到的

將計算出的128個人臉特徵值存回每個dict中的vector128變數

for face in faces_list:
	print('Encoding ' + face['path'] + '...')
	img = cv2.imread(face['path'])
	# WARNING: ONLY THE FIRST DETECTED FACE IN THE IMAGE IS USED
	face_rect = detector(img, 1)[0]
	face['face_rect'] = face_rect
	shape68 = predictor(img, face_rect)
	face_descriptor = face_recognition_model.compute_face_descriptor(img, shape68)
	face_descriptor = np.array(face_descriptor)
	face['vector128'] = face_descriptor

 

有了每個人的128個臉特徵值後,我們就可以使用knn.fit()來讓我們的KNN演算法先認識這些特徵在128維度中的座標

但是在呼叫knn.fit()之前,我們必須先使用labelEncoder將它們的所屬類別轉換成數值形式0、1、2、...
(原本的類別名稱是人名資料夾的名稱)

labelEncoder = LabelEncoder()
labelEncoder.fit([face['person'] for face in faces_list])

knn = KNeighborsClassifier(n_neighbors=5)
knn.fit([face['vector128'] for face in faces_list], labelEncoder.transform([face['person'] for face in faces_list]))

 

當KNN已經學習這些特徵在128維度中的座標後,我們就要來讓KNN預測測試圖片中是誰的臉了

使用cv2.imread()讀入將被預測的圖片,抓取第一個辨識到的臉

取得該臉的68點座標後,產出該臉部的128個特徵值

img = cv2.imread(args['input_image'])
# WARNING: ONLY THE FIRST DETECTED FACE IN THE IMAGE IS USED
face_rect = detector(img, 1)[0]
shape68 = predictor(img, face_rect)
face_descriptor = np.array(face_recognition_model.compute_face_descriptor(img, shape68))

 

將128個特徵值輸入至knn.predict()進行預測,並輸出KNN所歸類到的人名以及機率

predict_person = np.squeeze(labelEncoder.inverse_transform(knn.predict([face_descriptor])))
person_classes = labelEncoder.classes_
predict_proba = knn.predict_proba([face_descriptor])

 

最後,在終端機印出結果,同時也將人臉框出並印上所預測的人名

print('\nPredict Person: {0}'.format(predict_person))
print('Probability:')
for i in range(len(person_classes)):
	print('{0}: {1}'.format(person_classes[i], np.squeeze(predict_proba)[i]))

cv2.rectangle(img, (face_rect.left(), face_rect.top()), (face_rect.right(), face_rect.bottom()), ( 0, 255, 0), 1, cv2.LINE_AA)
cv2.putText(img, str(predict_person), (face_rect.left(), face_rect.top() - 3), cv2.FONT_HERSHEY_DUPLEX, 1.1e-3 * img.shape[0], ( 0, 255, 0), 1, cv2.LINE_AA)
cv2.imshow('Facial Recognition', img)
cv2.waitKey()

 

到這邊為止我們已經寫好了KNN演算法的臉部辨識程式,接下來就來測試看看吧

 

測試採用KNN的臉部辨識

在終端機執行下面指令,參數-i-f-p-r後面就接自己命名的資料夾或檔案

python knn.py \
-i test.jpg \
-f data/ \
-p shape_predictor_68_face_landmarks.dat \
-r dlib_face_recognition_resnet_model_v1.dat

可以看到程式正在計算每張相片的128個人臉特徵值呢

計算完後,KNN就會預測出輸入的測試照片是屬於哪個人的臉以及機率

大家也可以改改看第46行的n_neighbors的值,讓KNN考慮要參考幾個最近點的類別

到這邊為止,大家是不是都成功地實作出採用KNN演算法的臉部辨識了呢

機器學習很有趣吧:)


發佈留言