最近在讀機器學習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演算法的臉部辨識了呢
機器學習很有趣吧:)