文章目录
对于一些代码块引用到的函数的介绍
Iou函数
这个函数位于unils.py
中,代码如下:
def IoU(box, boxes):
"""Compute IoU between detect box and gt boxes
Parameters:
----------
box: numpy array , shape (5, ): x1, y1, x2, y2, score
predicted boxes
boxes: numpy array, shape (n, 4): x1, y1, x2, y2
input ground truth boxes
Returns:
-------
ovr: numpy.array, shape (n, )
IoU
"""
#函数的传入参数为box(随机裁剪后的框)和boxes(实际人脸框)
'''至于源代码给的box里面有5个参数,我还没有明白,先记在这里。'''
box_area = (box[2] - box[0] + 1) * (box[3] - box[1] + 1)
#计算随机裁剪后的框的面积,因为传入的box是以x1, y1, x2, y2这样的数组形式,所以分别对应着左上角的顶点坐标和右下角的顶点坐标,根据这两个坐
#标点就可以确定出了一个裁剪框,然后横纵坐标的差值的乘积就是随机裁剪框的面积,
'''至于为什么分别加1我暂时没有理解'''
area = (boxes[:, 2] - boxes[:, 0] + 1) * (boxes[:, 3] - boxes[:, 1] + 1)
#同上,得出的是实际的人脸框的面积,但是这里要注意一点,因为一张图片的人脸是一个或者多个,所以说实际的boxes是个n行4列的数组,n>=1,n表示实
#际人脸的个数。故这里用到了boxes[:,2]-boxes[:,0]这样的写法,意思是取出所有维数的第3个元素减去对应的第1个元素,然后加上一,这样就把n个人
#脸对应的各自的面积存进了area这个数组里面
xx1 = np.maximum(box[0], boxes[:, 0])#将随机裁剪框的x1和各个人脸的x1比较,得到较大的xx1
yy1 = np.maximum(box[1], boxes[:, 1])#将随机裁剪框的y1和各个人脸的y1比较,得到较大的yy1
xx2 = np.minimum(box[2], boxes[:, 2])#将随机裁剪框的x2和各个人脸的x2比较,得到较小的xx2
yy2 = np.minimum(box[3], boxes[:, 3])#将随机裁剪框的y2和各个人脸的y2比较,得到较小的yy2
#这样做的目的是得出两个图片交叉重叠区域的矩形的左上角和右下角坐标
# compute the width and height of the bounding box
h = np.maximum(0, xx2 - xx1 + 1) '''原代码这里是宽(w),经过分析发现应为高(h)已经更正'''
w = np.maximum(0, yy2 - yy1 + 1) '''原代码这里是高(h),经过分析发现应为宽(w)已经更正'''
'''是根据这里的像素坐标系的定义(https://jingyan.baidu.com/album/63f2362826ea1c0208ab3dec.html?picindex=3)分析出来的结果'''
#获得0和(xx2-xx1)的最大值,因为当两个裁剪框没有丝毫重叠或者是只是一条高部分重合时,xx2-xx1<=0,有重叠时xx2-xx1>0,这个值就是重叠矩形的高,下面求宽也是一样的原理
inter = w * h #求得重叠区域的面积
ovr = inter / (box_area + area - inter) #重叠区域的面积除以真实人脸框的面积与随机裁剪区域面积的和减去重叠区域的面积就是重合率
return ovr #返回重合率
getDataFromTxt函数
这个函数位于BBox_utils.py
中,代码如下:
def getDataFromTxt(txt,data_path, with_landmark=True):
"""
Generate data from txt file
return [(img_path, bbox, landmark)]
bbox: [left, right, top, bottom]
landmark: [(x1, y1), (x2, y2), ...]
"""
with open(txt, 'r') as fd: #打开txt路径对应的文档
lines = fd.readlines() #读取文档内容
result = [] #先定义一个元组
for line in lines: #读取每一行的内容
line = line.strip() #去掉每一行首尾的换行符
components = line.split(' ') #以空格为分割符分开每一行的元素
img_path = os.path.join(data_path, components[0]).replace('\\','/')
# 将\\替换为/后把图片的路径储存起来
# bounding box, (x1, y1, x2, y2)
#bbox = (components[1], components[2], components[3], components[4])
bbox = (components[1], components[3], components[2], components[4])
#将bbox重新排序变成(x1,x2,y1,y2)的形式
bbox = [float(_) for _ in bbox] #先统一转换坐标为浮点型数据
bbox = list(map(int,bbox)) #再将数据统一转换为整数型
# landmark
if not with_landmark: #with_landmark传入此函数时默认为True
result.append((img_path, BBox(bbox))) #在result列表中加入路径和人脸框的坐标,BBox是一个类,BBox(bbox)返回了一个对象地址
continue
landmark = np.zeros((5, 2)) #先将landmark初始化为5行2列的全0数组
for index in range(0, 5): #获取5个landmark点坐标
rv = (float(components[5+2*index]), float(components[5+2*index+1]))
landmark[index] = rv
#normalize
'''
for index, one in enumerate(landmark):
rv = ((one[0]-bbox[0])/(bbox[2]-bbox[0]), (one[1]-bbox[1])/(bbox[3]-bbox[1]))
landmark[index] = rv
'''
result.append((img_path, BBox(bbox), landmark))
return result
这个函数最后返回了一个列表,里面是关键点图片的路径和对应图片人脸bounding box的地址和5个关键点的坐标。
flip函数
这个函数位于Landmark_utils.py
中,代码如下:
def flip(face, landmark):
"""
flip face
"""
face_flipped_by_x = cv2.flip(face, 1) #将人脸框水平翻转,参数为0时是垂直翻转,为-1时是水平垂直翻转
landmark_ = np.asarray([(1-x, y) for (x, y) in landmark])
landmark_[[0, 1]] = landmark_[[1, 0]]#left eye<->right eye 左右眼坐标的互换
landmark_[[3, 4]] = landmark_[[4, 3]]#left mouth<->right mouth 左右嘴角坐标的互换
return (face_flipped_by_x, landmark_)#
rotate函数
这个函数位于Landmark_utils.py
中,代码如下:
def rotate(img, bbox, landmark, alpha):
"""
given a face with bbox and landmark, rotate with alpha
and return rotated face with bbox, landmark (absolute position)
"""
#对于给定的bbox和landmark,旋转alpha角度,同时返回旋转后的图片和landmark的绝对坐标
center = ((bbox.left+bbox.right)/2, (bbox.top+bbox.bottom)/2) #中心坐标
rot_mat = cv2.getRotationMatrix2D(center, alpha, 1) #逆时针旋转5度,缩放因子是1
#whole image rotate
#pay attention: 3rd param(col*row)
img_rotated_by_alpha = cv2.warpAffine(img, rot_mat,(img.shape[1],img.shape[0]))
landmark_ = np.asarray([(rot_mat[0][0]*x+rot_mat[0][1]*y+rot_mat[0][2],
rot_mat[1][0]*x+rot_mat[1][1]*y+rot_mat[1][2]) for (x, y) in landmark])
#crop face
face = img_rotated_by_alpha[bbox.top:bbox.bottom+1,bbox.left:bbox.right+1]
return (face, landmark_)
'''
这个函数有点看=看不太懂,只知道具体的结果是什么,先摆在这里,日后再逐行注释
'''
_process_image_withoutcoder函数
这个函数位于tfrecord_utils.py
中,代码如下:
def _process_image_withoutcoder(filename):
#print(filename)
image = cv2.imread(filename) #读取图片
#print(type(image))
# transform data into string format
image_data = image.tostring() #构建一个包含ndarray的原始字节数据的字节字符串
assert len(image.shape) == 3 #判断图片的格式是否为(高、宽、通道数)
height = image.shape[0] #传入图片的高
width = image.shape[1] #传入图片的宽
assert image.shape[2] == 3 #判断图片是否为RGB三色通道
# return string data and initial height and width of the image
return image_data, height, width #返回字符串形式的数据和图片原始高宽
_convert_to_example_simple函数
这个函数位于tfrecord_utils.py
中,代码如下:
def _convert_to_example_simple(image_example, image_buffer):
"""
covert to tfrecord file
:param image_example: dict, an image example
:param image_buffer: string, JPEG encoding of RGB image
:param colorspace:
:param channels:
:param image_format:
:return:
Example proto
"""
# filename = str(image_example['filename'])
# class label for the whole image
class_label = image_example['label'] #传入label值(1、0、-1、-2)
bbox = image_example['bbox'] #传入bbox
roi = [bbox['xmin'],bbox['ymin'],bbox['xmax'],bbox['ymax']] #传入坐标的4个值
landmark = [bbox['xlefteye'],bbox['ylefteye'],bbox['xrighteye'],bbox['yrighteye'],bbox['xnose'],bbox['ynose'],
bbox['xleftmouth'],bbox['yleftmouth'],bbox['xrightmouth'],bbox['yrightmouth']]
#传入landmark的10个值
#example
example = tf.train.Example(features=tf.train.Features(feature={
'image/encoded': _bytes_feature(image_buffer), #图片矩阵的字符串形式
'image/label': _int64_feature(class_label), #label
'image/roi': _float_feature(roi), #人脸框4个坐标
'image/landmark': _float_feature(landmark) #landmark的10个坐标
}))
return example
这个函数就是TFRecord格式的数据的制作过程。
read_single_tfrecord函数
这个函数位于read_tfrecord_v2.py
中,代码如下:
def read_single_tfrecord(tfrecord_file, batch_size, net):
# generate a input queue
# each epoch shuffle
#创建一个队列来维护输入文件列表
filename_queue = tf.train.string_input_producer([tfrecord_file],shuffle=True)
# read tfrecord
#读取tfrecord文件
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
image_features = tf.parse_single_example(
serialized_example,
features={
'image/encoded': tf.FixedLenFeature([], tf.string),#one image one record
'image/label': tf.FixedLenFeature([], tf.int64),
'image/roi': tf.FixedLenFeature([4], tf.float32),
'image/landmark': tf.FixedLenFeature([10],tf.float32)
}
)
if net == 'PNet':
image_size = 12
elif net == 'RNet':
image_size = 24
else:
image_size = 48
#tf.decode_raw可以将字符串解析成图像对应的像素数组
image = tf.decode_raw(image_features['image/encoded'], tf.uint8)
#reshape操作
image = tf.reshape(image, [image_size, image_size, 3])
#图像进行归一化,范围为[-1, 1]
image = (tf.cast(image, tf.float32)-127.5) / 128
# image = tf.image.per_image_standardization(image)
#读取label、roi(region of interest)、landmark
label = tf.cast(image_features['image/label'], tf.float32)
roi = tf.cast(image_features['image/roi'],tf.float32)
landmark = tf.cast(image_features['image/landmark'],tf.float32)
#tf.train.batch() 按顺序读取队列中的数据
#batch_size:从队列中提取新的批量大小
#num_threads:线程数量.若批次是不确定 num_threads > 1
#capacity:队列中元素的最大数量
image, label,roi,landmark = tf.train.batch(
[image, label,roi,landmark],
batch_size=batch_size,
num_threads=2,
capacity=1 * batch_size
)
label = tf.reshape(label, [batch_size])
roi = tf.reshape(roi,[batch_size,4])
landmark = tf.reshape(landmark,[batch_size,10])
return image, label, roi,landmark
#返回image, label, roi,landmark
P_Net(net_factory)函数
这个函数位于mtcnn_model.py
中,代码如下:
def P_Net(inputs,label=None,bbox_target=None,landmark_target=None,training=True):
#define common param
#关于slim.arg_scope的用法大家可以自行查找,网上有很多关于这个的介绍
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'):
print(inputs.get_shape())
#输出input的shape
net = slim.conv2d(inputs, 10, 3, stride=1,scope='conv1')
#卷积操作,卷积核的个数是10,卷积核的形式是[3,3],步长为1,其余的参数和上面的slim.arg_scope一样
_activation_summary(net)
#数据的记录(具体的解释看本博客的下一个函数解释)
print(net.get_shape())
#输出net的shape
net = slim.max_pool2d(net, kernel_size=[2,2], stride=2, scope='pool1', padding='SAME')
#池化
_activation_summary(net)
#同上
print(net.get_shape())
#同上
net = slim.conv2d(net,num_outputs=16,kernel_size=[3,3],stride=1,scope='conv2')
#卷积
_activation_summary(net)
#同上
print(net.get_shape())
#同上
net = slim.conv2d(net,num_outputs=32,kernel_size=[3,3],stride=1,scope='conv3')
#卷积
_activation_summary(net)
#同上
print(net.get_shape())
#同上
#batch*H*W*2
conv4_1 = slim.conv2d(net,num_outputs=2,kernel_size=[1,1],stride=1,scope='conv4_1',activation_fn=tf.nn.softmax)
#卷积;这里用了softmax函数,是个二分类的问题
_activation_summary(conv4_1)
#同上
#conv4_1 = slim.conv2d(net,num_outputs=1,kernel_size=[1,1],stride=1,scope='conv4_1',activation_fn=tf.nn.sigmoid)
print (conv4_1.get_shape())
#输出的shape是[batch,1,1,2]
#batch*H*W*4,(bbox的输入shape)
bbox_pred = slim.conv2d(net,num_outputs=4,kernel_size=[1,1],stride=1,scope='conv4_2',activation_fn=None)
_activation_summary(bbox_pred)
print (bbox_pred.get_shape())
#batch*H*W*10(landmark的输入shape)
landmark_pred = slim.conv2d(net,num_outputs=10,kernel_size=[1,1],stride=1,scope='conv4_3',activation_fn=None)
_activation_summary(landmark_pred)
print (landmark_pred.get_shape())
# add projectors for visualization
#cls_prob_original = conv4_1
#bbox_pred_original = bbox_pred
#training=True
if training:
#batch*2
# calculate classification loss
cls_prob = tf.squeeze(conv4_1,[1,2],name='cls_prob')
#tf.squeeze()删除conv4_1中所指定位置大小是1的维度,shape是[batch,2]
cls_loss = cls_ohem(cls_prob,label)
#获得人脸分类训练的loss值
#batch
# cal bounding box error, squared sum error
#获得人脸框训练的loss值
bbox_pred = tf.squeeze(bbox_pred,[1,2],name='bbox_pred')
bbox_loss = bbox_ohem(bbox_pred,bbox_target,label)
#batch*10
#获得关键点训练的loss值
landmark_pred = tf.squeeze(landmark_pred,[1,2],name="landmark_pred")
landmark_loss = landmark_ohem(landmark_pred,landmark_target,label)
#获得正则化损失和分类训练的准确率
accuracy = cal_accuracy(cls_prob,label)
L2_loss = tf.add_n(slim.losses.get_regularization_losses())
return cls_loss,bbox_loss,landmark_loss,L2_loss,accuracy
#test
else:
#when test,batch_size = 1
cls_pro_test = tf.squeeze(conv4_1, axis=0)
bbox_pred_test = tf.squeeze(bbox_pred,axis=0)
landmark_pred_test = tf.squeeze(landmark_pred,axis=0)
return cls_pro_test,bbox_pred_test,landmark_pred_test
这里面用到了_activation_summary函数()、cls_ohem()函数、bbox_ohem函数()、landmark_ohem()函数、cal_accuracy()函数,具体功能介绍在下面。
_activation_summary函数
这个函数位于mtcnn_model.py
中,代码如下:
def _activation_summary(x):
'''
creates a summary provides histogram of activations
creates a summary that measures the sparsity of activations
:param x: Tensor
:return:
'''
tensor_name = x.op.name
print('load summary for : ',tensor_name)
#打印tensor的名字
tf.summary.histogram(tensor_name + '/activations',x)
#以直方图的形式显示tensor在训练过程的值的分布情况
#tf.summary.scalar(tensor_name + '/sparsity', tf.nn.zero_fraction(x))
#construct Pnet
#label:batch
cls_ohem()函数
这个函数位于mtcnn_model.py
中,代码如下:
def cls_ohem(cls_prob, label):
#label = tf.placeholder(tf.float32, shape=[config.BATCH_SIZE], name='label')
#cls_prob的shape是[384,2]
#label的shape是[384]
zeros = tf.zeros_like(label)
#建立一个和label相同shape的全0数组
#label=-1 --> label=0net_factory
#pos -> 1, neg -> 0, others -> 0
#tf.less(x,y):x,y都是一个tensor,返回值是一个bool型的tensor,(逐元素返回是否x<y)
#tf.where(a,b,c)可以简单的认为a为真时,返回b,不然返回c
#所以下面的操作是将正样本的label保持为1,负样本label为0,其他的的两个样本label值为0。
label_filter_invalid = tf.where(tf.less(label,0), zeros, label)
#返回cls_prob中元素的个数,384*2
num_cls_prob = tf.size(cls_prob)
#reshape成768行
cls_prob_reshape = tf.reshape(cls_prob,[num_cls_prob,-1])
#将label_filter_invalid转化为int32类型
label_int = tf.cast(label_filter_invalid,tf.int32)
# get the number of rows of class_prob
#获取class_prob的行数,384
num_row = tf.to_int32(cls_prob.get_shape()[0])
#row = [0,2,4.....]
row = tf.range(num_row)*2
#因为conv4_1输出的的shape是[batch,2],每一张图片经过网络后,再经过softmax函数,输出的的是两个概率值
#第一个值表示非人脸的概率,第二个值表示是人脸的概率,加起来的和为1,此时的label_int仍是[batch]
#只是里面只有pos样本对应的label值是1,其余均为0,所以row + label_int表示的是cls_prob_reshape里面pos样本的索引
indices_ = row + label_int
#使用tf.gather函数将pos样本提取出来,再使用tf.squeeze函数将shape变成(384,)
#此时label_prob是里面有384个概率,是pos样本对应的概率和非pos样本对应的概率
label_prob = tf.squeeze(tf.gather(cls_prob_reshape, indices_))
#是一个二分类问题,使用的交叉熵损失函数。加上1e-10,是为了防止里面的label_prob值太小,输出为负无穷
loss = -tf.log(label_prob+1e-10)
zeros = tf.zeros_like(label_prob, dtype=tf.float32)
ones = tf.ones_like(label_prob,dtype=tf.float32)
# set pos and neg to be 1, rest to be 0
#建立一个valid_inds,里面将正样本和负样本的label设置为1,其余的为0
valid_inds = tf.where(label < zeros,zeros,ones)
# get the number of POS and NEG examples
#获得正样本和负样本的数量
num_valid = tf.reduce_sum(valid_inds)
#获取正样本和负样本数量的百分之70
keep_num = tf.cast(num_valid*num_keep_radio,dtype=tf.int32)
#FILTER OUT PART AND LANDMARK DATA
#去除掉part样本和landmark样本
loss = loss * valid_inds
#得到loss值中大小排前百分之七十的样本
loss,_ = tf.nn.top_k(loss, k=keep_num)
返回loss
return tf.reduce_mean(loss)
此函数的作用就是返回人脸分类任务训练的loss值,其中只用loss值较大的前百分之七十参与反向传播。
bbox_ohem函数()
这个函数位于mtcnn_model.py
中,代码如下:
def bbox_ohem(bbox_pred,bbox_target,label):
'''
:param bbox_pred:
:param bbox_target:
:param label: class label
:return: mean euclidean loss for all the pos and part examples
'''
zeros_index = tf.zeros_like(label, dtype=tf.float32)
ones_index = tf.ones_like(label,dtype=tf.float32)
#获取pos样本和part样本
valid_inds = tf.where(tf.equal(tf.abs(label), 1),ones_index,zeros_index)
#(batch,)
#计算平方和(按行)
square_error = tf.square(bbox_pred-bbox_target)
square_error = tf.reduce_sum(square_error,axis=1)
#计算pos样本和part样本的数量
num_valid = tf.reduce_sum(valid_inds)
keep_num = tf.cast(num_valid, dtype=tf.int32)
#去掉neg样本和landmark样本的平方和
square_error = square_error*valid_inds
# 获取前K个样本的索引,K为pos和part样本的数量
_, k_index = tf.nn.top_k(square_error, k=keep_num)
#将所有pos样本和part样本的平方和提取出来
square_error = tf.gather(square_error, k_index)
#返回均值
return tf.reduce_mean(square_error)
此函数的作用就是返回人脸框回归的损失,用的是pos和part样本
landmark_ohem()函数
这个函数位于mtcnn_model.py
中,代码如下:
def landmark_ohem(landmark_pred,landmark_target,label):
'''
:param landmark_pred:
:param landmark_target:
:param label:
:return: mean euclidean loss
'''
#keep label =-2 then do landmark detection
ones = tf.ones_like(label,dtype=tf.float32)
zeros = tf.zeros_like(label,dtype=tf.float32)
valid_inds = tf.where(tf.equal(label,-2),ones,zeros)
square_error = tf.square(landmark_pred-landmark_target)
square_error = tf.reduce_sum(square_error,axis=1)
num_valid = tf.reduce_sum(valid_inds)
#keep_num = tf.cast(num_valid*num_keep_radio,dtype=tf.int32)
keep_num = tf.cast(num_valid, dtype=tf.int32)
square_error = square_error*valid_inds
_, k_index = tf.nn.top_k(square_error, k=keep_num)
square_error = tf.gather(square_error, k_index)
return tf.reduce_mean(square_error)
此函数的代码解析和上面的bbox_ohem函数()的内容是一样的,就不在这里做详细介绍了。作用就是返回landmark的损失,用的是landmark样本。
cal_accuracy()函数
这个函数位于mtcnn_model.py
中,代码如下:
def cal_accuracy(cls_prob,label):
'''
:param cls_prob:
:param label:
:return:calculate classification accuracy for pos and neg examples only
'''
# get the index of maximum value along axis one from cls_prob
# 0 for negative 1 for positive
#按行返回cls_prob的最大值的索引,索引值为0或者1,因为输出cls_prob有两个值
#第一个值表示非人脸的概率,第二个值表示人脸的概率,所以索引等于0时
#表示这个图片网络预测为非人脸;为1时网络预测这张图片为人脸
pred = tf.argmax(cls_prob,axis=1)
label_int = tf.cast(label,tf.int64)
#tf.greater_equal()函数判断label_int是否大于等于0,返回True或者False
#tf.where函数()返回True值对应的索引,即cond是pos样本和part样本对应的索引
cond = tf.where(tf.greater_equal(label_int,0))
picked = tf.squeeze(cond)
# 获得pos样本和part样本的label
label_picked = tf.gather(label_int,picked)
#获得pos和part的预测值
pred_picked = tf.gather(pred,picked)
#通过tf.equal()函数返回的True或者False值得到网络的预测值是否准确
#将True和Flase转化为1和0求得平均值即得到准确率
accuracy_op = tf.reduce_mean(tf.cast(tf.equal(label_picked,pred_picked),tf.float32))
return accuracy_op
train_model()函数
这个函数位于mtcnn_model.py
中,代码如下:
def train_model(base_lr, loss, data_num):
"""
train model
:param base_lr: base learning rate = 0.01
:param loss: loss
:param data_num = 1429422
:return:
train_op, lr_op
"""
lr_factor = 0.1
global_step = tf.Variable(0, trainable=False)
#config.LR_EPOCH = [6,14,20]
#boundaried [num_batch_1,num_batch_2,num_batch_3]
boundaries = [int(epoch * data_num / config.BATCH_SIZE) for epoch in config.LR_EPOCH]
#lr_values[0.01,0.001,0.0001,0.00001]
lr_values = [base_lr * (lr_factor ** x) for x in range(0, len(config.LR_EPOCH) + 1)]
#控制学习率,表示在(0,nun_batch_1)之间学习率是lr_values[0],(num_batch_1,num_batch_2]之间学习率是lr_values[1]
#(num_batch_2,num_batch_3]之间学习率是lr_values[2],(num_batch_3)之后的学习率是lr_values[3]
#这样做的意义是将学习率分块使用,不同的训练周期使用不同的学习率
lr_op = tf.train.piecewise_constant(global_step, boundaries, lr_values)
#使用 tf.train.MomentumOptimizer.minimize优化器
optimizer = tf.train.MomentumOptimizer(lr_op, 0.9)
train_op = optimizer.minimize(loss, global_step)
return train_op, lr_op
random_flip_images()函数
这个函数位于trian.py
中,代码如下:
def random_flip_images(image_batch,label_batch,landmark_batch):
#mirror
if random.choice([0,1]) > 0:
num_images = image_batch.shape[0]
fliplandmarkindexes = np.where(label_batch==-2)[0]
flipposindexes = np.where(label_batch==1)[0]
#only flip
flipindexes = np.concatenate((fliplandmarkindexes,flipposindexes))
#random flip
for i in flipindexes:
cv2.flip(image_batch[i],1,image_batch[i])
#pay attention: flip landmark
for i in fliplandmarkindexes:
landmark_ = landmark_batch[i].reshape((-1,2))
landmark_ = np.asarray([(1-x, y) for (x, y) in landmark_])
landmark_[[0, 1]] = landmark_[[1, 0]]#left eye<->right eye
landmark_[[3, 4]] = landmark_[[4, 3]]#left mouth<->right mouth
landmark_batch[i] = landmark_.ravel()
return image_batch,landmark_batch
R_Net(net_factory)函数
这个函数位于mtcnn_model.py
中,代码如下:
def R_Net(inputs,label=None,bbox_target=None,landmark_target=None,training=True):
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'):
print (inputs.get_shape()) #(384, 24, 24, 3)
net = slim.conv2d(inputs, num_outputs=28, kernel_size=[3,3], stride=1, scope="conv1")
print (net.get_shape()) #(384, 22, 22, 28)
net = slim.max_pool2d(net, kernel_size=[3, 3], stride=2, scope="pool1", padding='SAME')
print(net.get_shape()) #(384, 11, 11, 28)
net = slim.conv2d(net,num_outputs=48,kernel_size=[3,3],stride=1,scope="conv2")
print(net.get_shape()) #(384, 9, 9, 48)
net = slim.max_pool2d(net,kernel_size=[3,3],stride=2,scope="pool2")
print(net.get_shape()) #(384, 4, 4, 48)
net = slim.conv2d(net,num_outputs=64,kernel_size=[2,2],stride=1,scope="conv3")
print(net.get_shape()) #(384, 3, 3, 64)
fc_flatten = slim.flatten(net)
print(fc_flatten.get_shape()) #(384, 576)
fc1 = slim.fully_connected(fc_flatten, num_outputs=128,scope="fc1")
print(fc1.get_shape()) #(384, 128)
#batch*2
cls_prob = slim.fully_connected(fc1,num_outputs=2,scope="cls_fc",activation_fn=tf.nn.softmax)
print(cls_prob.get_shape()) #(384,2)
#batch*4
bbox_pred = slim.fully_connected(fc1,num_outputs=4,scope="bbox_fc",activation_fn=None)
print(bbox_pred.get_shape()) #(384,4)
#batch*10
landmark_pred = slim.fully_connected(fc1,num_outputs=10,scope="landmark_fc",activation_fn=None)
print(landmark_pred.get_shape()) #(384, 10)
#train
if training:
cls_loss = cls_ohem(cls_prob,label)
bbox_loss = bbox_ohem(bbox_pred,bbox_target,label)
accuracy = cal_accuracy(cls_prob,label)
landmark_loss = landmark_ohem(landmark_pred,landmark_target,label)
L2_loss = tf.add_n(slim.losses.get_regularization_losses())
return cls_loss,bbox_loss,landmark_loss,L2_loss,accuracy
else:
return cls_prob,bbox_pred,landmark_pred
RNet的训练是两次卷积层和池化层的交替进行,最后加上一个卷积层,此时的网络输出的shape是(384, 3, 3, 64),接下来是全连接层,在这之前需要slim.flatten()函数将矩阵拉直为向量形式。拉直为向量后使用三个全连接层分别输出cls_prob、bbox_pred、landmark_pred,接下来的训练过程和PNet是一样的,就不做多介绍,可以对照之前的注释理解。