Skip to content

Commit

Permalink
Rewrites anchor reference generation and adds tests for it
Browse files Browse the repository at this point in the history
  • Loading branch information
vierja committed Aug 10, 2017
1 parent 527014d commit 54deec3
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 92 deletions.
121 changes: 29 additions & 92 deletions luminoth/utils/anchors.py
Original file line number Diff line number Diff line change
@@ -1,105 +1,42 @@
# --------------------------------------------------------
# Faster R-CNN
# Copyright (c) 2015 Microsoft
# Licensed under The MIT License [see LICENSE for details]
# Written by Ross Girshick and Sean Bell
# --------------------------------------------------------

import numpy as np

# Verify that we compute the same anchors as Shaoqing's matlab implementation:
#
# >> load output/rpn_cachedir/faster_rcnn_VOC2007_ZF_stage1_rpn/anchors.mat
# >> anchors
#
# anchors =
#
# -83 -39 100 56
# -175 -87 192 104
# -359 -183 376 200
# -55 -55 72 72
# -119 -119 136 136
# -247 -247 264 264
# -35 -79 52 96
# -79 -167 96 184
# -167 -343 184 360

# array([[ -83., -39., 100., 56.],
# [-175., -87., 192., 104.],
# [-359., -183., 376., 200.],
# [ -55., -55., 72., 72.],
# [-119., -119., 136., 136.],
# [-247., -247., 264., 264.],
# [ -35., -79., 52., 96.],
# [ -79., -167., 96., 184.],
# [-167., -343., 184., 360.]])
def generate_anchors_reference(base_size, aspect_ratios, scales):
"""Generate base anchor to be used as reference of generating all anchors.
Anchors vary only in width and height. Using the base_size and the
different ratios we can calculate the wanted widths and heights.
def generate_anchors_reference(base_size, ratios, scales):
"""
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 range(ratio_anchors.shape[0])])
return anchors


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
Scales apply to area of object.
Args:
base_size (int): Base size of the base anchor (square).
aspect_ratios: Ratios to use to generate different anchors. The ratio
is the value of height / width.
scales: Scaling ratios applied to area.
def _mkanchors(ws, hs, x_ctr, y_ctr):
Returns:
anchors: Numpy array with shape (total_aspect_ratios * total_scales, 4)
with the corner points of the reference base anchors using the
convention (x_min, y_min, x_max, y_max).
"""
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
scales_grid, aspect_ratios_grid = np.meshgrid(scales, aspect_ratios)
base_scales = scales_grid.reshape(-1)
base_aspect_ratios = aspect_ratios_grid.reshape(-1)

aspect_ratio_sqrts = np.sqrt(base_aspect_ratios)
heights = base_scales * aspect_ratio_sqrts * base_size
widths = base_scales / aspect_ratio_sqrts * base_size

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.sqrt(size_ratios) # np.round(np.sqrt(size_ratios))
hs = ws * ratios # np.round(ws * ratios)
anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
return anchors
# Center point has the same X, Y value.
center_xy = (base_size - 1) / 2.0

# Create anchor reference.
anchors = np.column_stack([
center_xy - (widths - 1) / 2,
center_xy - (heights - 1) / 2,
center_xy + (widths - 1) / 2,
center_xy + (heights - 1) / 2,
])

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


if __name__ == '__main__':
import time
t = time.time()
a = generate_anchors_reference(
base_size=16, ratios=[0.5, 1, 2], scales=2**np.arange(3, 6)
)
90 changes: 90 additions & 0 deletions luminoth/utils/anchors_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import numpy as np
import tensorflow as tf

from luminoth.utils.anchors import generate_anchors_reference, generate_anchors_reference_old


class AnchorsTest(tf.test.TestCase):

def _get_widths_heights(self, anchor_reference):
return np.column_stack((
(anchor_reference[:, 2] - anchor_reference[:, 0] + 1),
(anchor_reference[:, 3] - anchor_reference[:, 1] + 1)
))

def testAnchorReference(self):
# Test simple case with one aspect ratio and one scale.
base_size = 256
aspect_ratios = [1.]
scales = [1.]
anchor_reference = generate_anchors_reference(
base_size=base_size,
aspect_ratios=aspect_ratios,
scales=scales
)

# Should return a single anchor.
self.assertEqual(anchor_reference.shape, (1, 4))
self.assertAllEqual(
anchor_reference[0], [0, 0, base_size - 1, base_size - 1]
)

# Test with fixed ratio and different scales.
scales = np.array([0.5, 1., 2., 4.])
anchor_reference = generate_anchors_reference(
base_size=base_size,
aspect_ratios=aspect_ratios,
scales=scales
)

# Check that we have the correct number of anchors.
self.assertEqual(anchor_reference.shape, (4, 4))
width_heights = self._get_widths_heights(anchor_reference)
# Check that anchors are squares (aspect_ratio = [1.0]).
self.assertTrue((width_heights[:, 0] == width_heights[:, 1]).all())
# Check that widths are consistent with scales times base_size.
self.assertAllEqual(width_heights[:, 0], base_size * scales)
# Check exact values.
self.assertAllEqual(
anchor_reference,
np.array([[64., 64., 191., 191.],
[0., 0., 255., 255.],
[-128., -128., 383., 383.],
[-384., -384., 639., 639.]])
)

# Test with different ratios and scales.
scales = np.array([0.5, 1., 2.])
aspect_ratios = np.array([0.5, 1., 2.])
anchor_reference = generate_anchors_reference(
base_size=base_size,
aspect_ratios=aspect_ratios,
scales=scales
)

# Check we have the correct number of anchors.
self.assertEqual(
anchor_reference.shape, (len(scales) * len(aspect_ratios), 4)
)

width_heights = self._get_widths_heights(anchor_reference)

# Check ratios of height / widths
anchor_ratios = width_heights[:, 1] / width_heights[:, 0]
# Check scales (applied to )
anchor_scales = np.sqrt(
(width_heights[:, 1] * width_heights[:, 0]) / (base_size ** 2)
)

# Test that all ratios are used in the correct order.
self.assertAllClose(
anchor_ratios, [0.5, 0.5, 0.5, 1., 1., 1., 2., 2., 2.]
)
# Test that all scales are used in the correct order.
self.assertAllClose(
anchor_scales, [0.5, 1., 2., 0.5, 1., 2., 0.5, 1., 2.]
)


if __name__ == '__main__':
tf.test.main()

0 comments on commit 54deec3

Please sign in to comment.