MTCNN人臉檢測---PNet網路訓練

前言

本文主要介紹MTCNN中PNet的網路結構,訓練方式和BoundingBox的處理方式。PNet的網路結構是一個全卷積的神經網路結構,如下圖所:

輸入是一個12*12大小的圖片,所以訓練前需要把生成的訓練數據(通過生成bounding box,然後把該bounding box 剪切成12*12大小的圖片),轉換成12*12*3的結構。

  1. 通過10個3*3*3的卷積核,2*2的Max Pooling(stride=2)操作,生成10個5*5的特徵圖
  2. 通過16個3*3*10的卷積核,生成16個3*3的特徵圖
  3. 通過32個3*3*16的卷積核,生成32個1*1的特徵圖。
  4. 針對32個1*1的特徵圖,可以通過2個1*1*32的卷積核,生成2個1*1的特徵圖用於分類;4個1*1*32的卷積核,生成4個1*1的特徵圖用於回歸框判斷;10個1*1*32的卷積核,生成10個1*1的特徵圖用於人臉輪廓點的判斷。

模型代碼

PNet是一個全卷積網路,所以Input可以是任意大小的圖片,用來傳入我們要Inference的圖片,但是這個時候Pnet的輸出的就不是1*1大小的特徵圖了,而是一個W*H的特徵圖,每個特徵圖上的網格對應於我們上面所說的(2個分類信息,4個回歸框信息,10個人臉輪廓點信息)。W和H大小的計算,可以根據卷積神經網路W2=(W1-F+2P)/S+1, H2=(H1-F+2P)/S+1的方式遞歸計算出來,當然對於TensorFlow可以直接在程序中列印出最後Tensor的維度。相應的TensorFlow代碼如下:

with slim.arg_scope([slim.conv2d], activation_fn=prelu, weights_initializer=slim.xavier_initializer(), biases_initializer=tf.zeros_initializer(), weights_regularizer=slim.l2_regularizer(0.0005), padding=valid): net = slim.conv2d(inputs, 10, 3, stride=1,scope=conv1) net = slim.max_pool2d(net, kernel_size=[2,2], stride=2, scope=pool1, padding=SAME) net = slim.conv2d(net,num_outputs=16,kernel_size=[3,3],stride=1,scope=conv2) net = slim.conv2d(net,num_outputs=32,kernel_size=[3,3],stride=1,scope=conv3) conv4_1 = slim.conv2d(net,num_outputs=2,kernel_size=[1,1],stride=1,scope=conv4_1,activation_fn=tf.nn.softmax) bbox_pred = slim.conv2d(net,num_outputs=4,kernel_size=[1,1],stride=1,scope=conv4_2,activation_fn=None) landmark_pred = slim.conv2d(net,num_outputs=10,kernel_size=[1,1],stride=1,scope=conv4_3,activation_fn=None)

上述代碼使用了slim API,可參見:

https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim

使用Slim開發TensorFlow程序,增加了程序的易讀性和可維護性,簡化了hyper parameter的調優,使得開發的模型變得通用,集成了計算機視覺裡面的一些常用模型(比如vgg19,alexnet),並且容易擴展複雜的模型。Slim API主要包含如下組件:

  1. arg_scope: 使得TensorFlow多個operations可以使用該參數scope內的默認參數。
  2. data: 規範了數據集定義,預處理,讀取,解碼的相應流程。
  3. evaluation:規範了模型evaluation的常用API。
  4. layers:定義了High level的神經網路層的定義。
  5. learning:規範了模型訓練的常用API。
  6. losses:規範了模型loss值定義的常用API。
  7. metrics:規範了模型評估度量的常用API。
  8. nets:定義了一些常用的網路模型,如alexnet,vgg等。
  9. queues:提供了TensorFlow QueueRunner上下文的管理。
  10. regularizes:規範了權重正則化的使用API。
  11. variables:規範了變數的定義和使用API。

針對上面的PNet模型,介紹下相應的slimAPI:

採用TensorFlow默認API定義一個卷積操作一般採用如下代碼:

input = ...with tf.name_scope(conv1_1) as scope: kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32, stddev=1e-1), name=weights) conv = tf.nn.conv2d(input, kernel, [1, 1, 1, 1], padding=SAME) biases = tf.Variable(tf.constant(0.0, shape=[128], dtype=tf.float32), trainable=True, name=biases) bias = tf.nn.bias_add(conv, biases) conv1 = tf.nn.relu(bias, name=scope)

上述代碼表示,輸入為height*width*64,卷積大小為3*3*64,卷積核個數為128,stride為1*1,偏置項為bias,激活函數為relu。

採用slim介面可以簡化為如下代碼,簡化了開發:

input = ...net = slim.conv2d(input, 128, [3, 3], scope=conv1_1)

對TensorFlow的每個操作需要具體指定其中的參數,如下所示:

padding = SAMEinitializer = tf.truncated_normal_initializer(stddev=0.01)regularizer = slim.l2_regularizer(0.0005)net = slim.conv2d(inputs, 64, [11, 11], 4, padding=padding, weights_initializer=initializer, weights_regularizer=regularizer, scope=conv1)net = slim.conv2d(net, 128, [11, 11], padding=VALID, weights_initializer=initializer, weights_regularizer=regularizer, scope=conv2)net = slim.conv2d(net, 256, [11, 11], padding=padding, weights_initializer=initializer, weights_regularizer=regularizer, scope=conv3)

這樣會有大量的重複參數設定,可以採用arg_scope組件,想用scope內的操作,使用相同的參數設定。從而簡化參數的設定流程,簡化開發,如下所示:

with slim.arg_scope([slim.conv2d], padding=SAME, weights_initializer=tf.truncated_normal_initializer(stddev=0.01) weights_regularizer=slim.l2_regularizer(0.0005)): net = slim.conv2d(inputs, 64, [11, 11], scope=conv1) net = slim.conv2d(net, 128, [11, 11], padding=VALID, scope=conv2) net = slim.conv2d(net, 256, [11, 11], scope=conv3)

上述代碼中,對二維卷積conv2d操作使用相同的weight初始化和正則化參數。

模型訓練

PNet的訓練數據主要由4部分組成,包括正label數據(IOU>0.65,面部輪廓特值為0),負label數據(IOU<0.4,面部輪廓特值為0,回歸框值為0),中間數據(0.4<IOU<0.65,面部輪廓特值為0),面部輪廓數據(回歸框值為0)。把訓練數據輸入到網路中後,依據網路輸出,計算loss值,如下所示:

#分類loss值cls_prob = tf.squeeze(conv4_1,[1,2],name=cls_prob)cls_loss = cls_ohem(cls_prob,label)#回歸框loss值bbox_pred = tf.squeeze(bbox_pred,[1,2],name=bbox_pred)bbox_loss = bbox_ohem(bbox_pred,bbox_target,label)#面部輪廓loss值landmark_pred = tf.squeeze(landmark_pred,[1,2],name="landmark_pred")landmark_loss = landmark_ohem(landmark_pred,landmark_target,label)#L2loss值L2_loss = tf.add_n(slim.losses.get_regularization_losses())#total losstotal_loss=radio_cls_loss*cls_loss_op + radio_bbox_loss*bbox_loss_op + radio_landmark_loss*landmark_loss_op + L2_loss_op#優化操作optimizer = tf.train.MomentumOptimizer(lr, 0.9)train_op = optimizer.minimize(loss, global_step)

模型推理

由於RNet是一個全卷積網路,所以當作Inference的時候輸入數據可以是任意大小的圖片。這樣網路最後的輸出就不是一個1*1大小的特徵圖了,而是一個H*W大小的特徵網格。該特徵網格每個網格的坐標表示對應一個回歸框的位置信息,如下圖所示:

其中右面是PNet網路生成的特徵圖,左邊是原始圖片中對應的回歸框坐標。原始圖片中回歸框坐標需要經過反向運算,計算方式為

stride=2 (max pool, stride=2)cellSize=12 (one cell size equals 12)scale為圖片的縮放比例x1= (stride*1)/scale,y1= (stride*1)/scale,x2= ((stride*1)+cellSize)/scaley2= ((stride*1)+cellSize)/scale

根據(x1, y1), (x2, y2)及該cell對應的reg(回歸框的值),即可算出回歸款的具體坐標。回歸框的坐標的計算方式如下所示:

stride = 2cellsize = 12t_index = np.where(cls_map > threshold)# find nothingif t_index[0].size == 0: return np.array([])#offsetdx1, dy1, dx2, dy2 = [reg[t_index[0], t_index[1], i] for i in range(4)]reg = np.array([dx1, dy1, dx2, dy2])score = cls_map[t_index[0], t_index[1]]boundingbox = np.vstack([np.round((stride * t_index[1]) / scale), np.round((stride * t_index[0]) / scale), np.round((stride * t_index[1] + cellsize) / scale), np.round((stride * t_index[0] + cellsize) / scale), score, reg])

回歸框的非極大值抑制

由上述步驟,可以看到一個原始圖片會產生大量的回歸框,那麼到底要把那個回歸框讓RNet繼續訓練呢?這裡採用非極大值抑制方法(NMS),該演算法的主要思想是:將所有框的得分排序,選中最高分及其對應的框;遍歷其餘的框,如果和當前最高分框的重疊面積(IOU)大於一定閾值,我們就將框刪除;從未處理的框中繼續選一個得分最高的,重複上述過程。示例代碼如下:

x1 = dets[:, 0]y1 = dets[:, 1]x2 = dets[:, 2]y2 = dets[:, 3]scores = dets[:, 4]areas = (x2 - x1 + 1) * (y2 - y1 + 1)order = scores.argsort()[::-1]keep = []while order.size > 0: i = order[0] keep.append(i) xx1 = np.maximum(x1[i], x1[order[1:]]) yy1 = np.maximum(y1[i], y1[order[1:]]) xx2 = np.minimum(x2[i], x2[order[1:]]) yy2 = np.minimum(y2[i], y2[order[1:]]) w = np.maximum(0.0, xx2 - xx1 + 1) h = np.maximum(0.0, yy2 - yy1 + 1) inter = w * h if mode == "Union": ovr = inter / (areas[i] + areas[order[1:]] - inter) elif mode == "Minimum": ovr = inter / np.minimum(areas[i], areas[order[1:]]) #keep inds = np.where(ovr <= thresh)[0] order = order[inds + 1]

這樣我們就找到了要RNet繼續訓練(Refine)的回歸框數據了。


推薦閱讀:

論文解讀--用卷積網路基於視覺顯著性的方法提高人類檢測
基於深度學習的「目標檢測」演算法綜述
yolov3 強勢發布
PASCAL VOC數據集的標註格式
綜述:3D目標檢測於RGB-D(Object detection in RGB-D images)

TAG:深度學習DeepLearning | 人臉識別 | 目標檢測 |