Detectron总结(二)

1、数据标签生成部分
对于RPN部分的网络,是一个全卷积的网络结构,最后如果使用FPN,那么对于不同level产生的feature map来讲,对应的标签是什么呢?
以cls这一支为例:
RPN的分类这里会有5个分支,每一个分支是3*w-x*h-x的大小,对应的标签也是3*w_x*h_x的大小
在FPN.py里面的add_fpn_rpn_losses函数中

model.net.SpatialNarrowAs(
       ['rpn_labels_int32_wide_fpn' + slvl, 'rpn_cls_logits_fpn' + slvl],
       'rpn_labels_int32_fpn' + slvl
)

这个函数的目的是将rpn_labels_int32_wide_fpn_x的大小剪切成和rpn_cls_logits_fpn_x的大小,最后剪切后的标签保存在rpn_labels_int32_fpn_x中,为什么要这样做呢?
原因是因为rpn_labels_int32_wide_fpn_x去生成anchor对应的标签的时候是按照data_utils.py里面的

field_size = int(np.ceil(fpn_max_size / float(stride)))
shifts = np.arange(0, field_size) * stride
shift_x, shift_y = np.meshgrid(shifts, shifts)

去生成的,大小不是和图片的长和宽成正比而是一个方形的结构,用最大边做了约束,但是细想想也是合理的,最后的这一个SpatialNarrowAs操作已经足够可以保证切出来的这部分label使我们想要的label,来看看怎么切得吧
这里写图片描述
就是切割rpn_labels_int32_wide_fpn左上角即可。
对于bbox回归的分支来讲也是类似的道理,只不过bbox回归的时候,每一个level是(4*3)*w-x*h-x的大小,对于标签也是要做一个spatialnarrowas的操作保证标签是正确的

for key in ('targets', 'inside_weights', 'outside_weights'):
     model.net.SpatialNarrowAs(
     [
          'rpn_bbox_' + key + '_wide_fpn' + slvl,
          'rpn_bbox_pred_fpn' + slvl
     ],
     'rpn_bbox_' + key + '_fpn' + slv
)

2、faster的roi部分,则是N个proposal一起往前传,会涉及到一个batch的问题,最终的分类的部分的输出是model.num_classes维的,然后对于bbox部分输出是model.num_classes * 4维的,
bbox标签
这里写图片描述

3、对于faster的keypoint部分,最后的输出是Nx17xWxH的大小,但是我们可以注意到之前制作关键点标签有一个很关键的一步是
在keypoint_rcnn.py里面add_keypoint_rcnn_blobs里面有

shape = (sampled_fg_rois.shape[0] * cfg.KRCNN.NUM_KEYPOINTS, 1)
heats = heats.reshape(shape)
weights = weights.reshape(shape)

sampled_fg_rois *= im_scale

可见标签heats是(Nx17)x1,为了最后进行softmax损失,需要将最后的输出进行shape的重新调整。

model.net.Reshape(
   ['kps_score'], ['kps_score_reshaped', '_kps_score_old_shape'],
    shape=(-1, cfg.KRCNN.HEATMAP_SIZE * cfg.KRCNN.HEATMAP_SIZE)
)

4、一个batch两张图片,一起前传,但是roi都是混乱的,怎么最后确认是哪张图像的呢?网络前传到最后一次性传出两张图片输出,所以第一维是2
1)首先从proposal的产生开始,每张图片产生对应的roi,将batch黏贴在5维的第一维,这只是一个level其实是会有5个level的输出,每个level都是两张图片的输出
num_images = inputs[0].shape[0]

for im_i in range(num_images):
      im_i_boxes, im_i_probs = self.proposals_for_one_image(
      im_info[im_i, :], all_anchors, bbox_deltas[im_i, :, :, :],
      scores[im_i, :, :, :]
)
batch_inds = im_i * np.ones(
      (im_i_boxes.shape[0], 1), dtype=np.float32
)
im_i_rois = np.hstack((batch_inds, im_i_boxes))
rois = np.append(rois, im_i_rois, axis=0)
roi_probs = np.append(roi_probs, im_i_probs, axis=0)

然后将两张图片的roi全部都连接到一起,送到output里面,[‘rpn_rois_fpn’ + slvl, ‘rpn_roi_probs_fpn’ + slvl]每个level都有自己的

outputs[0].reshape(rois.shape)

2)产生完proposal之后就要进行distribute和collect了
model.CollectAndDistributeFpnRpnProposals()
首先会collect一下,collect的目的是将各个level的roi给连在一起,每个level包含两张图片对应level的roi
rois = collect(inputs, self._train)
这样rois里面存储的就是两张图片的rois了

json_dataset.add_proposals(roidb, rois, im_scales, crowd_thresh=0)

从下面的代码里面也可以看到将提出来的proposal依次到对应的图片里面取,roi是哪张图片的就到那张图片

def add_proposals(roidb, rois, scales, crowd_thresh):
    """Add proposal boxes (rois) to an roidb that has ground-truth annotations
    but no proposals. If the proposals are not at the original image scale,
    specify the scale factor that separate them in scales.
    """
    box_list = []
    for i in range(len(roidb)):
        inv_im_scale = 1. / scales[i]
        idx = np.where(rois[:, 0] == i)[0]
        box_list.append(rois[idx, 1:] * inv_im_scale)
    _merge_proposal_boxes_into_roidb(roidb, box_list)
    if crowd_thresh > 0:
        _filter_crowd_proposals(roidb, crowd_thresh)
    _add_class_assignments(roidb)


def _merge_proposal_boxes_into_roidb(roidb, box_list):
    """Add proposal boxes to each roidb entry."""
    assert len(box_list) == len(roidb)
    for i, entry in enumerate(roidb):
        boxes = box_list[i]
        num_boxes = boxes.shape[0]
        gt_overlaps = np.zeros(
            (num_boxes, entry['gt_overlaps'].shape[1]),
            dtype=entry['gt_overlaps'].dtype
        )
        box_to_gt_ind_map = -np.ones(
            (num_boxes), dtype=entry['box_to_gt_ind_map'].dtype
        )

这样roidb里面的每个entry也就是每张图片就存着自己对应的boxes了,接下来就是add blob的时候了,blob这一步操作会有选择的去选择到底用哪些roi,毕竟roi鱼龙混杂的,但是仍然是按照每张图片每张图片去取的,这个时候roi的cls label和bbox label也就出来了,存储的形式和roi一样,也就是每张图片内存自己的label

def add_fast_rcnn_blobs(blobs, im_scales, roidb):
    """Add blobs needed for training Fast R-CNN style models."""
    # Sample training RoIs from each image and append them to the blob lists
    for im_i, entry in enumerate(roidb):
        frcn_blobs = _sample_rois(entry, im_scales[im_i], im_i)
        for k, v in frcn_blobs.items():
            blobs[k].append(v)

这里也是每张图片加载自己的,对应的blob取append。blob会append对应图片的roi和label。append操作能够区分出到底是哪张图片的。关键点的部分也是加载自己的。
这个时候blob里面的存储都是一张图片一张图片的存储。接下来的这个操作很关键

    for k, v in blobs.items():
        if isinstance(v, list) and len(v) > 0:
            blobs[k] = np.concatenate(v)

原来的blob是按照两张图片存储的,存成了两份,这样concatenate正好把两张图片的所有的roi都分配进来了,也就是混在了一起,这个时候label也进行混合,也就是把两张图片的混合在一起了

接下来进行roi的level分配,也就是这些拿出来的roi到底怎么分配到每个level上面。这里的分配是对两张图片一起操作的
_add_multilevel_rois(blobs)
这样分配之后rois_fpn_x,x=2,3,4,5,keypoint_rois_fpn_x ,x= 2,3,4,5 都存储的是对应的level的两张图片的所有的roi信息,在这个过程中会有一个rois_idx_restore,因为这个时候blob都已经乱了因为level分配,定义这个rois_idx_restore就是为了后来能够与label对应到底为止,所有的blob的信息都已经处理完了,目前为止都是两张图片混在一起了

这个时候rois_fpn_x,x=2,3,4,5,keypoint_rois_fpn_x ,x= 2,3,4,5 都存储的是对应level的roi,而且是一连两张的存,那么怎么来和标签对应的呢???
通过上面我们可以发现再进行_add_multilevel_rois(blobs)分配的时候,这里的分配并没有取分配label,只是分配了roi,那么后期怎么处理的呢?
接下来看model.RoIFeatureTransform的操作,这个操作解释了怎么进行!!!!!!!!
1)首先是对每个level的roi在各自的所属的水平上进行pooling,输出为256x14x14
2)

 xform_shuffled, _ = self.net.Concat(
         bl_out_list, [blob_out + '_shuffled', '_concat_' + blob_out],
         axis=0
 )

上面的代码将输出的各个level的roi pooling后的结果concat在一起形成一个4D的Tensor,NX256X14X14
3)

xform_out = self.net.BatchPermutation(
        [xform_shuffled, restore_bl], blob_out
)

然后将各个roi对应道原来的顺序上,这样可以和原来的label相对应,然后进行之后的损失计算
4)经过卷积,对于fast的部分最后分类生成Nxnum_class个结果,bbox生成Nx(4xnum_classes),对于keypoint部分则是(Nx17)x(56x56)的,也适合标签进行对应的

!!!!!roipooling的时候会根据roi的[id,x1,y1,x2,y2]的id来确定roi用NxCxHxW的那个batch也即N中的哪一batch的特征

5 整个过程涉及两次scale的变换过程
在json_dataset中add_proposal的过程中inv_im_scale = 1. / scales[i],box_list.append(rois[idx, 1:] * inv_im_scale)了一次,然后在sampled_rois = sampled_boxes * im_scale有重新变换到缩放后的图像的大小
再来分析一下,整个代码第一次出现img_scales的时候是在add_training_input的时候,创建RoIDataLoader对象,进入该对象的初始化中,然后self.create_threads()会进入minibatch_loader_thread(),然后进入get_next_minibatch(),再进入get_minibatch()中,然后调用im_blob, im_scales = _get_image_blob(roidb),这是第一次出现计算im_scale的时刻,在之前加在roidb的时候,roidb的每个entry的gtbox是标注里面的标准大小,没有进行scale,那么在这了进行了哪些缩放呢?再进入roi_data.rpn.add_rpn_blobs(blobs, im_scales, roidb)

im_height = np.round(entry['height'] * scale)#在这个上面布置anchor,也就是在缩放后的图像进行anchor的布置
im_width = np.round(entry['width'] * scale)
gt_rois = entry['boxes'][gt_inds, :] * scale #既然要和gt进行监督这个时候gt的roi就要重新计算了,需要乘上scale

但无论怎样,roidb[boxes]里面的gt还是原来的图像的大小的对应的gt

这部分结束后就进入roi和keypoint的部分来看一下具体怎么做的?
经过了collect之后,进入json_dataset.add_proposals(roidb, rois, im_scales, crowd_thresh=0),

inv_im_scale = 1. / scales[i]
box_list.append(rois[idx, 1:] * inv_im_scale)

经过rpn提取的roi是缩放后的图像提出来的,所以需要把他们重新变换成原来图像也即未经过缩放的图像的大小,方便和gt进行各类的标签的计算
然后进入_merge_proposal_boxes_into_roidb(roidb, box_list),在这个函数里面的计算都是对应原图像的roi和对应原图像的gt进行各类的计算,然后进入roi_data.fast_rcnn.add_fast_rcnn_blobs(blobs, im_scales, roidb)中,_sample_rois会用到im_scales,怎么用的呢?

sampled_rois = sampled_boxes * im_scale

就这么一句话
在关键点部分

sampled_fg_rois *= im_scale

也就这么一句话
将rois往前传的话,那肯定是要往前传缩放后的rois
但是在计算label的时候以target为例,是按照原图的proposal和原图的gt计算偏差,这样计算出来的bbox_targets因为要做一个尺度的归一化,所以对于缩放和不缩放都没有什么关系,对于关键点也是这样的,因为关键点要做一个栅格的处理来产生关键点的label,所以对于缩放不缩放都没有什么影响,也就是说无论关键点还是bbox都直接用原图的Proposal和gt进行计算就可以啦!!!

6、在最初加入roidb的时候,也即roidb = combined_roidb_for_training会有bbox-targets这个选项
这里写图片描述
但是到后来加载minibatch的时候roidb的内容发生了相应的变化
这里写图片描述
后来的roi需要自己去计算和gt的bbox-targets
这里写图片描述

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值