使用卷積神經(jīng)網(wǎng)絡(luò)(CNN)做人臉識(shí)別的示例代碼
上回書說(shuō)到了對(duì)人臉的檢測(cè),這回就開始正式進(jìn)入人臉識(shí)別的階段。
關(guān)于人臉識(shí)別,目前有很多經(jīng)典的算法,當(dāng)我大學(xué)時(shí)代,我的老師給我推薦的第一個(gè)算法是特征臉?lè)?,原理是先將圖像灰度化,然后將圖像每行首尾相接拉成一個(gè)列向量,接下來(lái)為了降低運(yùn)算量要用PCA降維, 最后進(jìn)分類器分類,可以使用KNN、SVM、神經(jīng)網(wǎng)絡(luò)等等,甚至可以用最簡(jiǎn)單的歐氏距離來(lái)度量每個(gè)列向量之間的相似度。OpenCV中也提供了相應(yīng)的EigenFaceRecognizer庫(kù)來(lái)實(shí)現(xiàn)該算法,除此之外還有FisherFaceRecognizer、LBPHFaceRecognizer以及最近幾年興起的卷積神經(jīng)網(wǎng)絡(luò)等。
卷積神經(jīng)網(wǎng)絡(luò)(CNN)的前級(jí)包含了卷積和池化操作,可以實(shí)現(xiàn)圖片的特征提取和降維,最近幾年由于計(jì)算機(jī)算力的提升,很多人都開始轉(zhuǎn)向這個(gè)方向,所以我這次打算使用它來(lái)試試效果。
老規(guī)矩,先配置下編程的環(huán)境:
- 系統(tǒng):windows / linux
- 解釋器:python 3.6
- 依賴庫(kù):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
上次文章有位讀者評(píng)論說(shuō):
所以,為了照顧初學(xué)者,這里簡(jiǎn)單介紹下Anaconda的安裝方法,Anaconda是一個(gè)開源的Python發(fā)行版本,其包含了Conda、Python等180多個(gè)科學(xué)包及其依賴項(xiàng)。因?yàn)榘舜罅康目茖W(xué)包,Anaconda 的下載文件比較大,所以有python包安裝基礎(chǔ)的人還是建議通過(guò)pip來(lái)安裝所需的依賴。
首先進(jìn)入Anaconda下載頁(yè)(https://www.anaconda.com/download/):
這里根據(jù)自己的電腦系統(tǒng)來(lái)選擇相應(yīng)的系統(tǒng)選項(xiàng),至于是64位還是32位要根據(jù)自己電腦的內(nèi)存大小和系統(tǒng)位數(shù)來(lái)選擇,python版本選擇3.6。
下載完成安裝,打開程序,切換左側(cè)菜單到Environment,選擇all,輸入想要安裝的模塊并搜索,選中后點(diǎn)擊右下角的Apply就開始安裝了。
基本思路:
我的設(shè)計(jì)思路是這樣的,先用上節(jié)講到的人臉檢測(cè)方法來(lái)檢測(cè)出人臉位置,然后根據(jù)返回的坐標(biāo)、尺寸把臉用數(shù)組切片的方法截取下來(lái),然后把截取的小圖片送進(jìn)訓(xùn)練好的卷積神經(jīng)網(wǎng)絡(luò)模型,得出人臉的分類結(jié)果,最后在原圖片上打上包圍框并且把結(jié)果寫在包圍框的上端:
原諒我拙劣的繪畫技巧
當(dāng)然了,實(shí)現(xiàn)這一步驟的前提就是要有一個(gè)訓(xùn)練好的可以做人臉識(shí)別的模型,所以本文的主要內(nèi)容都會(huì)放在訓(xùn)練上面。
深度學(xué)習(xí)框架的選擇:
卷積神經(jīng)網(wǎng)絡(luò)是深度學(xué)習(xí)在圖像方面的應(yīng)用,所以最高效的方法就是選擇合適的深度學(xué)習(xí)框架來(lái)實(shí)現(xiàn)它,現(xiàn)在市面上有很多深度學(xué)習(xí)框架可供選擇, 比如基于 C++ 的 Caffe 、基于 Python 的TensorFlow、Pytorch、Theano、CNTK 以及前兩天一個(gè)好友提到的她正在用來(lái)做推薦算法的 MXNET 。
這些都是搭建深度學(xué)習(xí)框架不錯(cuò)的選擇,不過(guò)搭建的步驟會(huì)比較繁瑣,會(huì)讓很多初學(xué)者瞬間放棄,還好世界上出現(xiàn)了Keras,它可以使用TensorFlow、Theano、CNTK作為后端運(yùn)算引擎,提供了高層的,更易于使用的函數(shù),可以讓不太了解深度學(xué)習(xí)原理的人也能快速上手,用通俗的話說(shuō)就是:“ Keras是為人類而不是天頂星人設(shè)計(jì)的API ”。
本文所使用后端運(yùn)算引擎為TensorFlow,簡(jiǎn)稱 TF (掏糞)。
人臉收集:
我的目的是希望在很多人中可以識(shí)別出自己的臉,所以對(duì)這個(gè)系統(tǒng)的要求是:
- 不能把別人識(shí)別成我
- 要能在我出現(xiàn)的時(shí)候識(shí)別出我
于是我需要自己的一些圖照片,來(lái)教會(huì)神經(jīng)網(wǎng)絡(luò),這個(gè)就是我,以及一堆其他人的照片來(lái)告訴它,這些不是我,或者說(shuō)這些人分別是誰(shuí)。
現(xiàn)在需要去采集一些其他人的圖片,這些數(shù)據(jù)集可以自己用相機(jī)照、或者寫個(gè)爬蟲腳本去網(wǎng)上爬,不過(guò)由于人臉識(shí)別早在幾十年前就一直有前輩在研究,很多大學(xué)和研究機(jī)構(gòu)都采集并公布了一些人臉數(shù)據(jù)集專門用作圖像識(shí)別算法的研究和驗(yàn)證用,像耶魯大學(xué)的Yale人臉庫(kù),劍橋大學(xué)的ORL人臉庫(kù)以及美國(guó)國(guó)防部的FERET人臉庫(kù)等,我在這里用了耶魯大學(xué)的Yale人臉庫(kù),里面包含15個(gè)人,每人11張照片,主要包括光照條件的變化,表情的變化,接下來(lái)我會(huì)把自己的幾張照片混進(jìn)去,看看訓(xùn)練過(guò)后能不能被神經(jīng)網(wǎng)絡(luò)良好的識(shí)別。
頭像提?。?/strong>
提取自己照片使用的是上篇文章提到的方法:
獲取文件夾下所有圖片文件 -> 檢測(cè)人臉位置 -> 根據(jù)人臉位置及尺寸剪裁出人臉 -> 保存。
這是我的目錄結(jié)構(gòu):
代碼:
# _*_ coding:utf-8 _*_ import cv2 import os CASE_PATH = "haarcascade_frontalface_default.xml" RAW_IMAGE_DIR = 'me/' DATASET_DIR = 'jm/' face_cascade = cv2.CascadeClassifier(CASE_PATH) def save_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 for image_path in image_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, y, width, height) in faces: save_feces(image, '%ss%d.bmp' % (DATASET_DIR, count), x, y - 30, width, height+30) count += 1
得到了還蠻不錯(cuò)的效果:
尺寸變換:
現(xiàn)在有了所有的圖片,可以開始訓(xùn)練了,不過(guò)Yale人臉庫(kù)里面所有照片都是100*100的尺寸,所以將要構(gòu)建的卷積神經(jīng)網(wǎng)絡(luò)的輸入就是100*100,而我新生成的圖片樣本形狀都是不規(guī)則的,為了使它可以順利進(jìn)入卷積層,第一步就要對(duì)圖片做尺寸變換,當(dāng)然不能暴力的resize成100*100,否則會(huì)引起圖片的變形,所以這里采用了一種數(shù)字圖像處理中常用的手段,就是將較短的一側(cè)涂黑,使它變成和目標(biāo)圖像相同的比例,然后再resize,這樣既可以保留原圖的人臉信息,又可以防止圖像形變:
def resize_without_deformation(image, size = (100, 100)): height, width, _ = image.shape longest_edge = max(height, width) top, bottom, left, right = 0, 0, 0, 0 if height < longest_edge: height_diff = longest_edge - height top = int(height_diff / 2) bottom = height_diff - top elif width < longest_edge: width_diff = longest_edge - width left = int(width_diff / 2) right = width_diff - left image_with_border = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value = [0, 0, 0]) resized_image = cv2.resize(image_with_border, size) return resized_image
調(diào)用了該函數(shù)出現(xiàn)了下面的效果:
下面是讀取照片的函數(shù),可以傳入尺寸,默認(rèn)尺寸是100*100,返回了兩個(gè)列表,第一個(gè)列表中每一個(gè)元素都是一張圖片,第二個(gè)列表中則對(duì)應(yīng)存儲(chǔ)了圖片的標(biāo)簽,這里用1、2、3.......來(lái)指代,因?yàn)槲腋静恢肋@些人的名字是什么:
def read_image(size = None): data_x, data_y = [], [] for i in range(1,177): try: im = cv2.imread('jm/s%s.bmp' % str(i)) #im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) if size is None: 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))) except IOError as e: print(e) except: print('Unknown Error!') return data_x, data_y
訓(xùn)練:
接下來(lái)就是最重要的一步了,訓(xùn)練卷積神經(jīng)網(wǎng)絡(luò),訓(xùn)練的好壞會(huì)直接影響識(shí)別的準(zhǔn)確度。
引進(jìn)卷積和池化層,卷積類似于圖像處理中的特征提取操作,池化則很類似于降維,常用的有最大池化和平均池化:
from keras.layers import Conv2D, MaxPooling2D
引入全連接層、Dropout、Flatten。
全連接層就是經(jīng)典的神經(jīng)網(wǎng)絡(luò)全連接。
Dropout用來(lái)在訓(xùn)練時(shí)按一定概率隨機(jī)丟棄一些神經(jīng)元,以獲得更高的訓(xùn)練速度以及防止過(guò)擬合。
Flatten用于卷積層與全連接層之間,把卷積輸出的多維數(shù)據(jù)拍扁成一維數(shù)據(jù)送進(jìn)全連接層(類似shape方法):
from keras.layers import Dense, Dropout, Flatten
引入SGD(梯度下降優(yōu)化器)來(lái)使損失函數(shù)最小化,常用的優(yōu)化器還有Adam:
from keras.optimizers import SGD
讀入所有圖像及標(biāo)簽:
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) #把圖像轉(zhuǎn)換為float類型,方便歸一化
神經(jīng)網(wǎng)絡(luò)需要數(shù)值進(jìn)行計(jì)算,需要對(duì)字符型類別標(biāo)簽進(jìn)行編碼,最容易想到的就是把他們編碼成1、2、3.......這種,但是這樣也就出現(xiàn)了強(qiáng)行給它們定義了大小的問(wèn)題,因?yàn)槿绻粋€(gè)類別是2,一個(gè)是4,他們之間就會(huì)有兩倍的關(guān)系,但是實(shí)際上他們之間并沒(méi)有直接的倍數(shù)關(guān)系,所以這里使用one-hot編碼規(guī)則,做到所有標(biāo)簽的平等化。on-hot編碼:
from keras.utils import np_utils ont_hot_labels = np_utils.to_categorical(raw_labels)
在所有讀入的圖像和標(biāo)簽中,需要?jiǎng)澐忠徊糠钟脕?lái)訓(xùn)練,一部分用來(lái)測(cè)試,這里使用了sklearn中的train_test_split方法,不僅可以分割數(shù)據(jù),還可以把數(shù)據(jù)打亂,訓(xùn)練集 :測(cè)試集 = 7 : 3 :
from sklearn.model_selection import train_test_split train_input, valid_input, train_output, valid_output =train_test_split(raw_images, ont_hot_labels, test_size = 0.3)
數(shù)據(jù)歸一化,圖像數(shù)據(jù)只需要每個(gè)像素除以255就可以:
train_input /= 255.0 valid_input /= 255.0
構(gòu)建卷積神經(jīng)網(wǎng)絡(luò)的每一層:
添加卷積層,32個(gè)卷積核,每個(gè)卷積核是3 * 3,邊緣不補(bǔ)充,卷積步長(zhǎng)向右、向下都為1, 后端運(yùn)算使用 tf , 圖片輸入尺寸是(100,100, 3),使用relu作為激活函數(shù),也可以用sigmoid函數(shù)等,relu收斂速度比較快:
face_recognition_model = keras.Sequential() face_recognition_model.add(Conv2D(32, 3, 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, 3, 3,border_mode='valid', subsample = (1, 1), dim_ordering = 'tf', activation = 'relu'))
池化層,過(guò)濾器尺寸是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, 3, 3, border_mode='valid', subsample = (1, 1), dim_ordering = 'tf', activation = 'relu')) face_recognition_model.add(Conv2D(64, 3, 3, border_mode='valid', subsample = (1, 1), dim_ordering = 'tf', activation = 'relu')) face_recognition_model.add(MaxPooling2D(pool_size=(2, 2))) face_recognition_model.add(Dropout(0.2))
Flatten層,處于卷積層與Dense(全連層)之間,將圖片的卷積輸出壓扁成一個(gè)一維向量:
face_recognition_model.add(Flatten())
全連接層, 經(jīng)典的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu),512個(gè)神經(jīng)元:
face_recognition_model.add(Dense(512, activation = 'relu'))
face_recognition_model.add(Dropout(0.4))
輸出層,神經(jīng)元數(shù)是標(biāo)簽種類數(shù),使用sigmoid激活函數(shù),輸出最終結(jié)果:
face_recognition_model.add(Dense(len(ont_hot_labels[0]), activation = 'sigmoid'))
有點(diǎn)不放心,把神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)打印出來(lái)看一下:
face_recognition_model.summary()
看起來(lái)沒(méi)什么問(wèn)題。
使用SGD作為反向傳播的優(yōu)化器,來(lái)使損失函數(shù)最小化,學(xué)習(xí)率(learning_rate)是0.01,學(xué)習(xí)率衰減因子(decay)用來(lái)隨著迭代次數(shù)不斷減小學(xué)習(xí)率,防止出現(xiàn)震蕩。引入沖量(momentum),不僅可以在學(xué)習(xí)率較小的時(shí)候加速學(xué)習(xí),又可以在學(xué)習(xí)率較大的時(shí)候減速,使用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)
編譯模型,損失函數(shù)使用交叉熵,交叉熵函數(shù)隨著輸出和期望的差距越來(lái)越大,輸出曲線會(huì)越來(lái)越陡峭,對(duì)權(quán)值的懲罰力度也會(huì)增大,如果其他的損失函數(shù),如均方差可以可以的,各有優(yōu)劣:
face_recognition_model.compile(loss = 'categorical_crossentropy', optimizer = sgd_optimizer, metrics = ['accuracy'])
開始訓(xùn)練,訓(xùn)練100次(epochs),每次訓(xùn)練分幾個(gè)批次,每批(batch_size)20個(gè),shuffle用來(lái)打亂樣本順序:
batch_size = 20 #每批訓(xùn)練數(shù)據(jù)量的大小 epochs = 100 face_recognition_model.fit(train_input, train_output, epochs = epochs, batch_size = batch_size, shuffle = True, validation_data = (valid_input, valid_output))
現(xiàn)在離開座位,找一個(gè)西瓜,慢慢吃,一定要慢,因?yàn)橛?xùn)練的時(shí)間著實(shí)太長(zhǎng),配上薯片會(huì)更好。
訓(xùn)練完成后在測(cè)試集上評(píng)估結(jié)果并保存模型供以后加載使用:
print(face_recognition_model.evaluate(valid_input, valid_output, verbose=0)) MODEL_PATH = 'face_model.h5' face_recognition_model.save(MODEL_PATH)
識(shí)別:
要開始寫在識(shí)別時(shí)正式運(yùn)行的程序了:
import cv2 import numpy as np import keras from keras.models import load_model
加載級(jí)聯(lián)分類器模型:
CASE_PATH = "haarcascade_frontalface_default.xml" face_cascade = cv2.CascadeClassifier(CASE_PATH)
加載卷積神經(jīng)網(wǎng)絡(luò)模型:
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)
人臉檢測(cè):
faces = faceCascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5, minSize=(30, 30),)
根據(jù)檢測(cè)到的坐標(biāo)及尺寸裁剪、無(wú)形變r(jià)esize、并送入模型運(yùn)算,得到結(jié)果后在人臉上打上矩形框并在矩形框上方寫上識(shí)別結(jié)果:
for (x, y, width, height) in faces: img = image[y:y+height, x:x+width] img = resize_without_deformation(img) img = img.reshape((1, 100, 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 if result[0] == 15: cv2.putText(image, 'kangChi', (x, y-2), font, 0.7, (0, 255, 0), 2) else: cv2.putText(image, 'No.%d' % result[0], (x, y-2), font, 0.7, (0, 255, 0), 2) cv2.imshow('', image) cv2.waitKey(0)
看效果:
當(dāng)然了,識(shí)別的效果還是取決于訓(xùn)練好的模型的質(zhì)量,我差不多用了吃2/3個(gè)西瓜的時(shí)間來(lái)訓(xùn)練,還是有一些誤識(shí)別的情況出現(xiàn):
總結(jié)了下這次的人臉識(shí)別系統(tǒng),感覺(jué)人臉檢測(cè)效果還需要改進(jìn),識(shí)別準(zhǔn)確度也有待提升,之后要多收集各個(gè)角度的照片樣本和改進(jìn)網(wǎng)絡(luò)參數(shù)。
到此這篇關(guān)于使用卷積神經(jīng)網(wǎng)絡(luò)(CNN)做人臉識(shí)別的示例代碼的文章就介紹到這了,更多相關(guān)卷積神經(jīng)網(wǎng)絡(luò)CNN 人臉識(shí)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家
- 卷積神經(jīng)網(wǎng)絡(luò)Inception?V3網(wǎng)絡(luò)結(jié)構(gòu)圖
- 使用pytorch提取卷積神經(jīng)網(wǎng)絡(luò)的特征圖可視化
- Python深度學(xué)習(xí)之實(shí)現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)
- Numpy實(shí)現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)(CNN)的示例
- TensorFlow keras卷積神經(jīng)網(wǎng)絡(luò) 添加L2正則化方式
- 卷積神經(jīng)網(wǎng)絡(luò)經(jīng)典模型及其改進(jìn)點(diǎn)學(xué)習(xí)匯總
相關(guān)文章
python+appium+yaml移動(dòng)端自動(dòng)化測(cè)試框架實(shí)現(xiàn)詳解
這篇文章主要介紹了python+appium+yaml移動(dòng)端自動(dòng)化測(cè)試框架實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11python中如何實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用
這篇文章主要介紹了python中如何實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下2021-03-03Python3.6日志Logging模塊簡(jiǎn)單用法示例
這篇文章主要介紹了Python3.6日志Logging模塊簡(jiǎn)單用法,結(jié)合實(shí)例形式分析了Python3.6環(huán)境下日志Logging模塊設(shè)置格式、文件流輸出相關(guān)操作技巧,需要的朋友可以參考下2018-06-06Python學(xué)習(xí)之線程池與GIL全局鎖詳解
本文我們將學(xué)習(xí)線程池的創(chuàng)建與全局鎖。線程池的創(chuàng)建于進(jìn)程池的原理是相同的;關(guān)于GIL全局鎖,暫時(shí)沒(méi)有代碼上的練習(xí),而是對(duì)其概念進(jìn)行一個(gè)簡(jiǎn)單的啟蒙,感興趣的可以了解一下2022-04-04