【Caffe實踐】基于Caffe的人臉檢測實現

jopen 8年前發布 | 154K 次閱讀 圖形/圖像處理

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

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!