解讀卷積神經(jīng)網(wǎng)絡(luò)的人臉識別
作為一個小白,用人臉識別上手了一下,收獲還是挺大的。
使用環(huán)境
- python3.7
- tensorflow 2.2.0
- opencv-python 4.4.0.40
- Keras 2.4.3
- numpy 1.18.5,具體安裝過程以及環(huán)境搭建省略,可借鑒網(wǎng)上。
具體目標(biāo)
通過卷積神經(jīng)網(wǎng)絡(luò)訓(xùn)練自己數(shù)據(jù)并能成功識別自己
實(shí)現(xiàn)步驟如下圖所示

1. 人臉數(shù)據(jù)采集與讀取
1.1 數(shù)據(jù)采集
本數(shù)據(jù)集使用opencv打開攝像頭,從攝像頭當(dāng)中采集圖片信息以及外來的某些人圖片,共采集5個人的信息(這里沒有直接從攝像頭裁剪臉部信息是為了方便外部采集的圖片進(jìn)行處理,每個人圖片為800張,本數(shù)據(jù)集把采取到每個人的圖片以名字縮寫開始放在同一個文件夾里面。)
相關(guān)代碼如下:
import os
import cv2
import time
from PIL import Image
#只實(shí)現(xiàn)截屏的功能
global path
path='./images/'
#人臉采樣,封裝函數(shù)
def cy(path):
#path為保存圖片的路徑
#調(diào)用筆記本內(nèi)置攝像頭,參數(shù)為0,如果有其他的攝像頭可以調(diào)整參數(shù)為1,2
cap = cv2.VideoCapture(0)
#為即將錄入的臉標(biāo)記一個id
face_id = input('\n 用戶臉部信息錄入,輸入用戶名字(最好用英文):\n')
#sampleNum用來計(jì)數(shù)樣本數(shù)目
count = 0
while True:
#從攝像頭讀取圖片
success,img = cap.read()
count += 1
#保存圖像,把灰度圖片看成二維數(shù)組來檢測人臉區(qū)域
#保存到相應(yīng)的文件夾里
cv2.imwrite(path+str(face_id)+'.'+str(count)+'.jpg',img)
#顯示圖片
cv2.imshow('image',img)
#保持畫面的連續(xù)。waitkey方法可以綁定按鍵保證畫面的收放,通過q鍵退出攝像
k = cv2.waitKey(1)
if k == '27':
break
#或者得到800個樣本后退出攝像,這里可以根據(jù)實(shí)際情況修改數(shù)據(jù)量,實(shí)際測試后800張的效果是比較理想的
elif count >= 500:
time.sleep(2)
success,img = cap.read()
break
#關(guān)閉攝像頭,釋放資源
cap.release()
cv2.destroyAllWindows()
#調(diào)用函數(shù)進(jìn)行人臉采樣
cy(path)獲取每個人的人臉部分區(qū)域,這里用到人臉檢測級聯(lián)分類器,并將圖片保存到特定文件夾中。
import cv2
import os
#對圖片進(jìn)行處理,輸入的不是灰度圖片,方便外部采集的圖片進(jìn)行處理
CASE_PATH = "haarcascade_frontalface_default.xml"
RAW_IMAGE_DIR = 'images/'
DATASET_DIR = 'hh/'
path='D:\\pythonlx\\test\\images\\'
#人臉分類器
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) #列出文件夾下所有的目錄與文件
for image_path in range(len(image_list)):
gh=path+image_list[image_path]
# print(gh)
image = cv2.imread(gh)
#gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(image,
scaleFactor=1.2,
minNeighbors=5,
minSize=(5, 5), )
for (x, y, width, height) in faces:
save_feces(image, '%ss%d.jpg' % (DATASET_DIR, image_path+1), x, y - 30, width, height+30)
1.2 數(shù)據(jù)讀取
將圖片數(shù)據(jù)集轉(zhuǎn)為四維數(shù)組,進(jìn)行歸一化處理,并使用one-hot編碼將標(biāo)簽向量化,按照訓(xùn)練集80%,測試集20%隨機(jī)劃分。
并進(jìn)行歸一化處理。
#讀取圖片
def read_image():
data_x, data_y = [], []
image_list = os.listdir('mine/')
for i in range(len(image_list)):
try:
im = cv2.imread('mine/{}'.format(image_list[i]))
im = resize_without_deformation(im)
data_x.append(np.asarray(im, dtype = np.int8))
#定義標(biāo)簽
a=image_list[i].split('.')[0]
if a=='s2':
data_y.append(0)
elif a=='s4':
data_y.append(1)
elif a=='s5':
data_y.append(2)
elif a=='s6':
data_y.append(3)
elif a=='s7':
data_y.append(4)
except IOError as e:
print(e)
except:
print('Unknown Error!')
return data_x,data_y
#讀取所有圖片以及標(biāo)簽
raw_images, raw_labels = read_image()
##查看數(shù)據(jù)每個標(biāo)簽的數(shù)據(jù)量
#a=raw_labels.count(0)#583
#b=raw_labels.count(1)#621
#c=raw_labels.count(2)#717
#d=raw_labels.count(3)#765
#e=raw_labels.count(4)#698
#轉(zhuǎn)為浮點(diǎn)型
raw_images, raw_labels = np.asarray(raw_images, dtype = np.float32),np.asarray(raw_labels, dtype = np.int32)
#將標(biāo)簽轉(zhuǎn)化為one_hot類型
ont_hot_labels = np_utils.to_categorical(raw_labels)
#劃分?jǐn)?shù)據(jù)集,訓(xùn)練集80%,測試集20%
train_input, valid_input, train_output, valid_output =train_test_split(raw_images,
ont_hot_labels,
test_size = 0.2)
#數(shù)據(jù)歸一化處理
train_input /= 255.0
valid_input /= 255.02. 圖片預(yù)處理
采集的圖片樣本形狀可能存在不規(guī)則大小,須對圖片做尺寸變換,轉(zhuǎn)化為100*100大小,為防止圖片變形,將圖片較短的一側(cè)涂黑進(jìn)行填充。
使它變成和目標(biāo)圖像相同的比例,然后再resize,這樣既可以保留原圖的人臉信息,又可以防止圖像形變;最后對灰度圖片直方圖均衡化,增強(qiáng)圖片的細(xì)節(jié)與對比度,提高識別率。
def resize_without_deformation(image, size = (100, 100)):
height, width, _ = image.shape
#對于長度不等的邊,找到最長邊
longest_edge = max(height, width)
#使用0填充邊框
top, bottom, left, right = 0, 0, 0, 0
#計(jì)算短邊需要增加多上像素寬度使其與長邊等長
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
#給圖像增加邊界,是圖片長、寬等長,cv2.BORDER_CONSTANT指定邊界顏色由value指定
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)
#將裁剪的圖片轉(zhuǎn)化為灰度圖
resize_image= cv2.cvtColor(resized_image,cv2.COLOR_BGR2GRAY)
#直方圖均衡化
hist = cv2.equalizeHist(resize_image)
img2 = hist.reshape((100, 100, 1))
return img23. 模型搭建與訓(xùn)練
根據(jù)卷積神經(jīng)網(wǎng)絡(luò)中各組成結(jié)構(gòu)的不同作用搭建卷積網(wǎng)絡(luò)模型,調(diào)整各個參數(shù),實(shí)現(xiàn)對模型的優(yōu)化,提高模型訓(xùn)練效果。
模型框架:

模型參數(shù):

搭建卷積網(wǎng)絡(luò)及訓(xùn)練:
由于數(shù)據(jù)集的圖片可能過于單一,以及變化小,因此后面加了一個數(shù)據(jù)提升,利用生成器進(jìn)行訓(xùn)練模型。
#搭建卷積神經(jīng)網(wǎng)絡(luò),順序模型
model = models.Sequential()
#卷積層,卷積核大小32,每個大小383,步長為1,輸入的類型為(100,100,1),1為通道,激活函數(shù)relu
model.add(Conv2D(filters=32,kernel_size=(3,3),padding='valid',strides= (1, 1),#1
input_shape = (100, 100,1),
activation='relu'))
model.add(Conv2D(filters=32,kernel_size=(3,3),padding='valid',strides= (1, 1),#2
activation='relu'))
#池化層
model.add(MaxPooling2D(pool_size=(2, 2)))#3
#Dropout層
model.add(Dropout(0.25))#4
#卷積層
model.add(Conv2D(64, (3, 3), padding='valid',
strides = (1, 1),
activation = 'relu'))#5
model.add(Conv2D(64, (3, 3), padding='valid',
strides = (1, 1),
activation = 'relu'))#6
#池化層
model.add(MaxPooling2D(pool_size=(2, 2)))#7
model.add(Dropout(0.25))#8
#全連接
model.add(Flatten())#9
model.add(Dense(512, activation = 'relu'))#10
model.add(Dropout(0.25))#11
#輸出層,神經(jīng)元數(shù)是標(biāo)簽種類數(shù),使用sigmoid激活函數(shù),輸出最終結(jié)果
model.add(Dense(len(ont_hot_labels[0]), activation = 'sigmoid'))#12
#優(yōu)化模型,
#SGD----梯度下降算子
#learning_rate = 1#學(xué)習(xí)率
#decay = 1e-6#學(xué)習(xí)率衰減因子
#momentum = 0.8#沖量
#nesterov = True
#sgd_optimizer = SGD(lr = learning_rate, decay = decay,
# momentum = momentum, nesterov = nesterov)
#categorical_crossentropy
#優(yōu)化器用Adam算法,損失函數(shù)用交叉熵的方法
model.compile(optimizer='adam',loss='categorical_crossentropy',
metrics=['accuracy'])
#輸出模型參數(shù)
model.summary()
#定義數(shù)據(jù)生成器用于數(shù)據(jù)提升,其返回一個生成器對象datagen,datagen每被調(diào)用一
#次其生成一組數(shù)據(jù)(順序生成),節(jié)省內(nèi)存,其實(shí)就是python的數(shù)據(jù)生成器
datagen = ImageDataGenerator(
featurewise_center = False, #是否使輸入數(shù)據(jù)去中心化(均值為0),
samplewise_center = False, #是否使輸入數(shù)據(jù)的每個樣本均值為0
featurewise_std_normalization = False, #是否數(shù)據(jù)標(biāo)準(zhǔn)化(輸入數(shù)據(jù)除以數(shù)據(jù)集的標(biāo)準(zhǔn)差)
samplewise_std_normalization = False, #是否將每個樣本數(shù)據(jù)除以自身的標(biāo)準(zhǔn)差
zca_whitening = False, #是否對輸入數(shù)據(jù)施以ZCA白化
rotation_range = 20, #數(shù)據(jù)提升時圖片隨機(jī)轉(zhuǎn)動的角度(范圍為0~180)
width_shift_range = 0.2, #數(shù)據(jù)提升時圖片水平偏移的幅度(單位為圖片寬度的占比,0~1之間的浮點(diǎn)數(shù))
height_shift_range = 0.2, #同上,只不過這里是垂直
horizontal_flip = True, #是否進(jìn)行隨機(jī)水平翻轉(zhuǎn)
vertical_flip = False) #是否進(jìn)行隨機(jī)垂直翻轉(zhuǎn)
#計(jì)算整個訓(xùn)練樣本集的數(shù)量以用于特征值歸一化、ZCA白化等處理
datagen.fit(train_input)
#利用生成器開始訓(xùn)練模型
history=model.fit_generator(datagen.flow(train_input, train_output,batch_size = 50),
epochs = 10,
validation_data = (valid_input, valid_output))
#模型驗(yàn)證
print(model.evaluate(valid_input, valid_output, verbose=2))
##畫出LOSS變化曲線圖
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'],label='val_accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0.5,1])
plt.legend(loc='lower right')
#保存模型
MODEL_PATH = 'face_model.h5'
model.save(MODEL_PATH)4. 識別與驗(yàn)證
將保存的模型重新進(jìn)行加載,打開攝像頭進(jìn)行識別,可以很準(zhǔn)確的識別出自己以及別人(由于不能輕易公開別人的身份,將預(yù)測改為識別自己與非己),在進(jìn)行模型預(yù)測時,需要將從攝像頭中獲取的每一幀圖片進(jìn)行處理轉(zhuǎn)化為相應(yīng)的格式,否則會出現(xiàn)shape的問題。
并且預(yù)測出來的是對應(yīng)每個標(biāo)簽的置信度,返回到其最大值的列索引即可知道對應(yīng)的標(biāo)簽,即可識別出是誰。
效果圖如下所示
- 識別自己與自己:

- 識別自己與非己:

5 總結(jié)
在初次嘗試進(jìn)行模型訓(xùn)練時,沒有對圖片進(jìn)行細(xì)節(jié)處理以及數(shù)據(jù)提升,雖然模型準(zhǔn)確率很高,但當(dāng)使用攝像頭進(jìn)行識別時,會存在識別不準(zhǔn)確的情況,因此后面增加了對圖像的細(xì)節(jié)處理以及數(shù)據(jù)提升處理,預(yù)測效果達(dá)到預(yù)期值,模型最高準(zhǔn)確率達(dá)到99.4%,損失率0.015。
數(shù)據(jù)集的問題,由于是直接采用攝像頭進(jìn)行拍攝,會存在有些角度沒有采集完全或者會受到環(huán)境因素的影響。到人臉識別對比時,就會只能識別那些角度的特征,換了一個角度就會識別不出來.
本例當(dāng)中對于人臉的定位是直接使用opencv自帶的級聯(lián)分類器,可以在安裝的opencv文件夾下找到相關(guān)的分類器。沒有自己寫算法。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Python實(shí)戰(zhàn)之實(shí)現(xiàn)獲取動態(tài)圖表
這篇文章主要介紹了利用Python實(shí)現(xiàn)動態(tài)化圖表,文中的示例代碼介紹詳細(xì),對我們的工作或?qū)W習(xí)有一定的價值,感興趣的同學(xué)可以學(xué)習(xí)一下2021-12-12
python實(shí)現(xiàn)人臉簽到系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)人臉簽到系統(tǒng),帶數(shù)據(jù)庫存儲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-04-04
TensorFlow卷積神經(jīng)網(wǎng)絡(luò)MNIST數(shù)據(jù)集實(shí)現(xiàn)示例
這篇文章主要介紹了TensorFlow卷積神經(jīng)網(wǎng)絡(luò)MNIST數(shù)據(jù)集的實(shí)現(xiàn)示例的過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-11-11
Pyserial設(shè)置緩沖區(qū)大小失敗的問題解決
本文主要介紹了Pyserial設(shè)置緩沖區(qū)大小失敗的問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
Python pyinotify日志監(jiān)控系統(tǒng)處理日志的方法
這篇文章主要介紹了Python pyinotify日志監(jiān)控系統(tǒng)處理日志的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
Python生成器深度解析如何構(gòu)建強(qiáng)大的數(shù)據(jù)處理管道
這篇文章主要為大家介紹了Python生成器深度解析如何構(gòu)建強(qiáng)大的數(shù)據(jù)處理管道,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
python如何給字典的鍵對應(yīng)的值為字典項(xiàng)的字典賦值
這篇文章主要介紹了python如何給字典的鍵對應(yīng)的值為字典項(xiàng)的字典賦值,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
Django + Uwsgi + Nginx 實(shí)現(xiàn)生產(chǎn)環(huán)境部署的方法
Django的部署可以有很多方式,采用nginx+uwsgi的方式是其中比較常見的一種方式。這篇文章主要介紹了Django + Uwsgi + Nginx 實(shí)現(xiàn)生產(chǎn)環(huán)境部署,感興趣的小伙伴們可以參考一下2018-06-06
webdriver.Chrome()沒反應(yīng)解決詳細(xì)圖文教程
這篇文章主要給大家介紹了關(guān)于webdriver.Chrome()沒反應(yīng)的解決辦法,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-03-03

