使用 Face Recognition 套件快速建立自己的人臉辨識系統!

Face Recognition 是在 github 上開源的 python library,作者透過包裝 dlib 的方式來實現快速且簡單的人臉辨識開發,同時提供已經訓練好的 CNN 人臉偵測模型、resnet 人臉辨識模型,開發者只需要準備好想要辨識的人臉圖片,就可以簡單用幾行 code 實現!就讓我們一起來看看怎麼做吧!

開發前準備

首先安裝所需套件

  1. OpenCV-python:我們使用它來載入圖片或影片
  2. numpy
  3. face-recognition:今日主角
  4. Pillow
pip install opencv-python numpy face-recognition pillow

如何使用 Face Recognition 建立人臉辨識?

Step 1:準備已知和未知的人臉圖片

準備好已知的人臉圖片,最好是一張只有一個人臉,且足夠清晰,這樣我們下一步轉成特徵向量比較方便。同時準備一些「未知」的人臉圖片,用來測試人臉辨識的結果。

為了讓大家在學習的時候眼睛也很舒服,找了帥哥美女的圖片來做練習,分別是

孫藝珍!

使用 Face Recognition 套件快速建立自己的人臉辨識系統!
孫藝珍.jpeg

以及玄彬!!

使用 Face Recognition 套件快速建立自己的人臉辨識系統!
玄彬.jpeg

欣賞完畢後,我們立刻開始吧!

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]

這邊的人臉辨識原理是拿未知人臉的特徵向量,一一去比對已知人臉的特徵向量距離,其中距離最接近的已知人臉名字就認為是未知人臉的主人。

使用 Face Recognition 套件快速建立自己的人臉辨識系統!
辨識結果回傳「已知人臉A」

但如果已知人臉中並沒有未知人臉的主人,這個演算法還是會依照距離最小值回傳一個人名,但並不是我們要的結果。

使用 Face Recognition 套件快速建立自己的人臉辨識系統!
辨識結果回傳錯誤的「已知人臉B」

所以我們需要設定一個 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)) 

讓我們來看看結果

使用 Face Recognition 套件快速建立自己的人臉辨識系統!

效果還不錯!這些簡單的 code 已經足夠開發自己的人臉辨識系統,並且 deploy 在 Raspberry Pi 之類的單板電腦上。但還有地方可以再優化!下一篇文章:如何優化 Face Recognition 套件的人臉辨識?將會分享我覺得可以調整的地方!如果覺得我文章內容對你有幫助的話,請在文章後面幫我按 5 個讚!讓我知道大家都喜歡什麼內容哦!

範例原始碼在此下載:github

延伸閱讀:
如何優化 Face Recognition 套件的人臉辨識?
用 MotionEye + Raspberry pi 做一個網路監控系統吧!

Written by J
雖然大學唸的是生物,但持著興趣與熱情自學,畢業後轉戰硬體工程師,與宅宅工程師們一起過著沒日沒夜的生活,做著台灣最薄的 intel 筆電,要與 macbook air 比拼。 離開後,憑著一股傻勁與朋友創業,再度轉戰軟體工程師,一手扛起前後端、雙平台 app 開發,過程中雖跌跌撞撞,卻也累計不少經驗。 可惜不是那 1% 的成功人士,於是加入其他成功人士的新創公司,專職開發後端。沒想到卻在採前人坑的過程中,拓寬了眼界,得到了深層的領悟。