|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""Builder function for post processing operations.""" |
|
import functools |
|
|
|
import tensorflow as tf |
|
from object_detection.builders import calibration_builder |
|
from object_detection.core import post_processing |
|
from object_detection.protos import post_processing_pb2 |
|
|
|
|
|
def build(post_processing_config): |
|
"""Builds callables for post-processing operations. |
|
|
|
Builds callables for non-max suppression, score conversion, and (optionally) |
|
calibration based on the configuration. |
|
|
|
Non-max suppression callable takes `boxes`, `scores`, and optionally |
|
`clip_window`, `parallel_iterations` `masks, and `scope` as inputs. It returns |
|
`nms_boxes`, `nms_scores`, `nms_classes` `nms_masks` and `num_detections`. See |
|
post_processing.batch_multiclass_non_max_suppression for the type and shape |
|
of these tensors. |
|
|
|
Score converter callable should be called with `input` tensor. The callable |
|
returns the output from one of 3 tf operations based on the configuration - |
|
tf.identity, tf.sigmoid or tf.nn.softmax. If a calibration config is provided, |
|
score_converter also applies calibration transformations, as defined in |
|
calibration_builder.py. See tensorflow documentation for argument and return |
|
value descriptions. |
|
|
|
Args: |
|
post_processing_config: post_processing.proto object containing the |
|
parameters for the post-processing operations. |
|
|
|
Returns: |
|
non_max_suppressor_fn: Callable for non-max suppression. |
|
score_converter_fn: Callable for score conversion. |
|
|
|
Raises: |
|
ValueError: if the post_processing_config is of incorrect type. |
|
""" |
|
if not isinstance(post_processing_config, post_processing_pb2.PostProcessing): |
|
raise ValueError('post_processing_config not of type ' |
|
'post_processing_pb2.Postprocessing.') |
|
non_max_suppressor_fn = _build_non_max_suppressor( |
|
post_processing_config.batch_non_max_suppression) |
|
score_converter_fn = _build_score_converter( |
|
post_processing_config.score_converter, |
|
post_processing_config.logit_scale) |
|
if post_processing_config.HasField('calibration_config'): |
|
score_converter_fn = _build_calibrated_score_converter( |
|
score_converter_fn, |
|
post_processing_config.calibration_config) |
|
return non_max_suppressor_fn, score_converter_fn |
|
|
|
|
|
def _build_non_max_suppressor(nms_config): |
|
"""Builds non-max suppresson based on the nms config. |
|
|
|
Args: |
|
nms_config: post_processing_pb2.PostProcessing.BatchNonMaxSuppression proto. |
|
|
|
Returns: |
|
non_max_suppressor_fn: Callable non-max suppressor. |
|
|
|
Raises: |
|
ValueError: On incorrect iou_threshold or on incompatible values of |
|
max_total_detections and max_detections_per_class. |
|
""" |
|
if nms_config.iou_threshold < 0 or nms_config.iou_threshold > 1.0: |
|
raise ValueError('iou_threshold not in [0, 1.0].') |
|
if nms_config.max_detections_per_class > nms_config.max_total_detections: |
|
raise ValueError('max_detections_per_class should be no greater than ' |
|
'max_total_detections.') |
|
|
|
non_max_suppressor_fn = functools.partial( |
|
post_processing.batch_multiclass_non_max_suppression, |
|
score_thresh=nms_config.score_threshold, |
|
iou_thresh=nms_config.iou_threshold, |
|
max_size_per_class=nms_config.max_detections_per_class, |
|
max_total_size=nms_config.max_total_detections, |
|
use_static_shapes=nms_config.use_static_shapes) |
|
return non_max_suppressor_fn |
|
|
|
|
|
def _score_converter_fn_with_logit_scale(tf_score_converter_fn, logit_scale): |
|
"""Create a function to scale logits then apply a Tensorflow function.""" |
|
def score_converter_fn(logits): |
|
scaled_logits = tf.divide(logits, logit_scale, name='scale_logits') |
|
return tf_score_converter_fn(scaled_logits, name='convert_scores') |
|
score_converter_fn.__name__ = '%s_with_logit_scale' % ( |
|
tf_score_converter_fn.__name__) |
|
return score_converter_fn |
|
|
|
|
|
def _build_score_converter(score_converter_config, logit_scale): |
|
"""Builds score converter based on the config. |
|
|
|
Builds one of [tf.identity, tf.sigmoid, tf.softmax] score converters based on |
|
the config. |
|
|
|
Args: |
|
score_converter_config: post_processing_pb2.PostProcessing.score_converter. |
|
logit_scale: temperature to use for SOFTMAX score_converter. |
|
|
|
Returns: |
|
Callable score converter op. |
|
|
|
Raises: |
|
ValueError: On unknown score converter. |
|
""" |
|
if score_converter_config == post_processing_pb2.PostProcessing.IDENTITY: |
|
return _score_converter_fn_with_logit_scale(tf.identity, logit_scale) |
|
if score_converter_config == post_processing_pb2.PostProcessing.SIGMOID: |
|
return _score_converter_fn_with_logit_scale(tf.sigmoid, logit_scale) |
|
if score_converter_config == post_processing_pb2.PostProcessing.SOFTMAX: |
|
return _score_converter_fn_with_logit_scale(tf.nn.softmax, logit_scale) |
|
raise ValueError('Unknown score converter.') |
|
|
|
|
|
def _build_calibrated_score_converter(score_converter_fn, calibration_config): |
|
"""Wraps a score_converter_fn, adding a calibration step. |
|
|
|
Builds a score converter function witha calibration transformation according |
|
to calibration_builder.py. Calibration applies positive monotonic |
|
transformations to inputs (i.e. score ordering is strictly preserved or |
|
adjacent scores are mapped to the same score). When calibration is |
|
class-agnostic, the highest-scoring class remains unchanged, unless two |
|
adjacent scores are mapped to the same value and one class arbitrarily |
|
selected to break the tie. In per-class calibration, it's possible (though |
|
rare in practice) that the highest-scoring class will change, since positive |
|
monotonicity is only required to hold within each class. |
|
|
|
Args: |
|
score_converter_fn: callable that takes logit scores as input. |
|
calibration_config: post_processing_pb2.PostProcessing.calibration_config. |
|
|
|
Returns: |
|
Callable calibrated score coverter op. |
|
""" |
|
calibration_fn = calibration_builder.build(calibration_config) |
|
def calibrated_score_converter_fn(logits): |
|
converted_logits = score_converter_fn(logits) |
|
return calibration_fn(converted_logits) |
|
calibrated_score_converter_fn.__name__ = ( |
|
'calibrate_with_%s' % calibration_config.WhichOneof('calibrator')) |
|
return calibrated_score_converter_fn |
|
|