Faster RCNN 源码理解

1. Anchor Generation Layer

对于生成anchors的源码理解主要来源于两个代码

由于两种方法生成anchors的技巧不同,故分开讨论,并主要以RBG大神的代码为主,讲解anchors的生成原理与生成技巧。

1.1 Caffe源码

  • 首先,解释一下,重要的参数

    • base_size=16,由于原图经过卷积池化后得到的特征图是原图的$\frac{1}{16}$,故用于采样anchor的特征图上的一个cell就相当于原图的$16 \times 16$区域。
    • ratios=[0.5, 1, 2],固定anchor面积下的长宽比,即$[1:2 \quad 1:1 \quad 2:1]$
    • scales=[8, 16, 32],即将anchors放大的倍数,具体在哪里用到会在后面详细解释
  • 其次,我们根据RBG大神的源码走一遍anchors生成的流程

    • def generate_anchors(base_size=16, ratios=[0.5, 1, 2],
                           scales=2**np.arange(3, 6)):
          """
          Generate anchor (reference) windows by enumerating aspect ratios X
          scales wrt a reference (0, 0, 15, 15) window.
          """
      
          base_anchor = np.array([1, 1, base_size, base_size]) - 1
          ratio_anchors = _ratio_enum(base_anchor, ratios)
          anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales)
                               for i in xrange(ratio_anchors.shape[0])])
          return anchors
      
      • generate_anchors()函数是一切的开端,首先定义了base_anchor,由于图像的坐标以左上角为原点且值为(0, 0),故base_anchor的坐标(xmin, ymin, xmax, ymax)为(0, 0, 15, 15)。
      • 其次,调用_ratio_enum()函数如下
    • def _ratio_enum(anchor, ratios):
          """
          Enumerate a set of anchors for each aspect ratio wrt an anchor.
          """
      
          w, h, x_ctr, y_ctr = _whctrs(anchor)
          size = w * h
          size_ratios = size / ratios
          ws = np.round(np.sqrt(size_ratios))
          hs = np.round(ws * ratios)
          anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
          return anchors
      
      • 为了计算w, h, x_ctr, y_ctr,又调用了_whctrs()函数,如下所示
    • def _whctrs(anchor):
          """
          Return width, height, x center, and y center for an anchor (window).
          """
      
          w = anchor[2] - anchor[0] + 1
          h = anchor[3] - anchor[1] + 1
          x_ctr = anchor[0] + 0.5 * (w - 1)
          y_ctr = anchor[1] + 0.5 * (h - 1)
          return w, h, x_ctr, y_ctr
      
      • _whctrs()函数的功能就是传入参数为(左上角x,左上角y,右上角x,右上角y),将其转换为(宽, 高, 中心坐标x,中心坐标y)
    • 让我们回到_ratio_enum()函数

      • 得到base_anchor的(宽, 高, 中心坐标x,中心坐标y),经过计算值为(16, 16, 7.5, 7.5)
      • size = w x h = 16 x 16 = 256
      • size_ratios = $\frac{256}{[0.5 \quad 1 \quad 2]}$ = $[512, 256, 128]$
      • 对size_ratios开根号,再四舍五入,得到 ws = [23, 16, 11]
      • ws和ratios相乘就得到了 hs = [12, 16, 22]
      • ws和hs其实是相同面积下,anchor不同长宽比条件下,得到的长和宽。但由于四舍五入的缘故,ws x hs的面积值不一定相等
      • 得到上面的变量值后,又调用了_mkanchors()函数返回计算后的anchors,函数如下
    • def _mkanchors(ws, hs, x_ctr, y_ctr):
          """
          Given a vector of widths (ws) and heights (hs) around a center
          (x_ctr, y_ctr), output a set of anchors (windows).
          """
      
          ws = ws[:, np.newaxis]
          hs = hs[:, np.newaxis]
          anchors = np.hstack((x_ctr - 0.5 * (ws - 1),
                               y_ctr - 0.5 * (hs - 1),
                               x_ctr + 0.5 * (ws - 1),
                               y_ctr + 0.5 * (hs - 1)))
          return anchors
      
      • 根据上面的代码,会得到如下的计算公式

      • 最后anchors的值为$\left[\begin{matrix} -3.5 & 1.5 & 18.5 & 13.5\\ 0 & 0 & 15 & 15\\ 2.5 & -3 & 12.5 & 18\end{matrix}\right]$

      • 这里得到的是,面积都为256下,以(7.5, 7.5)为中心坐标的,不同长宽比例下的anchor坐标。根据坐标的计算公式,可以发现,都是以7.5为中心坐标减去一半的长或宽,那么得到的是新的(左上角x,左上角y,右上角x,右上角y)形式的坐标值。为什么坐标会是负数,因为左上角坐标超出了图片范围,故为负数。

    • 得到以上anchors后,我们直接返回到generate_anchors()函数

      • 通过一系列函数的调用,我们得到了ratio_anchors的值,即$\left[\begin{matrix} -3.5 & 1.5 & 18.5 & 13.5\\ 0 & 0 & 15 & 15\\ 2.5 & -3 & 12.5 & 18\end{matrix}\right]$
      • 最后一步,就是调用_scale_enum()函数,得到不同scale下,不同长宽比例的anchors。目前的scale为[8, 16, 32],对于每一个scale都要调用_scale_enum()函数;传入不同长宽比、以(7.5, 7.5)为中心坐标的anchors(即ratio_anchors的每一行),每次返回3组变换尺度后的anchors,故最后会有9组anchors。_scale_enum()函数如下
    • def _scale_enum(anchor, scales):
          """
          Enumerate a set of anchors for each scale wrt an anchor.
          """
      
          w, h, x_ctr, y_ctr = _whctrs(anchor)
          ws = w * scales
          hs = h * scales
          anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
          return anchors
      
      • 我们以$[-3.5 \quad 1.5 \quad 18.5 \quad 13.5]$为例
      • 调用_whctrs()函数,得到中心坐标表示,w, h, x_ctr, y_ctr = $[23 \quad 12 \quad 7.5\quad 7.5]$
      • ,其实是宽为23的情况下,放大宽的值
      • ,其实是长为12的情况下,放大长的值
      • 由于中心坐标都是(7.5, 7.5)不变,但宽和高的值变了,所以新得到的anchors坐标需要再次调用_mkanchors()对坐标进行调整。在新的长和宽下,仍然以(7.5, 7.5)为中心坐标。
      • 最后计算得到的anchors坐标为$\left[\begin{matrix} -83 & -39 & 100 & 56\\ -175 & -87 & 192 & 104\\ -359 & -183 & 376 & 200\end{matrix}\right]$
  • 至此,RBG大神生成Anchors的方法就介绍完毕

1.2 Pytorch源码

  • Pytorch版本就不详细解释了,直接上代码,简单易懂

    def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2],
                             anchor_scales=[8, 16, 32]):
        """
        Returns:
            ~numpy.ndarray:
            An array of shape :math:`(R, 4)`.
            Each element is a set of coordinates of a bounding box.
            The second axis corresponds to
            :math:`(y_{min}, x_{min}, y_{max}, x_{max})` of a bounding box.
        """
        py = base_size / 2.
        px = base_size / 2.
    
        anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4),
                               dtype=np.float32)
        for i in six.moves.range(len(ratios)):
            for j in six.moves.range(len(anchor_scales)):
                h = base_size * anchor_scales[j] * np.sqrt(ratios[i])
                w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i])
    
                index = i * len(anchor_scales) + j
                anchor_base[index, 0] = py - h / 2.
                anchor_base[index, 1] = px - w / 2.
                anchor_base[index, 2] = py + h / 2.
                anchor_base[index, 3] = px + w / 2.
        return anchor_base
    
    • 参数和caffee一致,不同点在于,计算anchor_base的方式
    • 这里的anchor_base没有-1
    • 调用了两个循环,即遍历9次,每次得到一个anchors的坐标
    • 计算的公式很奇怪,为何对ratios开根号,应该是有奇怪的转换公式的
    • 最后,是直接求anchor_base的每一个坐标,以中心坐标为基准,计算(ymin, xmin, ymax, xmax)

未完待续~~~