【Caffe实践】基于Caffe的人脸检测实现

jopen 9年前

0. 引言

深度学习可以说是在人脸分析相关领域遍地开花,近年来在人脸识别,深度学习在人脸检测,人脸关键点检测中有很广泛的应用,这篇文章中,初步实现了基于深度学习CNN的人脸检测。

1. 方法讨论

深度学习一般没有进行直接的检测,现有的检测大多都是基于分类的检测,主要的方法有两种:

1.1. 基于滑动窗口的分类

最典型的方法就是OverFeat那一套,其主要的方法是:对于每一个尺度、每一个可能的滑动窗口,进行分类。其主要的缺点是:对于稍微大一点的图像,滑动窗口往往有好几百万个之多,所以直接利用这个方法往往速度比较的慢。

如果只是对每一个滑动窗口进行分类的话,那速度的确会变得非常的慢,但是,卷积有一个显著的优点就是权值共享,它可以很好的进行计算结果的重复利用。所以最后基于CNN的全卷积网络速度也不会特别的慢。

1.2. 基于目标显著性方法

最典型的方法是R-CNN那一套,其主要的方法是:先快速的检测可能的目标区域块,然后用训练好的深度网络模型进行特征提取,之后再进行分类。它主要解决的问题就是基于滑动窗口的目标检测方法窗口过多的问题。

然而这种方法可能不适合于人脸检测,因为人脸是属于局部目标,而显著目标检测通常用来检测通用的完整目标区域。

在这里,我实现的是基于滑动窗口的检测方法,利用caffe的机制,直接将训练好了的网络模型转换为全卷积网络,从而实现直接输入任意图像的大小。

2. 实验步骤

2.1. 数据生成

首先是样本的采样,需要的是两类数据,人脸图像和非人脸图像。可以用自己喜欢的方法进行人脸框和非人脸框的选取,并把截取的人脸图像块分别放在face-images 和no-face-images 文件夹中。

在这里需要注意的一点是:如果随机采样,很有可能正负数据及其的不平衡,从而导致网络无法训练,需要特别注意。

紧接着是将数据转换为LMDB,这一点其实挺重要的,直接的文件列表虽然方便,但是训练速度会比LMDB格式的低5倍左右,而且LMDB或者LevelDB支持更多的数据预处理方法。

利用如下脚本:{convert_data_lmdb.sh},可以将数据转化为LMDB。

#!/usr/bin/env sh  # Create the image to lmdb inputs    TOOLS=/home/crw/caffe-master/.build_release/tools    #图像文件的存放位置  TRAIN_DATA_ROOT=/media/crw/MyBook/Dataset/faceImages/  VAL_DATA_ROOT=/media/crw/MyBook/Dataset/faceImages/    IMAGE_LIST_ROOT=./  #LMDB文件的存放位置  ROOT_LMDB=/media/crw/MyBook/TrainData/LMDB/FaceDetection/50000_32X32    # Set RESIZE=true to resize the images to 256x256. Leave as false if images have  # already been resized using another tool.    #是否剪切为相同的大小  RESIZE=true  if $RESIZE; then    RESIZE_HEIGHT=32    RESIZE_WIDTH=32  else    RESIZE_HEIGHT=0    RESIZE_WIDTH=0  fi    if [ ! -d "$TRAIN_DATA_ROOT" ]; then    echo "Error: TRAIN_DATA_ROOT is not a path to a directory: $TRAIN_DATA_ROOT"    echo "Set the TRAIN_DATA_ROOT variable in create_imagenet.sh to the path" \         "where the ImageNet training data is stored."    exit 1  fi    if [ ! -d "$VAL_DATA_ROOT" ]; then    echo "Error: VAL_DATA_ROOT is not a path to a directory: $VAL_DATA_ROOT"    echo "Set the VAL_DATA_ROOT variable in create_imagenet.sh to the path" \         "where the ImageNet validation data is stored."    exit 1  fi    echo "Creating train lmdb..."    GLOG_logtostderr=1 $TOOLS/convert_imageset \      --resize_height=$RESIZE_HEIGHT \      --resize_width=$RESIZE_WIDTH \      --shuffle \      --gray \      $TRAIN_DATA_ROOT \      $IMAGE_LIST_ROOT/train_2.list \      $ROOT_LMDB/train    echo "Creating val lmdb..."    GLOG_logtostderr=1 $TOOLS/convert_imageset \      --resize_height=$RESIZE_HEIGHT \      --resize_width=$RESIZE_WIDTH \      --shuffle \      --gray \      $VAL_DATA_ROOT \      $IMAGE_LIST_ROOT/val_2.list \      $ROOT_LMDB/val    $TOOLS/compute_image_mean $ROOT_LMDB/train \    $ROOT_LMDB/mean.binaryproto    echo "Done."

2.2. 网络配置

由于我们是用来做人脸二分类,所以没有必要训练一个非常大的网络,小一点的就可以,我这边是改进DeepID的网络,采用人脸图像大小是48*48 彩色图像。当然你也可以直接那别人训练好了的网络进行微调处理。

网络结构图如下所示:
网络结构

完整的训练参数及其文件在最后面的链接文件给出。。

2.3. 训练网络

训练网络也跟普通的所有的分类网络训练一样。
配置好相对应的路径和超参数,在当前路径下,运行

./train.sh

由于是二分类,网络收敛的很快,差不多几万个迭代就可以达到99%以上的二分类精度。

3. 测试

3.1. 网络转换

训练好了的人脸二分类器,不能直接应用于人脸检测,需要进行转换为全卷积网络的格式,具体的方法在Caffe官网上有详细的说明,这里不再赘述。

关键代码如下:

def convert_full_conv(model_define,model_weight,model_define_fc,model_weight_fc):      '''      @breif : 将原始网络转换为全卷积模型      @param: model_define,二分类网络定义文件      @param: model_weight,二分类网络训练好的参数      @param: model_define_fc,生成的全卷积网络定义文件      @param: model_weight_fc,转化好的全卷积网络的参数      '''      net = caffe.Net(model_define, model_weight, caffe.TEST)      fc_params = {pr: (net.params[pr][0].data, net.params[pr][1].data) for pr in params}      net_fc = caffe.Net(model_define_fc, model_weight, caffe.TEST)      conv_params = {pr: (net_fc.params[pr][0].data, net_fc.params[pr][1].data) for pr in params_fc}      for pr, pr_conv in zip(params, params_fc):         conv_params[pr_conv][0].flat = fc_params[pr][0].flat  # flat unrolls the arrays         conv_params[pr_conv][1][...] = fc_params[pr][1]      net_fc.save(model_weight_fc)      print 'convert done!'      return net_fc

3.2. 非极大值阈值

直接使用了这个代码,已经实现了非极大值阈值。

3.3. 人脸检测

主要代码如下:

def face_detection_image(net,net_vf,image_name):      '''      @检测单张人脸图像      '''      scales = []      imgs = skimage.io.imread(image_name)      if imgs.ndim==3:              rows,cols,ch = imgs.shape      else:              rows,cols = imgs.shape      #计算需要的检测的尺度因子      min = rows if  rows<=cols  else  cols      max = rows if  rows>=cols  else  cols      # 放大的尺度          delim = 2500/max      while (delim >= 1):          scales.append(delim)          delim=delim-0.5      #缩小的尺度      min = min * factor      factor_count = 1      while(min >= face_w):          scale = pow(factor,  factor_count)          scales.append(scale)          min = min * factor          factor_count += 1      #=========================      #scales.append(1)      total_boxes = []      ###显示热图用      num_scale = len(scales)      s1=int(np.sqrt(num_scale))+1      tt=1      plt.subplot(s1, s1+1, tt)      plt.axis('off')      plt.title("Input Image")      im=caffe.io.load_image(image_name)      plt.imshow(im)      #============      for scale in scales:          w,h = int(rows* scale),int(cols* scale)          scale_img= tf.resize(imgs,(w,h))          #更改网络输入data图像的大小          net.blobs['data'].reshape(1,channel,w,h)          #转换结构          transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})          #transformer.set_mean('data', np.load(caffe_root + 'python/caffe/imagenet/ilsvrc_2012_mean.npy').mean(1).mean(1))          transformer.set_transpose('data', (2,0,1))          transformer.set_channel_swap('data', (2,1,0))          transformer.set_raw_scale('data', raw_scale)          #前馈一次          out = net.forward_all(data=np.asarray([transformer.preprocess('data', scale_img)]))          ###显示热图用          tt=tt+1          plt.subplot(s1, s1+1, tt)          plt.axis('off')          plt.title("sacle: "+ "%.2f" %scale)          plt.imshow(out['prob'][0,map_idx])          #===========          boxes = generateBoundingBox(out['prob'][0,map_idx], scale)          if(boxes):              total_boxes.extend(boxes)      #非极大值抑制      boxes_nms = np.array(total_boxes)      true_boxes1 = nms_max(boxes_nms, overlapThresh=0.3)      true_boxes = nms_average(np.array(true_boxes1), overlapThresh=0.07)      #===================      plt.savefig('heatmap/'+image_name.split('/')[-1])      #在图像中画出检测到的人脸框      fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))      ax.imshow(imgs)      for box in true_boxes:          im_crop = im[box[0]:box[2],box[1]:box[3],:]          if im_crop.shape[0] == 0 or im_crop.shape[1] == 0:              continue          if re_verify(net_vf, im_crop) == True:              rect = mpatches.Rectangle((box[0], box[1]), box[2]-box[0], box[3]-box[1],                  fill=False, edgecolor='red', linewidth=1)              ax.text(box[0], box[1]+20,"{0:.3f}".format(box[4]),color='white', fontsize=6)              ax.add_patch(rect)      plt.savefig('result/'+image_name.split('/')[-1])      plt.close()      return out['prob'][0,map_idx]

4. 实验结果

4.1. 响应图

其中,颜色越红的地方出现就是检测器判断人脸出现的地方。

响应图1
这里写图片描述
这里写图片描述
这里写图片描述

4.2 检测结果图

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

这里面已经设置了比较高的阈值,不然误检率会很高。

5. 讨论

1,阈值的设定,是在准确率和召回率之前的权衡。
2,基于以上方法,定位还不够准确。

所有代码

地址:Github代码

PS: 如果对你有帮助,还请点个star吧

至此,完成了基于Caffe的人脸检测、人脸点检测、人脸识别的基本工作,后面还需要努力搞创新,come on ~

来自: http://blog.csdn.net/chenriwei2/article/details/50321085