如果关注Kaggle 机器学习项目的同学,一定很熟悉人脸关键点检测这个任务,在2013 年的时候,ICML举办一个的challgene,现在放在kaggle 上作为 一种最常规kaggle入门任务而存在。
本文的主要目的在于验证深度学习模型在人脸点检测效果,踩踩里面的坑。
人脸关键点检测,也称之为人脸点检测,是在一张已经被人脸检测器检测到的人脸图像中,再进一步检测出五官等关键点的二维坐标信息,以便于后期的人脸对齐(face alignment)任务。
根据不同的任务,需要检测的关键点数目有多有少,有些仅要求检测2只眼睛的坐标位置,有些要求检测眼睛、嘴巴、鼻子的5个坐标位置,还有更多的,68个位置,它包含了五官的轮廓信息。
如图所示:
根据任务,可以把要学习的模型函数表示为:
这是一个典型的回归问题,可以采用最简单的平方误差损失函数,然后用机器学习方法学习这个模型。
其中
很显然,也很容易的就将该任务放到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)
跟以往的图像分类采用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]的网络结构,相比于其它的大型的网络的特点在于:
1,输入图像小。
2,权值非共享。
这样的网络相比ImageNet 上的那些模型的优点很明显,参数比较少,学习相对快一些,
图片1:是论文中描绘的网络结构:
需要注意的是,caffe 的master分支是没有local 层的,这个local 层去年(Caffe Local)就已经请求合并,然而由于各种原因却一直未能合入正式的版本。大家可以从上面那个链接里面clone 版本进行实验。
正确的结果
用深度学习来做回归任务,很容易出现回归到均值的问题,在人脸关键点检测的任务中,就是检测到的人脸点是所有值的平均值。在上面失败的例子中,两只眼睛和嘴巴预测的都比较靠近。//修正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.
联系客服