face detection[Multi-view face detection&& MTCNN]



因為這兩篇論文感覺內容較短,故而合並到一個博文中。

Multi-view face detection

本文來自《Multi-view Face Detection Using Deep Convolutional Neural Networks》的解讀。時間線是2015年4月。

本文考慮的是多角度的人臉檢測問題。在當前已經有很多這方面的工作,而當前最好的方法都需要對人臉關鍵點進行標注,如TSM,或者需要對人臉姿態進行標注,同時還需要訓練十幾個模型,從而能夠在所有方向上抓取所有的人臉,例如HeadHunter方法中需要22個模型。而本文提出深度密度人臉檢測(deep dense face detector,DDFD),不需要姿態或者關鍵點標注,而且能夠用一個單一的模型區抓取各個方向上的人臉。而且不需要額外的組件,比如分割,候選框回歸,或者SVM分類器等等。此外,還分析了本文的方法:發現

  • 1)所提出的方法能夠從不同的角度檢測面部,並且可以在一定程度上處理遮擋;
  • 2)似乎訓練集中正樣本的分布與本文提出的人臉檢測器的得分之間有一定相關性。

后者表明,通過使用更好的采樣策略和更復雜的數據增強技術,可以進一步提高該模型的性能。

0.引言

2001年,Viola和Jones當初發明的級聯人臉檢測器,將人臉檢測的速度大大提升,算是人臉發展史的一個里程碑。然而他們的方法卻只能處理差不多是正臉且正向的人臉,而對不同方向不同姿態的人臉就沒辦法了。

對於這二十幾年在多角度人臉檢測上的工作,大致可以分成以下三個類別:

  • 基於級聯的:這些方法多是在Viola和Jones檢測器上進行擴展;
  • 基於DPM的:這些方法基於可變形部件模型,其中人臉被定義為不同部件的組合。 這些部件是通過無監督或有監督訓練定義的,並且訓練潛在SVM分類器以及這些部件之間的幾何關系。 這些人臉檢測器對部分遮擋是比較魯棒,因為即使某些部件不存在,它們也可以探測到其他部件並組合成人臉。 然而,這些方法是計算密集型的
  • 因為1)它們需要為每個候選位置求解潛在的SVM;
  • 2)必須訓練和組合多個DPM以實現最好的效果。 此外,在某些情況下,基於DPM的模型需要用於訓練的人臉關鍵點標注數據:
  • 基於神經網絡的:使用神經網絡來做人臉檢測的歷史也是挺長的。

多角度人臉檢測中的難點是傳統模型使用的特征不夠魯棒,不足以表征不同姿態的人臉,因此導致分類器也無法正確分類。然而隨着DL的發展,這個問題被不斷的逼近解決。本文提出的單一神經網絡模型,就是不需要額外的關鍵點和姿態標注數據,且不需要引入額外的如SVM的分類器。

1. 本文方法

首先准備數據對AlexNet網絡進行微調,然后通過對正樣本進行裁剪然后使用IOU超過50%的作為正樣本填充,並通過隨機翻轉等打到了一共200K個正樣本和20百萬個負樣本。然后統一縮放到227x227,並用來微調一個經過預訓練的Alexnet。然后作者采用了划窗的方法,不斷的從輸入圖片中提取圖片塊,先經過Alexnet做特征映射,然后將全連接層reshape成二維的,再通過一個人臉分類器。該人臉分類器是由一個5層CNN+3層全連接層組成的網絡。

ps:個人認為,就是不斷的划窗,然后對一堆窗口進行分類是否是人臉,然后再進行NMS


MTCNN

0 引言

MTCNN的靈感來自《A convolutional neural network cascade for face detection》,凱鵬認為,

  • 該作者卷積層中的濾波器缺少多樣化限制了模型的判別能力;
  • 相比其他多累目標檢測和分類任務,人臉檢測就是一個極具挑戰的二分類任務,所以每一層需要更少數量的濾波器。

所以凱鵬減少了濾波器的個數,並將5x5的大小變成3x3的大小,然后加深了網絡的通道數量。

上圖就是MTCNN的網絡層與級聯CNN的網絡層的對比。

MTCNN主要貢獻就是一個框架可以同時預測人臉區域並且同時預測人臉關鍵點,且能夠在線進行硬樣本的挖掘,從而提升性能。通過采用多任務學習將級聯CNN進行統一起來,該提出的CNN框架中包含三個階段:

  • 通過一個淺層CNN快速的生成候選框;
  • 然后通過一個更復雜的CNN拒絕大量的非人臉框;
  • 最后使用一個更強勁的CNN去再次調整結果,並輸出5個關鍵點位置。

1 結構

這里我們先給出MTCNN的操作流程圖和對應的網絡結構圖

圖1.1 MTCNN操作流程圖

圖1.2 MTCNN的網絡結構圖
如圖1.1所示,給定一張圖片,先對圖片進行金字塔構建,保證整個網絡結構的尺度不變性。

  • 階段1:利用一個全卷積網絡,叫做proposal network(P-net),用於獲取候選人臉窗口和他們的框回歸向量。然后通過估計的邊界框回歸向量對候選框進行校正。然后用NMS來融合高度重疊的候選框。
  • 階段2:所有的候選框被送入另一個CNN中,叫做Refine Network(R-Net),該網絡可以拒絕大量的假候選框,基於邊界框回歸進行校正,並執行NMS;
  • 階段3:該階段相似於第二個階段,但是在該階段中,我們通過更多的有監督信息去識別人臉區域,最后,該網絡會輸出5個人臉關鍵點位置。

2 訓練過程

MTCNN用了三個任務去訓練整個CNN檢測器:

  • 是否是人臉的分類;
  • 邊界框的回歸;
  • 人臉關鍵點的定位。

2.1 人臉分類

這是一個二分類問題,那么采用交叉熵loss:

這里\(p_i\)是網絡預測\(x_i\)是人臉的概率,其中\(y_j^{det}\in {0,1}\)是ground-truth

2.2 邊界框回歸

對每個候選框,都預測基於最近的ground truth的偏移量(邊界框的【左上角的坐標,寬,高】四個量),這是一個回歸問題,使用歐式距離:

其中\(\hat y_j^{box}\)是網絡預測的結果;\(y_i^{box}\)是ground-truth;

2.3 人臉關鍵點定位

和邊界框回歸任務一樣,采用歐式距離進行回歸

這里\(\hat y_i^{landmark}\)是人臉關鍵點的預測值;\(y_i^{landmark}\)是ground truth。

2.4 多源訓練

因為在每個CNN中有不同的任務存在,所以這里在學習過程中也有不同類型的訓練樣本,比如人臉,非人臉,半對齊的人臉等等。這種情況下,上面三個公式在某些情況下就不能完全使用,比如對背景區域采用上,就只啟動\(L_i^{det}\),並直接將其他兩個loss置0。這是通過采用類型指示器完成的,如果將上述三個loss函數統一起來,就瑞下圖:

這里\(N\)是樣本個數,\(\alpha_j\)表示任務重要程度,這里使用的值是:

  • 在P-net和R-net中:\(\alpha_{det}=1,\alpha_{box}=0.5,\alpha_{landmark}=0.5\)
  • 在O-net中:\(\alpha_{det}=1,\alpha_{box}=0.5,\alpha_{landmark}=1\)

其中\(\beta_j^j\in {0,1}\)是采樣類型指示器。

2.5 在線硬樣本挖掘

不同於傳統的,基於原始分類器訓練后的硬樣本挖掘,這里使用的是在線硬樣本挖掘。在每個mini-batch中,先基於所有樣本前向一次,並計算loss值,然后進行排序,選擇前70%的作為硬樣本。這樣只需要計算這部分硬樣本的BP,不需要計算所有樣本的BP。其內在含義就是,如果loss值小意味着當前樣本擬合的不錯了,就不需要訓練了,主要就是關注分類嚴重錯誤的那些樣本
我們這里比較關心,通過圖像金字塔對原圖進行縮放之后,是如何經過整個Pnet,Rnet和Onet的,有兩種方法:

  • 圖像金字塔只經過Pnet,然后進行融合結果;
  • 每一個尺度的圖像經過所有三個網絡,然后最后再做結果融合。

這里我們調試了下pangyupo/mxnet_mtcnn_face_detection的代碼。





從上圖可以知道,首先計算多個縮放因子,然后一個縮放因子對應一個進程進行處理,最后將結果通過除以縮放因子,將結果還原到scale=1的原圖空間中。即圖像金字塔只存在於Pnet過程中。

# https://github.com/pangyupo/mxnet_mtcnn_face_detection/blob/master/helper.py
def generate_bbox(map, reg, scale, threshold):
     """
         generate bbox from feature map
     Parameters:
     ----------
         map: numpy array , n x m x 1
             detect score for each position
         reg: numpy array , n x m x 4
             bbox
         scale: float number
             scale of this detection
         threshold: float number
             detect threshold
     Returns:
     -------
         bbox array
     """
     stride = 2
     cellsize = 12

     t_index = np.where(map>threshold)

     # find nothing
     if t_index[0].size == 0:
         return np.array([])

     dx1, dy1, dx2, dy2 = [reg[0, i, t_index[0], t_index[1]] for i in range(4)]

     reg = np.array([dx1, dy1, dx2, dy2])
     score = map[t_index[0], t_index[1]]
     # 獲取當前結果之后,通過下面的除以scale,將結果映射回scale=1的原圖中。
     boundingbox = np.vstack([np.round((stride*t_index[1]+1)/scale),
                              np.round((stride*t_index[0]+1)/scale),
                              np.round((stride*t_index[1]+1+cellsize)/scale),
                              np.round((stride*t_index[0]+1+cellsize)/scale),
                              score,
                              reg])

     return boundingbox.T


def detect_first_stage(img, net, scale, threshold):
    """
        run PNet for first stage

    Parameters:
    ----------
        img: numpy array, bgr order
            input image
        scale: float number
            how much should the input image scale
        net: PNet
            worker
    Returns:
    -------
        total_boxes : bboxes
    """
    height, width, _ = img.shape
    hs = int(math.ceil(height * scale))
    ws = int(math.ceil(width * scale))

    im_data = cv2.resize(img, (ws,hs)) # 基於縮放因子對圖片進行縮放

    # adjust for the network input
    input_buf = adjust_input(im_data)
    output = net.predict(input_buf)    # 獲取PNet網絡的輸出
    '''添加如下代碼    
    print(f'len(output):{len(output)} output[0].shape:{output[0].shape} output[1].shape:{output[1].shape}')
      輸出結果為(下面為4個進程的輸出結果,對應4個不同的縮放因子):

# 第一個結果中的32 61 表示的是對應的划框map中的結果,即此縮放因子下一共有31x61個划框
len(output):2 output[0].shape:(1, 4, 32, 61) output[1].shape:(1, 2, 32, 61) 
len(output):2 output[0].shape:(1, 4, 48, 88) output[1].shape:(1, 2, 48, 88)
len(output):2 output[0].shape:(1, 4, 69, 126) output[1].shape:(1, 2, 69, 126)
len(output):2 output[0].shape:(1, 4, 99, 179) output[1].shape:(1, 2, 99, 179)
通過shape的數量可以判定,P-Net只輸出【邊界框的四個預測值;是否有人臉的兩個預測值】,並不輸出對應的人臉關鍵點位置
然后通過下面的generate_bbox函數,帶上縮放因子統一的將每個縮放因子結果再映射回原圖中,從而完成圖像金字塔的結果融合
    '''
    boxes = generate_bbox(output[1][0,1,:,:], output[0], scale, threshold)

    if boxes.size == 0:
        return None
    # nms
    pick = nms(boxes[:,0:5], 0.5, mode='Union')
    boxes = boxes[pick]
    return boxes


def detect_first_stage_warpper( args ):
    return detect_first_stage(*args)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM