@[TOC]戴口罩情境下的人脸识别项目
提示:如有问题请在评论区或者私聊我均可,希望共同交流;
这个戴口罩的人脸识别项目是人脸识别部分用的s—算法,目前正在准备用CNN来进行识别,等到CNN模型搭好后,在进行博客展示。
前言
疫情爆发以来,戴口罩成为基本的防范新冠病毒的行为;在进出火车站等公共场所时,需要进行对应的人脸识别进站、出站。前几年的人脸识别技术已相对成熟,但在口罩遮挡下的人脸识别技术在疫情的背景下油然而生,之前的技术需要进行新的情境调整。本人就所做的《不完整的图像检索研究》方向,进行细致化的展示,就“戴口罩的人脸识别”进行研究。本博客主要讲述一下大致思路,具体源码的获取联系方式我将放在评论区,有需要的Q我。
一、什么是人脸识别?
1、深度学习下的人脸识别
人脸识别,通俗来讲,就是由摄像机等外接设备将图像输入到终端,抠出相对应的人脸区域,之后定位区域内的眼鼻口等特征点,通过仿射变换进行人脸对齐矫正(alignment),将对齐后的人脸图片输入到进行特征提取的神经网络中,得到特征提取,后对比终端数据库中存储的人脸图片信息特征,常规的人脸识别就是通过计算不同图片中的欧氏距离,进行计算,小于一定阈值threshold后说明二者就是同一个人脸,最后输出结果;
二、戴口罩情境下的人脸识别demo拆解
1.目标检测
综述:作为学生阶段,最常用的人脸目标检测,主要是opencv和dlib,这两个开源框架进行人脸检测效果还是很不错的;同时,通过网上查询资料,还有一种mtcnn(Multi-Task Convolutional Neural Network)目标检测,然后利用facenet进行人脸识别的方法。基于本项目最大的难点就是如何对有遮挡物的部分人脸也能进行精准检测;以opencv为一类的开源库,进行人脸识别的模型主要是已经训练好的Haar特征模型(haarcascade_frontalfac e_fault.xml)来检测人脸,当带上口罩时,该模型也就失效了,所以我们要自己重新训练一个新的模型,来对有遮挡物的人脸进行检测;
1、基于opencv进行二分类模型的训练过程
分述:
(1)采用正样本与负样本进行二分类,正样本图片(戴口罩的人脸图片)采用统一大小的图片,因为获得图片中可能会有胳膊、肩膀等身体部位,对于训练时可能会产生更多的噪音,所以要使用代码进行正样本的图片人脸区域裁剪;同时opencv获取的图片读入之后是BGR空间格式,所以直接进行BGR2GRAY进行格式灰度化处理;
(2)负样本只需进行图片的灰度化处理即可,不需进行额外的噪声处理,各种各样的负样本会使得训练出的模型对于正样本的检测更加精确;
(3)正样本数量:负样本数量 = 1:3
2、基于MTCNN进行戴口罩人脸检测模型的训练过程
分述:MTCNN,顾名思义是多任务卷积神经网络,该网络模型,主要通过三个级联网络进行人脸框定,P-net、R-net、O-net;关于mtcnn的模型训练,输入的图片全部都是正样本(戴口罩的照片),未采用负样本的模型输入;
(1)构建P-net代码如下:
# -----------------------------#
# 粗略获取人脸框
# 输出bbox位置和是否有人脸
# -----------------------------#
def create_Pnet(weight_path):
# h,w
input = Input(shape=[None, None, 3])
# h,w,3 -> h/2,w/2,10
x = Conv2D(10, (3, 3), strides=1, padding='valid', name='conv1')(input)
x = PReLU(shared_axes=[1, 2], name='PReLU1')(x)
x = MaxPool2D(pool_size=2)(x)
# h/2,w/2,10 -> h/2,w/2,16
x = Conv2D(16, (3, 3), strides=1, padding='valid', name='conv2')(x)
x = PReLU(shared_axes=[1, 2], name='PReLU2')(x)
# h/2,w/2,32
x = Conv2D(32, (3, 3), strides=1, padding='valid', name='conv3')(x)
x = PReLU(shared_axes=[1, 2], name='PReLU3')(x)
# h/2, w/2, 2
classifier = Conv2D(2, (1, 1), activation='softmax', name='conv4-1')(x)
# 无激活函数,线性。
# h/2, w/2, 4
bbox_regress = Conv2D(4, (1, 1), name='conv4-2')(x)
model = Model([input], [classifier, bbox_regress])
model.load_weights(weight_path, by_name=True)
return model
(2)构建P-net代码如下:
# -----------------------------#
# mtcnn的第二段
# 精修框
# -----------------------------#
def create_Rnet(weight_path):
input = Input(shape=[24, 24, 3])
# 24,24,3 -> 11,11,28
x = Conv2D(28, (3, 3), strides=1, padding='valid', name='conv1')(input)
x = PReLU(shared_axes=[1, 2], name='prelu1')(x)
x = MaxPool2D(pool_size=3, strides=2, padding='same')(x)
# 11,11,28 -> 4,4,48
x = Conv2D(48, (3, 3), strides=1, padding='valid', name='conv2')(x)
x = PReLU(shared_axes=[1, 2], name='prelu2')(x)
x = MaxPool2D(pool_size=3, strides=2)(x)
# 4,4,48 -> 3,3,64
x = Conv2D(64, (2, 2), strides=1, padding='valid', name='conv3')(x)
x = PReLU(shared_axes=[1, 2], name='prelu3')(x)
# 3,3,64 -> 64,3,3
x = Permute((3, 2, 1))(x)
x = Flatten()(x)
# 576 -> 128
x = Dense(128, name='conv4')(x)
x = PReLU(name='prelu4')(x)
# 128 -> 2 128 -> 4
classifier = Dense(2, activation='softmax', name='conv5-1')(x)
bbox_regress = Dense(4, name='conv5-2')(x)
model = Model([input], [classifier, bbox_regress])
model.load_weights(weight_path, by_name=True)
return model
(3)构建O-net代码如下:
# -----------------------------#
# mtcnn的第三段
# 精修框并获得五个点
# -----------------------------#
def create_Onet(weight_path):
input = Input(shape=[48, 48, 3])
# 48,48,3 -> 23,23,32
x = Conv2D(32, (3, 3), strides=1, padding='valid', name='conv1')(input)
x = PReLU(shared_axes=[1, 2], name='prelu1')(x)
x = MaxPool2D(pool_size=3, strides=2, padding='same')(x)
# 23,23,32 -> 10,10,64
x = Conv2D(64, (3, 3), strides=1, padding='valid', name='conv2')(x)
x = PReLU(shared_axes=[1, 2], name='prelu2')(x)
x = MaxPool2D(pool_size=3, strides=2)(x)
# 8,8,64 -> 4,4,64
x = Conv2D(64, (3, 3), strides=1, padding='valid', name='conv3')(x)
x = PReLU(shared_axes=[1, 2], name='prelu3')(x)
x = MaxPool2D(pool_size=2)(x)
# 4,4,64 -> 3,3,128
x = Conv2D(128, (2, 2), strides=1, padding='valid', name='conv4')(x)
x = PReLU(shared_axes=[1, 2], name='prelu4')(x)
# 3,3,128 -> 128,3,3
x = Permute((3, 2, 1))(x)
# 1152 -> 256
x = Flatten()(x)
x = Dense(256, name='conv5')(x)
x = PReLU(name='prelu5')(x)
# 鉴别
# 256 -> 2 256 -> 4 256 -> 10
classifier = Dense(2, activation='softmax', name='conv6-1')(x)
bbox_regress = Dense(4, name='conv6-2')(x)
landmark_regress = Dense(10, name='conv6-3')(x)
model = Model([input], [classifier, bbox_regress, landmark_regress])
model.load_weights(weight_path, by_name=True)
return model
2.检测人脸特征点
分述:
此任务模块,主要调用dlib开源库来提取128个特征点,输入的戴口罩人脸图片,对于鼻子、嘴两个部位,在口罩的遮挡下,该模型自动补充特征点进行人脸特征点提取;
代码如下:
self.sp = dlib.shape_predictor('data/shape_predictor_128_face_landmarks.dat')
3.人脸编码
分述:获取脸部128个点的特征编码,基于人脸编码信息矩阵,计算不同人脸之间的距离计算;
代码如下:
def _get_img_face_encoding(self, fpath):
"""
获取路径图片的脸部特征编码
:param fpath: 图片路径
:return: 128位的向量
"""
img_x = cv2.imread(fpath)
img_x = cv2.cvtColor(img_x, cv2.COLOR_BGR2RGB)
item = self.face_detector.detect(img_x)
assert item is not None, 'can not find the face box,please check %s' % fpath
box, _ = item
return self._get_face_feat(img_x, box)
def _face_distance(self, face_encodings, face_to_compare):
"""
基于人脸编码信息矩阵,比较不同人脸之间的距离
"""
if len(face_encodings) == 0:
return np.empty((0))
face_encodings = np.asarray(face_encodings)
return np.linalg.norm(face_encodings - face_to_compare, axis=1)
4.进行人脸识别
分述:计算距离的方法都已布置完毕之后,进行最后一步的人脸识别
(1)、计算数据库中的人脸数据,将已存储的图片经过信息编码,存储到已知人脸信息和姓名列表中,即_known_faces[]、_know_name[];
def create_known_faces(self, root):
"""
# 构建目标库,在程序启动时,或有新员工添加时执行
:param root:目标图片存放路径
:return: [[id..] [feat..]]
"""
for fname in os.listdir(root):
fpath = os.path.join(root, fname)
if os.path.splitext(fpath)[-1] != ".jpg":
continue
self._known_faces.append(self._get_img_face_encoding(fpath))
self._know_name.append(fname.split('.')[0])
(2)、负责人脸识别的核心方法代码如下:
def recognize(self, image, score_threshold=1):
"""
识别人脸
:param image: 图片路径
:param score_threshold: 人脸识别得分阈值
:return: (know_name[ix], face_locations, cls) (人员id,box坐标(left,top,right,bottom),是否戴了口罩(0,1))
"""
if isinstance(image, str):
image = cv2.imread(image)
# image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
item = self.face_detector.detect(image)
if item:
box, cls = item
face_feat = self._get_face_feat(image, box)
scores = 1 - self._face_distance(self._known_faces, face_feat)
ix = np.argmax(scores).item()
if scores[ix] > score_threshold:
# 1 for mask,返回姓名,人脸框,cls如果是0,则返回1,代表戴了口罩
return self._know_name[ix], box, 1 - int(cls), scores[ix]
else:
self._know_name[0] = 'unknow'
scores[0] = '0'
return self._know_name[0], box, 1 - int(cls), scores[0]
(3)、最后的main()主函数代码如下:
def main():
frame_interval = 10 # Number of frames after which to run face detection
fps_display_interval = 1 # seconds
frame_rate = 0
frame_count = 0
# 捕捉摄像头的时时输入图片
video_capture0 = cv2.VideoCapture(0)
# 计时开始
start_time = time.time()
# 调用脸部识别函数
recognizer = FaceRecognizer()
# 存储录入数据库的人脸信息
recognizer.create_known_faces("./data/mask_nomask/")
# 循环检测识别人脸
while True:
# 读取一帧视频
ret, frame = video_capture0.read()
if (frame_count % frame_interval) == 0:
if ret is True:
# 图像灰化,降低计算复杂度
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
else:
continue
item = recognizer.recognize(frame_gray, 0.55)
# 检查最近的fps
end_time = time.time()
if (end_time - start_time) > fps_display_interval:
frame_rate = int(frame_count / (end_time - start_time))
start_time = time.time()
frame_count = 0
# frame_gray = cv2.resize(frame_gray, dsize=video_size)
add_overlays(frame, item, frame_rate)
frame_count += 1
cv2.imshow("Face Recognition", frame)
# 等待10毫秒看是否有按键输入
k = cv2.waitKey(10)
# 如果输入q则退出循环
if k & 0xFF == ord(' '):
break
# 释放摄像头并销毁所有窗口
video_capture0.release()
cv2.destroyAllWindows()
总结与结果展示
以上就是今天要讲的内容,本文就戴口罩的人脸识别这一任务驱动展开,大致讲解了一下思路,各个互联网公司都有自己的一套人脸识别系统,我这里所展示的只是一个简单的任务解决,精度可能没有那么严谨。
结果图:
出现数据文件中不存在的人脸信息时: