Tensorflow 检测中的 SSD 锚 API

SSD anchors in Tensorflow detection API

我想训练一个SSD detector on a custom dataset of N by N images. So I dug into Tensorflow object detection API,在基于MobileNet v2的COCO上找到了SSD300x300的预训练模型。

查看用于训练的配置文件时:字段 anchor_generator 如下所示:(跟在论文后面)

anchor_generator {
  ssd_anchor_generator {
    num_layers: 6
    min_scale: 0.2
    max_scale: 0.9
    aspect_ratios: 1.0
    aspect_ratios: 2.0
    aspect_ratios: 0.5
    aspect_ratios: 3.0
    aspect_ratios: 0.33
    }
}

在查看 SSD anchor generator proto 时,我的假设是否正确:base_anchor_height=base_anchor_width=1 ?

如果是,我假设得到的锚点是通过阅读 Multiple Grid anchors generator(如果图像是 300x300 正方形)是: 尺寸范围从 0.2300=6060 像素到 0.9300=270270 像素(不同纵横比) ?

因此,如果想通过固定字段在 NxN 图像上进行训练:

fixed_shape_resizer {
  height: N
  width: N
}

他将使用相同的配置文件锚点,范围从 (0.2N,0.2N) 像素到 (0.9N,0.9 N) 像素(具有不同的纵横比)?

我做了很多假设,因为代码很难掌握,而且似乎还没有文档。我对么?有没有一种简单的方法可以在不训练模型的情况下可视化使用的锚点?

这里有一些函数可以用来在不训练模型的情况下生成和可视化锚框坐标。我们在这里所做的只是调用 training/inference.

期间图中使用的相关操作

首先,我们需要知道构成给定大小的输入图像的对象检测层的特征图的分辨率(形状)是多少。

import tensorflow as tf 
from object_detection.anchor_generators.multiple_grid_anchor_generator import create_ssd_anchors
from object_detection.models.ssd_mobilenet_v2_feature_extractor_test import SsdMobilenetV2FeatureExtractorTest

def get_feature_map_shapes(image_height, image_width):
    """
    :param image_height: height in pixels
    :param image_width: width in pixels
    :returns: list of tuples containing feature map resolutions
    """
    feature_extractor = SsdMobilenetV2FeatureExtractorTest()._create_feature_extractor(
        depth_multiplier=1,
        pad_to_multiple=1,
    )
    image_batch_tensor = tf.zeros([1, image_height, image_width, 1])
    
    return [tuple(feature_map.get_shape().as_list()[1:3])
            for feature_map in feature_extractor.extract_features(image_batch_tensor)]

这将 return 特征图形状列表,例如 [(19,19), (10,10), (5,5), (3,3), (2,2), (1,1)] 您可以将其传递给第二个函数,该函数 return 是锚框的坐标。

def get_feature_map_anchor_boxes(feature_map_shape_list, **anchor_kwargs):
    """
    :param feature_map_shape_list: list of tuples containing feature map resolutions
    :returns: dict with feature map shape tuple as key and list of [ymin, xmin, ymax, xmax] box co-ordinates
    """
    anchor_generator = create_ssd_anchors(**anchor_kwargs)

    anchor_box_lists = anchor_generator.generate(feature_map_shape_list)
    
    feature_map_boxes = {}

    with tf.Session() as sess:
        for shape, box_list in zip(feature_map_shape_list, anchor_box_lists):
            feature_map_boxes[shape] = sess.run(box_list.data['boxes'])
            
    return feature_map_boxes

在你的例子中你可以这样称呼它:

boxes = get_feature_map_boxes(
    min_scale=0.2,
    max_scale=0.9,
    feature_map_shape_list=get_feature_map_shapes(300, 300)
)

您无需指定纵横比,因为您配置中的纵横比与 create_ssd_anchors 的默认值相同。

最后,我们在反映给定层分辨率的网格上绘制锚框。请注意,模型中锚框和预测框的坐标在 0 和 1 之间进行了归一化处理。

def draw_boxes(boxes, figsize, nrows, ncols, grid=(0,0)):

    fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize) 

    for ax, box in zip(axes.flat, boxes):
        ymin, xmin, ymax, xmax = box
        ax.add_patch(patches.Rectangle((xmin, ymin), xmax-xmin, ymax-ymin, 
                                fill=False, edgecolor='red', lw=2))

        # add gridlines to represent feature map cells
        ax.set_xticks(np.linspace(0, 1, grid[0] + 1), minor=True)
        ax.set_yticks(np.linspace(0, 1, grid[1] + 1), minor=True)
        ax.grid(True, which='minor', axis='both')
              
    fig.tight_layout()
    
    return fig

如果我们以第四层有一个3x3的特征图为例

draw_boxes(feature_map_boxes[(3,3)], figsize=(12,16), nrows=9, ncols=6, grid=(3,3))

在上图中,每一行代表 3x3 特征图中的一个不同单元格,而每一列代表一个特定的纵横比。

您的初始假设是正确的,例如,最高层(具有最低分辨率特征图)中的纵横比为 1.0 的锚框将具有 height/width 等于输入图像大小的 0.9,而那些最低层中的 height/width 等于输入图像大小的 0.2。中间层的锚尺寸在这些限制之间线性插值。

但是,有一些关于 TensorFlow 锚点生成的微妙之处值得注意:

  1. 请注意,在图像示例中,每个网格单元有 6 个锚点,但我们只指定了 5 个纵横比。这是因为为每一层添加了一个额外的锚点,其大小介于当前层的锚点大小和下一层的锚点大小之间。这可以通过使用上面 anchor_kwargs 中的 interpolated_scale_aspect_ratio 参数来修改(或删除),或者同样在您的配置中。
  2. 默认情况下,在对象检测特征图的最低层(具有最高分辨率)中会忽略预先指定的纵横比列表,并仅替换为 3 个纵横比。这可以用 reduce_boxes_in_lowest_layer 布尔参数覆盖。
  3. 正如您正确指出的那样,默认情况下 base_anchor_height = base_anchor_width = 1。但是,如果您的输入图像不是正方形并且在预处理过程中被重新整形,那么纵横比为 1.0 的“正方形”锚点实际上不会针对原始图像中正方形的锚定对象进行优化(当然,它可以学习在训练期间预测这些形状)。

可以找到完整的要点 here