Face Recognition 是在 github 上開源的 python library,作者透過包裝 dlib 的方式來實現快速且簡單的人臉辨識開發,同時提供已經訓練好的 CNN 人臉偵測模型、resnet 人臉辨識模型,開發者只需要準備好想要辨識的人臉圖片,就可以簡單用幾行 code 實現!就讓我們一起來看看怎麼做吧!
目錄
開發前準備
首先安裝所需套件
- OpenCV-python:我們使用它來載入圖片或影片
- numpy
- face-recognition:今日主角
- Pillow
pip install opencv-python numpy face-recognition pillow
如何使用 Face Recognition 建立人臉辨識?
Step 1:準備已知和未知的人臉圖片
準備好已知的人臉圖片,最好是一張只有一個人臉,且足夠清晰,這樣我們下一步轉成特徵向量比較方便。同時準備一些「未知」的人臉圖片,用來測試人臉辨識的結果。
為了讓大家在學習的時候眼睛也很舒服,找了帥哥美女的圖片來做練習,分別是
孫藝珍!
以及玄彬!!
欣賞完畢後,我們立刻開始吧!
Step 2:將已知人臉轉成特徵向量
我們分別將上述兩張圖片分別命名為「玄彬.jpeg」和「孫藝珍.jpeg」,放在與 notebook 同一個目錄中
在 notebook 一開始先 import 必要的 library
import cv2
import numpy as np
import face_recognition
先宣告一組 data,用來存放已知人臉姓名、檔案路徑以及特徵向量
known_face_list = [
{
'name': 'Hyun Bin',
'filename': '玄彬.jpeg',
'encode': None,
},
{
'name': 'Son Ye Jin',
'filename': '孫藝珍.jpeg',
'encode': None,
},
]
接下來我們用 OpenCV 將圖檔讀入後,透過 face recognition 轉成特徵向量塞入剛剛宣告的 data,讓之後步驟可以使用。
其中特別解說一下,如果圖中有多個人臉,方法 face_encodings() 會回傳多個特徵值,如果沒有臉則回傳空陣列。為了寫法方便,已知人臉圖片須確保一張只有一個人臉,如此這邊取特徵值的寫法才不會出錯。
for data in known_face_list:
img = cv2.imread(data['filename'])
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
data['encode'] = face_recognition.face_encodings(img)[0]
到這就已經把特徵值萃取出來,如果好奇長什麼樣子,可以試著印出看看,會發現是一大長串的數值陣列,他就是人臉的特徵向量。
>>> print(known_face_list)
[
{
'name': 'Hyun Bin',
'filename': '玄彬.jpeg',
'encode': array([-8.83631632e-02, 6.91961050e-02, 2.87680216e-02, -5.09743392e-02,......]),
},
{
'name': 'Son Ye Jin',
'filename': '孫藝珍.jpeg',
'encode': array([-0.04686793, 0.09257459, 0.03252909, -0.08914616, -0.0796747 ,....]),
}
]
Step 3:辨識前準備
接下來準備三張孫藝珍的照片,以及孫藝珍和玄彬的合照,我們要來測試人臉辨識
test_fn_list = ['孫藝珍-t1.jpeg', '孫藝珍-t2.jpeg', '孫藝珍-t3.jpeg', '玄彬+孫藝珍.jpeg']
首先我們將 data 中的特徵向量先拉出來成一個陣列備用
known_face_encodes = [data['encode'] for data in known_face_list]
這邊的人臉辨識原理是拿未知人臉的特徵向量,一一去比對已知人臉的特徵向量距離,其中距離最接近的已知人臉名字就認為是未知人臉的主人。
但如果已知人臉中並沒有未知人臉的主人,這個演算法還是會依照距離最小值回傳一個人名,但並不是我們要的結果。
所以我們需要設定一個 tolerance,當最短距離超過此值時,我們認定無法辨識,回傳 unknown。
tolerance = 0.6
Step 4:找出未知圖片中人臉的主人
準備完畢,我們正式進入辨識的程式區塊!
test_fn_list = ['孫藝珍-t1.jpeg', '孫藝珍-t2.jpeg', '孫藝珍-t3.jpeg', '玄彬+孫藝珍.jpeg']
for fn in test_fn_list:
img = cv2.imread(fn)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
cur_face_locs = face_recognition.face_locations(img)
cur_face_encodes = face_recognition.face_encodings(img, cur_face_locs, model='large')
for cur_face_encode in cur_face_encodes:
face_distance_list = face_recognition.face_distance(known_face_encodes, cur_face_encode)
min_distance_index = np.argmin(face_distance_list)
if face_distance_list[min_distance_index] < tolerance:
result = known_face_list[min_distance_index]['name']
else:
result = 'unknown'
print(f'辨識檔案: {fn}, 辨識結果: {result}')
接下來分程式區塊一一解釋:
首先迴圈透過 OpenCV 依序將未知人臉圖片從檔案中讀入。OpenCV 都會以「BGR」的方式讀入的圖片,因此我們要把它轉成「RGB」比較直覺,方便我們後續處理。
img = cv2.imread(fn)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
接下來透過 face_recognition 的方法 face_locations() 先辨識出人臉的位置,再將此位置丟入 face_encodings() 中進行辨識。
這裡特別把 face_locations 獨立出來處理的原因是,後面我們會修改程式,利用這些位置座標來畫紅框。如果 face_encodings() 呼叫時沒有給定 location,函數內也會再呼叫一次 locations 方法來取位置,所以這樣寫不會比較耗費效能。
cur_face_locs = face_recognition.face_locations(img)
cur_face_encodes = face_recognition.face_encodings(img, cur_face_locs)
未知人臉圖片可能會有多張人臉,所以我們迭代所有未知人臉的特徵向量陣列,並透過 face_distance() 方法,將未知人臉特徵向量一次與所有已知人臉做特徵距離計算。
for cur_face_encode in cur_face_encodes:
face_distance_list = face_recognition.face_distance(known_face_encodes, cur_face_encode)
透過 numpy 的方法 argmin 從特徵距離陣列中取出最小值的 index,再將最小距離值取出和 tolerance 比對。若小於 tolerance 表示辨識出人名,反之則 unknow。最後再印出結果。
min_distance_index = np.argmin(face_distance_list)
if face_distance_list[min_distance_index] < tolerance:
result = known_face_list[min_distance_index]['name']
else:
result = 'unknown'
print(f'辨識檔案: {fn}, 辨識結果: {result}')
解釋完畢,我們立刻來看看辨識結果
辨識檔案: 孫藝珍-t1.jpeg, 辨識結果: Son Ye Jin
辨識檔案: 孫藝珍-t2.jpeg, 辨識結果: Son Ye Jin
辨識檔案: 孫藝珍-t3.jpeg, 辨識結果: Son Ye Jin
辨識檔案: 玄彬+孫藝珍.jpeg, 辨識結果: Hyun Bin
辨識檔案: 玄彬+孫藝珍.jpeg, 辨識結果: Son Ye Jin
看起來蠻理想的!接下來我們將辨識結果畫上圖片看看!
Step 5:開始比對並繪製紅色方框
先準備好一個繪製紅色方框的 utility function,他接收 img 和 match_results,依序拉出裡面的 location 和 name 來繪製。
RED_COLOR = (200, 58, 76)
WHITE_COLOR = (255, 255, 255)
def draw_locations(img, match_results):
for match_result in match_results:
y1, x2, y2, x1 = match_result['location']
cv2.rectangle(img, (x1, y1), (x2, y2), RED_COLOR, 2)
cv2.rectangle(img, (x1, y2 + 35), (x2, y2), RED_COLOR, cv2.FILLED)
cv2.putText(img, match_result['name'], (x1 + 10, y2 + 25), cv2.FONT_HERSHEY_COMPLEX, 0.8, WHITE_COLOR, 2)
小小改寫我們上一步的程式後段,依序找出未知人臉名字後,將結果與位置存入 match_results 陣列,最後一次呼叫 draw_locations() 並顯示。
from IPython.display import display
from PIL import Image
for fn in test_fn_list:
match_results = []
img = cv2.imread(fn)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
cur_face_locs = face_recognition.face_locations(img)
cur_face_encodes = face_recognition.face_encodings(img, cur_face_locs)
for cur_face_encode, cur_face_loc in zip(cur_face_encodes, cur_face_locs):
face_distance_list = face_recognition.face_distance(known_face_encodes, cur_face_encode)
min_distance_index = np.argmin(face_distance_list)
if face_distance_list[min_distance_index] < tolerance:
name = known_face_list[min_distance_index]['name']
else:
name = 'unknown'
match_results.append({
'name': name,
'location': cur_face_loc,
})
draw_locations(img, match_results)
display(Image.fromarray(img))
讓我們來看看結果
效果還不錯!這些簡單的 code 已經足夠開發自己的人臉辨識系統,並且 deploy 在 Raspberry Pi 之類的單板電腦上。但還有地方可以再優化!下一篇文章:如何優化 Face Recognition 套件的人臉辨識?將會分享我覺得可以調整的地方!如果覺得我文章內容對你有幫助的話,請在文章後面幫我按 5 個讚!讓我知道大家都喜歡什麼內容哦!
範例原始碼在此下載:github
延伸閱讀:
如何優化 Face Recognition 套件的人臉辨識?
用 MotionEye + Raspberry pi 做一個網路監控系統吧!