使用Tensorflow复现faster-rcnn三.RPN网络的搭建

 更新项目github源码:https://github.com/LongJun123456/Faster-rcnn-tensorflow

 最近博主一直在准备找暑期实习,所以也是隔了很久没跟新博客。题外话,现在的计算机视觉岗竞争是真的激烈,最后才找到美团,网易,海康,Momenta的offer,打算入坑的朋友门需谨慎。最近也在Momenta实习,等实习完后会继续更新博客和继续完善github。

上一篇博文写到anchor的制作与处理了。这篇博文就主要讲一下rpn网络的搭建部分。首先是整个网络的特征提取部分,博主用的是其论文中用的是VGG16

    def vgg16(self, input_image):
        with tf.variable_scope('vgg_16') :
            with slim.arg_scope([slim.conv2d, slim.fully_connected],
                                activation_fn=tf.nn.relu):
                net = slim.repeat(input_image, 2, slim.conv2d, 64, [3, 3], trainable=False, scope='conv1')
                net = slim.max_pool2d(net, [2, 2], padding='SAME', scope='pool1')
                net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], trainable=False, scope='conv2')
                net = slim.max_pool2d(net, [2, 2], padding='SAME', scope='pool2')
                net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
                net = slim.max_pool2d(net, [2, 2], padding='SAME', scope='pool3')
                net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')
                net = slim.max_pool2d(net, [2, 2], padding='SAME', scope='pool4')
                net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')
        return net

特征提取层默认使用的激活函数是relu,整个网络搭建是用tf的slim库完成的,搭建也比较简洁。网络的输入就是输入的图片,网络整体结构是:

2个3*3 64通道卷积+池化+2个3*3 128通道卷积+池化+3个3*3 256通道卷积+池化+3个3*3 512通道卷积+池化+3个3*3 512通道卷积。

注意其中的padding参数,卷积层用的padding参数默认都是same padding,同时池化层也指定padding参数为same。这么做的目的是为了保证图片尺寸经过将采样后是正好缩小16倍(4个池化层带来的缩小),这样就能精确计算之前anchor的位置了。

完成VGG16提取特征后,就是RPN网络的部分了:

#rpn网络 输入feature_map,返回rois的cls和bbox
    def rpn_net(self, input_feature_map,num_anchor):
        with tf.variable_scope('rpn') :
            with slim.arg_scope([slim.conv2d, slim.fully_connected],
                                 activation_fn=tf.nn.relu,\
                                 weights_initializer=tf.random_normal_initializer(mean=0.0, stddev=0.01) ,\
                                 weights_regularizer=slim.l2_regularizer(0.0005)):
                 rpn_feature = slim.conv2d(input_feature_map, 512, [3,3], scope='conv6')
                 rois_cls = slim.conv2d(rpn_feature, 2*num_anchor, [1,1], scope='conv7')
                 rois_reg = slim.conv2d(rpn_feature, 4*num_anchor, [1,1], scope='conv8')
        return {'rois_cls':rois_cls, 'rois_bbx':rois_reg}

RPN网络部分的输入就是VGG16提取的特征,首先是3*3的滑动窗口也就是3*3卷积进一步提取特征,也就是conv6层。之后再分别接上一个分类头和一个回归头,分类头是一个1*1的卷积,输出通道数是2*num_anchor,也就是在特征图每一个点上都有2*num_anchor个输出,num_anchor就是特征图上每个点对应的anchor数,在实现过程中就是9。2代表是前景还是背景的类别,为了还要得到背景的类别呢,之前得到前景的输出概率不就完事了吗?楼主之前是这样想的,但是在实际训练过程中发现,如果只输出前景类别概率,并用这个做训练的话,损失是无法收敛的,因为输出的概率是一个0到正无穷的随机数,不符合概率分布的要求。因此要同时输出背景概率,之后再通过softmax,将背景和前景的概率映射到0,1之间,并且概率之和为1。回归头的输出是在特征图上每一个点生成4*num_anchor个输出,num_anchor和之前的定义相同,4代表输出roi的角标信息。

接下来就是rpn网络的损失函数部分了:

rpn_loss_obj = RPN_loss(net.rois_output['rois_bbx'], net.all_anchors, net.gt_boxes, \
                        net.rois_output['rois_cls'], net.labels, net.anchor_obj, tf.shape(net.gt_boxes)[0])

计算损失的功能由一个类来完成,类初始化传入的参数就是上一步rpn网络生成的回归框,制作好的anchors,真值的角标,rpn网络输出的前背景得分,labels是之前做的anchor的标记(0,1,-1),anchor_obj是每一个anchor对应IOU最大的那个gt的标号(一张图中可能有多个gt)。

class RPN_loss(object):
    def __init__(self, prediction_bbox, anchor, ground_truth, probability, label, label_gt_order,size):
        self.prediction_bbox = prediction_bbox 
        self.ground_truth = ground_truth
    
        self.label_1 = label
        self.useful_label = tf.reshape(tf.where(tf.not_equal(self.label_1, -1)), [-1])
        self.reg_loss_nor = tf.cast(tf.shape(self.label_1)[0]/9, tf.float32)    
    
        self.label_gather = tf.gather(self.label_1, self.useful_label)
        self.label_gather = tf.cast(self.label_gather, dtype=tf.int32)
        self.label_gt_order = tf.gather(label_gt_order, self.useful_label)  # 每个anchor对应的groundtruth编号,一维

        self.anchor = tf.gather(anchor, self.useful_label) #anchor也是按顺序一维排列 和prediction_bbox_gather 的shape一样
        self.size=size

        self.probability = probability
        self.probability = tf.squeeze(self.probability)
        self.probability = tf.reshape(self.probability, [-1,9*2])
        self.probability = tf.reshape(self.probability, [-1,2])
        self.probability_gather = tf.gather(self.probability, self.useful_label)
        self.probability_gather = tf.cast(self.probability_gather, dtype=tf.float32)

        self.prediction_bbox = tf.squeeze(self.prediction_bbox)
        self.prediction_bbox = tf.reshape(self.prediction_bbox, [-1,9*4])
        self.prediction_bbox = tf.reshape(self.prediction_bbox, [-1,4])
        self.prediction_bbox_gather = tf.gather(self.prediction_bbox, self.useful_label)

类初始化部分,去除掉label=-1的部分,找到需要参与计算的label的标号,之前说过Label=-1对应的anchor是不参与回归损失和分类损失计算的:

self.label_1 = label

self.useful_label = tf.reshape(tf.where(tf.not_equal(self.label_1, -1)), [-1])

获取useful_labe的对应label标签,预测概率,anchor,预测框,这些都是通过tf.gather函数实现的。这里大体说一下tf.gather函数的使用方法,tf.gather函数的第一个输入是一个高维矩阵,第二个输出是一个Index。功能就是选取第一维坐标是index的矩阵。相对应的代码:

self.label_gather = tf.gather(self.label_1, self.useful_label)

self.label_gather = tf.cast(self.label_gather, dtype=tf.int32)

self.label_gt_order = tf.gather(label_gt_order, self.useful_label) # 每个anchor对应的groundtruth编号,一维

 

self.anchor = tf.gather(anchor, self.useful_label) #anchor也是按顺序一维排列 和prediction_bbox_gather 的shape一样

self.size=size

 

self.probability = probability

self.probability = tf.squeeze(self.probability)

self.probability = tf.reshape(self.probability, [-1,9*2])

self.probability = tf.reshape(self.probability, [-1,2])

self.probability_gather = tf.gather(self.probability, self.useful_label)

self.probability_gather = tf.cast(self.probability_gather, dtype=tf.float32)

 

self.prediction_bbox = tf.squeeze(self.prediction_bbox)

self.prediction_bbox = tf.reshape(self.prediction_bbox, [-1,9*4])

self.prediction_bbox = tf.reshape(self.prediction_bbox, [-1,4])

self.prediction_bbox_gather = tf.gather(self.prediction_bbox, self.useful_label)

分类损失的计算:

def class_loss(self, p_pred, label):
        l_loss_sum = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=p_pred, labels=label))
        return l_loss_sum

通过类方法class_loss来实现,p_pred就是tf.gather后的self.probability_gather,label就是tf.gather后的self.label_gather 。计算损失用的方法是tf提供的tf.nn.sparse_softmax_cross_entropy_with_logits 这个函数的作用是先对输入的预测概率进行softmax处理,然后再跟真值计算交叉熵。

 

回归损失的计算:

回归损失在计算之前需要先对真值的坐标进行变换,也就是变成Faster rcnn论文中说的tx*,ty*,tw*,th*。这么做的目的是为了让网络拟合的target处在0-1之间,让网络能够正确收敛。原论文中的变换公式:

其中x*是真值的中心点x坐标,相对应的y*,w*,h*分别是真值的中心y坐标和宽度高度。xa,ya,wa,ha分别是anchor的中心坐标和宽度高度。然后实际我们rpn网络的输出是tx,ty,tw,th。也就是说,我们要让rpn网络输出的self.prediction_bbox向变换后的tx*,ty*,th*,tw*的方向拟合。代码中的变换公式:

def reconsitution_coords(self):

        self.re_prediction_bbox = self.prediction_bbox_gather
        anchor_x1 = self.anchor[:, 0]
        anchor_y1 = self.anchor[:, 1]
        anchor_x2 = self.anchor[:, 2]
        anchor_y2 = self.anchor[:, 3]

        self.re_anchor_0 = tf.cast((anchor_x2+anchor_x1)/2.0, dtype=tf.float32)
        self.re_anchor_1 = tf.cast((anchor_y2+anchor_y1)/2.0, dtype=tf.float32)
        self.re_anchor_2 = tf.cast((anchor_x2-anchor_x1), dtype=tf.float32)
        self.re_anchor_3 = tf.cast((anchor_y2-anchor_y1), dtype=tf.float32)
        self.re_anchor = tf.squeeze(tf.stack(
            [self.re_anchor_0, self.re_anchor_1, self.re_anchor_2, self.re_anchor_3], axis=1))


        ground_truth_x1 = self.ground_truth[:, 0]
        ground_truth_y1 = self.ground_truth[:, 1]
        ground_truth_x2 = self.ground_truth[:, 2]
        ground_truth_y2 = self.ground_truth[:, 3]

        re_ground_truth_0 = tf.expand_dims(tf.cast((ground_truth_x1+ground_truth_x2)/2.0, dtype=tf.float32),-1)
        re_ground_truth_1 = tf.expand_dims(tf.cast((ground_truth_y1+ground_truth_y2)/2.0, dtype=tf.float32),-1)
        re_ground_truth_2 = tf.expand_dims(tf.cast((ground_truth_x2-ground_truth_x1+1.0), dtype=tf.float32),-1)
        re_ground_truth_3 = tf.expand_dims(tf.cast((ground_truth_y2-ground_truth_y1+1.0), dtype=tf.float32),-1)
        self.re_ground_truth = tf.concat([re_ground_truth_0, re_ground_truth_1, re_ground_truth_2, re_ground_truth_3], axis=1)

        
        #self.gt_map=tf.one_hot(self.label_gt_order,self.size)
        #self.re_label_gt_order=tf.matmul(self.gt_map,self.re_ground_truth)
        #self.re_label_gt_order=tf.cast(self.re_label_gt_order,dtype=tf.float32)
        self.re_label_gt_order = tf.gather(self.re_ground_truth, self.label_gt_order)
        self.re_label_gt_order = tf.cast(self.re_label_gt_order, dtype=tf.float32)
        self.label_weight_c = tf.cast((self.label_gather>0), tf.float32)
        self.label_weight_c = tf.expand_dims(self.label_weight_c, axis=1)

其中这里还有最后的self.label_weight_c,是用来标记哪些anchor参与最终的回归损失计算的,也就是label=1对应的那些positive anchor。最终损失函数的计算是Smooth L1损失函数,具体的计算:

def smooth_l1_loss(self, bbox_predicted, bbox_ground_truth,  weight, lmd=10.0, sigma=3.0, dim2mean=1):
        # if the 4 figures of bbox have been calculated
        # weight:to delete negative anchors
        sigma_1 = sigma ** 2
        bbox_ground_truth_0 = tf.cast((bbox_ground_truth[:, 0]-self.re_anchor_0)/self.re_anchor_2, dtype=tf.float32)
        bbox_ground_truth_1 = tf.cast((bbox_ground_truth[:, 1]-self.re_anchor_1)/self.re_anchor_3, dtype=tf.float32)
        bbox_ground_truth_2 = tf.cast(tf.log(bbox_ground_truth[:, 2]/self.re_anchor_2), dtype=tf.float32)
        bbox_ground_truth_3 = tf.cast(tf.log(bbox_ground_truth[:, 3]/self.re_anchor_3), dtype=tf.float32)
        re_bbox_ground_truth = tf.stack([bbox_ground_truth_0, bbox_ground_truth_1, bbox_ground_truth_2, bbox_ground_truth_3], axis=1)
        re_bbox_predicted = bbox_predicted
        bbox_diff = re_bbox_predicted - re_bbox_ground_truth       
        t_diff = bbox_diff*weight
        t_diff_abs = tf.abs(t_diff)
        compare_1 = tf.stop_gradient(tf.to_float(tf.less(t_diff_abs, 1.0/sigma_1)))
        sl_loss_box = (sigma_1/2.0)*compare_1*tf.pow(t_diff_abs, 2) + (1.0-compare_1)*(t_diff_abs-0.5/sigma_1)
        sum_loss_box = tf.reduce_sum(sl_loss_box)
        loss_box = sum_loss_box*lmd/cfg.anchor_batch	
        print('reg')
        print(loss_box)
        return loss_box

同时再完成计算后还对损失除了个cfg.anchor_batch,也就是原论文中说的256。

    def add_loss(self):
        self.reconsitution_coords()
        self.log_loss = self.class_loss(self.probability_gather, self.label_gather)
        self.reg_loss = self.smooth_l1_loss(self.re_prediction_bbox, self.re_label_gt_order, self.label_weight_c)
        self.rpn_loss = self.log_loss+self.reg_loss
        tf.summary.scalar('rpn_log_loss', self.log_loss)
        tf.summary.scalar('rpn_reg_loss', self.reg_loss)
        return self.rpn_loss

最终将回归损失和分类损失相加后返回,在主程序中通过调用类方法add_loss就能获得rpn部分的损失了。

这就是整个rpn网络部分的搭建及损失相关的描述。接下来就是检测网络的搭建了,完成检测网络的搭建后就差不多完成整个项目可以开始训练了。最终的完成的代码楼主会尽快的建立一个github项目,也希望广大博友们多多点赞,谢谢支持啦~

 

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值