圖像處理 人臉識別的三種經典算法與簡單的CNN 【附Python實現】

一點說明

1. 完成狀況

  • 詳細闡述了人臉識別中的經典算法與深度學習算法
  • 手動實現了三種人臉識別經典算法 【代碼地址】
    • 基於主成分分析(PCA)的Eigenfaces特徵臉方法
    • 基於線性判別分析(LDA)的Fisherfaces特徵臉方法
    • 局部二進制模式(LBP)直方圖方法
  • 實驗對比分析了三種人臉識別經典算法 和 CNN 實現人臉識別的特色以及異同點

2. 項目結構

  • data/(存放項目用到的數據集,若有更改,記得修改代碼中的引用地址)
  • src/(存放源代碼,直接運行 src/ 中的 Classical_Methods.py 便可)
  • README.md (實驗報告)

思路分析

0. 人臉識別 綜述

參考連接:《Face Recognition: From Traditional to Deep Learning Methods》《人臉識別合集 | 人臉識別概述》html

人臉識別(Face Recognition)是指 可以識別或驗證圖像或視頻中的主體的身份 的技術。自上個世紀七十年代首我的臉識別算法被提出以來,人臉識別已經成爲了計算機視覺與生物識別領域被研究最多的主題之一。究其火爆的緣由,一方面是它的挑戰性——在無約束條件的環境中的人臉信息,也就是所謂天然人臉(Faces in-the-wild),具備高度的可變性,以下圖所示;另外一方面是因爲相比於指紋或虹膜識別等傳統上被認爲更加穩健的生物識別方法,人臉識別本質上是非侵入性的,這意味着它是最天然、最符合人類直覺的一種生物識別方法。python

image-20200528151906435

​ 現代人臉識別技術的研究熱潮,已經從使用人工設計的特徵(如邊和紋理描述量等)與機器學習技術(如主成分分析、線性判別分析和支持向量機等)組合的傳統方法的研究,逐漸轉移到使用龐大人臉數據集搭建與在其基礎上訓練深度神經網絡的研究。可是,不管是基於傳統方法仍是深度神經網絡,人臉識別的流程都是類似的,大概由如下四個模塊組成:git

  • 人臉檢測 :提取圖像中的人臉,劃定邊界;
  • 人臉對齊 :使用一組位於圖像中固定位置的參考點來縮放和裁剪人臉圖像;
  • 人臉表徵 :人臉圖像的像素值會被轉換成緊湊且可判別的特徵向量,或稱模板;
  • 人臉匹配 :選取並計算兩個模板間的類似度分數來度量二者屬於同一個主體的可能性。

preview

1. 傳統算法——主成分分析(PCA)與 線性判別分析(LDA)

準確地說,是 基於主成分分析的Eigenfaces特徵臉方法基於線性判別分析的Fisherfaces特徵臉方法,這是根據總體特徵進行人臉辨別的兩種方法。github

1)算法框架

​ 使用 PCALDA 進行人臉識別的算法流程十分類似,具體步驟以下。算法

  1. 讀取人臉圖片數據庫的圖像及標籤,並進行灰度化處理(能夠同時進行直方圖均衡等)
  2. 將讀入的二維圖像數據信息轉爲一維向量,而後按列組合成原始數據矩陣
  3. 對原始矩陣進行歸一化處理,並使用PCA或LDA算法對原始數據矩陣進行特徵分析與降維(計算過程當中,根據原始數據獲得一維均值向量通過維度的還原之後獲得的圖像爲「平均臉」)
  4. 讀取待識別的圖像,將其轉化爲與訓練集中的一樣的向量表示,遍歷訓練集,尋找與待識別圖像的差值小於閾值(或差值最小)的圖像,即爲識別結果

2)PCA 原理

參考連接:《PCA的數學原理》 (強烈推薦,講得很是透徹!)數據庫

PCA(Principal Component Analysis,主成分分析)是一種經常使用的數據分析方法。PCA經過線性變換將原始數據變換爲一組各維度線性無關的表示,可用於提取數據的主要特徵份量,經常使用於高維數據的降維。數組

I. 爲何要降維?

​ 數據分析時,原始數據的維度與算法的複雜度有着密切的關係,在保留原始數據特徵的狀況下,對數據進行降維能夠有效地提升時間效率,減小算力損失。網絡

II. PCA降維的原理是什麼?

​ 降維意味着信息的丟失,可是因爲實際數據內部每每具備相關性,因此咱們能夠利用這種相關性,經過某些方法使得在數據維度減小的同時保留儘量多的原始特徵,這就是PCA算法的初衷。架構

​ 那麼這一想法如何轉化爲算法呢?咱們知道,在N維空間對應了由N個線性無關的基向量構成的一組基,空間中的任一貫量均可用這組基來表示。咱們要將一組N維向量降爲K維(K大於0,小於N),其實只須要經過矩陣乘法將原N維空間中的向量轉化爲由K個線性無關的基向量構成的一組基下的表示。app

​ 可是,這一組K維的基並非隨便指定的。爲了儘量保留原始特徵,咱們但願將原始數據向量投影到低維空間時,投影后各字段(行向量)不重合(顯然重合會覆蓋特徵),也就是使變換後數據點儘量分散,這就天然地聯繫到了線性代數中的方差與協方差。

image-20200528152425799

​ 因此,綜合來看,咱們降維的目標爲 選擇K個基向量(通常轉化爲單位長度的正交基),使得原始數據變換到這組基上後,各字段兩兩間協方差爲0,而字段的方差則儘量大(在正交的約束下,取最大的K個方差)。這也就是PCA的原理——PCA本質上是將方差最大的方向做爲主要特徵,而且在各個正交方向上將數據「離相關」,也就是讓它們在不一樣正交方向上沒有相關性。

III. 如何使用PCA進行降維?

​ 在上一個問題中,咱們其實已經介紹了PCA的算法流程。轉化成具體的數學方法,主要有如下幾步(設有 \(m\)\(n\) 維數據,將其降爲 \(k\) 維):

  • 將原始數據按列組成 \(n\)\(m\) 列矩陣 \(X\)
  • 矩陣 \(X\) 中每一維的數據都減去該維的均值,使得變換後矩陣 \(X’\) 每一維均值爲 \(0\)
  • 求出協方差矩陣 \(C=\frac{1}{m}X'X'^T\),進一步求出矩陣 \(C\) 的特徵值及對應的特徵向量
  • 將特徵向量按對應特徵值大小從上到下按行排列成矩陣,取前 \(k\) 行組成矩陣 \(P\)
  • \(Y=PX\) 即爲降維到 \(k\) 維後的數據
IV. PCA的缺點與不足

​ PCA是一種無參數技術,沒法進行個性化的優化;PCA能夠解除線性相關,但沒法處理高階的相關性;PCA假設數據各主特徵分佈在正交方向,沒法較好應對主特徵在非正交方向的狀況。

V. PCA 的 python 實現

用 Python 語言實現上述算法,代碼以下,僅展現代碼核心部分,詳細代碼請見附件

參考連接:《人臉識別經典算法實現(一)——特徵臉法》《opencv學習之路(40)、人臉識別算法——EigenFace、FisherFace、LBPH》《經典人臉識別算法小結——EigenFace, FisherFace & LBPH(下)》

def algorithm_pca(data_mat):
    """
    PCA函數,用於數據降維
    :param data_mat: 樣本矩陣
    :return: 降維後的樣本矩陣和變換矩陣
    """
    mean_mat = np.mat(np.mean(data_mat, 1)).T
    cv2.imwrite('./data/face_test/mean_face.jpg', np.reshape(mean_mat, IMG_SIZE))

    diff_mat = data_mat - mean_mat
    # print('差值矩陣', diff_mat.shape, diff_mat)
    cov_mat = (diff_mat.T * diff_mat) / float(diff_mat.shape[1])
    # print('協方差矩陣', cov_mat.shape, cov_mat)

    eig_vals, eig_vecs = np.linalg.eig(np.mat(cov_mat))
    # print('特徵值(全部):', eig_vals, '特徵向量(全部):', eig_vecs.shape, eig_vecs)

    eig_vecs = diff_mat * eig_vecs
    eig_vecs = eig_vecs / np.linalg.norm(eig_vecs, axis=0)
    eig_val = (np.argsort(eig_vals)[::-1])[:DIM]
    eig_vec = eig_vecs[:, eig_val]
    # print('特徵值(選取):', eig_val, '特徵向量(選取):', eig_vec.shape, eig_vec)

    low_mat = eig_vec.T * diff_mat
    # print('低維矩陣:', low_mat)

    return low_mat, eig_vec

3)LDA 原理

參考連接:《Linear Discriminant Analysis》《人臉識別經典算法三:Fisherface(LDA)》《人臉識別系列二 | FisherFace,LBPH算法及Dlib人臉檢測》

LDA(Linear Discriminant Analysis,線性判別分析)算法的思路與PCA相似,都是對圖像的總體分析。不一樣之處在於,PCA是經過肯定一組正交基對數據進行降維,而LDA是經過肯定一組投影向量使得數據集不一樣類的數據投影的差異較大、同一類的數據通過投影更加聚合。在形式上,PCA與LDA的最大區別在於,PCA中最終求得的特徵向量是正交的,而LDA中的特徵向量不必定正交。

I. LDA的原理是什麼?

​ 在上面咱們已經介紹了LDA的目標:不一樣的分類獲得的投影點要儘可能分開;同一個分類投影后獲得的點要儘可能聚合。

image-20200528165432681

​ 爲了定量分析這兩點,以計算合適的投影矩陣,咱們定義了類內散列度矩陣 \(S_{w}\)和 類間散列度矩陣 $S_B $ 。 其中 \(S_w=\sum ^c_{i=1}{\sum _{x\in ω_i}(x-μ_i)(x-μ_i)^T}\), \(c\) 爲類別總數,\(μ_i\)表明類別 \(i\) 的均值矩陣;\(S_B=\sum ^c_{i=1}N_i(μ_i-μ)(μ_i-μ)^T\)\(N_i\) 爲類別\(i\) 的數據點數。定義 \(J(w)=\frac{|W^TS_BW|}{|W^TS_wW|}\) 爲目標函數,其中矩陣 \(W\) 是投影矩陣,那麼咱們就是要求出使 \(J(w)\) 取最大值的投影矩陣 \(W\) 。該最大值可由拉格朗日乘數法求得,再也不贅述。最終問題可化簡爲,\(S_w^{-1}S_Bw_i=λw_i\) ,即求得矩陣的特徵向量,而後取按特徵值從大到小排列的前 \(k\) 個特徵向量即爲所需的投影矩陣。

II. LDA 與 PCA 的比較
  • LDA與PCA算法的不一樣之處:

    • 從數學角度來看,LDA選擇分類性能最好的投影方向,而PCA選擇樣本投影點具備最大方差的方向;
    • LDA是有監督的降維方法,而PCA是無監督的;
    • 對於 \(K\) 維的數據,LDA只能將其降到 \(K-1\) 維度,而 PCA 不受此限制。
  • LDA與PCA算法的相同之處:

    • 在降維的時候,二者都使用了矩陣的特徵分解思想;
    • 兩種算法都假設數據集中原始數據符合高斯分佈。
III. LDA 的 python 實現

用python語言實現上述算法,代碼以下,僅展現代碼核心部分,詳細代碼請見附件

參考連接:《人臉識別經典算法實現(二)——Fisher線性判別分析》《opencv學習之路(40)、人臉識別算法——EigenFace、FisherFace、LBPH》《經典人臉識別算法小結——EigenFace, FisherFace & LBPH(下)》

def algorithm_lda(data_list):
    """
    多分類問題的線性判別分析算法
    :param data_list: 樣本矩陣列表
    :return: 變換後的矩陣列表和變換矩陣
    """
    n = data_list[0].shape[0]
    Sw = np.zeros((n, n))
    u = np.zeros((n, 1))
    Sb = np.zeros((n, n))
    N = 0
    mean_list = []
    sample_num = []

    for data_mat in data_list:
        mean_mat = np.mat(np.mean(data_mat, 1)).T
        mean_list.append(mean_mat)
        sample_num.append(data_mat.shape[1])
        data_mat = data_mat - mean_mat
        Sw += data_mat * data_mat.T

    for index, mean_mat in enumerate(mean_list):
        m = sample_num[index]
        u += m * mean_mat
        N += m
    u = u / N

    for index, mean_mat in enumerate(mean_list):
        m = sample_num[index]
        sb = m * (mean_mat - u) * (mean_mat - u).T
        Sb += sb

    eig_vals, eig_vecs = np.linalg.eig(np.mat(np.linalg.inv(Sw) * Sb))
    eig_vecs = eig_vecs / np.linalg.norm(eig_vecs, axis=0)
    eig_val = (np.argsort(eig_vals)[::-1])[:DIM]
    eig_vec = eig_vecs[:, eig_val]

    trans_mat_list = []
    for data_mat in data_list:
        trans_mat_list.append(eig_vec.T * data_mat)
    return trans_mat_list, eig_vec

3. 傳統算法——局部二進制模式直方圖(LBPH)

LBPH算法「人」如其名,採用的識別方法是局部特徵提取的方法,這是與前兩種方法的最大區別。相似的局部特徵提取算法還有離散傅里葉變換(DCT)與蓋伯小波(Gabor Waelets)等。

LBPH(Local Binary Pattern Histograms,局部二進制模式直方圖)人臉識別方法的核心是 LBP算子。LBP是一種用來描述圖像局部紋理特徵的算子,它反映內容是每一個像素與周圍像素的關係。

I. LBP 的原理是什麼?

參考連接:《LBP簡介》《人臉識別經典算法二:LBP方法》

  • 原始 LBP

    ​ 最初的LBP是定義在像素3x3鄰域內的,以鄰域中心像素爲閾值,將相鄰的8個像素的灰度值與其進行比較,若周圍像素值大於中心像素值,則該像素點的位置被標記爲1,不然爲0。這樣,3x3鄰域內的8個點經比較可產生8位二進制數(一般轉換爲十進制數即LBP碼,共256種),即獲得該鄰域中心像素點的LBP值,並用這個值來反映該區域的紋理信息。以下圖所示:

    image-20200528204656131

  • 均勻 LBP(本次做業中代碼採用的LBP算子)

    ​ 研究者發現根據原始LBP計算出來的90%以上的值具備某種特性,即屬於 均勻模式(Uniform Pattern)——二進制序列(這個二進制序列首尾相連)中數字從0到1或是從1到0的變化不超過2次。好比,01011111的變化次數爲3次,那麼該序列不屬於均勻模式。根據這個算法,全部的8位二進制數中共有58(變化次數爲0的有2種,變化次數爲1的有0種,變化次數爲2的有56種)個均勻模式。

    ​ 因此,咱們能夠根據這一數據分佈特色將原始的LBP值分爲59類,58個均勻模式爲一類,其他爲第59類。這樣就將直方圖從原來的256維變成了59維,起到了降維的效果。

III. LBPH 的算法流程

LBPH的算法其實很是簡單,只有兩步:

  • LBP特徵提取:根據上述的均勻LBP算子處理原始圖像;
  • LBP特徵匹配(計算直方圖):將圖像分爲若干個的子區域,並在子區域內根據LBP值統計其直方圖,以直方圖做爲其判別特徵。
III. LBP 的特色?

​ LBP的優勢是對光照不敏感。根據算法,每一個像素都會根據鄰域信息獲得一個LBP值,若是以圖像的形式顯示出來能夠獲得下圖。相比於PCA或者LDA直接使用灰度值去參與運算,LBP算子是一種相對性質的數量關係,這是LBP應對不一樣光照條件下人臉識別場景的優點所在。可是對於不一樣角度、遮擋等場景,LBP也無能爲力。

image-20200528212302668

IV. LBPH 的 python 實現

用python語言實現上述算法,代碼以下,僅展現代碼核心部分,詳細代碼請見附件

參考連接:《人臉識別經典算法實現(三)——LBP算法》《opencv學習之路(40)、人臉識別算法——EigenFace、FisherFace、LBPH》《經典人臉識別算法小結——EigenFace, FisherFace & LBPH(下)》

class AlgorithmLbp(object):
    def load_img_list(self, dir_name):
        """
        加載圖像矩陣列表
        :param dir_name:文件夾路徑
        :return: 包含最原始的圖像矩陣的列表和標籤矩陣
        """
        # 請見附件

    def get_hop_counter(self, num):
        """
        計算二進制序列是否只變化兩次
        :param num: 數字
        :return: 01變化次數
        """
        bin_num = bin(num)
        bin_str = str(bin_num)[2:]
        n = len(bin_str)
        if n < 8:
            bin_str = "0" * (8 - n) + bin_str
        n = len(bin_str)
        counter = 0
        for i in range(n):
            if i != n - 1:
                if bin_str[i + 1] != bin_str[i]:
                    counter += 1
            else:
                if bin_str[0] != bin_str[i]:
                    counter += 1
        return counter

    def get_table(self):
        """
        生成均勻對應字典
        :return: 均勻LBP特徵對應字典
        """
        counter = 1
        for i in range(256):
            if self.get_hop_counter(i) <= 2:
                self.table[i] = counter
                counter += 1
            else:
                self.table[i] = 0
        return self.table

    def get_lbp_feature(self, img_mat):
        """
        計算LBP特徵
        :param img_mat:圖像矩陣
        :return: LBP特徵圖
        """
        m = img_mat.shape[0]
        n = img_mat.shape[1]
        neighbor = [0] * 8
        feature_map = np.mat(np.zeros((m, n)))
        for y in range(1, m - 1):
            for x in range(1, n - 1):
                neighbor[0] = img_mat[y - 1, x - 1]
                neighbor[1] = img_mat[y - 1, x]
                neighbor[2] = img_mat[y - 1, x + 1]
                neighbor[3] = img_mat[y, x + 1]
                neighbor[4] = img_mat[y + 1, x + 1]
                neighbor[5] = img_mat[y + 1, x]
                neighbor[6] = img_mat[y + 1, x - 1]
                neighbor[7] = img_mat[y, x - 1]
                center = img_mat[y, x]
                temp = 0
                for k in range(8):
                    temp += (neighbor[k] >= center) * (1 << k)
                feature_map[y, x] = self.table[temp]
        feature_map = feature_map.astype('uint8') 
        return feature_map

    def get_hist(self, roi):
        """
        計算直方圖
        :param roi:圖像區域
        :return: 直方圖矩陣
        """
        hist = cv2.calcHist([roi], [0], None, [59], [0, 256]) 
        return hist

4. 神經網絡——卷積神經網絡(CNN)

CNN(Convolutional Neural Networks,卷積神經網絡)是人臉識別方面最經常使用的一類深度學習方法。深度學習方法的主要優點是可用大量數據來訓練,從而學到對訓練數據中出現的變化狀況穩健的人臉表徵。這種方法不須要設計對不一樣類型的類內差別(好比光照、姿式、面部表情、年齡等)穩健的特定特徵,而是能夠從訓練數據中學到它們。

​ 深度學習方法的主要短板是它們須要使用很是大的數據集來訓練,並且這些數據集中須要包含足夠的變化,從而能夠泛化到不曾見過的樣本上。幸運的是,一些包含天然人臉圖像的大規模人臉數據集已被公開;不幸的是,本人設備算力實在有限,沒法支持在大數據集上運行深度學習代碼。

I. 深度學習的幾個經典的案例

參考連接:《人臉識別合集 | 緒論與目錄》《Face Recognition: From Traditional to Deep Learning Methods》

  • DeepFace,2014年

    ​ DeepFace主要先訓練Softmax多分類器人臉識別框架;而後抽取特徵層,用特徵再訓練另外一個神經網絡、孿生網絡或組合貝葉斯等人臉驗證框架。想同時擁有人臉驗證和人臉識別系統,須要分開訓練兩個神經網絡。但線性變換矩陣W的大小隨着身份數量n的增長而線性增大。

    ​ DeepFace的主要貢獻是,(1)一個基於明確的 3D 人臉建模的高效的人臉對齊系統;(2)一個包含局部鏈接的層的 CNN 架構 ,這些層不一樣於常規的卷積層,能夠從圖像中的每一個區域學到不一樣的特徵。

  • DeepID系列,2014年

    ​ DeepID框架與DeepFace相似,採用的是 CNN+Softmax;而DeepID二、DeepID2+、DeepID3都採用 CNN+Softmax+Contrastive Loss,使得同類特徵的L2距離儘量小,不一樣類特徵的L2距離大於某個間隔。

  • FaceNet,2015年

    ​ 2015年FaceNet提出了一個絕大部分人臉問題的統一解決框架,直接學習嵌入特徵,而後人臉識別、人臉驗證和人臉聚類等都基於這個特徵來作。FaceNet在 DeepID2 的基礎上,拋棄了分類層,再將 Contrastive Loss 改進爲 Triplet Loss,得到類內緊湊和類間差別。但人臉三元組的數量出現爆炸式增加,特別是對於大型數據集,致使迭代次數顯著增長;樣本挖掘策略形成很難有效的進行模型的訓練。

II. 深度學習的算法流程

參考連接:《人臉識別合集 | 人臉識別概述》《Face Recognition: From Traditional to Deep Learning Methods》

​ 用於人臉識別的 CNN 模型主要有如下兩種設計思路:

  • 基於度量的學習:經過優化配對的人臉或人臉三元組之間的距離度量來直接學習人臉表徵(常稱爲瓶頸特徵,bottleneck features);
  • 基於分類的學習:訓練集中的每一個主體都對應一個類別,經過去除分類層並將以前層的特徵用做瓶頸特徵而將該模型用於識別不存在於訓練集中的主體。在這第一個訓練階段以後,該模型可使用其它技術來進一步訓練,從而爲目標應用優化瓶頸特徵(好比使用聯合貝葉斯或使用一個不一樣的損失函數來微調該 CNN 模型 )。

image-20200529095549533

III. Python實現

卷積神經網絡部分的代碼非本人編寫,但願老師與助教知悉。【引用地址】

​ 與電腦環境鬥爭一天的我,面對無數的DDL,遂選擇擱置,往後我會將從新編寫的代碼放在個人博客中。但爲了使報告更加完整,兼顧傳統方法與深度學習方法,綜合對比不一樣方法在人臉識別中的表現,我使用了 17373489 張佳一 同窗的 CNN 代碼與數據(見結果分析部分)。

#實現CNN卷積神經網絡,並測試最終訓練樣本實現的檢測機率
#tf.layer方法能夠直接實現一個卷積神經網絡的搭建
#經過卷積方法實現
layer1 = tf.layers.conv2d(inputs=data_input, filters = 32,kernel_size=2,
strides=1,padding='SAME',activation=tf.nn.relu)
#實現池化層,減小數據量,pool_size=2表示數據量減小一半
layer1_pool = tf.layers.max_pooling2d(layer1,pool_size=2,strides=2)
#第二層設置輸出,完成維度的轉換,以第一次輸出做爲輸入,創建n行的32*32*32輸出
layer2 = tf.reshape(layer1_pool,[-1,32*32*32])
#設置輸出激勵函數
layer2_relu = tf.layers.dense(layer2, 1024, tf.nn.relu)
#完成輸出,設置輸入數據和輸出維度
output = tf.layers.dense(layer2_relu, num_people)
#創建損失函數
loss =
tf.losses.softmax_cross_entropy(onehot_labels=label_input,logits=output)
#使用梯度降低法進行訓練
train = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
#定義檢測機率
accuracy = tf.metrics.accuracy(
labels=tf.arg_max(label_input, 1), predictions=tf.arg_max(output, 1))
[1]

結果分析

0. 數據集說明

I. 基本信息(數據集地址
  • 數據集中共包含395人(男性和女性)的人臉圖像,每人20張圖像;
  • 數據集中人來自不一樣種族(但鮮有東方人);
  • 主要是大學一年級的本科生,故大多數的個體在18-20歲之間,但也有一些年齡較大的個體;
  • 數據集劃分爲四個子集,分別爲Faces9四、Faces9五、Faces96以及Grimace,識別難度遞增。
II. 數據集展現
Faces94(153人) Faces95(72人) Faces96(152人) Grimace(18人)
anonym2.2 adhast.2 9540547.2 mike_exp.2
cchris.14 jross.20 gghazv.1 ant_exp.13

1. 主成分分析(PCA)

I. PCA算法中的 「平均臉」 與 「特徵臉」

在代碼實現部分,我將 均值矩陣特徵矩陣 從新映射到 \([0,255]\) 的灰度值區間,獲得了所謂 「平均臉」 與 「特徵臉」。(從上到下,依次爲在 Faces9四、Faces9五、Faces9六、Grimace的結果)

image-20200529153451265 image-20200529153637277 image-20200529153604493 image-20200529153520283 image-20200529153423698
平均臉 特徵臉 1 特徵臉 2 特朗普 3 😜 特徵臉 4
image-20200529160618129 image-20200529160719210 image-20200529160701992 image-20200529160743278 image-20200529160802233
平均臉 特徵臉 1 特徵臉 2 特徵臉 3 特徵臉 4
image-20200529162005906 image-20200529162025283 image-20200529162042848 image-20200529162107583 image-20200529162142207
平均臉 特徵臉 1 特徵臉 2 特徵臉 3 特徵臉 4
image-20200529160132783 image-20200529160211493 image-20200529160239724 image-20200529160300530 image-20200529160317587
平均臉 特徵臉 1 特徵臉 2 特徵臉 3 特徵臉 4
II. 準確率與消耗時間

這裏展現的是該算法在不一樣數據集上識別的準確率,以及在本機上運行的時間(單位:ms)。

Faces94 Faces95
image-20200529154316549 image-20200529154440399
Faces96 Grimace
image-20200529155813754 image-20200529155905364
III. PCA 結果分析
  • 準確率方面:咱們發現,PCA在Faces94與Grimace上的識別準確率居然達到了100%,Faces96上次之,而在Faces95上的識別率最低。這與咱們的預期截然不同。結合「平均臉」與數據集特徵,——Faces95數據集中人臉的角度變化較大,而Faces96中背景的干擾較大——我認爲應該是由於在代碼實現時,數據預處理中沒有進行良好的人臉檢測與對齊,這也反映了,PCA在適應背景干擾與人臉角度變化方面,表現不佳;

  • 識別速度方面:隨數據集規模擴大,識別耗時基本呈指數增加的趨勢(橙色虛線爲指數擬合曲線)。

    image-20200529213252667

2. 線性判別分析(LDA)

代碼運行在Faces94與Faces95子集上會出現數組溢出的BUG,這與數據集中非法數據(類別中不足20張,包含.gif圖片等)有關,須要對數據集進行過濾,現僅在其他兩個數據集上測試。

I. LDA中的平均臉
Faces96 Grimace
image-20200529172252635 image-20200529173231579
II. 準確率與消耗時間
Faces96 Grimace
image-20200529165250674 image-20200529173145839
III. LDA 結果分析
  • 準確率方面:實驗結果顯示,LDA在Faces96上表現不佳,而在Grimace上則達到了100%識別。分析其緣由,LDA;

  • 識別速度方面:與PCA技術相比,通常狀況下,在相同的數據集上,LDA算法耗時要比PCA長,且當數據集增大時,LDA算法耗時與PCA的差值逐漸增大;可是,當數據集較小時,LDA的速度反而比PCA略快。

    image-20200530000757818

3. 局部二進制模式直方圖(LBPH)

I. LBPH 中間圖像

注意,爲了提升識別效率,在圖片讀取時,我已經將原圖尺寸統一調整爲 50 × 50 。

原圖 原始LBP 均衡模式
image-20200529201746853 image-20200529201838431 image-20200529201934509
II. 準確率與消耗時間

LBPH人臉比對時間過長,在測試時,我將其讀取到的圖片調整爲20 × 20,以提升識別速度。

Faces94 Faces95
image-20200530163044671 image-20200530173615958
Faces96 Grimace
image-20200531004930294 image-20200531095639006
III. 結果分析
  • 識別準確率上來看,LBPH整體表現較好,在大部分狀況下都表現較好,尤爲是在Faces94和Grimace上取得較好的識別效果,可是在Faces95數據集上的識別效果不佳。這應該與我在圖片壓縮時,將圖片從 180 × 200 壓縮到 20 × 20 所形成的局部信息損失有關。
  • 識別速度上來看,LBPH的人臉表徵的提取階段耗時較短,可是人臉識別階段耗時較長,這是由於每次識別都要遍歷訓練集中全部圖片進行計算(能夠經過調整圖片壓縮比以及直方圖劃塊的大小來調整識別的時間複雜度)。
IV. 三種經典算法的準確率對比

不一樣算法下,在不一樣特色的數據集中,人臉識別準確率的對好比下。

image-20200531165302774

V. 三種經典算法的速度對比

運行算法進行人臉表徵提取所需時間(單位:ms)。

image-20200531164604346

不一樣算法進行人臉識別所需時間(單位:ms)。

image-20200531170130094

4. 卷積神經網絡(CNN,分類模型)

用到的數據集:PIE dataset ,包含了68我的在五種不一樣姿態下的共11554張面部圖像數據,並以MAT格式的文件保存。

I. 準確率與損失函數收斂狀況
Pose07 Pose09 Pose27 Pose29
image-20200530003146949 image-20200530003156212 image-20200530003203409 image-20200530003210152
II. 分析(與經典算法的對比)
  • 咱們發現,當訓練的數據集規模不足夠大時,反而傳統方法的效果可能更好一些,由於傳統方法是人們基於圖像的某些規律,經過數學分析推導出的可以較好地描述圖像中人臉特徵的算法,於是對於較簡單的場景、較小的訓練集,傳統算法的優點明顯。這也是爲何早在上個世紀就出現了各類神經網絡,可是直到21世紀之後,基於神經網絡的深度學習在人臉識別中的應用才逐漸進入主流——巨型人臉數據集與深層神經網絡的搭建與計算機算力的進步。
  • 另外一個發現是,在訓練輪次等參數肯定的狀況下,相比於傳統方法,卷積神經網絡對不一樣數據集的適應性較強,或者說穩定性較強——實驗中,在不一樣的人臉數據集下,卷積神經網絡的準確率穩定在0.8~0.9之間,而傳統方法的準確率的方差較大。這其實與二者的原理直接相關,人工設計在無約束環境中對不一樣變化狀況穩健的特徵是很困難的,這使得過去的研究者側重研究針對每種變化類型的專用方法,好比能應對不一樣年齡的方法、能應對不一樣姿式的方法 、能應對不一樣光照條件的方法等;可是深度學習則否則,它們可用很是大型的數據集進行訓練,從而學到人臉圖像中穩健的特徵,可以應對在訓練過程當中使用的人臉圖像所呈現出的真實世界變化狀況。

附:關鍵代碼

1. 圖像加載與圖像矩陣列表創建(PCA、LDA)

def load_img(file_name):
    """
    載入圖像,統一尺寸,灰度化處理,直方圖均衡化
    :param file_name: 圖像文件名
    :return: 圖像矩陣
    """
    t_img_mat = cv2.imread(file_name)  # 載入圖像
    t_img_mat = cv2.resize(t_img_mat, IMG_SIZE)  # 統一尺寸
    t_img_mat = cv2.cvtColor(t_img_mat, cv2.COLOR_RGB2GRAY)  # 轉化爲灰度圖
    img_mat = cv2.equalizeHist(t_img_mat)  # 直方圖均衡
    return img_mat


def create_img_mat(dir_name, algorithm=0):
    """
    生成圖像樣本矩陣,組織形式爲行爲屬性,列爲樣本
    :param dir_name: 包含訓練數據集的圖像文件夾路徑
    :param algorithm: 識別算法,0-EigenFace,1-Fisher,2-LBP
    :return: 樣本矩陣,標籤矩陣
    """
    data_mat = np.zeros((IMG_SIZE[0] * IMG_SIZE[1], 1))
    label = []
    data_list = []
    for parent, dir_names, file_names in os.walk(dir_name):
        for t_dir_name in dir_names:
            for sub_parent, sub_dir_name, sub_file_names in os.walk(parent + '/' + t_dir_name):
                for t_index, t_file_name in enumerate(sub_file_names):
                    if not t_file_name.endswith('.jpg'):
                        continue
                    if t_file_name.endswith('.10.jpg'):
                        continue
                    t_img_mat = load_img(sub_parent + '/' + t_file_name)
                    img_mat = np.reshape(t_img_mat, (-1, 1))

                    if algorithm == 0:
                        data_mat = np.column_stack((data_mat, img_mat))
                    else:
                        data_mat = img_mat if t_index == 0 else np.column_stack((data_mat, img_mat))

                    label.append(sub_parent + '/' + t_file_name)
            data_list.append(data_mat[:, 1:] if algorithm == 0 else data_mat)
    return data_mat[:, 1:], label, data_list

2. 圖像列表創建(LBP)

class AlgorithmLbp(object):
    def __init__(self):
        self.table = {}
        self.ImgSize = IMG_SIZE
        self.BlockNum = DIM
        self.count = 0

    def load_img_list(self, dir_name):
        """
        加載圖像矩陣列表
        :param dir_name:文件夾路徑
        :return: 包含最原始的圖像矩陣的列表和標籤矩陣
        """
        img_list = []
        label = []
        for parent, dir_names, file_names in os.walk(dir_name):
            for t_dir_name in dir_names:
                for sub_parent, sub_dir_name, sub_filenames in os.walk(parent + '/' + t_dir_name):
                    for file_name in sub_filenames:
                        img_list.append(load_img(sub_parent + '/' + file_name))  
                        label.append(sub_parent + '/' + file_name)
        return img_list, label

3. 直方圖創建(LBP)

class AlgorithmLbp(object):
    def get_hop_counter(self, num):
        """
        計算二進制序列是否只變化兩次
        :param num: 數字
        :return: 01變化次數
        """
        bin_num = bin(num)
        bin_str = str(bin_num)[2:]
        n = len(bin_str)
        if n < 8:
            bin_str = "0" * (8 - n) + bin_str
        n = len(bin_str)
        counter = 0
        for i in range(n):
            if i != n - 1:
                if bin_str[i + 1] != bin_str[i]:
                    counter += 1
            else:
                if bin_str[0] != bin_str[i]:
                    counter += 1
        return counter

    def get_table(self):
        """
        生成均勻對應字典
        :return: 均勻LBP特徵對應字典
        """
        counter = 1
        for i in range(256):
            if self.get_hop_counter(i) <= 2:
                self.table[i] = counter
                counter += 1
            else:
                self.table[i] = 0
        return self.table

    def get_lbp_feature(self, img_mat):
        """
        計算LBP特徵
        :param img_mat:圖像矩陣
        :return: LBP特徵圖
        """
        m = img_mat.shape[0]
        n = img_mat.shape[1]
        neighbor = [0] * 8
        feature_map = np.mat(np.zeros((m, n)))
        t_map = np.mat(np.zeros((m, n)))
        for y in range(1, m - 1):
            for x in range(1, n - 1):
                neighbor[0] = img_mat[y - 1, x - 1]
                neighbor[1] = img_mat[y - 1, x]
                neighbor[2] = img_mat[y - 1, x + 1]
                neighbor[3] = img_mat[y, x + 1]
                neighbor[4] = img_mat[y + 1, x + 1]
                neighbor[5] = img_mat[y + 1, x]
                neighbor[6] = img_mat[y + 1, x - 1]
                neighbor[7] = img_mat[y, x - 1]
                center = img_mat[y, x]
                temp = 0
                for k in range(8):
                    temp += (neighbor[k] >= center) * (1 << k)
                feature_map[y, x] = self.table[temp]
                t_map[y, x] = temp
        feature_map = feature_map.astype('uint8')  
        t_map = t_map.astype('uint8')
        self.count += 1
        return feature_map

    def get_hist(self, roi):
        """
        計算直方圖
        :param roi:圖像區域
        :return: 直方圖矩陣
        """
        hist = cv2.calcHist([roi], [0], None, [59], [0, 256])  
        return hist

4. 測試圖像距離計算(PCA、LDA)

def method_compare(mat_list, test_img_vec, algorithm=0):
    """
    比較函數,這裏只是用了最簡單的歐氏距離比較,還可使用KNN等方法,如需修改修改此處便可
    :param mat_list: 樣本向量集
    :param test_img_vec: 測試圖像向量
    :param algorithm: 識別算法,0-EigenFace,1-Fisher,2-LBP
    :return: 與測試圖片最相近的圖像文件名的index
    """
    dis_list = []
    if algorithm == 0:
        for sample_vec in mat_list.T:
            dis_list.append(np.linalg.norm(test_img_vec - sample_vec))

    elif algorithm == 1:
        # print('mat_list.len=', len(mat_list))
        for trans_mat in mat_list:
            for sample_vec in trans_mat.T:
                dis_list.append(np.linalg.norm(test_img_vec - sample_vec))
    index = np.argsort(dis_list)[0]
    return index

5. 測試圖像距離計算(LBP)

class AlgorithmLbp(object):
    def compare(self, sampleImg, test_img):
        """
        比較函數,這裏使用的是歐氏距離排序,也可使用KNN,在此處更改
        :param sampleImg: 樣本圖像矩陣
        :param test_img: 測試圖像矩陣
        :return: k2值
        """
        testFeatureMap = self.get_lbp_feature(test_img)
        sampleFeatureMap = self.get_lbp_feature(sampleImg)
        # 計算步長,分割整個圖像爲小塊
        ystep = int(self.ImgSize[0] / self.BlockNum)
        xstep = int(self.ImgSize[1] / self.BlockNum)

        k2 = 0
        for y in range(0, self.ImgSize[0], ystep):
            for x in range(0, self.ImgSize[1], xstep):
                testroi = testFeatureMap[y:y + ystep, x:x + xstep]
                sampleroi = sampleFeatureMap[y:y + ystep, x:x + xstep]
                testHist = self.get_hist(testroi)
                sampleHist = self.get_hist(sampleroi)
                k2 += np.sum((sampleHist - testHist) ** 2) / np.sum((sampleHist + testHist))
        return k2

6. 學累了,聽首歌放鬆一下吧!

相關文章
相關標籤/搜索