• aspect ratios:高宽比率

假设 window 的尺寸为:\((w, h)\),锚框的尺寸为:\((w_1, h_1)\),则有:

\[\begin{cases} \frac{w_1h_1}{wh} = s^2\\ \frac{h_1}{w_1} = \frac{h}{w} r \end{cases} \]

可以化简为:

\[\begin{cases} w_s = \frac{w_1}{s} = \frac{w}{\sqrt{r}} \\ h_s = \frac{h_1}{s} = h \sqrt{r} \end{cases} \]

我们可以有两种编程实现方式:

1 \(w=h\)

\[\begin{cases} w_s = \frac{w_1}{s} = \frac{w}{\sqrt{r}} = \text{round}(\sqrt{\frac{wh}{r}})\\ h_s = \frac{h_1}{s} = h \sqrt{r} = \sqrt{whr} = \text{round}(w_s r) \end{cases} \]

编程实现:

import numpy as np


class AnchorBase:
    def __init__(self, base_size, scales, ratios):
        self.scales = np.array(scales)  #
        self.ratios = np.array(ratios)  #
        self.num_anchors = len(self.ratios) * len(self.scales)  # 锚框的个数
        self.base_size = base_size  # 滑动窗口的大小
        if isinstance(base_size, int):
            self._w, self._h = [base_size]*2
        elif len(base_size) == 2:
            self._w, self._h = base_size
        elif len(base_size) == 1:
            self._w, self._h = base_size*2

        self._anchor = np.array([1, 1, self._w, self._h]) - 1

    @property
    def anchor(self):
        return self._anchor

    @anchor.setter
    def anchor(self, new_anchor):
        self._anchor = new_anchor

    @property
    def w(self):
        '''
        锚框的宽度
        '''
        return self.anchor[2] - self.anchor[0] + 1

    @property
    def h(self):
        '''
        锚框的高度
        '''
        return self.anchor[3] - self.anchor[1] + 1

    @property
    def size(self):
        '''
        锚框的面积
        '''
        return self.w * self.h

    @property
    def _whctrs(self):
        """
        Return x center, and y center for an anchor (window). 锚框的中心坐标
        """
        x_ctr = self.anchor[0] + 0.5 * (self.w - 1)
        y_ctr = self.anchor[1] + 0.5 * (self.h - 1)
        return np.array([x_ctr, y_ctr])

    @staticmethod
    def _coordinate(aspect, ctr):
        '''
        依据宽高组合计算锚框的坐标
        '''
        k = (aspect - 1) / 2
        return np.concatenate([ctr - k, ctr + k], axis=1)


class AnchorRCNN(AnchorBase):
    def __init__(self, base_size, scales, ratios):
        super().__init__(base_size, scales, ratios)
        self.anchors = self.gen_anchors()

    @property
    def ratio_aspects(self):
        '''
        依据 ratios 获取锚框的所有宽高组合
        '''
        size_ratios = self.size / self.ratios
        ws = np.round(np.sqrt(size_ratios))
        hs = np.round(ws * self.ratios)
        return np.stack([ws, hs], axis=1)

    @property
    def ratio_anchors(self):
        return self._coordinate(self.ratio_aspects, self._whctrs)

    @property
    def scale_aspects(self):
        '''
        依据 scales 获取锚框的所有宽高组合
        '''
        ws = self.w * self.scales
        hs = self.h * self.scales
        return np.stack([ws, hs], axis=1)

    @property
    def scale_anchors(self):
        return self._coordinate(self.scale_aspects, self._whctrs)

    def gen_anchors(self):
        '''
        获取最终的 base_anchors
        '''
        anchors = []
        for anchor in self.ratio_anchors:
            self.anchor = anchor
            anchors.append(self.scale_anchors)
        return np.concatenate(anchors)
scales = [8, 16, 32]  # 尺度,面积比
ratios = [0.5, 1, 2]  # window(滑动窗口) 与锚框的面积的比率(aspect ratios)
base_size = 16  # 滑动窗口的大小

self = AnchorRCNN(base_size, scales, ratios)

self.anchors
array([[ -84.,  -40.,   99.,   55.],
       [-176.,  -88.,  191.,  103.],
       [-360., -184.,  375.,  199.],
       [ -56.,  -56.,   71.,   71.],
       [-120., -120.,  135.,  135.],
       [-248., -248.,  263.,  263.],
       [ -36.,  -80.,   51.,   95.],
       [ -80., -168.,   95.,  183.],
       [-168., -344.,  183.,  359.]])
self.ratio_anchors
array([[-3. ,  2.5, 18. , 12.5],
       [ 0. ,  0. , 15. , 15. ],
       [ 2.5, -3. , 12.5, 18. ]])

2

\[\begin{cases} \frac{w_1}{w} = \frac{s}{\sqrt{r}} = \text{round}(\frac{s}{\sqrt{r}})\\ \frac{h_1}{h} = s \sqrt{r} = \text{round}(\frac{w_1}{w} r) \end{cases} \]

\[\begin{cases} S = [s_1, s_1, \cdots, s_m]\\ R = [r_1, r_2, \cdots, r_n] \end{cases} \]

则有(下面的运算均是元素级别的元素):

\[\begin{cases} W = (\frac{s_i}{\sqrt{r_j}}) = \frac{S}{\sqrt{R}}\\ H = (s_i \sqrt{r_j}) = W \cdot R \end{cases} \]

class Anchor(AnchorBase):
    def __init__(self, base_size, scales, ratios):
        super().__init__(base_size, scales, ratios)

    @property
    def W(self):
        '''
        计算 w_1/ w
        '''
        W = self.scales[:, None] / np.sqrt(self.ratios)
        return np.round(W)

    @property
    def H(self):
        '''
        计算 h_1/ h
        '''
        H = self.W * self.ratios
        return np.round(H)

    @property
    def aspect(self):
        '''
        所有的宽高组合
        '''
        return np.stack([self.W.flatten(), self.H.flatten()], axis=1)

    @property
    def base_anchors(self):
        return self._coordinate(self.aspect, self._whctrs)

    @property
    def anchors(self):
        '''
        获取最终的 base_anchors
        '''
        return self.base_anchors * np.array([self.w, self.h]*2)
scales = [8, 16, 32]  # 尺度,面积比
ratios = [0.5, 1, 2]  #  window(滑动窗口) 与锚框的面积的比率(aspect ratios)
base_size = [16, 8]
self = Anchor(base_size, scales, ratios)

self.anchors
array([[  40.,    8.,  200.,   48.],
       [  64.,    0.,  176.,   56.],
       [  80.,  -16.,  160.,   72.],
       [ -56.,  -16.,  296.,   72.],
       [   0.,  -32.,  240.,   88.],
       [  40.,  -56.,  200.,  112.],
       [-232.,  -56.,  472.,  112.],
       [-128.,  -96.,  368.,  152.],
       [ -56., -152.,  296.,  208.]])

探寻有趣之事!