打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【Caffe实践】基于Caffe的人脸关键点检测实现

引言

如果关注Kaggle 机器学习项目的同学,一定很熟悉人脸关键点检测这个任务,在2013 年的时候,ICML举办一个的challgene,现在放在kaggle 上作为 一种最常规kaggle入门任务而存在。

本文的主要目的在于验证深度学习模型在人脸点检测效果,踩踩里面的坑。

任务介绍

人脸关键点检测,也称之为人脸点检测,是在一张已经被人脸检测器检测到的人脸图像中,再进一步检测出五官等关键点的二维坐标信息,以便于后期的人脸对齐(face alignment)任务。

根据不同的任务,需要检测的关键点数目有多有少,有些仅要求检测2只眼睛的坐标位置,有些要求检测眼睛、嘴巴、鼻子的5个坐标位置,还有更多的,68个位置,它包含了五官的轮廓信息。
如图所示:

根据任务,可以把要学习的模型函数表示为:

Y=FX,W

其中,X 是输入的人脸图像,W是我们要学习的模型参数,Y[(x1,y1),(x2,y2),(x3,y3),(x4,y4),(x5,y5)] 是我们需要检测的人脸点坐标位置。

这是一个典型的回归问题,可以采用最简单的平方误差损失函数,然后用机器学习方法学习这个模型。

Loss=15i=15((xi?xi)2+(yi?yi)2)

其中(xi,yi)为预测的位置,(xi,yi) 为标注的关键点位置。

很显然,也很容易的就将该任务放到caffe中进行学习。

实验过程:

数据准备

由于港中文[1]他们有公开了训练集,所以我们就可以直接使用他们提供的图像库就好了。
数据是需要转化的:

1、框出人脸图像部分,从新计算关键点的坐标
2、缩放人脸框大小,同时更新计算的关键点坐标
3、一些数据增强处理:我只采样了左右对称的增强方法(还可以采用的数据增强方法有旋转图像,图像平移部分)
转换脚本如下:

# -*- coding: utf-8 -*-# -*- coding: utf-8 -*-"""Created on Wed Nov 04 16:36:33 2015@author: RiweiChen <riwei.chen@outlook.com>"""import skimageimport skimage.ioimport numpy as npimport cv2def face_draw_point(filelist,savePath):    '''    人脸的剪切    '''    fid = open(filelist)    lines = fid.readlines()    fid.close()    for line in lines:        words = line.split(' ')        filename = words[0]        #im=skimage.io.imread(filename)        im=cv2.imread(filename)        #保存人脸的点,需要经过转换        point = np.zeros((10,))        point[0]=float(words[5])        point[1]=float(words[6])        point[2]=float(words[7])        point[3]=float(words[8])        point[4]=float(words[9])        point[5]=float(words[10])        point[6]=float(words[11])        point[7]=float(words[12])        point[8]=float(words[13])        point[9]=float(words[14])        for i in range(0,10,2):            #skimage.draw.circle(point[i+1],point[i])            cv2.circle(im, (int(point[i]),int(point[i+1])), 5, [0,0,255])                    #skimage.io.imsave(savePath+filename,imcrop)        cv2.imwrite(savePath+filename,im)        #print words[0]        #print words[1]def face_prepare(filelist,fileout,savePath,w,h):    '''    人脸的剪切    @debug: 谢谢网友@cyq0122 指出图像镜像过后,左右眼睛和嘴角都需要跟着改变的大BUG    '''    fid = open(filelist)    lines = fid.readlines()    fid.close()    fid = open(fileout,'w')    count = 1    for line in lines:        words = line.split(' ')        filename = words[0]        #im=skimage.io.imread(filename)        im=cv2.imread(filename)        print np.shape(im)        x1 = int(words[1])        y1 = int(words[2])        x2 = int(words[3])        y2 = int(words[4])        #缩放的比例        rate = float(y1-x1)/w        #imcrop = im[x2:y2,x1:y1,:]        imcrop = im[x2:y2,x1:y1,:]        print np.shape(imcrop)        #保存人脸的点,需要经过转换        point = np.zeros((10,))        point[0]=(float(words[5])-x1)/rate        point[1]=(float(words[6])-x2)/rate        point[2]=(float(words[7])-x1)/rate        point[3]=(float(words[8])-x2)/rate        point[4]=(float(words[9])-x1)/rate        point[5]=(float(words[10])-x2)/rate        point[6]=(float(words[11])-x1)/rate        point[7]=(float(words[12])-x2)/rate        point[8]=(float(words[13])-x1)/rate        point[9]=(float(words[14])-x2)/rate        imcrop = cv2.resize(imcrop,(w,h))        #原始图像        fid.write(str(count)+'.jpg')        for i in range(0,10,2):            #cv2.circle(imcrop, (int(point[i]),int(point[i+1])), 5, [0,0,255])              fid.write('\t'+str(point[i]))            fid.write('\t'+str(point[i+1]))        fid.write('\n')          # 翻转图像        # @cyq0122 指出,左右眼睛需要交换,嘴巴也一样。        imcrop_flip = cv2.flip(imcrop,1)        fid.write(str(count)+'_flip.jpg')              fid.write('\t'+str(w-point[2]-1))        fid.write('\t'+str(point[3]))        fid.write('\t'+str(w-point[0]-1))        fid.write('\t'+str(point[1]))        fid.write('\t'+str(w-point[4]-1))        fid.write('\t'+str(point[5]))        fid.write('\t'+str(w-point[8]-1))        fid.write('\t'+str(point[9]))        fid.write('\t'+str(w-point[6]-1))        fid.write('\t'+str(point[7]))        fid.write('\n')         #skimage.io.imsave(savePath+filename,imcrop)        #cv2.imwrite(savePath+filename,imcrop)        cv2.imwrite(savePath+str(count)+'_flip.jpg',imcrop_flip)        cv2.imwrite(savePath+str(count)+'.jpg',imcrop)        count = count + 1    fid.close()        #print words[0]        #print words[1]if __name__ == "__main__":    #train    w = 39    h = 39    filelist=r"F:\Dataset\FacePoints\train\trainImageList.txt"    filelistesave = r"F:\\MyDataset\\FacePoint\\train39X39\\train.list"    savePath='F:\\MyDataset\\FacePoint\\train39X39\\'    face_prepare(filelist,filelistesave,savePath,w,h)    filelist=r"F:\Dataset\FacePoints\train\testImageList.txt"    filelistesave = r"F:\\MyDataset\\FacePoint\\test39X39\\test.list"    savePath='F:\\MyDataset\\FacePoint\\test39X39\\'    face_prepare(filelist,filelistesave,savePath,w,h)    #face_draw_point(filelist,savePath)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138

数据转化

跟以往的图像分类采用LMDB或者LevelDB 作为数据处理不同,这里我们的标签是一个向量,而不是一个值,所以不能直接用LMDB来作为存储,还好,caffe提供了另外一种数据存储方法来处理这种需求,那就是HDF5, 直观的差别就在于LMDB的标签是一个数,而HDF5 的标签可以是任意(blob),也就是说对于分类任务,我们也可以采样HDF5作为存储的数据模式,但是HDF5直接读取文件,相比LMDB 速度上有所损失。

脚本如下:

# -*- coding: utf-8 -*-"""Created on Wed Apr 29 20:53:14 2015@author: crw"""import osimport randomimport h5pyimport numpy as npfrom skimage import iofrom skimage import transform as tftrainpairlist=''testpairlist=''def savehdf5(X,Y,filename):    with h5py.File(filename, 'w') as f:        f['data'] = X        f['label'] = Y        print 'having saving a hdf5 file !'def convert(source_path,pairlist,savepath,hdf5list,w,h):    '''    @brief: 将图像列表里的图像转化为矩阵,    @return: X,Y    '''    step = 5000    fid=open(pairlist)    lines= fid.readlines()    fid.close()    X=np.empty((step,3,w,h),dtype=np.float)    Y=np.empty((step,10,1,1),dtype=np.float)    i=0    t=1    #记得HDF5需要实现shuffle.    random.shuffle(lines)    for line in lines:        words=line.split('\t')        inputimage=words[0]        #image  标签        points = np.zeros((10,))        points[0]=float(words[1])        points[1]=float(words[2])        points[2]=float(words[3])        points[3]=float(words[4])        points[4]=float(words[5])        points[5]=float(words[6])        points[6]=float(words[7])        points[7]=float(words[8])        points[8]=float(words[9])        points[9]=float(words[10])        im=io.imread(source_path+inputimage,as_grey=False)        im=tf.resize(im,(w,h))        X[i,0,:,:]=im[:,:,0]        X[i,1,:,:]=im[:,:,1]        X[i,2,:,:]=im[:,:,2]        Y[i,:,0,0]=points        i=i+1        if i==step:            filename = os.path.join(savepath, str(t)+ '.h5')            savehdf5(X,Y,filename)            with open(os.path.join(savepath,hdf5list), 'a') as f:                f.write(filename + '\n')            i=0            t=t+1    if i > 0:        filename = os.path.join(savepath, str(t)+ '.h5')        savehdf5(X[0:i,:,:,:],Y[0:i,:,:,:],filename)        with open(os.path.join(savepath,hdf5list), 'a') as f:            f.write(filename + '\n')if __name__=='__main__':    w=39    h=39    source_path = '/media/crw/MyBook/MyDataset/FacePoint/test39X39/'    save_path = '/media/crw/MyBook/TrainData/HDF5/FacePoint/10000_39X39/test/'    hdf5list='/media/crw/MyBook/TrainData/HDF5/FacePoint/10000_39X39/test/test.txt'    filelist = '/media/crw/MyBook/MyDataset/FacePoint/test39X39/test.list'    convert(source_path,filelist,save_path,hdf5list,w,h)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

网络结构

数据处理好之后,就可以开始设计网络结构了。
网络结构采样的是参照[1]的网络结构,相比于其它的大型的网络的特点在于:
1,输入图像小。
2,权值非共享。
这样的网络相比ImageNet 上的那些模型的优点很明显,参数比较少,学习相对快一些,

图片1:是论文中描绘的网络结构:


图片2:是caffe实现的网络结构:

需要注意的是,caffe 的master分支是没有local 层的,这个local 层去年(Caffe Local)就已经请求合并,然而由于各种原因却一直未能合入正式的版本。大家可以从上面那个链接里面clone 版本进行实验。

实验结果

正确的结果


失败的结果
//update 2015.11.19://由于之前实验人脸镜面对称的时候,没有将左右眼睛和嘴巴的坐标也对换,导致嘴巴和眼睛都不准的情况出现。感谢 @cyq0122 指出

实验分析

用深度学习来做回归任务,很容易出现回归到均值的问题,在人脸关键点检测的任务中,就是检测到的人脸点是所有值的平均值。在上面失败的例子中,两只眼睛和嘴巴预测的都比较靠近。//修正bug 过后就不会出现这个问题了。

这篇文章中,我只是尝试复现人脸关键点检测文章[1]的第一步,后面有时间的话,也会考虑用caffe 复现所有的结果。

要想完全的复现文章的结果,还需要:
1,级联,从粗到细的检测
2,训练多个网络取平均值(各个网络的输入图像块不一样)

代码托管

DeepFace GitHub 托管 欢迎提改进建议~

//update 2015.11.18:添加了数据处理的python文件。

引用

[1] Y. Sun, X. Wang, and X. Tang. Deep Convolutional Network Cascade for Facial Point Detection. In Proceedings of IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2013. 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
OpenCV基于Landmark实现人脸交换
一个模仿LED的控件
1106
一个类虚函数表,虚表指针到底有几个
文档中心
DeepID:Python基于Caffe的DeepID2实现人脸识别的简介、实现之详细攻略
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服