目录
- 使用环境
- 具体目标
- 实现步骤如下图所示
- 1. 人脸数据采集与读取
- 2. 图片预处理
- 3. 模型搭建与训练
- 4. 识别与验证
- 5 总结
作为一个小白,用人脸识别上手了一下,收获还是挺大的。
使用环境
- python3.7
- tensorflow 2.2.0
- opencv-python 4.4.0.40
- Keras 2.4.3
- numpy 1.18.5,具体安装过程以及环境搭建省略,可借鉴网上。
具体目标
通过卷积神经网络训练自己数据并能成功识别自己
实现步骤如下图所示
1. 人脸数据采集与读取
1.1 数据采集
本数据集使用opencv打开摄像头,从摄像头当中采集图片信息以及外来的某些人图片,共采集5个人的信息(这里没有直接从摄像头裁剪脸部信息是为了方便外部采集的图片进行处理,每个人图片为800张,本数据集把采取到每个人的图片以名字缩写开始放在同一个文件夹里面。)
相关代码如下:
import os | |
import cv | |
import time | |
from PIL import Image | |
#只实现截屏的功能 | |
global path | |
path='./images/' | |
#人脸采样,封装函数 | |
def cy(path): | |
#path为保存图片的路径 | |
#调用笔记本内置摄像头,参数为,如果有其他的摄像头可以调整参数为1,2 | |
cap = cv.VideoCapture(0) | |
#为即将录入的脸标记一个id | |
face_id = input('\n 用户脸部信息录入,输入用户名字(最好用英文):\n') | |
#sampleNum用来计数样本数目 | |
count = | |
while True: | |
#从摄像头读取图片 | |
success,img = cap.read() | |
count += | |
#保存图像,把灰度图片看成二维数组来检测人脸区域 | |
#保存到相应的文件夹里 | |
cv.imwrite(path+str(face_id)+'.'+str(count)+'.jpg',img) | |
#显示图片 | |
cv.imshow('image',img) | |
#保持画面的连续。waitkey方法可以绑定按键保证画面的收放,通过q键退出摄像 | |
k = cv.waitKey(1) | |
if k == '': | |
break | |
#或者得到个样本后退出摄像,这里可以根据实际情况修改数据量,实际测试后800张的效果是比较理想的 | |
elif count >=: | |
time.sleep() | |
success,img = cap.read() | |
break | |
#关闭摄像头,释放资源 | |
cap.release() | |
cv.destroyAllWindows() | |
#调用函数进行人脸采样 | |
cy(path) |
获取每个人的人脸部分区域,这里用到人脸检测级联分类器,并将图片保存到特定文件夹中。
import cv | |
import os | |
#对图片进行处理,输入的不是灰度图片,方便外部采集的图片进行处理 | |
CASE_PATH = "haarcascade_frontalface_default.xml" | |
RAW_IMAGE_DIR = 'images/' | |
DATASET_DIR = 'hh/' | |
path='D:\\pythonlx\\test\\images\\' | |
#人脸分类器 | |
face_cascade = cv.CascadeClassifier(CASE_PATH) | |
#定义人脸大小 | |
def save_feces(img, name,x, y, width, height): | |
image = img[y:y+height, x:x+width] | |
cv.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 = cv.imread(gh) | |
#gray = cv.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
faces = face_cascade.detectMultiScale(image, | |
scaleFactor=.2, | |
minNeighbors=, | |
minSize=(, 5), ) | |
for (x, y, width, height) in faces: | |
save_feces(image, '%ss%d.jpg' % (DATASET_DIR, image_path+), x, y - 30, width, height+30) |
1.2 数据读取
将图片数据集转为四维数组,进行归一化处理,并使用one-hot编码将标签向量化,按照训练集80%,测试集20%随机划分。
并进行归一化处理。
#读取图片 | |
def read_image(): | |
data_x, data_y = [], [] | |
image_list = os.listdir('mine/') | |
for i in range(len(image_list)): | |
try: | |
im = cv.imread('mine/{}'.format(image_list[i])) | |
im = resize_without_deformation(im) | |
data_x.append(np.asarray(im, dtype = np.int)) | |
#定义标签 | |
a=image_list[i].split('.')[] | |
if a=='s': | |
data_y.append() | |
elif a=='s': | |
data_y.append() | |
elif a=='s': | |
data_y.append() | |
elif a=='s': | |
data_y.append() | |
elif a=='s': | |
data_y.append() | |
except IOError as e: | |
print(e) | |
except: | |
print('Unknown Error!') | |
return data_x,data_y | |
#读取所有图片以及标签 | |
raw_images, raw_labels = read_image() | |
##查看数据每个标签的数据量 | |
#a=raw_labels.count()#583 | |
#b=raw_labels.count()#621 | |
#c=raw_labels.count()#717 | |
#d=raw_labels.count()#765 | |
#e=raw_labels.count()#698 | |
#转为浮点型 | |
raw_images, raw_labels = np.asarray(raw_images, dtype = np.float),np.asarray(raw_labels, dtype = np.int32) | |
#将标签转化为one_hot类型 | |
ont_hot_labels = np_utils.to_categorical(raw_labels) | |
#划分数据集,训练集%,测试集20% | |
train_input, valid_input, train_output, valid_output =train_test_split(raw_images, | |
ont_hot_labels, | |
test_size =.2) | |
#数据归一化处理 | |
train_input /=.0 | |
valid_input /=.0 |
2. 图片预处理
采集的图片样本形状可能存在不规则大小,须对图片做尺寸变换,转化为100*100大小,为防止图片变形,将图片较短的一侧涂黑进行填充。
使它变成和目标图像相同的比例,然后再resize,这样既可以保留原图的人脸信息,又可以防止图像形变;最后对灰度图片直方图均衡化,增强图片的细节与对比度,提高识别率。
def resize_without_deformation(image, size = (, 100)): | |
height, width, _ = image.shape | |
#对于长度不等的边,找到最长边 | |
longest_edge = max(height, width) | |
#使用填充边框 | |
top, bottom, left, right =, 0, 0, 0 | |
#计算短边需要增加多上像素宽度使其与长边等长 | |
if height < longest_edge: | |
height_diff = longest_edge - height | |
top = int(height_diff //) | |
bottom = height_diff - top | |
elif width < longest_edge: | |
width_diff = longest_edge - width | |
left = int(width_diff //) | |
right = width_diff - left | |
#给图像增加边界,是图片长、宽等长,cv.BORDER_CONSTANT指定边界颜色由value指定 | |
image_with_border = cv.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value = [0, 0, 0]) | |
resized_image = cv.resize(image_with_border, size) | |
#将裁剪的图片转化为灰度图 | |
resize_image= cv.cvtColor(resized_image,cv2.COLOR_BGR2GRAY) | |
#直方图均衡化 | |
hist = cv.equalizeHist(resize_image) | |
img = hist.reshape((100, 100, 1)) | |
return img |
3. 模型搭建与训练
根据卷积神经网络中各组成结构的不同作用搭建卷积网络模型,调整各个参数,实现对模型的优化,提高模型训练效果。
模型框架:
模型参数:
搭建卷积网络及训练:
由于数据集的图片可能过于单一,以及变化小,因此后面加了一个数据提升,利用生成器进行训练模型。
#搭建卷积神经网络,顺序模型 | |
model = models.Sequential() | |
#卷积层,卷积核大小,每个大小383,步长为1,输入的类型为(100,100,1),1为通道,激活函数relu | |
model.add(ConvD(filters=32,kernel_size=(3,3),padding='valid',strides= (1, 1),#1 | |
input_shape = (, 100,1), | |
activation='relu')) | |
model.add(ConvD(filters=32,kernel_size=(3,3),padding='valid',strides= (1, 1),#2 | |
activation='relu')) | |
#池化层 | |
model.add(MaxPoolingD(pool_size=(2, 2)))#3 | |
#Dropout层 | |
model.add(Dropout(.25))#4 | |
#卷积层 | |
model.add(ConvD(64, (3, 3), padding='valid', | |
strides = (, 1), | |
activation = 'relu'))# | |
model.add(ConvD(64, (3, 3), padding='valid', | |
strides = (, 1), | |
activation = 'relu'))# | |
#池化层 | |
model.add(MaxPoolingD(pool_size=(2, 2)))#7 | |
model.add(Dropout(.25))#8 | |
#全连接 | |
model.add(Flatten())# | |
model.add(Dense(, activation = 'relu'))#10 | |
model.add(Dropout(.25))#11 | |
#输出层,神经元数是标签种类数,使用sigmoid激活函数,输出最终结果 | |
model.add(Dense(len(ont_hot_labels[]), activation = 'sigmoid'))#12 | |
#优化模型, | |
#SGD----梯度下降算子 | |
#learning_rate =#学习率 | |
#decay =e-6#学习率衰减因子 | |
#momentum =.8#冲量 | |
#nesterov = True | |
#sgd_optimizer = SGD(lr = learning_rate, decay = decay, | |
# momentum = momentum, nesterov = nesterov) | |
#categorical_crossentropy | |
#优化器用Adam算法,损失函数用交叉熵的方法 | |
model.compile(optimizer='adam',loss='categorical_crossentropy', | |
metrics=['accuracy']) | |
#输出模型参数 | |
model.summary() | |
#定义数据生成器用于数据提升,其返回一个生成器对象datagen,datagen每被调用一 | |
#次其生成一组数据(顺序生成),节省内存,其实就是python的数据生成器 | |
datagen = ImageDataGenerator( | |
featurewise_center = False, #是否使输入数据去中心化(均值为), | |
samplewise_center = False, #是否使输入数据的每个样本均值为 | |
featurewise_std_normalization = False, #是否数据标准化(输入数据除以数据集的标准差) | |
samplewise_std_normalization = False, #是否将每个样本数据除以自身的标准差 | |
zca_whitening = False, #是否对输入数据施以ZCA白化 | |
rotation_range =, #数据提升时图片随机转动的角度(范围为0~180) | |
width_shift_range =.2, #数据提升时图片水平偏移的幅度(单位为图片宽度的占比,0~1之间的浮点数) | |
height_shift_range =.2, #同上,只不过这里是垂直 | |
horizontal_flip = True, #是否进行随机水平翻转 | |
vertical_flip = False) #是否进行随机垂直翻转 | |
#计算整个训练样本集的数量以用于特征值归一化、ZCA白化等处理 | |
datagen.fit(train_input) | |
#利用生成器开始训练模型 | |
history=model.fit_generator(datagen.flow(train_input, train_output,batch_size =), | |
epochs =, | |
validation_data = (valid_input, valid_output)) | |
#模型验证 | |
print(model.evaluate(valid_input, valid_output, verbose=)) | |
##画出LOSS变化曲线图 | |
plt.plot(history.history['accuracy']) | |
plt.plot(history.history['val_accuracy'],label='val_accuracy') | |
plt.xlabel('Epoch') | |
plt.ylabel('Accuracy') | |
plt.ylim([.5,1]) | |
plt.legend(loc='lower right') | |
#保存模型 | |
MODEL_PATH = 'face_model.h' | |
model.save(MODEL_PATH) |
4. 识别与验证
将保存的模型重新进行加载,打开摄像头进行识别,可以很准确的识别出自己以及别人(由于不能轻易公开别人的身份,将预测改为识别自己与非己),在进行模型预测时,需要将从摄像头中获取的每一帧图片进行处理转化为相应的格式,否则会出现shape的问题。
并且预测出来的是对应每个标签的置信度,返回到其最大值的列索引即可知道对应的标签,即可识别出是谁。
效果图如下所示
- 识别自己与自己:
- 识别自己与非己:
5 总结
在初次尝试进行模型训练时,没有对图片进行细节处理以及数据提升,虽然模型准确率很高,但当使用摄像头进行识别时,会存在识别不准确的情况,因此后面增加了对图像的细节处理以及数据提升处理,预测效果达到预期值,模型最高准确率达到99.4%,损失率0.015。
数据集的问题,由于是直接采用摄像头进行拍摄,会存在有些角度没有采集完全或者会受到环境因素的影响。到人脸识别对比时,就会只能识别那些角度的特征,换了一个角度就会识别不出来.
本例当中对于人脸的定位是直接使用opencv自带的级联分类器,可以在安装的opencv文件夹下找到相关的分类器。没有自己写算法。