人臉識別-PCA特徵臉(大BOSS)

人臉識別-PCA特徵臉(大BOSS)

寫在前面

你好,我是禪墨!好久,不見!

在忙了好久的各科考試之後,我終於閒下來了。

自從小凱上次輸給我K210之後,一心想搞我,想要報仇。

小凱:阿墨,我給你說啊,我XX不服,給你三個小時,你給我做出來一個人臉識別,識別咱兩個班一共57人,正確率不能低於96%,我賭100塊錢!!!你得在我的監視下完成!

禪墨:確定?我怕你後悔!

小凱:確定!後悔,不存在的!你搞吧!!!小樣!

思考了片刻決定用PCA實現!

PCA特徵臉

PCA原理

PCA全名爲主成分分析,其主要目的就是尋找一個矩陣,然後把原來的一組帶有相關性的矩陣映射到尋找到的那個矩陣中,達到降維的目的。一般的,如果我們有M個N維向量,想將其變換爲由R個N維向量表示的新空間中,那麼首先將R個基按行組成矩陣A,然後將向量按列組成矩陣B,那麼兩矩陣的乘積AB就是變換結果,其中AB的第m列爲A中第m列變換後的結果。 這句話就相當於找到了一個R行N列矩陣,然後乘一個N行M列矩陣,這樣就得到了一個R行M列矩陣(其中R<=N),達到降維的目的。其中M和N的含義爲,M可以代表樣本個數,而N代表每個樣本的特徵個數,所以最終結果就是把原來N個特徵變爲了R個特徵,達到降維目的。

算法解析

1、構建一個樣本集合S={T1,T2,...,TM}S =\{T_1,T_2,...,T_M\},SS 可以看做是一個N行M列的矩陣,也就是有M個樣本,每個樣本有N個特徵。其中TiT_i是一個向量。
2、0均值化,爲了便於計算方差的時候需要減去均值,所以如果本身樣本是零均值的,就方便計算。

m=1Mi=1MTim = \frac{1}{M}\sum_{i=1}^{M}T_i ,這個是計算均值在python中可以使用

m = T.mean(axis = 1)  

進行計算,其中axis = 1代表按行求均值。
然後A=TmA = T -m 這個相當於把每個樣本都減去均值,這樣之後就相當於做了0均值化。

3、計算投影矩陣(就是相當於上面的那個R行M列矩陣)
這個投影矩陣其實就是由AATA*A^T矩陣的特徵向量構成,但是由於大多數情況AATA*A^T的維度太大(AATA*A^T是N行N列矩陣,如果是一張圖片的話N就代表像素點個數,所以是相當大的),所以這個時候就利用數學的小技巧轉化爲先求ATAA^T*A的特徵向量矩陣V,其中V的每一列是一個特徵向量,那麼V是一個M行M列的矩陣,然後我們再從V中取出前R個最大特徵值對應的特徵向量,所以V就變成了M行R列矩陣,然後C=AVC = AV,那麼這個C矩陣就是計算出的投影矩陣,C爲一個N行R列的矩陣。

在這裏插入圖片描述

4、把原來樣本進行投影

第三步我們得到了一個N行R列的矩陣C,其中每一列是一個特徵向量,但是我們在講PCA原理的時候我們需要一個R行N列的矩陣,每一行是一個特徵向量,所以我們可以使用CTC^T,所以我們投影后的樣本變爲P=CTAP = C^T A 其中P就是一個R行M列的矩陣,可以看出已經達到了降維的目的。

特徵臉的實現

特徵臉就是我們上面求得的C矩陣,所謂的基於特徵臉進行的人臉識別,就是先把人臉映射到一個低緯空間,然後再計算映射後的臉之間的距離,把距離最近的兩個特徵臉歸爲同一個人的臉。

所以特徵臉的步驟爲:

  1. 加載訓練集中的臉,轉爲一個M行N列矩陣T
  2. 對T進行均值化
  3. 找到T的投影矩陣C
  4. 計算投影后的矩陣P
  5. 設置簡單的UI界面,進行圖片選擇與識別
  6. 加載一個測試圖片,並利用C矩陣也把其投影爲test_P
  7. 計算test_P和P中每個樣本的距離,最近的就是結果
  8. 通過結果檢索對應的列表,找到並輸出人名

Python程序解析

createDatabase函數

創建一個存放所有圖片的數據庫,具體的步驟看程序註釋

def createDatabase(path):
    # 查看路徑下所有文件
    TrainFiles = os.listdir(path)
    # 計算有幾個文件(圖片命名都是以 序號.jpg方式)
    Train_Number = len(TrainFiles) 
    T = []
    # 把所有圖片轉爲1-D並存入T中
    for i in range(0,Train_Number):
        ip = path+'/'+str(i)+'.jpg'
        image = cv.imread(ip,cv.IMREAD_GRAYSCALE)
        image=cv.resize(image,img_size)
        # 轉爲1-D
        image = image.reshape(image.size,1)
        T.append(image)        
    T = np.array(T)
    # 不能直接T.reshape(T.shape[1],T.shape[0]) 這樣會打亂順序,
    T = T.reshape(T.shape[0],T.shape[1])
    return np.mat(T).T

eigenfaceCore函數

特徵臉核心處理函數,對T進行數據處理操作,包括進行均值化,計算特徵向量和特徵值。

def eigenfaceCore(T):
    # 把均值變爲0 axis = 1代表對各行求均值
    m = T.mean(axis = 1)
    A = T-m
    L = (A.T)*(A)

    # 計算AT *A的 特徵向量和特徵值V是特徵值,D是特徵向量
    # L = np.cov(A,rowvar = 0)
    V, D = np.linalg.eig(L)
    L_eig = []
    for i in range(A.shape[1]):
            L_eig.append(D[:,i])
    L_eig = np.mat(np.reshape(np.array(L_eig),(-1,len(L_eig))))
    # 計算 A *AT的特徵向量
    eigenface = A * L_eig
    return eigenface,m,A  

recognize函數

識別器函數:找到投影矩陣C,計算投影后的矩陣樣本P,加載一個測試圖片投影爲test-p,然後在總樣本P中進行比對,找到與test-p距離最近的樣本,即爲比對結果

def recognize(testImage, eigenface,m,A):
    _,trainNumber = np.shape(eigenface)
    # 投影到特徵臉後的
    projectedImage = eigenface.T*(A)
    # 可解決中文路徑不能打開問題(相當於英文路徑下imread)
    testImageArray = cv.imdecode(np.fromfile(testImage,dtype=np.uint8),cv.IMREAD_GRAYSCALE)
    # 轉爲1-D
    testImageArray=cv.resize(testImageArray,img_size)
    testImageArray = testImageArray.reshape(testImageArray.size,1)
    testImageArray = np.mat(np.array(testImageArray))
    differenceTestImage = testImageArray - m
    projectedTestImage = eigenface.T*(differenceTestImage)
    distance = []
    for i in range(0, trainNumber):
        q = projectedImage[:,i]
        temp = np.linalg.norm(projectedTestImage - q)
        distance.append(temp)
  
    minDistance = min(distance)
    index = distance.index(minDistance)
    cv.imshow("recognize result",cv.imread('./TrainDatabase'+'/'+str(index+1 )+'.jpg',cv.IMREAD_GRAYSCALE))
    cv.waitKey()
    return index+1

構建可視化界面

製作一個簡單的UI界面,通過簡單的按鈕選擇需要識別人臉的圖片,點擊開始識別,輸出結果,檢索列表輸出人名

def gui():
    root = tk.Tk()
    root.title("pca face")
    #點擊選擇圖片時調用
    def select():
        filename = tkinter.filedialog.askopenfilename()
        if filename != '':
            s=filename
            # jpg圖片文件名 和 路徑。
            im=Image.open(s)
            tkimg=ImageTk.PhotoImage(im)
            # 執行此函數之前, Tk() 必須已經實例化。
            l.config(image=tkimg)
            btn1.config(command=lambda : example(filename))
            btn1.config(text = "開始識別")
            btn1.pack()
            # 重新繪製
            root.mainloop()
    # 顯示圖片的位置
    l = tk.Label(root)
    l.pack()
    
    btn = tk.Button(root,text="選擇識別的圖片",command=select)
    btn.pack()
    
    btn1 = tk.Button(root) # 開始識別按鈕,剛開始不顯示
    root.mainloop()
if __name__ == "__main__":
    gui()

圖片問題

我覺得有必要吐槽一下給的570張圖片:jpg , png, jpeg, pmg各種格式各種分辨率應有盡有,真的是搞死我了!(一寸人臉照一般爲92*112) 不過還好,只有七八十張,其他的就是正常的jpg格式,怎麼辦呢,我不可能說一張張進行裁剪,所以 就寫個程序,批量裁剪吧。我直接給出核心代碼,比較簡單,就不廢話說明了

for (path, dirnames, filenames) in os.walk(input_dir):
    for filename in filenames:
        if filename.endswith('.jpg'):
            print('正在處理第 %s 張圖片' % index)
            img_path = path + '/' + filename
            print(img_path)
            img = cv.imdecode(np.fromfile(img_path,dtype=np.uint8),-1)
            new_img = cv.resize(img, (width, height))
            imwritedir = output_dir + '/' + str(499+index) + '.jpg'
            print(imwritedir)
            cv.imwrite(imwritedir, new_img)
            index += 1
            key = cv.waitKey(30) & 0xff
            if key == 27:
                sys.exit(0)

用過opencv的應該知道,讀取中文路徑會報錯,怎麼解決呢,也很簡單

cv.imdecode(np.fromfile(img_path,dtype=np.uint8),-1)

用這個代替imread 完美!

做些優化

由於部分照片的問題,背景太花,所以做些調整,就是先把人臉裁剪出來。
這裏就是用到上篇博客基於OpenCV的人臉及笑臉檢測
就是檢測出來,保存人臉尺寸照片

寫在後面

計時結束:2:58:56!

哈哈哈,小凱又輸了,收錢去!

憑良心說,這一篇博文乾貨滿滿。

簡單的總結:

1.OpenCV中文路徑解決方法

2.批量進行圖片的簡單裁剪

3.解析PCA算法原理

4.Python程序實現

歡迎關注:禪墨雲

公衆號:在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章