git地址:https://github.com/chenlinzhong/face-login
人脸检测MTCNN
文件:fcce_detect.py
model= os.path.abspath(face_comm.get_conf('mtcnn','model'))
class Detect:
def __init__(self):
self.detector = MtcnnDetector(model_folder=model, ctx=mx.cpu(0), num_worker=4, accurate_landmark=False)
def detect_face(self,image):
img = cv2.imread(image)
results =self.detector.detect_face(img)
boxes=[]
key_points = []
if results is not None:
#box框
boxes=results[0]
#人脸5个关键点
points = results[1]
for i in results[0]:
faceKeyPoint = []
for p in points:
for i in range(5):
faceKeyPoint.append([p[i], p[i + 5]])
key_points.append(faceKeyPoint)
return {"boxes":boxes,"face_key_point":key_points}
Multi-task convolutional neural network(多任务卷积神经网络),将人脸区域检测与人脸关键点检测放在了一起,它的主题框架类似于cascade。总体可分为P-Net、R-Net、和O-Net三层网络结构。
它是2016年中国科学院深圳研究院提出的用于人脸检测任务的多任务神经网络模型,该模型主要采用了三个级联的网络,采用候选框加分类器的思想,进行快速高效的人脸检测。这三个级联的网络分别是快速生成候选窗口的P-Net、进行高精度候选窗口过滤选择的R-Net和生成最终边界框与人脸关键点的O-Net。和很多处理图像问题的卷积神经网络模型,该模型也用到了图像金字塔、边框回归、非最大值抑制等技术。
原理
图像金字塔
对图片进行Resize操作,将原始图像缩放成不同的尺度,生成图像金字塔。然后将不同尺度的图像送入到这三个子网络中进行训练,目的是为了可以检测到不同大小的人脸,从而实现多尺度目标检测。
三个子网络图
P-Net(Proposal Network)
R-Net(Refine Network)
O-Net(Output Network)
从P-Net到R-Net,再到最后的O-Net,网络输入的图像越来越大,卷积层的通道数越来越多,网络的深度(层数)也越来越深,因此识别人脸的准确率应该也是越来越高的。
MTCNN的损失函数
针对人脸识别问题,直接使用交叉熵代价函数,对于框回归和关键点定位,使用L2损失。最后把这三部分的损失各自乘以自身的权重累加起来,形成最后的总损失。
1、人脸识别损失函数(cross-entry loss)
2、回归框的损失函数 (Euclidean loss)
3、关键点的损失函数 (Euclidean loss)
剪切人脸区域facenet embedding
Facenet是谷歌研发的人脸识别系统,该系统是基于百万级人脸数据训练的深度卷积神经网络,可以将人脸图像embedding(映射)成128维度的特征向量。以该向量为特征,采用knn或者svm等机器学习方法实现人脸识别。Facenet在LFW数据集上识别准确率为0.9963
mtcnn人脸检测方法精确度很高,但是依然存在一些问题。
一,该方法无法识别倾斜度大于45度的人脸和侧面的人脸。如图6-2所示,当倾斜度大于45度后,系统无法检测出人脸。该问题原因可能是mtcnn网络训练数据中没有大倾斜度的人脸照片。解决的方法可以有两种:(1)对训练照片进行旋转,在但前mtcnn网络的基础上进行finetune;(2)在视频帧读取后,旋转不同角度后,分别传入mtcnn进行人脸检测。前一种方法需要处理大量的人脸数据;后一种方法会影响实时检测的速度。
二、可能将动物脸识别成人脸
OpenCV之仿射变换(Affine Transformation)
文件:face_alignment.py
def align_face(self,opic,faceKeyPoint):
img = cv2.imread(opic)
faceKeyPoint = faceKeyPoint[0]
#根据两个鼻子和眼睛进行3点对齐
eye1 = faceKeyPoint[0]
eye2 = faceKeyPoint[1]
noise = faceKeyPoint[2]
source_point = np.array(
[eye1, eye2, noise], dtype=np.float32
)
eye1_noraml= [int(x) for x in face_comm.get_conf('alignment','left_eye').split(',')]
eye2_noraml=[int(x) for x in face_comm.get_conf('alignment','right_eye').split(',')]
noise_normal=[int(x) for x in face_comm.get_conf('alignment','noise').split(',')]
#设置的人脸标准模型
dst_point = np.array(
[eye1_noraml,
eye2_noraml,
noise_normal],
dtype=np.float32)
tranform = cv2.getAffineTransform(source_point, dst_point)
imagesize=tuple([int(x) for x in face_comm.get_conf('alignment','imgsize').split(',')])
img_new = cv2.warpAffine(img, tranform, imagesize)
new_image= os.path.abspath(face_comm.get_conf('alignment','aligment_face_dir'))
new_image= new_image+'/'+'%d_%d.png'%(time.time(),random.randint(0,100))
if cv2.imwrite(new_image, img_new):
return new_image
return None
图像的几何变换——拉伸、收缩、扭曲、旋转(stretch,shrink,distortion,rotation)
仿射变换是指在向量空间中进行一次线性变换(乘以一个矩阵)并加上一个平移(加上一个向量),变换为另一个向量空间的过程。在有限维的情况下,每个仿射变换可以由一个矩阵A和一个向量b给出,它可以写作A和一个附加的列b。一个仿射变换对应于一个矩阵和一个向量的乘法,而仿射变换的复合对应于普通的矩阵乘法,只要加入一个额外的行到矩阵的底下,这一行全部是0除了最右边是一个1,而列向量的底下要加上一个1.
是一种从二维坐标到二维坐标的变换
OpenCV通过两个函数的组合使用来实现仿射变换,也就是仿射变换的两步:
第一步:获取仿射映射矩阵(两种):
第二步:进行仿射变换
扩展: 透视变换(perspective transform)
在3D平面中,透视变换又有了自己的一席之地。两种变换原理相似,结果也类似,可针对不同的场合使用适当的变换
人脸特征索引
#人脸特征先存储在lmdb文件中格式(id,vector),所以这里从lmdb文件中加载
lmdb_file = self.lmdb_file if os.path.isdir(lmdb_file):
evn = lmdb.open(lmdb_file)
wfp = evn.begin()
annoy = AnnoyIndex(self.f)
for key, value in wfp.cursor():
key = int(key)
value = face_comm.str_to_embed(value)
annoy.add_item(key,value)annoy.build(self.num_trees)
annoy.save(self.annoy_index_path)
人脸识别的时候不能对每一个人脸都进行比较,太慢了,相同的人得到的特征索引都是比较类似,可以采用KNN分类算法去识别,这里采用是更高效annoy算法对人脸特征创建索引
annoy 算法的目标是建立一个数据结构能够在较短的时间内找到任何查询点的最近点,在精度允许的条件下通过牺牲准确率来换取比暴力搜索要快的多的搜索速度。
Annoy搜索算法(Approximate Nearest Neighbors Oh Yeah)
从数据规模上来看,如果你的向量维度小于1000,向量数在百万级别,要快速搭一个demo,建议使用annoy,annoy上手更快,支持的距离更多。如果数据量更大(如billion),那么最好用faiss。从性能上看,annoy比faiss-ivf性能稍微差一些,不过faiss的性能很取决于index。