1. 程式人生 > 程式設計 >使用卷積神經網路(CNN)做人臉識別的示例程式碼

使用卷積神經網路(CNN)做人臉識別的示例程式碼

上回書說到了對人臉的檢測,這回就開始正式進入人臉識別的階段。

關於人臉識別,目前有很多經典的演算法,當我大學時代,我的老師給我推薦的第一個演算法是特徵臉法,原理是先將影象灰度化,然後將影象每行首尾相接拉成一個列向量,接下來為了降低運算量要用PCA降維, 最後進分類器分類,可以使用KNN、SVM、神經網路等等,甚至可以用最簡單的歐氏距離來度量每個列向量之間的相似度。OpenCV中也提供了相應的EigenFaceRecognizer庫來實現該演算法,除此之外還有FisherFaceRecognizer、LBPHFaceRecognizer以及最近幾年興起的卷積神經網路等。

卷積神經網路(CNN)的前級包含了卷積和池化操作,可以實現圖片的特徵提取和降維,最近幾年由於計算機算力的提升,很多人都開始轉向這個方向,所以我這次打算使用它來試試效果。

老規矩,先配置下程式設計的環境:

  • 系統:windows / linux
  • 直譯器:python 3.6
  • 依賴庫:numpy、opencv-python 3、tensorflow、keras、scikit-learn
pip3 install numpy
pip3 install opencv-python
pip3 install keras
pip3 install scikit-learn
pip3 install tensorflow

如果手中有一塊支援Cuda加速的GPU建議安裝GPU版本:

pip3 install tensorflow-gpu

上次文章有位讀者評論說:

使用卷積神經網路(CNN)做人臉識別的示例程式碼

所以,為了照顧初學者,這裡簡單介紹下Anaconda的安裝方法,Anaconda是一個開源的Python發行版本,其包含了Conda、Python等180多個科學包及其依賴項。因為包含了大量的科學包,Anaconda 的下載檔案比較大,所以有python包安裝基礎的人還是建議通過pip來安裝所需的依賴。

首先進入Anaconda下載頁(https://www.anaconda.com/download/):

使用卷積神經網路(CNN)做人臉識別的示例程式碼

這裡根據自己的電腦系統來選擇相應的系統選項,至於是64位還是32位要根據自己電腦的記憶體大小和系統位數來選擇,python版本選擇3.6。

下載完成安裝,開啟程式,切換左側選單到Environment,選擇all,輸入想要安裝的模組並搜尋,選中後點擊右下角的Apply就開始安裝了。

使用卷積神經網路(CNN)做人臉識別的示例程式碼

基本思路:

我的設計思路是這樣的,先用上節講到的人臉檢測方法來檢測出人臉位置,然後根據返回的座標、尺寸把臉用陣列切片的方法擷取下來,然後把擷取的小圖片送進訓練好的卷積神經網路模型,得出人臉的分類結果,最後在原圖片上打上包圍框並且把結果寫在包圍框的上端:

使用卷積神經網路(CNN)做人臉識別的示例程式碼

原諒我拙劣的繪畫技巧

當然了,實現這一步驟的前提就是要有一個訓練好的可以做人臉識別的模型,所以本文的主要內容都會放在訓練上面。

深度學習框架的選擇:

卷積神經網路是深度學習在影象方面的應用,所以最高效的方法就是選擇合適的深度學習框架來實現它,現在市面上有很多深度學習框架可供選擇, 比如基於 C++ 的Caffe 、基於 Python 的TensorFlow、Pytorch、Theano、CNTK以及前兩天一個好友提到的她正在用來做推薦演算法的MXNET。

使用卷積神經網路(CNN)做人臉識別的示例程式碼

這些都是搭建深度學習框架不錯的選擇,不過搭建的步驟會比較繁瑣,會讓很多初學者瞬間放棄,還好世界上出現了Keras,它可以使用TensorFlow、Theano、CNTK作為後端運算引擎,提供了高層的,更易於使用的函式,可以讓不太瞭解深度學習原理的人也能快速上手,用通俗的話說就是:“ Keras是為人類而不是天頂星人設計的API ”。

使用卷積神經網路(CNN)做人臉識別的示例程式碼

本文所使用後端運算引擎為TensorFlow,簡稱 TF (掏糞)。

使用卷積神經網路(CNN)做人臉識別的示例程式碼

人臉收集:

我的目的是希望在很多人中可以識別出自己的臉,所以對這個系統的要求是:

  • 不能把別人識別成我
  • 要能在我出現的時候識別出我

使用卷積神經網路(CNN)做人臉識別的示例程式碼

於是我需要自己的一些圖照片,來教會神經網路,這個就是我,以及一堆其他人的照片來告訴它,這些不是我,或者說這些人分別是誰。

現在需要去採集一些其他人的圖片,這些資料集可以自己用相機照、或者寫個爬蟲指令碼去網上爬,不過由於人臉識別早在幾十年前就一直有前輩在研究,很多大學和研究機構都採集並公佈了一些人臉資料集專門用作影象識別演算法的研究和驗證用,像耶魯大學的Yale人臉庫,劍橋大學的ORL人臉庫以及美國國防部的FERET人臉庫等,我在這裡用了耶魯大學的Yale人臉庫,裡面包含15個人,每人11張照片,主要包括光照條件的變化,表情的變化,接下來我會把自己的幾張照片混進去,看看訓練過後能不能被神經網路良好的識別。

頭像提取:

提取自己照片使用的是上篇文章提到的方法:

獲取資料夾下所有圖片檔案 -> 檢測人臉位置 -> 根據人臉位置及尺寸剪裁出人臉 -> 儲存。

這是我的目錄結構:

使用卷積神經網路(CNN)做人臉識別的示例程式碼

程式碼:

#_*_coding:utf-8_*_
importcv2
importos

CASE_PATH="haarcascade_frontalface_default.xml"
RAW_IMAGE_DIR='me/'
DATASET_DIR='jm/'

face_cascade=cv2.CascadeClassifier(CASE_PATH)

defsave_feces(img,name,x,y,width,height):
image=img[y:y+height,x:x+width]
cv2.imwrite(name,image)

image_list=os.listdir(RAW_IMAGE_DIR)#列出資料夾下所有的目錄與檔案
count=166
forimage_pathinimage_list:
image=cv2.imread(RAW_IMAGE_DIR+image_path)
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
faces=face_cascade.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=5,minSize=(5,5),)
for(x,height)infaces:
save_feces(image,'%ss%d.bmp'%(DATASET_DIR,count),y-30,height+30)
count+=1

得到了還蠻不錯的效果:

使用卷積神經網路(CNN)做人臉識別的示例程式碼

尺寸變換:

現在有了所有的圖片,可以開始訓練了,不過Yale人臉庫裡面所有照片都是100*100的尺寸,所以將要構建的卷積神經網路的輸入就是100*100,而我新生成的圖片樣本形狀都是不規則的,為了使它可以順利進入卷積層,第一步就要對圖片做尺寸變換,當然不能暴力的resize成100*100,否則會引起圖片的變形,所以這裡採用了一種數字影象處理中常用的手段,就是將較短的一側塗黑,使它變成和目標影象相同的比例,然後再resize,這樣既可以保留原圖的人臉資訊,又可以防止影象形變:

defresize_without_deformation(image,size=(100,100)):
height,_=image.shape
longest_edge=max(height,width)
top,bottom,left,right=0,0
ifheight<longest_edge:
height_diff=longest_edge-height
top=int(height_diff/2)
bottom=height_diff-top
elifwidth<longest_edge:
width_diff=longest_edge-width
left=int(width_diff/2)
right=width_diff-left

image_with_border=cv2.copyMakeBorder(image,top,right,cv2.BORDER_CONSTANT,value = [0,0])

resized_image=cv2.resize(image_with_border,size)

returnresized_image

呼叫了該函數出現了下面的效果:

使用卷積神經網路(CNN)做人臉識別的示例程式碼

下面是讀取照片的函式,可以傳入尺寸,預設尺寸是100*100,返回了兩個列表,第一個列表中每一個元素都是一張圖片,第二個列表中則對應儲存了圖片的標籤,這裡用1、2、3.......來指代,因為我根本不知道這些人的名字是什麼:

defread_image(size=None):
data_x,data_y=[],[]
foriinrange(1,177):
try:
im=cv2.imread('jm/s%s.bmp'%str(i))
#im=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ifsizeisNone:
size=(100,100)
im=resize_without_deformation(im,size)
data_x.append(np.asarray(im,dtype=np.int8))
data_y.append(str(int((i-1)/11.0)))
exceptIOErrorase:
print(e)
except:
print('UnknownError!')

returndata_x,data_y

訓練:

接下來就是最重要的一步了,訓練卷積神經網路,訓練的好壞會直接影響識別的準確度。

引進卷積和池化層,卷積類似於影象處理中的特徵提取操作,池化則很類似於降維,常用的有最大池化和平均池化:

from keras.layers import Conv2D,MaxPooling2D

引入全連線層、Dropout、Flatten。

全連線層就是經典的神經網路全連線。

Dropout用來在訓練時按一定概率隨機丟棄一些神經元,以獲得更高的訓練速度以及防止過擬合。

Flatten用於卷積層與全連線層之間,把卷積輸出的多維資料拍扁成一維資料送進全連線層(類似shape方法):

from keras.layers import Dense,Dropout,Flatten

引入SGD(梯度下降優化器)來使損失函式最小化,常用的優化器還有Adam:

from keras.optimizers import SGD

讀入所有影象及標籤:

IMAGE_SIZE=100
raw_images,raw_labels=read_image(size=(IMAGE_SIZE,IMAGE_SIZE))
raw_images,raw_labels=np.asarray(raw_images,dtype=np.float32),np.asarray(raw_labels,dtype=np.int32)#把影象轉換為float型別,方便歸一化

神經網路需要數值進行計算,需要對字元型類別標籤進行編碼,最容易想到的就是把他們編碼成1、2、3.......這種,但是這樣也就出現了強行給它們定義了大小的問題,因為如果一個類別是2,一個是4,他們之間就會有兩倍的關係,但是實際上他們之間並沒有直接的倍數關係,所以這裡使用one-hot編碼規則,做到所有標籤的平等化。on-hot編碼:

from keras.utils import np_utils
ont_hot_labels = np_utils.to_categorical(raw_labels)

在所有讀入的影象和標籤中,需要劃分一部分用來訓練,一部分用來測試,這裡使用了sklearn中的train_test_split方法,不僅可以分割資料,還可以把資料打亂,訓練集 :測試集 = 7 : 3 :

fromsklearn.model_selectionimporttrain_test_split
train_input,valid_input,train_output,valid_output=train_test_split(raw_images,ont_hot_labels,test_size=0.3)

資料歸一化,影象資料只需要每個畫素除以255就可以:

train_input /= 255.0
valid_input /= 255.0

構建卷積神經網路的每一層:

添加捲積層,32個卷積核,每個卷積核是3 * 3,邊緣不補充,卷積步長向右、向下都為1,後端運算使用 tf,圖片輸入尺寸是(100,100, 3),使用relu作為啟用函式,也可以用sigmoid函式等,relu收斂速度比較快:

face_recognition_model=keras.Sequential()
 
face_recognition_model.add(Conv2D(32,3,border_mode='valid',subsample=(1,1),dim_ordering='tf',input_shape=(IMAGE_SIZE,IMAGE_SIZE,3),activation='relu'))
 
face_recognition_model.add(Conv2D(32,activation='relu'))

池化層,過濾器尺寸是2 * 2:

face_recognition_model.add(MaxPooling2D(pool_size=(2,2)))

Dropout層:

face_recognition_model.add(Dropout(0.2))
face_recognition_model.add(Conv2D(64,activation='relu'))
 
face_recognition_model.add(Conv2D(64,activation='relu'))
 
face_recognition_model.add(MaxPooling2D(pool_size=(2,2)))
face_recognition_model.add(Dropout(0.2))

Flatten層,處於卷積層與Dense(全連層)之間,將圖片的卷積輸出壓扁成一個一維向量:

face_recognition_model.add(Flatten())

全連線層,經典的神經網路結構,512個神經元:

face_recognition_model.add(Dense(512,activation = 'relu'))
face_recognition_model.add(Dropout(0.4))

輸出層,神經元數是標籤種類數,使用sigmoid啟用函式,輸出最終結果:

face_recognition_model.add(Dense(len(ont_hot_labels[0]),activation = 'sigmoid'))

有點不放心,把神經網路結構打印出來看一下:

face_recognition_model.summary()

看起來沒什麼問題。

使用SGD作為反向傳播的優化器,來使損失函式最小化,學習率(learning_rate)是0.01,學習率衰減因子(decay)用來隨著迭代次數不斷減小學習率,防止出現震盪。引入衝量(momentum),不僅可以在學習率較小的時候加速學習,又可以在學習率較大的時候減速,使用nesterov:

learning_rate=0.01
decay=1e-6
momentum=0.8
nesterov=True
sgd_optimizer=SGD(lr=learning_rate,decay=decay,momentum=momentum,nesterov=nesterov)

編譯模型,損失函式使用交叉熵,交叉熵函式隨著輸出和期望的差距越來越大,輸出曲線會越來越陡峭,對權值的懲罰力度也會增大,如果其他的損失函式,如均方差可以可以的,各有優劣:

face_recognition_model.compile(loss='categorical_crossentropy',optimizer=sgd_optimizer,metrics=['accuracy'])

開始訓練,訓練100次(epochs),每次訓練分幾個批次,每批(batch_size)20個,shuffle用來打亂樣本順序:

batch_size=20#每批訓練資料量的大小
epochs=100
face_recognition_model.fit(train_input,epochs=epochs,batch_size=batch_size,shuffle=True,validation_data=(valid_input,valid_output))

現在離開座位,找一個西瓜,慢慢吃,一定要慢,因為訓練的時間著實太長,配上薯片會更好。

訓練完成後在測試集上評估結果並儲存模型供以後載入使用:

print(face_recognition_model.evaluate(valid_input,valid_output,verbose=0))
MODEL_PATH='face_model.h5'
face_recognition_model.save(MODEL_PATH)

識別:

要開始寫在識別時正式執行的程式了:

import cv2
import numpy as np
import keras
from keras.models import load_model

載入級聯分類器模型:

CASE_PATH = "haarcascade_frontalface_default.xml"
face_cascade = cv2.CascadeClassifier(CASE_PATH)

載入卷積神經網路模型:

face_recognition_model = keras.Sequential()
MODEL_PATH = 'face_model.h5'
face_recognition_model = load_model(MODEL_PATH)

開啟攝像頭,獲取圖片並灰度化:

cap = cv2.VideoCapture(0) 
ret,image = cap.read()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)

人臉檢測:

faces = faceCascade.detectMultiScale(gray,minSize=(30,30),)

根據檢測到的座標及尺寸裁剪、無形變resize、並送入模型運算,得到結果後在人臉上打上矩形框並在矩形框上方寫上識別結果:

for(x,height)infaces:
img=image[y:y+height,x:x+width]
img=resize_without_deformation(img)
 
img=img.reshape((1,100,3))
img=np.asarray(img,dtype=np.float32)
img/=255.0
 
result=face_recognition_model.predict_classes(img)
 
cv2.rectangle(image,(x,y),(x+width,y+height),(0,255,0),2)
font=cv2.FONT_HERSHEY_SIMPLEX
ifresult[0]==15:
cv2.putText(image,'kangChi',y-2),font,0.7,2)
else:
cv2.putText(image,'No.%d'%result[0],2)

cv2.imshow('',image)
cv2.waitKey(0)

看效果:

使用卷積神經網路(CNN)做人臉識別的示例程式碼

當然了,識別的效果還是取決於訓練好的模型的質量,我差不多用了吃2/3個西瓜的時間來訓練,還是有一些誤識別的情況出現:

使用卷積神經網路(CNN)做人臉識別的示例程式碼

總結了下這次的人臉識別系統,感覺人臉檢測效果還需要改進,識別準確度也有待提升,之後要多收集各個角度的照片樣本和改進網路引數。

到此這篇關於使用卷積神經網路(CNN)做人臉識別的示例程式碼的文章就介紹到這了,更多相關卷積神經網路CNN 人臉識別內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們