当前位置:   article > 正文

MTCNN人脸检测---PNet网络训练_p net卷积

p net卷积

前言

本文主要介绍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代码如下:

  1. with slim.arg_scope([slim.conv2d],
  2. activation_fn=prelu,
  3. weights_initializer=slim.xavier_initializer(),
  4. biases_initializer=tf.zeros_initializer(),
  5. weights_regularizer=slim.l2_regularizer(0.0005),
  6. padding='valid'):
  7. net = slim.conv2d(inputs, 10, 3, stride=1,scope='conv1')
  8. net = slim.max_pool2d(net, kernel_size=[2,2], stride=2, scope='pool1', padding='SAME')
  9. net = slim.conv2d(net,num_outputs=16,kernel_size=[3,3],stride=1,scope='conv2')
  10. net = slim.conv2d(net,num_outputs=32,kernel_size=[3,3],stride=1,scope='conv3')
  11. conv4_1 = slim.conv2d(net,num_outputs=2,kernel_size=[1,1],stride=1,scope='conv4_1',activation_fn=tf.nn.softmax)
  12. bbox_pred = slim.conv2d(net,num_outputs=4,kernel_size=[1,1],stride=1,scope='conv4_2',activation_fn=None)
  13. 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定义一个卷积操作一般采用如下代码:

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

上述代码表示,输入为height*width*64,卷积大小为3*3*64,卷积核个数为128,stride为1*1,偏置项为bias,激活函数为relu。

采用slim接口可以简化为如下代码,简化了开发:

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

对TensorFlow的每个操作需要具体指定其中的参数,如下所示:

  1. padding = 'SAME'
  2. initializer = tf.truncated_normal_initializer(stddev=0.01)
  3. regularizer = slim.l2_regularizer(0.0005)
  4. net = slim.conv2d(inputs, 64, [11, 11], 4,
  5. padding=padding,
  6. weights_initializer=initializer,
  7. weights_regularizer=regularizer,
  8. scope='conv1')
  9. net = slim.conv2d(net, 128, [11, 11],
  10. padding='VALID',
  11. weights_initializer=initializer,
  12. weights_regularizer=regularizer,
  13. scope='conv2')
  14. net = slim.conv2d(net, 256, [11, 11],
  15. padding=padding,
  16. weights_initializer=initializer,
  17. weights_regularizer=regularizer,
  18. scope='conv3')

这样会有大量的重复参数设定,可以采用arg_scope组件,想用scope内的操作,使用相同的参数设定。从而简化参数的设定流程,简化开发,如下所示:

  1. with slim.arg_scope([slim.conv2d], padding='SAME',
  2. weights_initializer=tf.truncated_normal_initializer(stddev=0.01)
  3. weights_regularizer=slim.l2_regularizer(0.0005)):
  4. net = slim.conv2d(inputs, 64, [11, 11], scope='conv1')
  5. net = slim.conv2d(net, 128, [11, 11], padding='VALID', scope='conv2')
  6. 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值,如下所示:

  1. #分类loss值
  2. cls_prob = tf.squeeze(conv4_1,[1,2],name='cls_prob')
  3. cls_loss = cls_ohem(cls_prob,label)
  4. #回归框loss值
  5. bbox_pred = tf.squeeze(bbox_pred,[1,2],name='bbox_pred')
  6. bbox_loss = bbox_ohem(bbox_pred,bbox_target,label)
  7. #面部轮廓loss值
  8. landmark_pred = tf.squeeze(landmark_pred,[1,2],name="landmark_pred")
  9. landmark_loss = landmark_ohem(landmark_pred,landmark_target,label)
  10. #L2loss值
  11. L2_loss = tf.add_n(slim.losses.get_regularization_losses())
  12. #total loss
  13. total_loss=radio_cls_loss*cls_loss_op + radio_bbox_loss*bbox_loss_op + radio_landmark_loss*landmark_loss_op + L2_loss_op
  14. #优化操作
  15. optimizer = tf.train.MomentumOptimizer(lr, 0.9)
  16. train_op = optimizer.minimize(loss, global_step)

模型推理

由于RNet是一个全卷积网络,所以当作Inference的时候输入数据可以是任意大小的图片。这样网络最后的输出就不是一个1*1大小的特征图了,而是一个H*W大小的特征网格。该特征网格每个网格的坐标表示对应一个回归框的位置信息,如下图所示:

其中右面是PNet网络生成的特征图,左边是原始图片中对应的回归框坐标。原始图片中回归框坐标需要经过反向运算,计算方式如下,其中cellSize=12,是因为12*12的图片进去后变成1*1,;stride=2是因为几层卷积中只有一个stride为2

  1. stride=2 (max pool, stride=2)
  2. cellSize=12 (one cell size equals 12)
  3. scale为图片的缩放比例
  4. x1= (stride*1)/scale,
  5. y1= (stride*1)/scale,
  6. x2= ((stride*1)+cellSize)/scale
  7. y2= ((stride*1)+cellSize)/scale

根据(x1, y1), (x2, y2)及该cell对应的reg(回归框的值),即可算出回归款的具体坐标。回归框的坐标的计算方式如下所示:

  1. stride = 2
  2. cellsize = 12
  3. t_index = np.where(cls_map > threshold)
  4. # find nothing
  5. if t_index[0].size == 0:
  6. return np.array([])
  7. #offset
  8. dx1, dy1, dx2, dy2 = [reg[t_index[0], t_index[1], i] for i in range(4)]
  9. reg = np.array([dx1, dy1, dx2, dy2])
  10. score = cls_map[t_index[0], t_index[1]]
  11. boundingbox = np.vstack([np.round((stride * t_index[1]) / scale),
  12. np.round((stride * t_index[0]) / scale),
  13. np.round((stride * t_index[1] + cellsize) / scale),
  14. np.round((stride * t_index[0] + cellsize) / scale),
  15. score,
  16. reg])

回归框的非极大值抑制

由上述步骤,可以看到一个原始图片会产生大量的回归框,那么到底要把那个回归框让RNet继续训练呢?这里采用非极大值抑制方法(NMS),该算法的主要思想是:将所有框的得分排序,选中最高分及其对应的框;遍历其余的框,如果和当前最高分框的重叠面积(IOU)大于一定阈值,我们就将框删除;从未处理的框中继续选一个得分最高的,重复上述过程。示例代码如下:

  1. x1 = dets[:, 0]
  2. y1 = dets[:, 1]
  3. x2 = dets[:, 2]
  4. y2 = dets[:, 3]
  5. scores = dets[:, 4]
  6. areas = (x2 - x1 + 1) * (y2 - y1 + 1)
  7. order = scores.argsort()[::-1]
  8. keep = []
  9. while order.size > 0:
  10. i = order[0]
  11. keep.append(i)
  12. xx1 = np.maximum(x1[i], x1[order[1:]])
  13. yy1 = np.maximum(y1[i], y1[order[1:]])
  14. xx2 = np.minimum(x2[i], x2[order[1:]])
  15. yy2 = np.minimum(y2[i], y2[order[1:]])
  16. w = np.maximum(0.0, xx2 - xx1 + 1)
  17. h = np.maximum(0.0, yy2 - yy1 + 1)
  18. inter = w * h
  19. if mode == "Union":
  20. ovr = inter / (areas[i] + areas[order[1:]] - inter)
  21. elif mode == "Minimum":
  22. ovr = inter / np.minimum(areas[i], areas[order[1:]])
  23. #keep
  24. inds = np.where(ovr <= thresh)[0]
  25. order = order[inds + 1]

这样我们就找到了要RNet继续训练(Refine)的回归框数据了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/349786
推荐阅读
相关标签
  

闽ICP备14008679号