发新帖

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

[复制链接]
3298 4
博客链接:http://blog.csdn.net/chenriwei2/article/details/50321085
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。
[C++] 纯文本查看 复制代码
#!/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 彩色图像。当然你也可以直接那别人训练好了的网络进行微调处理。
网络结构图如下所示:
20151215203246288.jpg


完整的训练参数及其文件在最后面的链接文件给出。。
2.3. 训练网络训练网络也跟普通的所有的分类网络训练一样。
    配置好相对应的路径和超参数,在当前路径下,运行  
./train.sh
  • 1
由于是二分类,网络收敛的很快,差不多几万个迭代就可以达到99%以上的二分类精度。
3. 测试3.1. 网络转换训练好了的人脸二分类器,不能直接应用于人脸检测,需要进行转换为全卷积网络的格式,具体的方法在Caffe官网上有详细的说明,这里不再赘述。
关键代码如下:
[Shell] 纯文本查看 复制代码
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. 人脸检测主要代码如下:
[Shell] 纯文本查看 复制代码
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. 响应图其中,颜色越红的地方出现就是检测器判断人脸出现的地方。
QQ截图20151230233634.jpg
QQ截图20151230233641.jpg
QQ截图20151230233649.jpg
QQ截图20151230233702.jpg
4.2 检测结果图
QQ截图20151230233709.jpg
QQ截图20151230233717.jpg
QQ截图20151230233726.jpg
这里面已经设置了比较高的阈值,不然误检率会很高。
5. 讨论1,阈值的设定,是在准确率和召回率之前的权衡。
2,基于以上方法,定位还不够准确。
所有代码地址:Github代码
PS: 如果对你有帮助,还请点个star吧
至此,完成了基于Caffe的人脸检测、人脸点检测、人脸识别的基本工作,后面还需要努力搞创新,come on ~


举报 使用道具

回复

精彩评论4

bowrain  中级会员  发表于 2015-12-31 23:25:21 | 显示全部楼层
不错,谢谢分享

举报 使用道具

回复 支持 反对
wqvbjhc  新手上路  发表于 2016-1-6 13:58:26 | 显示全部楼层
不错。原来caffe还能做人脸检测

举报 使用道具

回复 支持 反对
handspeaker  注册会员  发表于 2016-1-12 14:40:20 | 显示全部楼层
赞,非常有启发

举报 使用道具

回复 支持 反对
mychina  新手上路  发表于 2016-9-14 14:13:07 | 显示全部楼层
这个检测效果一般啊,有些不太难得没检出来。

举报 使用道具

回复 支持 反对
*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

更多

关注我们

QQ:448109455 周一至周日8:30-20:30
快速回复 返回顶部 返回列表