Source code for gluoncv.nn.matcher
# pylint: disable=arguments-differ
"""Matchers for target assignment.
Matchers are commonly used in object-detection for anchor-groundtruth matching.
The matching process is a prerequisite to training target assignment.
Matching is usually not required during testing.
"""
from __future__ import absolute_import
from mxnet import gluon
from mxnet.gluon import nn
[docs]class CompositeMatcher(gluon.HybridBlock):
"""A Matcher that combines multiple strategies.
Parameters
----------
matchers : list of Matcher
Matcher is a Block/HybridBlock used to match two groups of boxes
"""
def __init__(self, matchers):
super(CompositeMatcher, self).__init__()
assert len(matchers) > 0, "At least one matcher required."
for matcher in matchers:
assert isinstance(matcher, (gluon.Block, gluon.HybridBlock))
self._matchers = nn.HybridSequential()
for m in matchers:
self._matchers.add(m)
[docs] def hybrid_forward(self, F, x):
matches = [matcher(x) for matcher in self._matchers]
return self._compose_matches(F, matches)
def _compose_matches(self, F, matches):
"""Given multiple match results, compose the final match results.
The order of matches matters. Only the unmatched(-1s) in the current
state will be substituted with the matching in the rest matches.
Parameters
----------
matches : list of NDArrays
N match results, each is an output of a different Matcher
Returns
-------
one match results as (B, N, M) NDArray
"""
result = matches[0]
for match in matches[1:]:
result = F.where(result > -0.5, result, match)
return result
[docs]class BipartiteMatcher(gluon.HybridBlock):
"""A Matcher implementing bipartite matching strategy.
Parameters
----------
threshold : float
Threshold used to ignore invalid paddings
is_ascend : bool
Whether sort matching order in ascending order. Default is False.
eps : float
Epsilon for floating number comparison
share_max : bool, default is True
The maximum overlap between anchor/gt is shared by multiple ground truths.
We recommend Fast(er)-RCNN series to use ``True``, while for SSD, it should
defaults to ``False`` for better result.
"""
def __init__(self, threshold=1e-12, is_ascend=False, eps=1e-12, share_max=True):
super(BipartiteMatcher, self).__init__()
self._threshold = threshold
self._is_ascend = is_ascend
self._eps = eps
self._share_max = share_max
[docs] def hybrid_forward(self, F, x):
"""BipartiteMatching
Parameters:
----------
x : NDArray or Symbol
IOU overlaps with shape (N, M), batching is supported.
"""
match = F.contrib.bipartite_matching(x, threshold=self._threshold,
is_ascend=self._is_ascend)
# make sure if iou(a, y) == iou(b, y), then b should also be a good match
# otherwise positive/negative samples are confusing
# potential argmax and max
pargmax = x.argmax(axis=-1, keepdims=True) # (B, num_anchor, 1)
maxs = x.max(axis=-2, keepdims=True) # (B, 1, num_gt)
if self._share_max:
mask = F.broadcast_greater_equal(x + self._eps, maxs) # (B, num_anchor, num_gt)
mask = F.sum(mask, axis=-1, keepdims=True) # (B, num_anchor, 1)
else:
pmax = F.pick(x, pargmax, axis=-1, keepdims=True) # (B, num_anchor, 1)
mask = F.broadcast_greater_equal(pmax + self._eps, maxs) # (B, num_anchor, num_gt)
mask = F.pick(mask, pargmax, axis=-1, keepdims=True) # (B, num_anchor, 1)
new_match = F.where(mask > 0, pargmax, F.ones_like(pargmax) * -1)
result = F.where(match[0] < 0, new_match.squeeze(axis=-1), match[0])
return result
[docs]class MaximumMatcher(gluon.HybridBlock):
"""A Matcher implementing maximum matching strategy.
Parameters
----------
threshold : float
Matching threshold.
"""
def __init__(self, threshold):
super(MaximumMatcher, self).__init__()
self._threshold = threshold
[docs] def hybrid_forward(self, F, x):
argmax = F.argmax(x, axis=-1)
match = F.where(F.pick(x, argmax, axis=-1) >= self._threshold, argmax,
F.ones_like(argmax) * -1)
return match