人臉辨識模型 Google Facenet 介紹與使用

近年來透過深度學習以及CNN,不但開啟了人臉辨識領域的新紀元,也讓傳統的LBP、HOG搭配支援向量機SVM的辨識方法顯得落伍。這些複雜的深度學習模型不但辨識率極高,而且能識別的人數級別與相片總量也都以萬級來計算,若侷限於學術的實驗情境下與人眼來比較,這成績已經遠遠抛開我們肉眼的能力了。

6012_oNheY4kd5w

2015年由Google所提出的Facenet,在LFW人臉資料庫以99.63%的最佳成績刷新了記錄,由於其易於理解的演算原理以及應用方便,使得Facenet在眾多競爭者中(如DeepFace、DeepID、Face++…等)異軍突起,成為目前最流行的臉部識別技術。下面,我們大致來瞭解一下Facenet,並且學習如何來使用它。

Ps. LFW資料集中收錄了5749位公眾人物的人臉影像,總共有超過一萬三千多張影像檔案。但大部份公眾人物的影像都只有一張,只有1680位有超過一張照片,而極少數有超過10張照片。(http://www.cc.ntu.edu.tw/chinese/epaper/0043/20171220_4306.html)

輸出量化特徵值,而非輸出分類結果

下圖是標準的臉部辨識作業流程,首先1.針對輸入的影片或相片進行臉孔偵測Face Detection,接著2.進行臉部校正對齊Face Alignment,然後開始3.依據不同的算法來取得臉部的特徵Feature Extraction,有了特徵,傳統的作法是4.據此來計算與匹配不同的臉孔圖片,最後5.透過softmax輸出至各分類結果,但Facenet則不然,模型所輸出的是該臉孔特徵的歐式距離總和。

Face-Recognition-Pipeline

由於Facenet模型輸出的是量化的數值,因此我們就能利用此數值來比對多張臉孔的差異度,並應用於如下的人臉辨識領域,顯示出Facenet相較其它的技術更為通用。

  1. Face Verification 驗証是否為同一人
  2. Face Identification 辨識身份或姓名
  3. Face Cluster 相似的人分類在一起
  4. Face Search 搜索相似的人
  5. Face Tracking  跟蹤特定的人臉

Triplet Loss:模型的核心

模型需要取得圖片不同的特徵並映射到歐幾里德的空間中(即計算特徵間的歐氏距離),所採用的作法是Triplet loss,如下圖原論文中的圖示說明。

preview

Triplet Loss想法在於,如果我們選取與該樣本最相像(差異最小)的錯誤臉孔,與最不像(差異度大)的正確臉孔,同時進行特徵訓練,迭代訓練至誤差降至最小,用以改進模型中的臉孔特徵定義,如此一來,便能得到最佳的辨識結果。

Facenet架構

Facenet主要由以下步驟所組成:

https://mmbiz.qpic.cn/mmbiz_png/fsbDGCu9Wrak9aicQBv5Scy7ZvHSia0CqIpW0kdqoaZicxSfycP5yoIKU2H5icFwnIWodJa2z1PfmfBRafHsDMZMIQ/0?wx_fmt=png

  1. Batch → Batch input layer 將臉孔圖片輸入模型,這些圖片需已經Facial alignment及resize處理。原開發者使用MTCNN來進行臉孔偵測及校準
  2. Deep Architecture → 即用於特徵學習的CNN架構,具彈性可以採用不同的網路。剛推出時使用Zeiler&Fergus架構和Google的Inception v1,今年最新版改為Inception ResNet-v2。不同架構對於辨識結果有顯著的影響,如下圖為使用不同的network model的辨識成績差異。

  1. L2 → L2 normalization 歸一化,讓資料能對應到一個超平面。
  2. Embedding → 經過CNN模型以及L2歸一化後生成的特徵向量
  3. ​Triplet Loss → 從向量中取得一個embedding函數f(x),讓相同孔之間的特徵距離要盡可能的小,而不同孔之間的特徵距離要盡可能的大。

在Keras環境使用Facenet

Facenet使用Tensorflow開發,不過已有很多有心人士另外撰寫為Cafffe或Keras等版本,如果我們想要在Keras環境中使用,推薦可以採用keras-facenet(https://github.com/nyoki-mtl/keras-facenet)。作者提供了一個預訓練好的Keras model可直接下載使用。當然也可以下載其它的預訓練models,再透過作者提供的轉檔程式tf_to_keras.ipynb轉為.h5 model來使用。

下載預訓練模型

作者提供的預訓練模型是使用MS-Celeb-1M dataset訓練,用於一般環境的辨識來說效果已相當不錯,並不一定要自行搜集臉孔資料來訓練。MS-Celeb-1M是微軟於2016年6月所公開發佈的人臉資料庫,包含100萬個名人總共約800多萬張人臉影像。

下面說明如何在使用Keras版本的Facenet:

Step1. git clone https://github.com/nyoki-mtl/keras-facenet.git

Step2. 下載預訓練模型https://drive.google.com/open?id=1pwQ3H4aJ8a6yyJHZkTwtjcL4wYWQb7bn,將該檔facenet_keras.h5放置於model/keras/目錄下

程式說明

我改編了作者用於示範的.pynb程式,可方便驗證compares列表中相片的人與valid相片中的人,其差異度為多少。

import numpy as np

import os, time

import cv2

from skimage.transform import resize

from scipy.spatial import distance

from keras.models import load_model

#驗證compares列表中相片的人與valid相片中的人

valid = “200127/200127_1.jpg"

compares = [“200002/200002_1.jpg", “200127/200127_1″, “200127/o.jpg" ]

#用OpenCV的Cascade classifier來偵測臉部,不一定跟Facenet一樣要用MTCNN。

cascade_path = ‘haarcascade_frontalface_default.xml’

#我們的人像相片都放置於validPicPath

validPicPath = ‘members/’

#此版Facenet model需要的相片尺寸為160×160

image_size = 160

#使用MS-Celeb-1M dataset pretrained好的Keras model

model_path = ‘model/facenet_keras.h5’

model = load_model(model_path)

#————————————————————

#圖像白化(whitening)可用於對過度曝光或低曝光的圖片進行處理,處理的方式就是改變圖像的平均像素值為 0 ,改變圖像的方差為單位方差 1。

def prewhiten(x):

    if x.ndim == 4:

        axis = (1, 2, 3)

        size = x[0].size

    elif x.ndim == 3:

        axis = (0, 1, 2)

        size = x.size

    else:

        raise ValueError(‘Dimension should be 3 or 4’)

    mean = np.mean(x, axis=axis, keepdims=True)

    std = np.std(x, axis=axis, keepdims=True)

    std_adj = np.maximum(std, 1.0/np.sqrt(size))

    y = (x – mean) / std_adj

    return y

#使用L1或L2標準化圖像,可強化其特徵。

def l2_normalize(x, axis=-1, epsilon=1e-10):

    output = x / np.sqrt(np.maximum(np.sum(np.square(x), axis=axis, keepdims=True), epsilon))

    return output

#偵測並取得臉孔area,接著再resize為模型要求的尺寸(下方例子並未作alignment)

def align_image(img, margin):

    cascade = cv2.CascadeClassifier(cascade_path)

    faces = cascade.detectMultiScale(img, scaleFactor=1.1, minNeighbors=3)

    if(len(faces)>0):

        (x, y, w, h) = faces[0]

        face = img[y:y+h, x:x+w]

        faceMargin = np.zeros((h+margin*2, w+margin*2, 3), dtype = “uint8″)

        faceMargin[margin:margin+h, margin:margin+w] = face

        cv2.imwrite(str(time.time())+".jpg", faceMargin)

        aligned = resize(faceMargin, (image_size, image_size), mode=’reflect’)

        cv2.imwrite(str(time.time())+"_aligned.jpg", aligned)

        return aligned

    else:

        return None

#圖像的預處理(即前述的幾項步驟)

def preProcess(img):

    whitenImg = prewhiten(img)

    whitenImg = whitenImg[np.newaxis, :]

    return whitenImg

#————————————————-

imgValid = validPicPath + valid

aligned = align_image(cv2.imread(imgValid), 6)

if(aligned is None):

    print(“Cannot find any face in image: {}".format(imgValid))

else:

    faceImg = preProcess(aligned)

    #–> model會輸出128維度的臉孔特徵向量,接著我們將它們合併並進行L2正規化。Z

    embs_valid = l2_normalize(np.concatenate(model.predict(faceImg)))

  #同上方的valid圖片,依序取得各圖片人臉的臉孔特徵向量,再與valid進行歐氏距離計算。

    for member in compares:

        img_file = validPicPath + member

        aligned = align_image(cv2.imread(img_file), 6)

        if(aligned is not None):

            faceImg = preProcess(aligned)

            embs = l2_normalize(np.concatenate(model.predict(faceImg)))

            distanceNum = distance.euclidean(embs_valid, embs)

            print(“Diff with {} is {}".format(member, distanceNum))

程式測試

我們將一些不同人的相片放置於members目錄下,目錄名稱為其人名或代表工號,接著來測試看看。

正面臉孔:

F:\Temp\members\200127\200127_1.jpg F:\Temp\members\200002\200002_1.jpg 1.082138
F:\Temp\members\200127\o.jpg 0.493925

效果看來不錯,只憑左邊一張相片,Facenet就能判斷出右下方臉孔與左圖較為相似,雖然其拍攝角度的差異更大。

這次測試下方往上的正面臉孔:

F:\Temp\members\200249\o.jpg
1.0013418 1.0399750 0.7001376
F:\Temp\members\200280\200280_1.jpg F:\Temp\members\200296\o.jpg F:\Temp\members\200249\200249_2.jpg

毫無懸念的Facenet又猜對了,雖然我們肉眼感覺下圖中間的人臉其構圖上更為近似。

側面的臉孔:

F:\Temp\members\200296\valid-h.jpg

0.36971601843833923

0.9557983875274658

F:\Temp\members\200296\200296_1.jpg

F:\Temp\members\200345\200345_1.jpg

0.7647050619125366

0.6167847514152527

F:\Temp\members\200249\200249_2.jpg

F:\Temp\members\200296\o.jpg

        使用一張側面的臉孔來預測其它角度的臉孔,果然差異值最小的那兩張與上方的確是同一人沒錯。

        本文簡單的介紹了Facenet,並且示範如何於Keras framework使用。dataset並非自行訓練而是使用微軟MS-Celeb-1M dataset,雖然如此,單張相片的verify能力已相當令人驚豔,如果希望應用在公司內部並提昇到更高的辨識率,建議應考慮加入自行搜集的相片重新訓練以製作更符合公司人員的model。

另外,如果在一般的PC上執行,您會發現Facenet速度不是很快速,此時可以考慮改為在GPU上執行,在迅速的辨識速度下,可作出各種有趣且實用的人臉辨識專案。