|
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
"""Evaluate Object Detection result on a single image.
|
|
|
|
Annotate each detected result as true positives or false positive according to
|
|
a predefined IOU ratio. Non Maximum Supression is used by default. Multi class
|
|
detection is supported by default.
|
|
Based on the settings, per image evaluation is either performed on boxes or
|
|
on object masks.
|
|
"""
|
|
import numpy as np
|
|
|
|
from object_detection.utils import np_box_list
|
|
from object_detection.utils import np_box_list_ops
|
|
from object_detection.utils import np_box_mask_list
|
|
from object_detection.utils import np_box_mask_list_ops
|
|
|
|
|
|
class PerImageEvaluation(object):
|
|
"""Evaluate detection result of a single image."""
|
|
|
|
def __init__(self,
|
|
num_groundtruth_classes,
|
|
matching_iou_threshold=0.5,
|
|
nms_iou_threshold=0.3,
|
|
nms_max_output_boxes=50,
|
|
group_of_weight=0.0):
|
|
"""Initialized PerImageEvaluation by evaluation parameters.
|
|
|
|
Args:
|
|
num_groundtruth_classes: Number of ground truth object classes
|
|
matching_iou_threshold: A ratio of area intersection to union, which is
|
|
the threshold to consider whether a detection is true positive or not
|
|
nms_iou_threshold: IOU threshold used in Non Maximum Suppression.
|
|
nms_max_output_boxes: Number of maximum output boxes in NMS.
|
|
group_of_weight: Weight of the group-of boxes.
|
|
"""
|
|
self.matching_iou_threshold = matching_iou_threshold
|
|
self.nms_iou_threshold = nms_iou_threshold
|
|
self.nms_max_output_boxes = nms_max_output_boxes
|
|
self.num_groundtruth_classes = num_groundtruth_classes
|
|
self.group_of_weight = group_of_weight
|
|
|
|
def compute_object_detection_metrics(
|
|
self, detected_boxes, detected_scores, detected_class_labels,
|
|
groundtruth_boxes, groundtruth_class_labels,
|
|
groundtruth_is_difficult_list, groundtruth_is_group_of_list,
|
|
detected_masks=None, groundtruth_masks=None):
|
|
"""Evaluates detections as being tp, fp or weighted from a single image.
|
|
|
|
The evaluation is done in two stages:
|
|
1. All detections are matched to non group-of boxes; true positives are
|
|
determined and detections matched to difficult boxes are ignored.
|
|
2. Detections that are determined as false positives are matched against
|
|
group-of boxes and weighted if matched.
|
|
|
|
Args:
|
|
detected_boxes: A float numpy array of shape [N, 4], representing N
|
|
regions of detected object regions.
|
|
Each row is of the format [y_min, x_min, y_max, x_max]
|
|
detected_scores: A float numpy array of shape [N, 1], representing
|
|
the confidence scores of the detected N object instances.
|
|
detected_class_labels: A integer numpy array of shape [N, 1], repreneting
|
|
the class labels of the detected N object instances.
|
|
groundtruth_boxes: A float numpy array of shape [M, 4], representing M
|
|
regions of object instances in ground truth
|
|
groundtruth_class_labels: An integer numpy array of shape [M, 1],
|
|
representing M class labels of object instances in ground truth
|
|
groundtruth_is_difficult_list: A boolean numpy array of length M denoting
|
|
whether a ground truth box is a difficult instance or not
|
|
groundtruth_is_group_of_list: A boolean numpy array of length M denoting
|
|
whether a ground truth box has group-of tag
|
|
detected_masks: (optional) A uint8 numpy array of shape
|
|
[N, height, width]. If not None, the metrics will be computed based
|
|
on masks.
|
|
groundtruth_masks: (optional) A uint8 numpy array of shape
|
|
[M, height, width].
|
|
|
|
Returns:
|
|
scores: A list of C float numpy arrays. Each numpy array is of
|
|
shape [K, 1], representing K scores detected with object class
|
|
label c
|
|
tp_fp_labels: A list of C boolean numpy arrays. Each numpy array
|
|
is of shape [K, 1], representing K True/False positive label of
|
|
object instances detected with class label c
|
|
is_class_correctly_detected_in_image: a numpy integer array of
|
|
shape [C, 1], indicating whether the correponding class has a least
|
|
one instance being correctly detected in the image
|
|
"""
|
|
detected_boxes, detected_scores, detected_class_labels, detected_masks = (
|
|
self._remove_invalid_boxes(detected_boxes, detected_scores,
|
|
detected_class_labels, detected_masks))
|
|
scores, tp_fp_labels = self._compute_tp_fp(
|
|
detected_boxes=detected_boxes,
|
|
detected_scores=detected_scores,
|
|
detected_class_labels=detected_class_labels,
|
|
groundtruth_boxes=groundtruth_boxes,
|
|
groundtruth_class_labels=groundtruth_class_labels,
|
|
groundtruth_is_difficult_list=groundtruth_is_difficult_list,
|
|
groundtruth_is_group_of_list=groundtruth_is_group_of_list,
|
|
detected_masks=detected_masks,
|
|
groundtruth_masks=groundtruth_masks)
|
|
|
|
is_class_correctly_detected_in_image = self._compute_cor_loc(
|
|
detected_boxes=detected_boxes,
|
|
detected_scores=detected_scores,
|
|
detected_class_labels=detected_class_labels,
|
|
groundtruth_boxes=groundtruth_boxes,
|
|
groundtruth_class_labels=groundtruth_class_labels,
|
|
detected_masks=detected_masks,
|
|
groundtruth_masks=groundtruth_masks)
|
|
|
|
return scores, tp_fp_labels, is_class_correctly_detected_in_image
|
|
|
|
def _compute_cor_loc(self, detected_boxes, detected_scores,
|
|
detected_class_labels, groundtruth_boxes,
|
|
groundtruth_class_labels, detected_masks=None,
|
|
groundtruth_masks=None):
|
|
"""Compute CorLoc score for object detection result.
|
|
|
|
Args:
|
|
detected_boxes: A float numpy array of shape [N, 4], representing N
|
|
regions of detected object regions.
|
|
Each row is of the format [y_min, x_min, y_max, x_max]
|
|
detected_scores: A float numpy array of shape [N, 1], representing
|
|
the confidence scores of the detected N object instances.
|
|
detected_class_labels: A integer numpy array of shape [N, 1], repreneting
|
|
the class labels of the detected N object instances.
|
|
groundtruth_boxes: A float numpy array of shape [M, 4], representing M
|
|
regions of object instances in ground truth
|
|
groundtruth_class_labels: An integer numpy array of shape [M, 1],
|
|
representing M class labels of object instances in ground truth
|
|
detected_masks: (optional) A uint8 numpy array of shape
|
|
[N, height, width]. If not None, the scores will be computed based
|
|
on masks.
|
|
groundtruth_masks: (optional) A uint8 numpy array of shape
|
|
[M, height, width].
|
|
|
|
Returns:
|
|
is_class_correctly_detected_in_image: a numpy integer array of
|
|
shape [C, 1], indicating whether the correponding class has a least
|
|
one instance being correctly detected in the image
|
|
|
|
Raises:
|
|
ValueError: If detected masks is not None but groundtruth masks are None,
|
|
or the other way around.
|
|
"""
|
|
if (detected_masks is not None and
|
|
groundtruth_masks is None) or (detected_masks is None and
|
|
groundtruth_masks is not None):
|
|
raise ValueError(
|
|
'If `detected_masks` is provided, then `groundtruth_masks` should '
|
|
'also be provided.'
|
|
)
|
|
|
|
is_class_correctly_detected_in_image = np.zeros(
|
|
self.num_groundtruth_classes, dtype=int)
|
|
for i in range(self.num_groundtruth_classes):
|
|
(gt_boxes_at_ith_class, gt_masks_at_ith_class,
|
|
detected_boxes_at_ith_class, detected_scores_at_ith_class,
|
|
detected_masks_at_ith_class) = self._get_ith_class_arrays(
|
|
detected_boxes, detected_scores, detected_masks,
|
|
detected_class_labels, groundtruth_boxes, groundtruth_masks,
|
|
groundtruth_class_labels, i)
|
|
is_class_correctly_detected_in_image[i] = (
|
|
self._compute_is_class_correctly_detected_in_image(
|
|
detected_boxes=detected_boxes_at_ith_class,
|
|
detected_scores=detected_scores_at_ith_class,
|
|
groundtruth_boxes=gt_boxes_at_ith_class,
|
|
detected_masks=detected_masks_at_ith_class,
|
|
groundtruth_masks=gt_masks_at_ith_class))
|
|
|
|
return is_class_correctly_detected_in_image
|
|
|
|
def _compute_is_class_correctly_detected_in_image(
|
|
self, detected_boxes, detected_scores, groundtruth_boxes,
|
|
detected_masks=None, groundtruth_masks=None):
|
|
"""Compute CorLoc score for a single class.
|
|
|
|
Args:
|
|
detected_boxes: A numpy array of shape [N, 4] representing detected box
|
|
coordinates
|
|
detected_scores: A 1-d numpy array of length N representing classification
|
|
score
|
|
groundtruth_boxes: A numpy array of shape [M, 4] representing ground truth
|
|
box coordinates
|
|
detected_masks: (optional) A np.uint8 numpy array of shape
|
|
[N, height, width]. If not None, the scores will be computed based
|
|
on masks.
|
|
groundtruth_masks: (optional) A np.uint8 numpy array of shape
|
|
[M, height, width].
|
|
|
|
Returns:
|
|
is_class_correctly_detected_in_image: An integer 1 or 0 denoting whether a
|
|
class is correctly detected in the image or not
|
|
"""
|
|
if detected_boxes.size > 0:
|
|
if groundtruth_boxes.size > 0:
|
|
max_score_id = np.argmax(detected_scores)
|
|
mask_mode = False
|
|
if detected_masks is not None and groundtruth_masks is not None:
|
|
mask_mode = True
|
|
if mask_mode:
|
|
detected_boxlist = np_box_mask_list.BoxMaskList(
|
|
box_data=np.expand_dims(detected_boxes[max_score_id], axis=0),
|
|
mask_data=np.expand_dims(detected_masks[max_score_id], axis=0))
|
|
gt_boxlist = np_box_mask_list.BoxMaskList(
|
|
box_data=groundtruth_boxes, mask_data=groundtruth_masks)
|
|
iou = np_box_mask_list_ops.iou(detected_boxlist, gt_boxlist)
|
|
else:
|
|
detected_boxlist = np_box_list.BoxList(
|
|
np.expand_dims(detected_boxes[max_score_id, :], axis=0))
|
|
gt_boxlist = np_box_list.BoxList(groundtruth_boxes)
|
|
iou = np_box_list_ops.iou(detected_boxlist, gt_boxlist)
|
|
if np.max(iou) >= self.matching_iou_threshold:
|
|
return 1
|
|
return 0
|
|
|
|
def _compute_tp_fp(self, detected_boxes, detected_scores,
|
|
detected_class_labels, groundtruth_boxes,
|
|
groundtruth_class_labels, groundtruth_is_difficult_list,
|
|
groundtruth_is_group_of_list,
|
|
detected_masks=None, groundtruth_masks=None):
|
|
"""Labels true/false positives of detections of an image across all classes.
|
|
|
|
Args:
|
|
detected_boxes: A float numpy array of shape [N, 4], representing N
|
|
regions of detected object regions.
|
|
Each row is of the format [y_min, x_min, y_max, x_max]
|
|
detected_scores: A float numpy array of shape [N, 1], representing
|
|
the confidence scores of the detected N object instances.
|
|
detected_class_labels: A integer numpy array of shape [N, 1], repreneting
|
|
the class labels of the detected N object instances.
|
|
groundtruth_boxes: A float numpy array of shape [M, 4], representing M
|
|
regions of object instances in ground truth
|
|
groundtruth_class_labels: An integer numpy array of shape [M, 1],
|
|
representing M class labels of object instances in ground truth
|
|
groundtruth_is_difficult_list: A boolean numpy array of length M denoting
|
|
whether a ground truth box is a difficult instance or not
|
|
groundtruth_is_group_of_list: A boolean numpy array of length M denoting
|
|
whether a ground truth box has group-of tag
|
|
detected_masks: (optional) A np.uint8 numpy array of shape
|
|
[N, height, width]. If not None, the scores will be computed based
|
|
on masks.
|
|
groundtruth_masks: (optional) A np.uint8 numpy array of shape
|
|
[M, height, width].
|
|
|
|
Returns:
|
|
result_scores: A list of float numpy arrays. Each numpy array is of
|
|
shape [K, 1], representing K scores detected with object class
|
|
label c
|
|
result_tp_fp_labels: A list of boolean numpy array. Each numpy array is of
|
|
shape [K, 1], representing K True/False positive label of object
|
|
instances detected with class label c
|
|
|
|
Raises:
|
|
ValueError: If detected masks is not None but groundtruth masks are None,
|
|
or the other way around.
|
|
"""
|
|
if detected_masks is not None and groundtruth_masks is None:
|
|
raise ValueError(
|
|
'Detected masks is available but groundtruth masks is not.')
|
|
if detected_masks is None and groundtruth_masks is not None:
|
|
raise ValueError(
|
|
'Groundtruth masks is available but detected masks is not.')
|
|
|
|
result_scores = []
|
|
result_tp_fp_labels = []
|
|
for i in range(self.num_groundtruth_classes):
|
|
groundtruth_is_difficult_list_at_ith_class = (
|
|
groundtruth_is_difficult_list[groundtruth_class_labels == i])
|
|
groundtruth_is_group_of_list_at_ith_class = (
|
|
groundtruth_is_group_of_list[groundtruth_class_labels == i])
|
|
(gt_boxes_at_ith_class, gt_masks_at_ith_class,
|
|
detected_boxes_at_ith_class, detected_scores_at_ith_class,
|
|
detected_masks_at_ith_class) = self._get_ith_class_arrays(
|
|
detected_boxes, detected_scores, detected_masks,
|
|
detected_class_labels, groundtruth_boxes, groundtruth_masks,
|
|
groundtruth_class_labels, i)
|
|
scores, tp_fp_labels = self._compute_tp_fp_for_single_class(
|
|
detected_boxes=detected_boxes_at_ith_class,
|
|
detected_scores=detected_scores_at_ith_class,
|
|
groundtruth_boxes=gt_boxes_at_ith_class,
|
|
groundtruth_is_difficult_list=
|
|
groundtruth_is_difficult_list_at_ith_class,
|
|
groundtruth_is_group_of_list=
|
|
groundtruth_is_group_of_list_at_ith_class,
|
|
detected_masks=detected_masks_at_ith_class,
|
|
groundtruth_masks=gt_masks_at_ith_class)
|
|
result_scores.append(scores)
|
|
result_tp_fp_labels.append(tp_fp_labels)
|
|
return result_scores, result_tp_fp_labels
|
|
|
|
def _get_overlaps_and_scores_mask_mode(
|
|
self, detected_boxes, detected_scores, detected_masks, groundtruth_boxes,
|
|
groundtruth_masks, groundtruth_is_group_of_list):
|
|
"""Computes overlaps and scores between detected and groudntruth masks.
|
|
|
|
Args:
|
|
detected_boxes: A numpy array of shape [N, 4] representing detected box
|
|
coordinates
|
|
detected_scores: A 1-d numpy array of length N representing classification
|
|
score
|
|
detected_masks: A uint8 numpy array of shape [N, height, width]. If not
|
|
None, the scores will be computed based on masks.
|
|
groundtruth_boxes: A numpy array of shape [M, 4] representing ground truth
|
|
box coordinates
|
|
groundtruth_masks: A uint8 numpy array of shape [M, height, width].
|
|
groundtruth_is_group_of_list: A boolean numpy array of length M denoting
|
|
whether a ground truth box has group-of tag. If a groundtruth box
|
|
is group-of box, every detection matching this box is ignored.
|
|
|
|
Returns:
|
|
iou: A float numpy array of size [num_detected_boxes, num_gt_boxes]. If
|
|
gt_non_group_of_boxlist.num_boxes() == 0 it will be None.
|
|
ioa: A float numpy array of size [num_detected_boxes, num_gt_boxes]. If
|
|
gt_group_of_boxlist.num_boxes() == 0 it will be None.
|
|
scores: The score of the detected boxlist.
|
|
num_boxes: Number of non-maximum suppressed detected boxes.
|
|
"""
|
|
detected_boxlist = np_box_mask_list.BoxMaskList(
|
|
box_data=detected_boxes, mask_data=detected_masks)
|
|
detected_boxlist.add_field('scores', detected_scores)
|
|
detected_boxlist = np_box_mask_list_ops.non_max_suppression(
|
|
detected_boxlist, self.nms_max_output_boxes, self.nms_iou_threshold)
|
|
gt_non_group_of_boxlist = np_box_mask_list.BoxMaskList(
|
|
box_data=groundtruth_boxes[~groundtruth_is_group_of_list],
|
|
mask_data=groundtruth_masks[~groundtruth_is_group_of_list])
|
|
gt_group_of_boxlist = np_box_mask_list.BoxMaskList(
|
|
box_data=groundtruth_boxes[groundtruth_is_group_of_list],
|
|
mask_data=groundtruth_masks[groundtruth_is_group_of_list])
|
|
iou = np_box_mask_list_ops.iou(detected_boxlist, gt_non_group_of_boxlist)
|
|
ioa = np.transpose(
|
|
np_box_mask_list_ops.ioa(gt_group_of_boxlist, detected_boxlist))
|
|
scores = detected_boxlist.get_field('scores')
|
|
num_boxes = detected_boxlist.num_boxes()
|
|
return iou, ioa, scores, num_boxes
|
|
|
|
def _get_overlaps_and_scores_box_mode(
|
|
self,
|
|
detected_boxes,
|
|
detected_scores,
|
|
groundtruth_boxes,
|
|
groundtruth_is_group_of_list):
|
|
"""Computes overlaps and scores between detected and groudntruth boxes.
|
|
|
|
Args:
|
|
detected_boxes: A numpy array of shape [N, 4] representing detected box
|
|
coordinates
|
|
detected_scores: A 1-d numpy array of length N representing classification
|
|
score
|
|
groundtruth_boxes: A numpy array of shape [M, 4] representing ground truth
|
|
box coordinates
|
|
groundtruth_is_group_of_list: A boolean numpy array of length M denoting
|
|
whether a ground truth box has group-of tag. If a groundtruth box
|
|
is group-of box, every detection matching this box is ignored.
|
|
|
|
Returns:
|
|
iou: A float numpy array of size [num_detected_boxes, num_gt_boxes]. If
|
|
gt_non_group_of_boxlist.num_boxes() == 0 it will be None.
|
|
ioa: A float numpy array of size [num_detected_boxes, num_gt_boxes]. If
|
|
gt_group_of_boxlist.num_boxes() == 0 it will be None.
|
|
scores: The score of the detected boxlist.
|
|
num_boxes: Number of non-maximum suppressed detected boxes.
|
|
"""
|
|
detected_boxlist = np_box_list.BoxList(detected_boxes)
|
|
detected_boxlist.add_field('scores', detected_scores)
|
|
detected_boxlist = np_box_list_ops.non_max_suppression(
|
|
detected_boxlist, self.nms_max_output_boxes, self.nms_iou_threshold)
|
|
gt_non_group_of_boxlist = np_box_list.BoxList(
|
|
groundtruth_boxes[~groundtruth_is_group_of_list])
|
|
gt_group_of_boxlist = np_box_list.BoxList(
|
|
groundtruth_boxes[groundtruth_is_group_of_list])
|
|
iou = np_box_list_ops.iou(detected_boxlist, gt_non_group_of_boxlist)
|
|
ioa = np.transpose(
|
|
np_box_list_ops.ioa(gt_group_of_boxlist, detected_boxlist))
|
|
scores = detected_boxlist.get_field('scores')
|
|
num_boxes = detected_boxlist.num_boxes()
|
|
return iou, ioa, scores, num_boxes
|
|
|
|
def _compute_tp_fp_for_single_class(
|
|
self, detected_boxes, detected_scores, groundtruth_boxes,
|
|
groundtruth_is_difficult_list, groundtruth_is_group_of_list,
|
|
detected_masks=None, groundtruth_masks=None):
|
|
"""Labels boxes detected with the same class from the same image as tp/fp.
|
|
|
|
Args:
|
|
detected_boxes: A numpy array of shape [N, 4] representing detected box
|
|
coordinates
|
|
detected_scores: A 1-d numpy array of length N representing classification
|
|
score
|
|
groundtruth_boxes: A numpy array of shape [M, 4] representing ground truth
|
|
box coordinates
|
|
groundtruth_is_difficult_list: A boolean numpy array of length M denoting
|
|
whether a ground truth box is a difficult instance or not. If a
|
|
groundtruth box is difficult, every detection matching this box
|
|
is ignored.
|
|
groundtruth_is_group_of_list: A boolean numpy array of length M denoting
|
|
whether a ground truth box has group-of tag. If a groundtruth box
|
|
is group-of box, every detection matching this box is ignored.
|
|
detected_masks: (optional) A uint8 numpy array of shape
|
|
[N, height, width]. If not None, the scores will be computed based
|
|
on masks.
|
|
groundtruth_masks: (optional) A uint8 numpy array of shape
|
|
[M, height, width].
|
|
|
|
Returns:
|
|
Two arrays of the same size, containing all boxes that were evaluated as
|
|
being true positives or false positives; if a box matched to a difficult
|
|
box or to a group-of box, it is ignored.
|
|
|
|
scores: A numpy array representing the detection scores.
|
|
tp_fp_labels: a boolean numpy array indicating whether a detection is a
|
|
true positive.
|
|
"""
|
|
if detected_boxes.size == 0:
|
|
return np.array([], dtype=float), np.array([], dtype=bool)
|
|
|
|
mask_mode = False
|
|
if detected_masks is not None and groundtruth_masks is not None:
|
|
mask_mode = True
|
|
|
|
if mask_mode:
|
|
(iou, ioa, scores,
|
|
num_detected_boxes) = self._get_overlaps_and_scores_mask_mode(
|
|
detected_boxes=detected_boxes,
|
|
detected_scores=detected_scores,
|
|
detected_masks=detected_masks,
|
|
groundtruth_boxes=groundtruth_boxes,
|
|
groundtruth_masks=groundtruth_masks,
|
|
groundtruth_is_group_of_list=groundtruth_is_group_of_list)
|
|
else:
|
|
(iou, ioa, scores,
|
|
num_detected_boxes) = self._get_overlaps_and_scores_box_mode(
|
|
detected_boxes=detected_boxes,
|
|
detected_scores=detected_scores,
|
|
groundtruth_boxes=groundtruth_boxes,
|
|
groundtruth_is_group_of_list=groundtruth_is_group_of_list)
|
|
|
|
if groundtruth_boxes.size == 0:
|
|
return scores, np.zeros(num_detected_boxes, dtype=bool)
|
|
|
|
tp_fp_labels = np.zeros(num_detected_boxes, dtype=bool)
|
|
is_matched_to_difficult_box = np.zeros(num_detected_boxes, dtype=bool)
|
|
is_matched_to_group_of_box = np.zeros(num_detected_boxes, dtype=bool)
|
|
|
|
# The evaluation is done in two stages:
|
|
# 1. All detections are matched to non group-of boxes; true positives are
|
|
# determined and detections matched to difficult boxes are ignored.
|
|
# 2. Detections that are determined as false positives are matched against
|
|
# group-of boxes and scored with weight w per ground truth box is
|
|
# matched.
|
|
|
|
# Tp-fp evaluation for non-group of boxes (if any).
|
|
if iou.shape[1] > 0:
|
|
groundtruth_nongroup_of_is_difficult_list = groundtruth_is_difficult_list[
|
|
~groundtruth_is_group_of_list]
|
|
max_overlap_gt_ids = np.argmax(iou, axis=1)
|
|
is_gt_box_detected = np.zeros(iou.shape[1], dtype=bool)
|
|
for i in range(num_detected_boxes):
|
|
gt_id = max_overlap_gt_ids[i]
|
|
if iou[i, gt_id] >= self.matching_iou_threshold:
|
|
if not groundtruth_nongroup_of_is_difficult_list[gt_id]:
|
|
if not is_gt_box_detected[gt_id]:
|
|
tp_fp_labels[i] = True
|
|
is_gt_box_detected[gt_id] = True
|
|
else:
|
|
is_matched_to_difficult_box[i] = True
|
|
|
|
scores_group_of = np.zeros(ioa.shape[1], dtype=float)
|
|
tp_fp_labels_group_of = self.group_of_weight * np.ones(
|
|
ioa.shape[1], dtype=float)
|
|
# Tp-fp evaluation for group of boxes.
|
|
if ioa.shape[1] > 0:
|
|
max_overlap_group_of_gt_ids = np.argmax(ioa, axis=1)
|
|
for i in range(num_detected_boxes):
|
|
gt_id = max_overlap_group_of_gt_ids[i]
|
|
if (not tp_fp_labels[i] and not is_matched_to_difficult_box[i] and
|
|
ioa[i, gt_id] >= self.matching_iou_threshold):
|
|
is_matched_to_group_of_box[i] = True
|
|
scores_group_of[gt_id] = max(scores_group_of[gt_id], scores[i])
|
|
selector = np.where((scores_group_of > 0) & (tp_fp_labels_group_of > 0))
|
|
scores_group_of = scores_group_of[selector]
|
|
tp_fp_labels_group_of = tp_fp_labels_group_of[selector]
|
|
|
|
return np.concatenate(
|
|
(scores[~is_matched_to_difficult_box
|
|
& ~is_matched_to_group_of_box],
|
|
scores_group_of)), np.concatenate(
|
|
(tp_fp_labels[~is_matched_to_difficult_box
|
|
& ~is_matched_to_group_of_box].astype(float),
|
|
tp_fp_labels_group_of))
|
|
|
|
def _get_ith_class_arrays(self, detected_boxes, detected_scores,
|
|
detected_masks, detected_class_labels,
|
|
groundtruth_boxes, groundtruth_masks,
|
|
groundtruth_class_labels, class_index):
|
|
"""Returns numpy arrays belonging to class with index `class_index`.
|
|
|
|
Args:
|
|
detected_boxes: A numpy array containing detected boxes.
|
|
detected_scores: A numpy array containing detected scores.
|
|
detected_masks: A numpy array containing detected masks.
|
|
detected_class_labels: A numpy array containing detected class labels.
|
|
groundtruth_boxes: A numpy array containing groundtruth boxes.
|
|
groundtruth_masks: A numpy array containing groundtruth masks.
|
|
groundtruth_class_labels: A numpy array containing groundtruth class
|
|
labels.
|
|
class_index: An integer index.
|
|
|
|
Returns:
|
|
gt_boxes_at_ith_class: A numpy array containing groundtruth boxes labeled
|
|
as ith class.
|
|
gt_masks_at_ith_class: A numpy array containing groundtruth masks labeled
|
|
as ith class.
|
|
detected_boxes_at_ith_class: A numpy array containing detected boxes
|
|
corresponding to the ith class.
|
|
detected_scores_at_ith_class: A numpy array containing detected scores
|
|
corresponding to the ith class.
|
|
detected_masks_at_ith_class: A numpy array containing detected masks
|
|
corresponding to the ith class.
|
|
"""
|
|
selected_groundtruth = (groundtruth_class_labels == class_index)
|
|
gt_boxes_at_ith_class = groundtruth_boxes[selected_groundtruth]
|
|
if groundtruth_masks is not None:
|
|
gt_masks_at_ith_class = groundtruth_masks[selected_groundtruth]
|
|
else:
|
|
gt_masks_at_ith_class = None
|
|
selected_detections = (detected_class_labels == class_index)
|
|
detected_boxes_at_ith_class = detected_boxes[selected_detections]
|
|
detected_scores_at_ith_class = detected_scores[selected_detections]
|
|
if detected_masks is not None:
|
|
detected_masks_at_ith_class = detected_masks[selected_detections]
|
|
else:
|
|
detected_masks_at_ith_class = None
|
|
return (gt_boxes_at_ith_class, gt_masks_at_ith_class,
|
|
detected_boxes_at_ith_class, detected_scores_at_ith_class,
|
|
detected_masks_at_ith_class)
|
|
|
|
def _remove_invalid_boxes(self, detected_boxes, detected_scores,
|
|
detected_class_labels, detected_masks=None):
|
|
"""Removes entries with invalid boxes.
|
|
|
|
A box is invalid if either its xmax is smaller than its xmin, or its ymax
|
|
is smaller than its ymin.
|
|
|
|
Args:
|
|
detected_boxes: A float numpy array of size [num_boxes, 4] containing box
|
|
coordinates in [ymin, xmin, ymax, xmax] format.
|
|
detected_scores: A float numpy array of size [num_boxes].
|
|
detected_class_labels: A int32 numpy array of size [num_boxes].
|
|
detected_masks: A uint8 numpy array of size [num_boxes, height, width].
|
|
|
|
Returns:
|
|
valid_detected_boxes: A float numpy array of size [num_valid_boxes, 4]
|
|
containing box coordinates in [ymin, xmin, ymax, xmax] format.
|
|
valid_detected_scores: A float numpy array of size [num_valid_boxes].
|
|
valid_detected_class_labels: A int32 numpy array of size
|
|
[num_valid_boxes].
|
|
valid_detected_masks: A uint8 numpy array of size
|
|
[num_valid_boxes, height, width].
|
|
"""
|
|
valid_indices = np.logical_and(detected_boxes[:, 0] < detected_boxes[:, 2],
|
|
detected_boxes[:, 1] < detected_boxes[:, 3])
|
|
detected_boxes = detected_boxes[valid_indices]
|
|
detected_scores = detected_scores[valid_indices]
|
|
detected_class_labels = detected_class_labels[valid_indices]
|
|
if detected_masks is not None:
|
|
detected_masks = detected_masks[valid_indices]
|
|
return [
|
|
detected_boxes, detected_scores, detected_class_labels, detected_masks
|
|
]
|