您好,登錄后才能下訂單哦!
使用卷積神經(jīng)網(wǎng)絡(luò)怎么實現(xiàn)人臉識別?相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
卷積神經(jīng)網(wǎng)絡(luò)(CNN)的前級包含了卷積和池化操作,可以實現(xiàn)圖片的特征提取和降維,最近幾年由于計算機算力的提升,很多人都開始轉(zhuǎn)向這個方向,所以我這次打算使用它來試試效果。
老規(guī)矩,先配置下編程的環(huán)境:
系統(tǒng):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
上次文章有位讀者評論說:
所以,為了照顧初學(xué)者,這里簡單介紹下Anaconda的安裝方法,Anaconda是一個開源的Python發(fā)行版本,其包含了Conda、Python等180多個科學(xué)包及其依賴項。因為包含了大量的科學(xué)包,Anaconda 的下載文件比較大,所以有python包安裝基礎(chǔ)的人還是建議通過pip來安裝所需的依賴。
首先進入Anaconda下載頁(https://www.anaconda.com/download/):
這里根據(jù)自己的電腦系統(tǒng)來選擇相應(yīng)的系統(tǒng)選項,至于是64位還是32位要根據(jù)自己電腦的內(nèi)存大小和系統(tǒng)位數(shù)來選擇,python版本選擇3.6。
下載完成安裝,打開程序,切換左側(cè)菜單到Environment,選擇all,輸入想要安裝的模塊并搜索,選中后點擊右下角的Apply就開始安裝了。
基本思路:
我的設(shè)計思路是這樣的,先用上節(jié)講到的人臉檢測方法來檢測出人臉位置,然后根據(jù)返回的坐標(biāo)、尺寸把臉用數(shù)組切片的方法截取下來,然后把截取的小圖片送進訓(xùn)練好的卷積神經(jīng)網(wǎng)絡(luò)模型,得出人臉的分類結(jié)果,最后在原圖片上打上包圍框并且把結(jié)果寫在包圍框的上端:
原諒我拙劣的繪畫技巧
當(dāng)然了,實現(xiàn)這一步驟的前提就是要有一個訓(xùn)練好的可以做人臉識別的模型,所以本文的主要內(nèi)容都會放在訓(xùn)練上面。
深度學(xué)習(xí)框架的選擇:
卷積神經(jīng)網(wǎng)絡(luò)是深度學(xué)習(xí)在圖像方面的應(yīng)用,所以最高效的方法就是選擇合適的深度學(xué)習(xí)框架來實現(xiàn)它,現(xiàn)在市面上有很多深度學(xué)習(xí)框架可供選擇, 比如基于 C++ 的 Caffe 、基于 Python 的TensorFlow、Pytorch、Theano、CNTK 以及前兩天一個好友提到的她正在用來做推薦算法的 MXNET 。
這些都是搭建深度學(xué)習(xí)框架不錯的選擇,不過搭建的步驟會比較繁瑣,會讓很多初學(xué)者瞬間放棄,還好世界上出現(xiàn)了Keras,它可以使用TensorFlow、Theano、CNTK作為后端運算引擎,提供了高層的,更易于使用的函數(shù),可以讓不太了解深度學(xué)習(xí)原理的人也能快速上手,用通俗的話說就是:“ Keras是為人類而不是天頂星人設(shè)計的API ”。
本文所使用后端運算引擎為TensorFlow,簡稱 TF (掏糞)。
人臉收集:
我的目的是希望在很多人中可以識別出自己的臉,所以對這個系統(tǒng)的要求是:
不能把別人識別成我
要能在我出現(xiàn)的時候識別出我
于是我需要自己的一些圖照片,來教會神經(jīng)網(wǎng)絡(luò),這個就是我,以及一堆其他人的照片來告訴它,這些不是我,或者說這些人分別是誰。
現(xiàn)在需要去采集一些其他人的圖片,這些數(shù)據(jù)集可以自己用相機照、或者寫個爬蟲腳本去網(wǎng)上爬,不過由于人臉識別早在幾十年前就一直有前輩在研究,很多大學(xué)和研究機構(gòu)都采集并公布了一些人臉數(shù)據(jù)集專門用作圖像識別算法的研究和驗證用,像耶魯大學(xué)的Yale人臉庫,劍橋大學(xué)的ORL人臉庫以及美國國防部的FERET人臉庫等,我在這里用了耶魯大學(xué)的Yale人臉庫,里面包含15個人,每人11張照片,主要包括光照條件的變化,表情的變化,接下來我會把自己的幾張照片混進去,看看訓(xùn)練過后能不能被神經(jīng)網(wǎng)絡(luò)良好的識別。
頭像提?。?/strong>
提取自己照片使用的是上篇文章提到的方法:
獲取文件夾下所有圖片文件 -> 檢測人臉位置 -> 根據(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
得到了還蠻不錯的效果:
尺寸變換:
現(xiàn)在有了所有的圖片,可以開始訓(xùn)練了,不過Yale人臉庫里面所有照片都是100*100的尺寸,所以將要構(gòu)建的卷積神經(jīng)網(wǎng)絡(luò)的輸入就是100*100,而我新生成的圖片樣本形狀都是不規(guī)則的,為了使它可以順利進入卷積層,第一步就要對圖片做尺寸變換,當(dāng)然不能暴力的resize成100*100,否則會引起圖片的變形,所以這里采用了一種數(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,返回了兩個列表,第一個列表中每一個元素都是一張圖片,第二個列表中則對應(yīng)存儲了圖片的標(biāo)簽,這里用1、2、3.......來指代,因為我根本不知道這些人的名字是什么:
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)練:
接下來就是最重要的一步了,訓(xùn)練卷積神經(jīng)網(wǎng)絡(luò),訓(xùn)練的好壞會直接影響識別的準(zhǔn)確度。
引進卷積和池化層,卷積類似于圖像處理中的特征提取操作,池化則很類似于降維,常用的有最大池化和平均池化:
from keras.layers import Conv2D, MaxPooling2D
引入全連接層、Dropout、Flatten。
全連接層就是經(jīng)典的神經(jīng)網(wǎng)絡(luò)全連接。
Dropout用來在訓(xùn)練時按一定概率隨機丟棄一些神經(jīng)元,以獲得更高的訓(xùn)練速度以及防止過擬合。
Flatten用于卷積層與全連接層之間,把卷積輸出的多維數(shù)據(jù)拍扁成一維數(shù)據(jù)送進全連接層(類似shape方法):
from keras.layers import Dense, Dropout, Flatten
引入SGD(梯度下降優(yōu)化器)來使損失函數(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ù)值進行計算,需要對字符型類別標(biāo)簽進行編碼,最容易想到的就是把他們編碼成1、2、3.......這種,但是這樣也就出現(xiàn)了強行給它們定義了大小的問題,因為如果一個類別是2,一個是4,他們之間就會有兩倍的關(guān)系,但是實際上他們之間并沒有直接的倍數(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)簽中,需要劃分一部分用來訓(xùn)練,一部分用來測試,這里使用了sklearn中的train_test_split方法,不僅可以分割數(shù)據(jù),還可以把數(shù)據(jù)打亂,訓(xùn)練集 :測試集 = 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ù)只需要每個像素除以255就可以:
train_input /= 255.0 valid_input /= 255.0
構(gòu)建卷積神經(jīng)網(wǎng)絡(luò)的每一層:
添加卷積層,32個卷積核,每個卷積核是3 * 3,邊緣不補充,卷積步長向右、向下都為1, 后端運算使用 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'))
池化層,過濾器尺寸是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(全連層)之間,將圖片的卷積輸出壓扁成一個一維向量:
face_recognition_model.add(Flatten())
全連接層, 經(jīng)典的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu),512個神經(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'))
有點不放心,把神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)打印出來看一下:
face_recognition_model.summary()
看起來沒什么問題。
使用SGD作為反向傳播的優(yōu)化器,來使損失函數(shù)最小化,學(xué)習(xí)率(learning_rate)是0.01,學(xué)習(xí)率衰減因子(decay)用來隨著迭代次數(shù)不斷減小學(xué)習(xí)率,防止出現(xiàn)震蕩。引入沖量(momentum),不僅可以在學(xué)習(xí)率較小的時候加速學(xué)習(xí),又可以在學(xué)習(xí)率較大的時候減速,使用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ù)隨著輸出和期望的差距越來越大,輸出曲線會越來越陡峭,對權(quán)值的懲罰力度也會增大,如果其他的損失函數(shù),如均方差可以可以的,各有優(yōu)劣:
face_recognition_model.compile(loss = 'categorical_crossentropy', optimizer = sgd_optimizer, metrics = ['accuracy'])
開始訓(xùn)練,訓(xùn)練100次(epochs),每次訓(xùn)練分幾個批次,每批(batch_size)20個,shuffle用來打亂樣本順序:
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)在離開座位,找一個西瓜,慢慢吃,一定要慢,因為訓(xùn)練的時間著實太長,配上薯片會更好。
訓(xùn)練完成后在測試集上評估結(jié)果并保存模型供以后加載使用:
print(face_recognition_model.evaluate(valid_input, valid_output, verbose=0)) MODEL_PATH = 'face_model.h6' face_recognition_model.save(MODEL_PATH)
識別:
要開始寫在識別時正式運行的程序了:
import cv2 import numpy as np import keras from keras.models import load_model
加載級聯(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.h6' 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, scaleFactor=1.2, minNeighbors=5, minSize=(30, 30),)
根據(jù)檢測到的坐標(biāo)及尺寸裁剪、無形變resize、并送入模型運算,得到結(jié)果后在人臉上打上矩形框并在矩形框上方寫上識別結(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)然了,識別的效果還是取決于訓(xùn)練好的模型的質(zhì)量,我差不多用了吃2/3個西瓜的時間來訓(xùn)練,還是有一些誤識別的情況出現(xiàn):
看完上述內(nèi)容,你們掌握使用卷積神經(jīng)網(wǎng)絡(luò)怎么實現(xiàn)人臉識別的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。