跳过正文
  1. Posts/

Faster Rcnn 目标检测算法

·4894 字·10 分钟

背景
#

2019 年对了好几次 faster rcnn,第一次是赛事之窗项目,和北京的同事对齐 sdk 和训练的实现。 第二次是被 tensorRT4 和 tensorRT5 之间默认参数不一致的问题坑了一下。 第三次是被 caffe proto 中 roi align 的默认参数坑了。

虽然 debug 了这么多次、踩了一堆坑,但是一段时间不用,细节就会慢慢不记得了,因此来记录一下。


faster rcnn 是一种 “two stage” 的目标检测算法。

所谓 “two stage”,是说在实际进行目标检测之前,先会通过某种 “region proposals” algorithm 获得一定数量的 RoI(Regions of Interest),我们下一阶段要检测的 object 有极大可能被包含在这些 RoI 中。这种 “Region based” 的方法是对基于 “sliding windows” 方法的极大改进,因为不需要遍历每一个可能的位置以及 crop 大小,只需要对这些 RoI 进行检测,有效减小了计算量。

下面简单说一下这一类 “Region based” 方法的历史脉络。

rcnn
#

RCNN 的做法是通过一种传统方法 “selective search” 来得到若干 RoI,然后在每一个 RoI 后面接 CNN 进行后续的检测。显然,这个方法的问题在于计算量非常大。

selective search 的策略是,既然不知道尺度是怎样的,那我们就尽可能遍历所有的尺度;但是不同于暴力穷举,我们可以先得到小尺度的区域,然后一次次合并得到大的尺寸。

fast rcnn
#

明眼人可以看出,rcnn 计算量过大的原因之一是做了非常多的重复计算。

因此 fast rcnn 做的改进是:与其把每一个通过 “selective search” 得到的 RoI 在原图上 crop 出来送进 CNN,不如先让整张图过一段 CNN,然后把通过 “selective search” 在原图上得到的 RoI 先映射到这段 CNN 的某个 conv feature map 上。相当于这部分 CNN 只做了一次,与 RoI 数量无关,极大地减小了计算量。

faster rcnn
#

终于轮到主角登场了。fast rcnn 极大提高了检测的速度,然后发现速度的瓶颈已经不在后续的检测部分了,而是在于 “region proposals” algorithm。

于是,faster-rcnn 提出 “Region proposal network” 来替代 “selective search”,进一步提高了检测速度。

放一张结构图,非常清楚。

R-CNN 系列结构对比

Region proposal network(RPN)
#

anchor
#

介绍 RPN 网络首先就要介绍一下 anchor。

(被坑过一次,某个足球项目上,training 和 inference 用的 anchor 竟然是不一致的。。)

其实 anchor 这个概念很简单,用大白话说就是:根据要检测的物体的形状(高矮胖瘦等),预先设置一些不同尺寸的粗略的框,然后对这些框做一个二分类,判断前景还是背景,同时做 bbox regression 来微调坐标,最终得到 proposals。

设置 anchor 的思路其实就是把 proposals 的默认位置改成生成的 anchors 的位置。对这些 anchors 进行微调总要比从零开始生成容易得多。

要注意的是,anchors 是在进入网络前预先生成的。 实际项目中,通常设置长宽比为 [1:1, 2:1, 1:2] 三种比例,然后通过 generate_anchors.py 来生成 anchors。

值得强调的是,anchor 的生成是与图像内容无关的。无论图像内容是什么,生成的 anchor 都是固定的,而 RPN 网络的目标就是去学习哪些 anchor 是好的 anchor,以及学习到一组 “regression coefficients” 来用在好的 anchor 上,从而变成更好的 anchor。

RPN 网络结构
#

Region Proposal Network

最下面的 conv feature map 是经过一段 CNN 得到的,这部分和 fast rcnn 是类似的。

这一段 CNN 是 RPN 和后面的 detector 共用的。

然后用一个 n*n 的滑动窗口(原 paper 中用的 3*3,其实就是做了个 3*3 的卷积)得到 256 维的 feature(因为这个 conv feature map 的深度是 256)。然后对于 cls 和 reg 分别做两个 fc,输出分别为 2 和 4。由于一共有 k 个 anchor,因此一共输出 2k 个 scores 和 4k 个 coordinates。

最初我的一个疑惑是,为什么这里要做 softmax 二分类呢?为什么要关心背景的置信度呢?

原文中的解释是:

For simplicity we implement the cls layer as a two-class softmax layer. Alternatively, one may use logistic regression to produce k scores.

实际上,torchvision 中 rpn 的实现就是使用了 logistic 来计算 k 个 score。代码如下:

代码实现
 1
 2
 3class RPNHead(nn.Module):
 4    """
 5    Adds a simple RPN Head with classification and regression heads
 6    Arguments:
 7        in_channels (int): number of channels of the input feature
 8        num_anchors (int): number of anchors to be predicted
 9    """
10
11    def __init__(self, in_channels, num_anchors):
12        super(RPNHead, self).__init__()
13        self.conv = nn.Conv2d(
14            in_channels, in_channels, kernel_size=3, stride=1, padding=1
15        )
16        self.cls_logits = nn.Conv2d(in_channels, num_anchors, kernel_size=1, stride=1)
17        self.bbox_pred = nn.Conv2d(
18            in_channels, num_anchors * 4, kernel_size=1, stride=1
19        )
20
21        for l in self.children():
22            torch.nn.init.normal_(l.weight, std=0.01)
23            torch.nn.init.constant_(l.bias, 0)
24
25    def forward(self, x):
26        # type: (List[Tensor])
27        logits = []
28        bbox_reg = []
29        for feature in x:
30            t = F.relu(self.conv(feature))
31            logits.append(self.cls_logits(t))
32            bbox_reg.append(self.bbox_pred(t))
33        return logits, bbox_reg

可以看到 self.cls_logits 的输出是 num_anchors 个。

如果是使用 softmax 来得到 2k 个 score,这里还有一个坑是 softmax 的维度。

“Number of labels must match number of predictions; " “e.g., if softmax axis == 1 and prediction shape is (N, C, H, W), " “label count (number of labels) must be N*H*W, " “with integer values in {0, 1, …, C-1}.”;

所以经常要在 softmax 前后进行 reshape,把 [1, 2x9, H, W] 变成 [1, 2, 9xH, W],“腾空”出来一个维度以便 softmax 分类,之后再 reshape 恢复原状。

再看一下 RPN 整体的 PyTorch 实现。由于我们不关心 training 的过程,为了代码的整洁,这里省略了 assign_targets_to_anchors 和 compute_loss 两个函数的代码。

这段代码的核心在于 filter_proposals 这个函数,相当于对 proposals 做一些后处理,比如去掉超出边界框的、去掉太小的 proposals,然后根据 NMS 再去掉一些。这里有几个参数要注意:

  • pre_nms_top_n:在进行 nms 之前保留的 proposals 个数。
  • post_nms_top_n:nms 之后保留的 proposals 个数。

(某足球项目,nms 还有 decode_box 这里的 bug 和北京的同事联调了半个月才全部解决 Orz.. 醉生梦死的经历。这部分实现其实细节还挺多的,又没有统一的标准,很容易造成误差… 下次有机会讲一下。)

然后 forward 函数就相当于某些框架中(比如 caffe)的 proposal layer。

代码实现
  1
  2class RegionProposalNetwork(torch.nn.Module):
  3    """
  4    Implements Region Proposal Network (RPN).
  5    Arguments:
  6        anchor_generator (AnchorGenerator): module that generates the anchors for a set of feature
  7            maps.
  8        head (nn.Module): module that computes the objectness and regression deltas
  9        fg_iou_thresh (float): minimum IoU between the anchor and the GT box so that they can be
 10            considered as positive during training of the RPN.
 11        bg_iou_thresh (float): maximum IoU between the anchor and the GT box so that they can be
 12            considered as negative during training of the RPN.
 13        batch_size_per_image (int): number of anchors that are sampled during training of the RPN
 14            for computing the loss
 15        positive_fraction (float): proportion of positive anchors in a mini-batch during training
 16            of the RPN
 17        pre_nms_top_n (Dict[int]): number of proposals to keep before applying NMS. It should
 18            contain two fields: training and testing, to allow for different values depending
 19            on training or evaluation
 20        post_nms_top_n (Dict[int]): number of proposals to keep after applying NMS. It should
 21            contain two fields: training and testing, to allow for different values depending
 22            on training or evaluation
 23        nms_thresh (float): NMS threshold used for postprocessing the RPN proposals
 24    """
 25    __annotations__ = {
 26        'box_coder': det_utils.BoxCoder,
 27        'proposal_matcher': det_utils.Matcher,
 28        'fg_bg_sampler': det_utils.BalancedPositiveNegativeSampler,
 29        'pre_nms_top_n': Dict[str, int],
 30        'post_nms_top_n': Dict[str, int],
 31    }
 32
 33    def __init__(self,
 34                 anchor_generator,
 35                 head,
 36                 #
 37                 fg_iou_thresh, bg_iou_thresh,
 38                 batch_size_per_image, positive_fraction,
 39                 #
 40                 pre_nms_top_n, post_nms_top_n, nms_thresh):
 41        super(RegionProposalNetwork, self).__init__()
 42        self.anchor_generator = anchor_generator
 43        self.head = head
 44        self.box_coder = det_utils.BoxCoder(weights=(1.0, 1.0, 1.0, 1.0))
 45
 46        # used during training
 47        self.box_similarity = box_ops.box_iou
 48
 49        self.proposal_matcher = det_utils.Matcher(
 50            fg_iou_thresh,
 51            bg_iou_thresh,
 52            allow_low_quality_matches=True,
 53        )
 54
 55        self.fg_bg_sampler = det_utils.BalancedPositiveNegativeSampler(
 56            batch_size_per_image, positive_fraction
 57        )
 58        # used during testing
 59        self._pre_nms_top_n = pre_nms_top_n
 60        self._post_nms_top_n = post_nms_top_n
 61        self.nms_thresh = nms_thresh
 62        self.min_size = 1e-3
 63
 64    def pre_nms_top_n(self):
 65        if self.training:
 66            return self._pre_nms_top_n['training']
 67        return self._pre_nms_top_n['testing']
 68
 69    def post_nms_top_n(self):
 70        if self.training:
 71            return self._post_nms_top_n['training']
 72        return self._post_nms_top_n['testing']
 73
 74    def assign_targets_to_anchors(self, anchors, targets):
 75        # type: (List[Tensor], List[Dict[str, Tensor]])
 76        pass
 77    def _get_top_n_idx(self, objectness, num_anchors_per_level):
 78        # type: (Tensor, List[int])
 79        r = []
 80        offset = 0
 81        for ob in objectness.split(num_anchors_per_level, 1):
 82            if torchvision._is_tracing():
 83                num_anchors, pre_nms_top_n = _onnx_get_num_anchors_and_pre_nms_top_n(ob, self.pre_nms_top_n())
 84            else:
 85                num_anchors = ob.shape[1]
 86                pre_nms_top_n = min(self.pre_nms_top_n(), num_anchors)
 87            _, top_n_idx = ob.topk(pre_nms_top_n, dim=1)
 88            r.append(top_n_idx + offset)
 89            offset += num_anchors
 90        return torch.cat(r, dim=1)
 91
 92    def filter_proposals(self, proposals, objectness, image_shapes, num_anchors_per_level):
 93        # type: (Tensor, Tensor, List[Tuple[int, int]], List[int])
 94        num_images = proposals.shape[0]
 95        device = proposals.device
 96        # do not backprop throught objectness
 97        objectness = objectness.detach()
 98        objectness = objectness.reshape(num_images, -1)
 99
100        levels = [
101            torch.full((n,), idx, dtype=torch.int64, device=device)
102            for idx, n in enumerate(num_anchors_per_level)
103        ]
104        levels = torch.cat(levels, 0)
105        levels = levels.reshape(1, -1).expand_as(objectness)
106
107        # select top_n boxes independently per level before applying nms
108        top_n_idx = self._get_top_n_idx(objectness, num_anchors_per_level)
109
110        image_range = torch.arange(num_images, device=device)
111        batch_idx = image_range[:, None]
112
113        objectness = objectness[batch_idx, top_n_idx]
114        levels = levels[batch_idx, top_n_idx]
115        proposals = proposals[batch_idx, top_n_idx]
116
117        final_boxes = []
118        final_scores = []
119        for boxes, scores, lvl, img_shape in zip(proposals, objectness, levels, image_shapes):
120            boxes = box_ops.clip_boxes_to_image(boxes, img_shape)
121            keep = box_ops.remove_small_boxes(boxes, self.min_size)
122            boxes, scores, lvl = boxes[keep], scores[keep], lvl[keep]
123            # non-maximum suppression, independently done per level
124            keep = box_ops.batched_nms(boxes, scores, lvl, self.nms_thresh)
125            # keep only topk scoring predictions
126            keep = keep[:self.post_nms_top_n()]
127            boxes, scores = boxes[keep], scores[keep]
128            final_boxes.append(boxes)
129            final_scores.append(scores)
130        return final_boxes, final_scores
131
132    def compute_loss(self, objectness, pred_bbox_deltas, labels, regression_targets):
133        # type: (Tensor, Tensor, List[Tensor], List[Tensor])
134        """
135        Arguments:
136            objectness (Tensor)
137            pred_bbox_deltas (Tensor)
138            labels (List[Tensor])
139            regression_targets (List[Tensor])
140        Returns:
141            objectness_loss (Tensor)
142            box_loss (Tensor)
143        """
144
145       pass
146    def forward(self, images, features, targets=None):
147        # type: (ImageList, Dict[str, Tensor], Optional[List[Dict[str, Tensor]]])
148        """
149        Arguments:
150            images (ImageList): images for which we want to compute the predictions
151            features (List[Tensor]): features computed from the images that are
152                used for computing the predictions. Each tensor in the list
153                correspond to different feature levels
154            targets (List[Dict[Tensor]]): ground-truth boxes present in the image (optional).
155                If provided, each element in the dict should contain a field `boxes`,
156                with the locations of the ground-truth boxes.
157        Returns:
158            boxes (List[Tensor]): the predicted boxes from the RPN, one Tensor per
159                image.
160            losses (Dict[Tensor]): the losses for the model during training. During
161                testing, it is an empty dict.
162        """
163        # RPN uses all feature maps that are available
164        features = list(features.values())
165        objectness, pred_bbox_deltas = self.head(features)
166        anchors = self.anchor_generator(images, features)
167
168        num_images = len(anchors)
169        num_anchors_per_level_shape_tensors = [o[0].shape for o in objectness]
170        num_anchors_per_level = [s[0] * s[1] * s[2] for s in num_anchors_per_level_shape_tensors]
171        objectness, pred_bbox_deltas = \
172            concat_box_prediction_layers(objectness, pred_bbox_deltas)
173        # apply pred_bbox_deltas to anchors to obtain the decoded proposals
174        # note that we detach the deltas because Faster R-CNN do not backprop through
175        # the proposals
176        proposals = self.box_coder.decode(pred_bbox_deltas.detach(), anchors)
177        proposals = proposals.view(num_images, -1, 4)
178        boxes, scores = self.filter_proposals(proposals, objectness, images.image_sizes, num_anchors_per_level)
179
180        losses = {}
181        if self.training:
182            assert targets is not None
183            labels, matched_gt_boxes = self.assign_targets_to_anchors(anchors, targets)
184            regression_targets = self.box_coder.encode(matched_gt_boxes, anchors)
185            loss_objectness, loss_rpn_box_reg = self.compute_loss(
186                objectness, pred_bbox_deltas, labels, regression_targets)
187            losses = {
188                "loss_objectness": loss_objectness,
189                "loss_rpn_box_reg": loss_rpn_box_reg,
190            }
191        return boxes, losses

ROI Pooling和ROI Align Pooling
#

接下来介绍一下 ROI Pooling,以及对它的改进 ROI Align Pooling。

为什么需要 ROI Pooling?原因是在经过 RPN 网络后,得到的 proposals 的尺寸是不统一的,而 CNN 网络需要固定尺寸的输入。

ROI Pooling

举个例子,上面 8*8 的 feature map 和 7*5 的 proposal,希望得到 2*2 的输出。 但是没办法整除,roi pooling 的做法是直接取整,把 7 分成 3、4 两部分,把 5 分成 2 和 3 两部分,然后在这四个区域做 pooling,得到 2*2 的尺寸。

这部分 torchvision 的 pytorch 实现里面混合了 mask rcnn 还有其他关键点算法,不太直观,可以参考 caffe 的 roi pooling layer。

可以看到里面 ceil 和 floor 的取整操作。

代码实现
 1
 2template <typename Dtype>
 3void ROIPoolingLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
 4      const vector<Blob<Dtype>*>& top) {
 5  const Dtype* bottom_data = bottom[0]->cpu_data();
 6  const Dtype* bottom_rois = bottom[1]->cpu_data();
 7  // Number of ROIs
 8  int num_rois = bottom[1]->num();
 9  int batch_size = bottom[0]->num();
10  int top_count = top[0]->count();
11  Dtype* top_data = top[0]->mutable_cpu_data();
12  caffe_set(top_count, Dtype(-FLT_MAX), top_data);
13  int* argmax_data = max_idx_.mutable_cpu_data();
14  caffe_set(top_count, -1, argmax_data);
15
16  // For each ROI R = [batch_index x1 y1 x2 y2]: max pool over R
17  for (int n = 0; n < num_rois; ++n) {
18    int roi_batch_ind = bottom_rois[0];
19    int roi_start_w = round(bottom_rois[1] * spatial_scale_);
20    int roi_start_h = round(bottom_rois[2] * spatial_scale_);
21    int roi_end_w = round(bottom_rois[3] * spatial_scale_);
22    int roi_end_h = round(bottom_rois[4] * spatial_scale_);
23    CHECK_GE(roi_batch_ind, 0);
24    CHECK_LT(roi_batch_ind, batch_size);
25
26    int roi_height = max(roi_end_h - roi_start_h + 1, 1);
27    int roi_width = max(roi_end_w - roi_start_w + 1, 1);
28    const Dtype bin_size_h = static_cast<Dtype>(roi_height)
29                             / static_cast<Dtype>(pooled_height_);
30    const Dtype bin_size_w = static_cast<Dtype>(roi_width)
31                             / static_cast<Dtype>(pooled_width_);
32
33    const Dtype* batch_data = bottom_data + bottom[0]->offset(roi_batch_ind);
34
35    for (int c = 0; c < channels_; ++c) {
36      for (int ph = 0; ph < pooled_height_; ++ph) {
37        for (int pw = 0; pw < pooled_width_; ++pw) {
38          // Compute pooling region for this output unit:
39          //  start (included) = floor(ph * roi_height / pooled_height_)
40          //  end (excluded) = ceil((ph + 1) * roi_height / pooled_height_)
41          int hstart = static_cast<int>(floor(static_cast<Dtype>(ph)
42                                              * bin_size_h));
43          int wstart = static_cast<int>(floor(static_cast<Dtype>(pw)
44                                              * bin_size_w));
45          int hend = static_cast<int>(ceil(static_cast<Dtype>(ph + 1)
46                                           * bin_size_h));
47          int wend = static_cast<int>(ceil(static_cast<Dtype>(pw + 1)
48                                           * bin_size_w));
49
50          hstart = min(max(hstart + roi_start_h, 0), height_);
51          hend = min(max(hend + roi_start_h, 0), height_);
52          wstart = min(max(wstart + roi_start_w, 0), width_);
53          wend = min(max(wend + roi_start_w, 0), width_);
54
55          bool is_empty = (hend <= hstart) || (wend <= wstart);
56
57          const int pool_index = ph * pooled_width_ + pw;
58          if (is_empty) {
59            top_data[pool_index] = 0;
60            argmax_data[pool_index] = -1;
61          }
62
63          for (int h = hstart; h < hend; ++h) {
64            for (int w = wstart; w < wend; ++w) {
65              const int index = h * width_ + w;
66              if (batch_data[index] > top_data[pool_index]) {
67                top_data[pool_index] = batch_data[index];
68                argmax_data[pool_index] = index;
69              }
70            }
71          }
72        }
73      }
74      // Increment all data pointers by one channel
75      batch_data += bottom[0]->offset(0, 1);
76      top_data += top[0]->offset(0, 1);
77      argmax_data += max_idx_.offset(0, 1);
78    }
79    // Increment ROI data pointer
80    bottom_rois += bottom[1]->offset(1);
81  }
82}

这个办法简单粗暴,而且很容易意识到,这样取整会对真实的信息产生偏差。大物体可能还好说,小物体的话这个问题就会比较严重。

因此在 Mask R-CNN 中提出了一种改进办法,称之为 “RoI Align Pooling”。

思路是说,如果不能整除,那么不取整,而是通过插值等手段近似出那个不整的点的值。

代码可以参考caffe的GPU实现:

代码实现
 1
 2template <typename T>
 3__global__ void RoIAlignForwardKernel(
 4    const int nthreads,
 5    const T* bottom_data,
 6    const T spatial_scale,
 7    const bool position_sensitive,
 8    const int channels,
 9    const int height,
10    const int width,
11    const int pooled_height,
12    const int pooled_width,
13    const int sampling_ratio,
14    const int num_roi_per_image,
15    const T* bottom_rois,
16    T* top_data) {
17  CUDA_KERNEL_LOOP(index, nthreads) {
18    // (n, c, ph, pw) is an element in the pooled output
19    int pw = index % pooled_width;
20    int ph = (index / pooled_width) % pooled_height;
21    int c = (index / pooled_width / pooled_height) % channels;
22    int n = index / pooled_width / pooled_height / channels;
23
24//    const T* offset_bottom_rois = bottom_rois + n * 5;
25    const T* offset_bottom_rois = bottom_rois + n * 4;
26    int roi_batch_ind = n / num_roi_per_image;
27
28    // Do not using rounding; this implementation detail is critical
29    T roi_start_w = offset_bottom_rois[0] * spatial_scale;
30    T roi_start_h = offset_bottom_rois[1] * spatial_scale;
31    T roi_end_w = offset_bottom_rois[2] * spatial_scale;
32    T roi_end_h = offset_bottom_rois[3] * spatial_scale;
33    // T roi_start_w = round(offset_bottom_rois[1] * spatial_scale);
34    // T roi_start_h = round(offset_bottom_rois[2] * spatial_scale);
35    // T roi_end_w = round(offset_bottom_rois[3] * spatial_scale);
36    // T roi_end_h = round(offset_bottom_rois[4] * spatial_scale);
37
38    // Force malformed ROIs to be 1x1
39    T roi_width = max(roi_end_w - roi_start_w, (T)1.);
40    T roi_height = max(roi_end_h - roi_start_h, (T)1.);
41    T bin_size_h = static_cast<T>(roi_height) / static_cast<T>(pooled_height);
42    T bin_size_w = static_cast<T>(roi_width) / static_cast<T>(pooled_width);
43
44    int c_unpooled = c;
45    int channels_unpooled = channels;
46    if (position_sensitive) {
47      c_unpooled = c * pooled_height * pooled_width + ph * pooled_width + pw;
48      channels_unpooled = channels * pooled_height * pooled_width;
49    }
50    const T* offset_bottom_data =
51        bottom_data + (roi_batch_ind * channels_unpooled + c_unpooled)
52            * height * width;
53
54    // We use roi_bin_grid to sample the grid and mimic integral
55    int roi_bin_grid_h = (sampling_ratio > 0)
56                         ? sampling_ratio
57                         : ceil(roi_height / pooled_height);  // e.g., = 2
58    int roi_bin_grid_w =
59        (sampling_ratio > 0) ? sampling_ratio : ceil(roi_width / pooled_width);
60
61    // We do average (integral) pooling inside a bin
62    const T count = roi_bin_grid_h * roi_bin_grid_w;  // e.g. = 4
63
64    T output_val = 0.;
65    for (int iy = 0; iy < roi_bin_grid_h; iy++) {  // e.g., iy = 0, 1
66      const T y = roi_start_h + ph * bin_size_h +
67          static_cast<T>(iy + .5f) * bin_size_h /
68              static_cast<T>(roi_bin_grid_h);  // e.g., 0.5, 1.5
69      for (int ix = 0; ix < roi_bin_grid_w; ix++) {
70        const T x = roi_start_w + pw * bin_size_w +
71            static_cast<T>(ix + .5f) * bin_size_w /
72                static_cast<T>(roi_bin_grid_w);
73
74        T val = bilinear_interpolate(
75            offset_bottom_data, height, width, y, x, index);
76        output_val += val;
77      }
78    }
79    output_val /= count;
80
81    top_data[index] = output_val;
82  }
83}

这里面有一个坑是,roi align layer 有个参数叫 num_roi_per_image

代码实现
1
2message RoIAlignParameter {
3  optional uint32 pooled_h = 1 [default = 0];
4  optional uint32 pooled_w = 2 [default = 0];
5  optional float spatial_scale = 3 [default = 1];
6  optional int32 sample_ratio = 4 [default = -1];
7  optional bool position_sensitive = 5 [default = false];
8  optional uint32 num_roi_per_image = 6 [default = 300];
9}
然后这东西是有一个默认值 300 的,因为在 roialign 之前的 proposal layer 的 post_nms 的值一般是 300,所以一般不会有什么影响。 但是如果 nms 之后的 roi 数量小于 300,而 num_roi_per_image 又是默认 300 的话,就会把后面图片的 roi 分给第一张图,导致结果错误。

具体请参考记一次faster-rcnn debug记录

总结
#

  • R-CNN对图像选取若干提议区域,然后用卷积神经网络对每个提议区域做前向计算抽取特征,再用这些特征预测提议区域的类别和边界框。
  • Fast R-CNN对R-CNN的一个主要改进在于只对整个图像做卷积神经网络的前向计算。它引入了兴趣区域池化层,从而令兴趣区域能够抽取出形状相同的特征。
  • Faster R-CNN将Fast R-CNN中的选择性搜索替换成区域提议网络,从而减少提议区域的生成数量,并保证目标检测的精度。
  • Mask R-CNN在Faster R-CNN基础上引入一个全卷积网络,从而借助目标的像素级位置进一步提升目标检测的精度。

参考链接
#

相关文章

SSD: Single Shot MultiBox Detector 学习笔记

·3374 字·7 分钟
概述 # SSD是一种单阶段目标检测算法.所谓单阶段,是指只使用了一个deep neural network,而不是像faster-rcnn这种两阶段网络. 为什么有了faster-rcnn还要使用SSD? 最主要是慢… 两阶段网络虽然准确率高,但是在嵌入式等算力不足的设备上做inference速度非常感人,很难达到real time的要求. (实际业务上也是这样,公有云上的检测模型几乎都是faster-rcnn,而到了一些盒子之类的硬件设备,检测模型就全是SSD等single stage 模型了)