人脸识别笔记
人脸识别入门(附带视频教程)为什么写这个博客前段时间一直在查找人脸识别相关的内容,找到了几个很好的开源项目,在学习的过程中往往不能按照博客或者GitHub上步骤执行下去,在中间时常会出现一两种错误,大都是环境的问题。写这篇博客我希望大家能够在入门阶段少走一些弯路,把时间和精力花费到原理的理解和创新,快速复现别人的论文idea上去。如果你通过我这篇博客无法成功的复现我的功能,那么请...
人脸识别入门
为什么写这个博客
前段时间一直在查找人脸识别相关的内容,找到了几个很好的开源项目,在学习的过程中往往不能按照博客或者GitHub上步骤执行下去,在中间时常会出现一两种错误,大都是环境的问题。写这篇博客我希望大家能够在入门阶段少走一些弯路,把时间和精力花费到原理的理解和创新,快速复现别人的论文idea上去。如果你通过我这篇博客无法成功的复现我的功能,那么请按照我录制的视频按照步骤来操作。完成之后再回去查找问题。
介绍的内容
本文将从感知机到CNN卷积网络介绍原理,最后利用keras实现CNN人脸识别。希望读者可以通过我的这个博客学习人脸识别的原理,如果愿意的话你可以也能够快速复现的我的成果,对我的成果进行改进和修改。
码云地址:https://gitee.com/keaideniya/FaceRecognition
软件环境
系统:window10
pythonIDE:an’acon’da+notebook
python版本:3.6.9
原理介绍
使用百度API快速实现人脸识别
参照我的上一篇博客:https://blog.csdn.net/liupeng19970119/article/details/101228547
手动实现人脸识别
- 首先我们打开anaconda新建一个test虚拟环境,创建完成之后还需要花一些时间让软件自动下载必备的包。anaconda好处是可以将不同版本的包进行隔离管理,互相不受影响。所以我们在复现阶段采用anaconda3。

- 安装额外的第三方包,在安装的时候我们有三种安装的方式,一种是pip安装,这里因为我在安装anaconda时候默认安装了3.7版本给root虚拟环境,所以要注意在CMD下安装软件包要使用anaconda 命令在test虚拟环境中安装,在cmd中直接pip安装会安装到root虚拟环境中。如果能在界面中搜索到软件包,可以直接进行界面操作安装。安装方式如下:

需要安装的包:numpy(矩阵图像处理),tensorflow(CPU1.9.0版本,用于模型的搭建),opencv(图片处理和人脸特征的提取),keras(一个基于theano和tensorflow的包),sklearn(),PILLOW(原来叫PIL),scikit-learn(数据清洗)
3.整理项目的工作步骤:
- 收集数据
- 利用harr特征提取将视频中的人脸提取出来
- 给数据打好标签,设置好大小
- 训练模型
- 调节参数,让模型精度可以达到应用的地步
- 利用验证集进行数据的测试
- 将训练好的数据放入到业务逻辑中(本文将做一个人脸识别门禁)
4.收集数据和整理数据
在收集数据的过程中为了防止工作量大,我把明星的脸作为其他人脸,这样便于收集,也能保持清晰度。自己的人脸本来打算使用摄像头进行采集,但是我的摄像头拍出来照片比较模糊,所以暂时使用视频流采集头像图片。后期检测的时候也是使用我的视频进行验证哈。在这里我通过录像软件对虎牙直播的一些星秀进行直播录制,因为他们都是全程露脸直播,直播时候表情丰富,所以比较方便人脸采集和数据训练。每三分钟录制成一个.avi,调用CatchUsbVideo(window_name, camera_idx)通过opencv里的haarcascade_frontalface_alt2.xml(这个分类器我从opencv安装目录直接拷贝到了当前目录,也可以指定\opencv\build\etc\haarcascades\这个路径,这样就不用拷贝了)harr分类器将每个人的800张人脸数据保存到data目录下。这里我们采集了5个人的人脸数据集,需要自己手动修改path进行图片的保存。
import cv2
import sys
from PIL import Image
def CatchUsbVideo(window_name, camera_idx):
num = 0
path_name = "data/guoyu/"
cv2.namedWindow(window_name)
#视频来源,可以来自一段已存好的视频,也可以直接来自USB摄像头
cap = cv2.VideoCapture(camera_idx)
#告诉OpenCV使用人脸识别分类器
classfier = cv2.CascadeClassifier("./haarcascade_frontalface_alt2.xml")
#识别出人脸后要画的边框的颜色,RGB格式
color = (0, 255, 0)
while cap.isOpened():
ok, frame = cap.read() #读取一帧数据
if not ok:
break
#将当前帧转换成灰度图像
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
faceRects = classfier.detectMultiScale(grey, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))
if len(faceRects) > 0: #大于0则检测到人脸
for faceRect in faceRects: #单独框出每一张人脸
x, y, w, h = faceRect
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
#将当前帧保存为图片
img_name = '%s/%d.jpg'%(path_name, num)
image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
cv2.imwrite(img_name, image)
num += 1
if num > (1000): #如果超过指定最大保存数量退出循环
break
#显示当前捕捉到了多少人脸图片了,这样站在那里被拍摄时心里有个数,不用两眼一抹黑傻等着
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame,'num:%d' % (num),(x + 30, y + 30), font, 1, (255,0,255),4)
#超过指定最大保存数量结束程序
if num > (catch_pic_num): break
#显示图像
cv2.imshow(window_name, frame)
c = cv2.waitKey(10)
if c & 0xFF == ord('q'):
break
#释放摄像头并销毁所有窗口
cap.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
if len(sys.argv) != 1:
print("Usage:%s camera_id\r\n" % (sys.argv[0]))
else:
CatchUsbVideo("识别人脸区域", "./dataresoure/guoyu.avi")
识别过程如下:
识别完成后我们需要进行手动的数据清洗,去除保存目录下不是头像的图片,清理完成之后我们在进行重新排序,方便后面添加数据标签。需要注意的是:在博客中的代码复制过去可能会有空格问题,若想直接使用建议复制我的github上代码进行学习,博客上的代码只是用来理解代码含义。
图片排序:
import os
pic_path="./data/guoyu" 注意跟换自己的路径
def rename():
piclist=os.listdir(pic_path)
total_num=len(piclist)
i=1
for pic in piclist:
if pic.endswith(".jpg"):
old_path=os.path.join(os.path.abspath(pic_path),pic)
new_path=os.path.join(os.path.abspath(pic_path),'0'+format(str(i),'0>3')+'.jpg')
os.renames(old_path,new_path) #将原路径的图片全部保存为0XXX.jpg格式。
print ("把原图片命名格式:"+old_path+u"转换为新图片命名格式:"+new_path)
i=i+1
if __name__ == '__main__':
rename()
- 前面的环节我们实现了人脸图片的从无到有,在收集数据过程中出现了一些误差图片,我们手动清洗后重新将数据进行排序,对5个人分别保存了800张人脸图片,在这一环节就要进行数据的预处理,将数据打好标签,并且设置好像素大小,为后面数据的训练提供良好的数据集合。创建一个dataPreprocess.py文件给数据打好标签,设置正方体规格大小。
import os
import sys
import numpy as np
import cv2
IMAGE_SIZE = 64
#按照指定图像大小调整尺寸
def resize_image(image, height = IMAGE_SIZE, width = IMAGE_SIZE):
top, bottom, left, right = (0, 0, 0, 0)
#获取图像尺寸
h, w, _ = image.shape
#对于长宽不相等的图片,找到最长的一边
longest_edge = max(h, w)
#计算短边需要增加多上像素宽度使其与长边等长
if h < longest_edge:
dh = longest_edge - h
top = dh // 2
bottom = dh - top
elif w < longest_edge:
dw = longest_edge - w
left = dw // 2 #可以结合35行看,这里可能是以图片中心为坐标
right = dw - left
else:
pass
#RGB颜色
BLACK = [0, 0, 0]
#给图像增加边界,是图片长、宽等长,cv2.BORDER_CONSTANT指定边界颜色由value指定
constant = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value = BLACK)
#调整图像大小并返回
return cv2.resize(constant, (height, width))
#读取训练数据
images = []
labels = []
def read_path(path_name):
for dir_item in os.listdir(path_name):
#从初始路径开始叠加,合并成可识别的操作路径
full_path = os.path.abspath(os.path.join(path_name, dir_item))
if os.path.isdir(full_path): #如果是文件夹,继续递归调用
read_path(full_path)
else: #文件
if dir_item.endswith('.jpg'):
image = cv2.imread(full_path)
image = resize_image(image, IMAGE_SIZE, IMAGE_SIZE)
#放开这个代码,可以看到resize_image()函数的实际调用效果
cv2.imwrite('demo.jpg', image)
images.append(image)
labels.append(path_name) #这里添加的标签是路径
#print(images)
#print(labels)
return images,labels
#从指定路径读取训练数据
def load_dataset(path_name):
images,labels = read_path(path_name)
#将输入的所有图片转成四维数组,尺寸为(图片数量*IMAGE_SIZE*IMAGE_SIZE*3)
#我和闺女两个人共1200张图片,IMAGE_SIZE为64,故对我来说尺寸为1600 * 64 * 64 * 3
#图片为64 * 64像素,一个像素3个颜色值(RGB)
images = np.array(images)
print(images.shape)
#标注数据,'liupeng'文件夹下都是我的脸部图像,全部指定为0,另外一个文件夹下是同学的,全部指定为1
labels = np.array([0 if label.endswith('aleng') else 1 for label in labels])
#print(labels)
return images, labels
if __name__ == '__main__':
if len(sys.argv) != 1:
print("Usage:%s path_name\r\n" % (sys.argv[0]))
else:
images, labels = load_dataset("./data")
6.训练数据:加载数据进行训练,将训练好的模型保存到/model目录下。在训练的过程中会出现一些调参问题。
import random
import numpy as np
import cv2
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.utils import np_utils
from keras.models import load_model
from keras import backend as K
from dataPreprocess import load_dataset, resize_image, IMAGE_SIZE
class Dataset:
def __init__(self, path_name):
#训练集
self.train_images = None
self.train_labels = None
#验证集
self.valid_images = None
self.valid_labels = None
#测试集
self.test_images = None
self.test_labels = None
#数据集加载路径
self.path_name = path_name
#当前库采用的维度顺序
self.input_shape = None
#加载数据集并按照交叉验证的原则划分数据集并进行相关预处理工作
def load(self, img_rows = IMAGE_SIZE, img_cols = IMAGE_SIZE,
img_channels = 3, nb_classes = 2): #2是分类的东西
#加载数据集到内存
images, labels = load_dataset(self.path_name)
train_images, valid_images, train_labels, valid_labels = train_test_split(images, labels, test_size = 0.3, random_state = random.randint(0, 100))
_, test_images, _, test_labels = train_test_split(images, labels, test_size = 0.5, random_state = random.randint(0, 100)) #选择合适数量的训练,,验证
#当前的维度顺序如果为'th',则输入图片数据时的顺序为:channels,rows,cols,否则:rows,cols,channels
#这部分代码就是根据keras库要求的维度顺序重组训练数据集
if K.image_dim_ordering() == 'th':
train_images = train_images.reshape(train_images.shape[0], img_channels, img_rows, img_cols)
valid_images = valid_images.reshape(valid_images.shape[0], img_channels, img_rows, img_cols)
test_images = test_images.reshape(test_images.shape[0], img_channels, img_rows, img_cols)
self.input_shape = (img_channels, img_rows, img_cols)
else:
train_images = train_images.reshape(train_images.shape[0], img_rows, img_cols, img_channels)
valid_images = valid_images.reshape(valid_images.shape[0], img_rows, img_cols, img_channels)
test_images = test_images.reshape(test_images.shape[0], img_rows, img_cols, img_channels)
self.input_shape = (img_rows, img_cols, img_channels)
#输出训练集、验证集、测试集的数量
print(train_images.shape[0], 'train samples')
print(valid_images.shape[0], 'valid samples')
print(test_images.shape[0], 'test samples')
#我们的模型使用categorical_crossentropy作为损失函数,因此需要根据类别数量nb_classes将
#类别标签进行one-hot编码使其向量化,在这里我们的类别只有两种,经过转化后标签数据变为二维
train_labels = np_utils.to_categorical(train_labels, nb_classes)
valid_labels = np_utils.to_categorical(valid_labels, nb_classes)
test_labels = np_utils.to_categorical(test_labels, nb_classes)
#像素数据浮点化以便归一化
train_images = train_images.astype('float32')
valid_images = valid_images.astype('float32')
test_images = test_images.astype('float32')
#将其归一化,图像的各像素值归一化到0~1区间
train_images /= 255
valid_images /= 255
test_images /= 255
self.train_images = train_images
self.valid_images = valid_images
self.test_images = test_images
self.train_labels = train_labels
self.valid_labels = valid_labels
self.test_labels = test_labels
#CNN网络模型类
class Model:
def __init__(self):
self.model = None
#建立模型
def build_model(self, dataset, nb_classes = 2):
#构建一个空的网络模型,它是一个线性堆叠模型,各神经网络层会被顺序添加,专业名称为序贯模型或线性堆叠模型
self.model = Sequential()
#以下代码将顺序添加CNN网络需要的各层,一个add就是一个网络层
self.model.add(Convolution2D(32, 3, 3, border_mode='same',
input_shape = dataset.input_shape)) #1 2维卷积层
self.model.add(Activation('relu')) #2 激活函数层
self.model.add(Convolution2D(32, 3, 3)) #3 2维卷积层
self.model.add(Activation('relu')) #4 激活函数层
self.model.add(MaxPooling2D(pool_size=(2, 2))) #5 池化层
self.model.add(Dropout(0.25)) #6 Dropout层
self.model.add(Convolution2D(64, 3, 3, border_mode='same')) #7 2维卷积层
self.model.add(Activation('relu')) #8 激活函数层
self.model.add(Convolution2D(64, 3, 3)) #9 2维卷积层
self.model.add(Activation('relu')) #10 激活函数层
self.model.add(MaxPooling2D(pool_size=(2, 2))) #11 池化层
self.model.add(Dropout(0.25)) #12 Dropout层
self.model.add(Flatten()) #13 Flatten层
self.model.add(Dense(512)) #14 Dense层,又被称作全连接层
self.model.add(Activation('relu')) #15 激活函数层
self.model.add(Dropout(0.5)) #16 Dropout层
self.model.add(Dense(nb_classes)) #17 Dense层
self.model.add(Activation('softmax')) #18 分类层,输出最终结果
#输出模型概况
self.model.summary()
#训练模型
def train(self, dataset, batch_size = 20, nb_epoch = 5, data_augmentation = True):
sgd = SGD(lr = 0.01, decay = 1e-6,
momentum = 0.9, nesterov = True) #采用SGD+momentum的优化器进行训练,首先生成一个优化器对象
self.model.compile(loss='categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy']) #完成实际的模型配置工作
#不使用数据提升,所谓的提升就是从我们提供的训练数据中利用旋转、翻转、加噪声等方法创造新的
#训练数据,有意识的提升训练数据规模,增加模型训练量
if not data_augmentation:
self.model.fit(dataset.train_images,
dataset.train_labels,
batch_size = batch_size,
nb_epoch = nb_epoch,
validation_data = (dataset.valid_images, dataset.valid_labels),
shuffle = True)
#使用实时数据提升
else:
#定义数据生成器用于数据提升,其返回一个生成器对象datagen,datagen每被调用一
#次其生成一组数据(顺序生成),节省内存,其实就是python的数据生成器
datagen = ImageDataGenerator(
featurewise_center = False, #是否使输入数据去中心化(均值为0),
samplewise_center = False, #是否使输入数据的每个样本均值为0
featurewise_std_normalization = False, #是否数据标准化(输入数据除以数据集的标准差)
samplewise_std_normalization = False, #是否将每个样本数据除以自身的标准差
zca_whitening = False, #是否对输入数据施以ZCA白化
rotation_range = 20, #数据提升时图片随机转动的角度(范围为0~180)
width_shift_range = 0.2, #数据提升时图片水平偏移的幅度(单位为图片宽度的占比,0~1之间的浮点数)
height_shift_range = 0.2, #同上,只不过这里是垂直
horizontal_flip = True, #是否进行随机水平翻转
vertical_flip = False) #是否进行随机垂直翻转
#计算整个训练样本集的数量以用于特征值归一化、ZCA白化等处理
datagen.fit(dataset.train_images)
#利用生成器开始训练模型
self.model.fit_generator(datagen.flow(dataset.train_images, dataset.train_labels,
batch_size = batch_size),
samples_per_epoch = dataset.train_images.shape[0],
nb_epoch = nb_epoch,
validation_data = (dataset.valid_images, dataset.valid_labels))
MODEL_PATH = '.model/ceshi.face.model.h5'
def save_model(self, file_path = MODEL_PATH):
self.model.save(file_path)
def load_model(self, file_path = MODEL_PATH):
self.model = load_model(file_path)
def evaluate(self, dataset):
score = self.model.evaluate(dataset.test_images, dataset.test_labels, verbose = 1)
print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100))
#识别人脸
def face_predict(self, image):
#依然是根据后端系统确定维度顺序
if K.image_dim_ordering() == 'th' and image.shape != (1, 3, IMAGE_SIZE, IMAGE_SIZE):
image = resize_image(image) #尺寸必须与训练集一致都应该是IMAGE_SIZE x IMAGE_SIZE
image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE)) #与模型训练不同,这次只是针对1张图片进行预测
elif K.image_dim_ordering() == 'tf' and image.shape != (1, IMAGE_SIZE, IMAGE_SIZE, 3):
image = resize_image(image)
image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3))
#浮点并归一化
image = image.astype('float32')
image /= 255
#给出输入属于各个类别的概率,我们是二值类别,则该函数会给出输入图像属于0和1的概率各为多少
result = self.model.predict_proba(image)
print('result:', result)
#给出类别预测:0或者1
result = self.model.predict_classes(image)
#返回类别预测结果
return result[0]
if __name__ == '__main__':
dataset = Dataset('./data/')
dataset.load()
model = Model()
model.build_model(dataset)
model.train(dataset)
model.save_model(file_path = './model/ceshi.face.model.h5')
if __name__ == '__main__':
dataset = Dataset('./data/')
dataset.load()
#评估模型
model = Model()
model.load_model(file_path = './model/ceshi.face.model.h5')
print("评估模型")
model.evaluate(dataset)
7.调节参数:在训练过程中发现刚开始分批训练的时候精确度很高,但是随着批处理数量的增加,模型的精确度一直在下降,所以我将nb_epoch从10修改到了5 并且重新验证精确度,发现数据验证的准确率达到了100%

8.验证模型的准确度:这里我们编写一个modelTest.py进行模型的验证,当数据为guoyu的时候结果准确预测为1,其他人均为0。预测的0与1概率也达到了很高的区别,可以利用这个模型进行后面的应用开发。
from keras.models import Sequential
import datatrain
import cv2
from dataPreprocess import load_dataset, resize_image, IMAGE_SIZE
if __name__ == '__main__':
model = datatrain.Model()
model.load_model(file_path = './model/ceshi.face.model.h5')
image = cv2.imread("demo2.jpg")
k = model.face_predict(image)
if k==1:
print("这是果雨")
else:
print("陌生人")
运行结果图:

8.利用训练好的模型设计门禁。
更多推荐
所有评论(0)