LBP Face recognition 臉孔辨識

        Face recognition是Face detection的更深一層的分析處理。Face detection框選出影像中的臉部區域後,便交由Face recognition透過機器學習或深度學習的方式來達成person identification的目的。

Face recognition主要有兩個phase:

  • Phase #1:偵測影像中的臉孔,使用的方法有Haar cascades、HOG + Linear SVM、 deep learning…等等。
  • Phase #2:取出影像中所偵測到的臉孔,取出並分析其特徵並加以辨識,使用的方法有MLDL等。

早期Face recognition的處理方式是透過計算臉上顯著器官的外形尺寸來辨識,例如頭圍、眉毛粗細角度、鼻子長度寬度、眼睛形狀、嘴脣厚度等等,近年來,藉助於電腦硬體及演算法的進步,採用機器學習和深度學習已成了Face recognition領域的主流方法。

Face recognition的歷史

        在電腦視覺的領域中,臉孔辨識算是較晚起步的領域,也讓人感覺這類的技術總是披覆著神奇色彩。1971年Goldstein、Harmon 、Lesk等三位學者於IEEE發佈了「Identification of human faces」論文,從255個人臉的資料庫中分析得出了辨識臉孔的22種臉部特徵,要正確的辨識這255個人臉,至少需要22種臉部特徵中的六種結果。不過,由於這些特徵是手動計算出來的,因此在脫離255個樣本的溫室後,這個理論就很難再保有滿意的結果。

        直到1987年,由Kirby和Sirovich兩位學者發佈了「A Low-Dimensional Procedure for the Characterization of Human Faces」論文後,才真正開啟了由電腦自動取得人臉特徵的新紀元,他們的方法被稱為Eigenfaces方法,透過線性代數的PCA技術,自動取得不多於100維度的人臉特徵向量就能進行準確的人臉辨識(Principal Component Analysis,PCA為一種分析、簡化數據的技術,經常應用於資料降維)。

        除了Eigenfaces方法,之後還有應用了Linear Discriminant Analysis(LDA)演算法的Fisherfaces的出現,LDA也是一種線性代數的降維方法,與PCA最大的差異在於,LDA強調保留降維後不同數據間的特色,PCA則是強調保留數據本身內在的訊息,因此,PCA可用於比較數據的重要性但不適合分類,而LDA正好相反。

                另外,採用Local Binary Patterns(LBA, 局部二值模式)的臉部辨識方法,在目前非Deep learning的臉部辨識技術中也是主流,下面我們就先來試看看LBA的臉孔辨識方法。

Local Binary Patterns

Local binary patterns(LBP)又稱局部二值模式,最初由Ojala et al.等人於2002年的一篇論文「Multiresolution Gray-Scale and Rotation Invariant Texture Classification with Local Binary Patterns」所提出。該LBP演算法最初主要用於判斷局部圖像中的材質紋理,由於其計算速度非常的快,且具有旋轉不變性和灰度不變性(即光照變化在處理時並不會造成影響)等顯著的優點,因此近年來也經常應用在人臉辨識的領域。

LBP演算法原理

Local Binary Patterns的原理其實並不難理解,我用一張資訊部辦公區的空照圖為例,先將其轉為灰階後再取出右上角的影像區塊,接下來利用此局部的影像區塊作為LBP的處理範例說明。

Step 1. 切分影像為3×3的cells

將整張影像切分為區塊3×3的cells(原始的LBP演算法使用3*3,實際應用時也可選擇其它尺寸,在此以3×3為例),總共切分為16×16個cells。

Step 2. 與相鄰點比較

如果我們把這些3×3的cells稱為windows,那麼,將每個window的中心點像素為閾值,與其相鄰的8個像素的灰度值進行比較(下圖左),若這8個相鄰點其值大於中心像素值,那麼該相鄰點像素的位置被標記為1,否則為0。

Step 3. 計算LBP mask值

比較完之後,我們會得到上圖右的表格,接著順時鐘從最左上角排列其值,可得到一組二進位值00111000,換算為10進位則為70,因此該cell的灰度值將重新定義為70,此值就稱為LBP Mask,可用來反映該區域的紋理訊息。

Step 4. 得到LBP影像

由於每個像素點都可以由其相鄰的八個像素點得到LBP mask,因此,利用上述方法可將一幅灰階圖像的pixels轉換為LBP特徵圖。

Step 5. 計算LBP Histogram

有了各個區域影像的LBP值,我們可以將其轉換為Histogram直方圖(X軸為像素強度,Y軸為像素數目),這樣在進行機器學習的運算上更為方便。每一個影像區域會得出一個直方圖,若將這些直方圖合併,則得到整張影像的直方圖。

LBP的改良版

近年來學者們針對LBP作了一些改良,不使用方形而採用「圓形」的LBP Mask。所謂的「圓形」是以中心像素點為圓心,半徑為R pixels畫圓,於該圓範圍內總共選取P個點作為採樣點,再利用前述的處理方式來得到LBP Mask值。因此,當我們在使用LBP時,通常會需要提供R以及P的參數值,其原理就是這麼來的。

R=1,P=8

R=1.5,P=16

R=2,P=16

由上述的步驟中會發現,由於LBP mask是經由與周圍各點的「比較」後所決定的,因此,如果整張圖片的亮度(或稱為灰度)改變(亦即整張圖片各像素的灰度值一同增加或減少固定數值),那麼經過LBP運算後結果仍會是00111000,對於結果並沒有影響,所以我們常聽到LBP演算法可抵抗光照變換所帶來的影響便是這麼來的。

使用LBP進行臉孔辨識

        我們以此張照片為例,假設臉孔偵測程式取得了圖中的臉孔,接下來要怎麼作呢?

Step 1. 首先,將此臉孔平均分割為7×7個cells。7×7這個數目是上面所提到Ojala et al.等人於2002年的一篇論文「Multiresolution Gray-Scale and Rotation Invariant Texture Classification with Local Binary Patterns」當中所建議的。

Step 2. 接著,針對每一個cells取出其LBP Histogram。如下圖所示,我們會得到49組不同位置的Historgram。雖然將圖像資訊轉為LBP Histogram之後會喪失位置相關資訊,但由於這49組Histogram是由原圖的7×7 cells所構成,因此這些LBP Histograms還是保有相對位置的資訊。

        

Step 3. 接下來,針對這7×7的LBP Histogram我們將套用一個weighting mask。如下圖,該mask的深淺層度代表了其權重,愈亮的權重愈高、愈深顏色則愈低。這個Mask可讓我們強調臉部識別時所要強調的位置,像臉部周圍在辨識上的重要性就比起眼睛或嘴巴來得低,而眼睛比起鼻樑的權重要大。

白色區域的權重可能是4,代表這為最重要的特徵區域,灰色為2,黑色為1,例如外側臉頰和鼻樑區,代表辨識時此區域的比重較低。

Step 4. 最後將加權後的49組LBP Histogram,串連起來成為我們需要的特徵向量,這組特徵向量便可用於machine learning for face recognition,例如,我們可以使用KNN最近鄰方法,將類似的臉孔進行分群。

最後,我們用實際的dataset套用LBP臉孔辨識來試看看。這是來自caltech.edu的CALTECH Faces dataset,一個正面臉部特寫圖片的資料集,由加州理工學院的博士生Markus Weber所搜集(他目前在Amazon擔任Director Operations),程式部份則參考自Pyimagesearch.com。

LBP Face Recognition

CALTECH Faces dataset

        下載位址:http://www.vision.caltech.edu/html-files/archive.html 

        資料集特色:

  1. 圖片數目:總共450張臉孔圖片,來自27位不同人物。每位人物張數不定,最多可能近30張,少則幾千張。
  2. 格式尺寸:JPEG、896 x 592 pixels
  3. 特色:相同人物的每張圖片有不同的背景、燈光、表情

Dataset資料清理

  • image_0399.jpg、image_0401.jpg:這兩位人物各僅有一張相片,由於樣本太少,因此予以刪除。

 

  • image_0400.jpg、image_0402.jpg、image_0403.jpg:此三張為卡通人物,不適合一起用於訓練,因此予以刪除。

  

        Dataset架構整理

剛下載的Caltech Face Dataset並沒有依不同人物分類而是在放置於同一層目錄,因此,我們將其依不同人物放置於不同的目錄下,目錄名稱可自訂,該名稱在訓練時將會作為其label,亦即該人物的名字。

如下圖,剛下載的dataset所有人物置於同一層目錄並未分類。

因此我們依人物不同放置於不同的資料夾下,以方便後續程式使用。

        程式說明

在Caltech Face Dataset中,有一個ImageData.mat檔案,這是for Matlab的臉部label area定義檔。下方的function會從指定的path讀入dataset的所有圖片後,參考ImageData.mat所定義的label將所有圖片資料讀入,整理成training、testing、及labels dataset回傳。

參數說明:        

  • datasetPath 臉孔資料集的路徑
  • min_faces 定義最少數目的訓練張數,若少於此數目,則該label將略過不予以訓練。
  • face_size 定義臉部進行訓練前要resize的尺寸
  • equal_samples 定義所有不同人物的訓練樣本數目是否要相同,此參數與min_faces參數搭配使用。
  • test_size 定義dataset要分出多少比例作為test dataset
  • seed 亂數種子,相同的種子在不同的電腦可得到一樣的結果。
  • flatten 是否要將讀入的圖片資料攤平為一維

def load_caltech_faces(datasetPath, min_faces=10, face_size=(47, 62), equal_samples=True, test_size=0.33, seed=42, flatten=False):

        imagePaths = sorted(list(paths.list_images(datasetPath)))

        bbData = io.loadmat(“{}/ImageData.mat".format(datasetPath))

        bbData = bbData[“SubDir_Data"].T

        random.seed(seed)

        data = []

        labels = []

        for (i, imagePath) in enumerate(imagePaths):

                image = cv2.imread(imagePath)

                gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

                k = int(imagePath[imagePath.rfind(“_") + 1:][:4]) – 1

                (xBL, yBL, xTL, yTL, xTR, yTR, xBR, yBR) = bbData[k].astype(“int")

                face = gray[yTL:yBR, xTL:xBR]

                face = cv2.resize(face, face_size)

                if flatten:

                        face = face.flatten()

                data.append(face)

                labels.append(imagePath.split(“/")[-2])

        data = np.array(data)

        labels = np.array(labels)

        if equal_samples:

                sampledIdxs = []

                for label in np.unique(labels):

                        labelIdxs = np.where(labels == label)[0]

                        if len(labelIdxs) >= min_faces:

                                labelIdxs = random.sample(list(labelIdxs), min_faces)

                                sampledIdxs.extend(labelIdxs)

                random.shuffle(sampledIdxs)

                data = data[sampledIdxs]

                labels = labels[sampledIdxs]

        idxs = range(0, len(data))

        random.shuffle(list(idxs))

        split = int(len(idxs) * (1.0 – test_size))

        (trainData, testData) = (data[:split], data[split:])

        (trainLabels, testLabels) = (labels[:split], labels[split:])

        training = Bunch(name="training", data=trainData, target=trainLabels)

        testing = Bunch(name="testing", data=testData, target=testLabels)

        return (training, testing, labels)

                載入dataset,轉為training、testing、names等三份datasets

(training, testing, names) = load_caltech_faces(“caltech_faces", min_faces=21, test_size=0.25)

                將training dataset的label透過encoder由字串轉為數字格式

le = LabelEncoder()

le.fit_transform(training.target)

判斷opencv版本,依據2.x或3.x執行不同的LBPHFaceRecognizer。這是一個好用的LBP Face Recognize library,必須安裝opencv_contrib才能使用。

if imutils.is_cv2():

        recognizer = cv2.LBPHFaceRecognizer_create(radius=2, neighbors=16, grid_x=8, grid_y=8)

else:

        recognizer = cv2.face.LBPHFaceRecognizer_create(radius=2, neighbors=16, grid_x=8, grid_y=8)

                直接把training dataset丟給recognizer,這樣訓練就完成了。

# train the Local Binary Pattern face recognizer

print(“[INFO] training face recognizer…")

recognizer.train(training.data, le.transform(training.target))

                定義存放預測testing dataset結果及信心指數的兩個變數

print(“[INFO] gathering predictions…")

predictions = []

confidence = []

這個face library實在太方便了,只要把圖片丟給它,就直接回傳預測答案和信心指數。

for i in range(0, len(testing.data)):

        print(“{} of {}".format(str(i), str(len(testing.data))))

        (prediction, conf) = recognizer.predict(testing.data[i])

        predictions.append(prediction)

        confidence.append(conf)

把預測結果和答案送給classification_report作分析報告,預測結果相當令人滿意。

print(classification_report(le.transform(testing.target), predictions,

        target_names=np.unique(names)))

                

        從上面的程式中,我們沒有透過深度學習,而是使用opencv提供的LBP相關library作機器學習訓練,訓練的樣本張數雖然不多,但結果卻相當的不錯。若是使用深度學習,相信步驟和作法將更為簡單容易(但需要花費更大的運算資源)。

        再接下來,我們將使用公司平時的活動相片,在取得在所有同仁的臉孔後,使用LBP方法來跑看看。