def matched_intersection(boxlist1, boxlist2, scope=None):
"""Compute intersection areas between corresponding boxes in two boxlists.
Args:
boxlist1: BoxList holding N boxes
boxlist2: BoxList holding N boxes
scope: name scope.
Returns:
a tensor with shape [N] representing pairwise intersections
"""
with tf.name_scope(scope, 'MatchedIntersection'):
y_min1, x_min1, y_max1, x_max1 = tf.split(
value=boxlist1.get(), num_or_size_splits=4, axis=1)
y_min2, x_min2, y_max2, x_max2 = tf.split(
value=boxlist2.get(), num_or_size_splits=4, axis=1)
min_ymax = tf.minimum(y_max1, y_max2)
max_ymin = tf.maximum(y_min1, y_min2)
intersect_heights = tf.maximum(0.0, min_ymax - max_ymin)
min_xmax = tf.minimum(x_max1, x_max2)
max_xmin = tf.maximum(x_min1, x_min2)
intersect_widths = tf.maximum(0.0, min_xmax - max_xmin)
return tf.reshape(intersect_heights * intersect_widths, [-1])
def matched_containing(boxlist1, boxlist2, scope=None):
"""Compute the smallest axis-aligned bounding box that fully contains
a pair of corresponding boxes from two boxlists.
Args:
boxlist1: BoxList holding N boxes
boxlist2: BoxList holding N boxes
scope: name scope.
Returns:
a tensor with shape [N] representing pairwise intersections
"""
with tf.name_scope(scope, 'MatchedContaining'):
y_min1, x_min1, y_max1, x_max1 = tf.split(
value=boxlist1.get(), num_or_size_splits=4, axis=1)
y_min2, x_min2, y_max2, x_max2 = tf.split(
value=boxlist2.get(), num_or_size_splits=4, axis=1)
max_ymax = tf.maximum(y_max1, y_max2)
min_ymin = tf.minimum(y_min1, y_min2)
containing_heights = tf.maximum(0.0, max_ymax - min_ymin)
max_xmax = tf.maximum(x_max1, x_max2)
min_xmin = tf.minimum(x_min1, x_min2)
containing_widths = tf.maximum(0.0, max_xmax - min_xmin)
return tf.reshape(containing_heights * containing_widths, [-1])
def matched_giou(boxlist1, boxlist2, scope=None):
"""Compute generalized intersection-over-union between corresponding boxes in boxlists.
Args:
boxlist1: BoxList holding N boxes
boxlist2: BoxList holding N boxes
scope: name scope.
Returns:
a tensor with shape [N] representing pairwise iou scores.
"""
with tf.name_scope(scope, 'MatchedGIoU'):
epsilon = 0.00001
safe_boxlist1 = safe_boxlist(boxlist1)
safe_boxlist2 = safe_boxlist(boxlist2)
intersections = matched_intersection(safe_boxlist1, safe_boxlist2)
areas1 = area(safe_boxlist1)
areas2 = area(safe_boxlist2)
unions = areas1 + areas2 - intersections
iou = tf.where(
tf.equal(intersections, 0.0),
tf.zeros_like(intersections), tf.truediv(intersections, unions))
containings = matched_containing(safe_boxlist1, safe_boxlist2)
# this is the `C_term` in `GIoU = IoU - C_term` as described in the GIoU paper
unoccupied_area = tf.div_no_nan((containings - unions), containings)
giou = tf.subtract(iou, unoccupied_area)
return giou
class WeightedGIoULocalizationLoss(Loss):
"""GIoU localization loss function.
"""
def _compute_loss(self, prediction_tensor, target_tensor, weights):
"""Compute loss function.
Args:
prediction_tensor: A float tensor of shape [batch_size, num_anchors, 4]
representing the decoded predicted boxes
target_tensor: A float tensor of shape [batch_size, num_anchors, 4]
representing the decoded target boxes
weights: a float tensor of shape [batch_size, num_anchors]
Returns:
loss: a float tensor of shape [batch_size, num_anchors] tensor
representing the value of the loss function.
"""
predicted_boxes = box_list.BoxList(tf.reshape(prediction_tensor, [-1, 4]))
target_boxes = box_list.BoxList(tf.reshape(target_tensor, [-1, 4]))
matched_giou = box_list_ops.matched_giou(predicted_boxes, target_boxes)
per_anchor_giou_loss = 1.0 - matched_giou
return tf.reshape(weights, [-1]) * per_anchor_giou_loss
def area(boxes):
"""Computes area of boxes.
Args:
boxes: Numpy array with shape [N, 4] holding N boxes
Returns:
a numpy array with shape [N*1] representing box areas
"""
return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
def safe_boxlist(boxlist, scope=None):
"""Checks the min/max of the boxlist
Handles cases where ymin/ymax xmin/max are swapped.
Args:
boxlist: BoxList holding N boxes
scope: name scope.
Returns:
a tensor with shape [N] representing box areas.
"""
with tf.name_scope(scope, 'SafeBoxlist'):
y_min, x_min, y_max, x_max = tf.split(
value=boxlist.get(), num_or_size_splits=4, axis=1)
min_y = tf.reshape(tf.minimum(y_min, y_max), [-1])
max_y = tf.reshape(tf.maximum(y_min, y_max), [-1])
min_x = tf.reshape(tf.minimum(x_min, x_max), [-1])
max_x = tf.reshape(tf.maximum(x_min, x_max), [-1])
safe_boxes = tf.stack([min_y, min_x, max_y, max_x], axis=1)
return box_list.BoxList(safe_boxes)