Path: blob/master/coco models/tflite mobnetv1 ssd/visualization_utils.py
455 views
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.1#2# Licensed under the Apache License, Version 2.0 (the "License");3# you may not use this file except in compliance with the License.4# You may obtain a copy of the License at5#6# http://www.apache.org/licenses/LICENSE-2.07#8# Unless required by applicable law or agreed to in writing, software9# distributed under the License is distributed on an "AS IS" BASIS,10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11# See the License for the specific language governing permissions and12# limitations under the License.13# ==============================================================================1415"""A set of functions that are used for visualization.1617These functions often receive an image, perform some visualization on the image.18The functions do not return a value, instead they modify the image itself.1920"""21from __future__ import absolute_import22from __future__ import division23from __future__ import print_function2425import abc26import collections27# Set headless-friendly backend.28import matplotlib; matplotlib.use('Agg') # pylint: disable=multiple-statements29import matplotlib.pyplot as plt # pylint: disable=g-import-not-at-top30import numpy as np31import PIL.Image as Image32import PIL.ImageColor as ImageColor33import PIL.ImageDraw as ImageDraw34import PIL.ImageFont as ImageFont35import six36from six.moves import range37from six.moves import zip38import tensorflow.compat.v1 as tf3940from object_detection.core import keypoint_ops41from object_detection.core import standard_fields as fields42from object_detection.utils import shape_utils4344_TITLE_LEFT_MARGIN = 1045_TITLE_TOP_MARGIN = 1046STANDARD_COLORS = [47'AliceBlue', 'Chartreuse', 'Aqua', 'Aquamarine', 'Azure', 'Beige', 'Bisque',48'BlanchedAlmond', 'BlueViolet', 'BurlyWood', 'CadetBlue', 'AntiqueWhite',49'Chocolate', 'Coral', 'CornflowerBlue', 'Cornsilk', 'Crimson', 'Cyan',50'DarkCyan', 'DarkGoldenRod', 'DarkGrey', 'DarkKhaki', 'DarkOrange',51'DarkOrchid', 'DarkSalmon', 'DarkSeaGreen', 'DarkTurquoise', 'DarkViolet',52'DeepPink', 'DeepSkyBlue', 'DodgerBlue', 'FireBrick', 'FloralWhite',53'ForestGreen', 'Fuchsia', 'Gainsboro', 'GhostWhite', 'Gold', 'GoldenRod',54'Salmon', 'Tan', 'HoneyDew', 'HotPink', 'IndianRed', 'Ivory', 'Khaki',55'Lavender', 'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue',56'LightCoral', 'LightCyan', 'LightGoldenRodYellow', 'LightGray', 'LightGrey',57'LightGreen', 'LightPink', 'LightSalmon', 'LightSeaGreen', 'LightSkyBlue',58'LightSlateGray', 'LightSlateGrey', 'LightSteelBlue', 'LightYellow', 'Lime',59'LimeGreen', 'Linen', 'Magenta', 'MediumAquaMarine', 'MediumOrchid',60'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue', 'MediumSpringGreen',61'MediumTurquoise', 'MediumVioletRed', 'MintCream', 'MistyRose', 'Moccasin',62'NavajoWhite', 'OldLace', 'Olive', 'OliveDrab', 'Orange', 'OrangeRed',63'Orchid', 'PaleGoldenRod', 'PaleGreen', 'PaleTurquoise', 'PaleVioletRed',64'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', 'Plum', 'PowderBlue', 'Purple',65'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Green', 'SandyBrown',66'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue',67'SlateGray', 'SlateGrey', 'Snow', 'SpringGreen', 'SteelBlue', 'GreenYellow',68'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'White',69'WhiteSmoke', 'Yellow', 'YellowGreen'70]717273def _get_multiplier_for_color_randomness():74"""Returns a multiplier to get semi-random colors from successive indices.7576This function computes a prime number, p, in the range [2, 17] that:77- is closest to len(STANDARD_COLORS) / 1078- does not divide len(STANDARD_COLORS)7980If no prime numbers in that range satisfy the constraints, p is returned as 1.8182Once p is established, it can be used as a multiplier to select83non-consecutive colors from STANDARD_COLORS:84colors = [(p * i) % len(STANDARD_COLORS) for i in range(20)]85"""86num_colors = len(STANDARD_COLORS)87prime_candidates = [5, 7, 11, 13, 17]8889# Remove all prime candidates that divide the number of colors.90prime_candidates = [p for p in prime_candidates if num_colors % p]91if not prime_candidates:92return 19394# Return the closest prime number to num_colors / 10.95abs_distance = [np.abs(num_colors / 10. - p) for p in prime_candidates]96num_candidates = len(abs_distance)97inds = [i for _, i in sorted(zip(abs_distance, range(num_candidates)))]98return prime_candidates[inds[0]]99100101def save_image_array_as_png(image, output_path):102"""Saves an image (represented as a numpy array) to PNG.103104Args:105image: a numpy array with shape [height, width, 3].106output_path: path to which image should be written.107"""108image_pil = Image.fromarray(np.uint8(image)).convert('RGB')109with tf.gfile.Open(output_path, 'w') as fid:110image_pil.save(fid, 'PNG')111112113def encode_image_array_as_png_str(image):114"""Encodes a numpy array into a PNG string.115116Args:117image: a numpy array with shape [height, width, 3].118119Returns:120PNG encoded image string.121"""122image_pil = Image.fromarray(np.uint8(image))123output = six.BytesIO()124image_pil.save(output, format='PNG')125png_string = output.getvalue()126output.close()127return png_string128129130def draw_bounding_box_on_image_array(image,131ymin,132xmin,133ymax,134xmax,135color='red',136thickness=4,137display_str_list=(),138use_normalized_coordinates=True):139"""Adds a bounding box to an image (numpy array).140141Bounding box coordinates can be specified in either absolute (pixel) or142normalized coordinates by setting the use_normalized_coordinates argument.143144Args:145image: a numpy array with shape [height, width, 3].146ymin: ymin of bounding box.147xmin: xmin of bounding box.148ymax: ymax of bounding box.149xmax: xmax of bounding box.150color: color to draw bounding box. Default is red.151thickness: line thickness. Default value is 4.152display_str_list: list of strings to display in box153(each to be shown on its own line).154use_normalized_coordinates: If True (default), treat coordinates155ymin, xmin, ymax, xmax as relative to the image. Otherwise treat156coordinates as absolute.157"""158image_pil = Image.fromarray(np.uint8(image)).convert('RGB')159draw_bounding_box_on_image(image_pil, ymin, xmin, ymax, xmax, color,160thickness, display_str_list,161use_normalized_coordinates)162np.copyto(image, np.array(image_pil))163164165def draw_bounding_box_on_image(image,166ymin,167xmin,168ymax,169xmax,170color='red',171thickness=4,172display_str_list=(),173use_normalized_coordinates=True):174"""Adds a bounding box to an image.175176Bounding box coordinates can be specified in either absolute (pixel) or177normalized coordinates by setting the use_normalized_coordinates argument.178179Each string in display_str_list is displayed on a separate line above the180bounding box in black text on a rectangle filled with the input 'color'.181If the top of the bounding box extends to the edge of the image, the strings182are displayed below the bounding box.183184Args:185image: a PIL.Image object.186ymin: ymin of bounding box.187xmin: xmin of bounding box.188ymax: ymax of bounding box.189xmax: xmax of bounding box.190color: color to draw bounding box. Default is red.191thickness: line thickness. Default value is 4.192display_str_list: list of strings to display in box193(each to be shown on its own line).194use_normalized_coordinates: If True (default), treat coordinates195ymin, xmin, ymax, xmax as relative to the image. Otherwise treat196coordinates as absolute.197"""198draw = ImageDraw.Draw(image)199im_width, im_height = image.size200if use_normalized_coordinates:201(left, right, top, bottom) = (xmin * im_width, xmax * im_width,202ymin * im_height, ymax * im_height)203else:204(left, right, top, bottom) = (xmin, xmax, ymin, ymax)205if thickness > 0:206draw.line([(left, top), (left, bottom), (right, bottom), (right, top),207(left, top)],208width=thickness,209fill=color)210try:211font = ImageFont.truetype('arial.ttf', 24)212except IOError:213font = ImageFont.load_default()214215# If the total height of the display strings added to the top of the bounding216# box exceeds the top of the image, stack the strings below the bounding box217# instead of above.218display_str_heights = [font.getsize(ds)[1] for ds in display_str_list]219# Each display_str has a top and bottom margin of 0.05x.220total_display_str_height = (1 + 2 * 0.05) * sum(display_str_heights)221222if top > total_display_str_height:223text_bottom = top224else:225text_bottom = bottom + total_display_str_height226# Reverse list and print from bottom to top.227for display_str in display_str_list[::-1]:228text_width, text_height = font.getsize(display_str)229margin = np.ceil(0.05 * text_height)230draw.rectangle(231[(left, text_bottom - text_height - 2 * margin), (left + text_width,232text_bottom)],233fill=color)234draw.text(235(left + margin, text_bottom - text_height - margin),236display_str,237fill='black',238font=font)239text_bottom -= text_height - 2 * margin240241242def draw_bounding_boxes_on_image_array(image,243boxes,244color='red',245thickness=4,246display_str_list_list=()):247"""Draws bounding boxes on image (numpy array).248249Args:250image: a numpy array object.251boxes: a 2 dimensional numpy array of [N, 4]: (ymin, xmin, ymax, xmax).252The coordinates are in normalized format between [0, 1].253color: color to draw bounding box. Default is red.254thickness: line thickness. Default value is 4.255display_str_list_list: list of list of strings.256a list of strings for each bounding box.257The reason to pass a list of strings for a258bounding box is that it might contain259multiple labels.260261Raises:262ValueError: if boxes is not a [N, 4] array263"""264image_pil = Image.fromarray(image)265draw_bounding_boxes_on_image(image_pil, boxes, color, thickness,266display_str_list_list)267np.copyto(image, np.array(image_pil))268269270def draw_bounding_boxes_on_image(image,271boxes,272color='red',273thickness=4,274display_str_list_list=()):275"""Draws bounding boxes on image.276277Args:278image: a PIL.Image object.279boxes: a 2 dimensional numpy array of [N, 4]: (ymin, xmin, ymax, xmax).280The coordinates are in normalized format between [0, 1].281color: color to draw bounding box. Default is red.282thickness: line thickness. Default value is 4.283display_str_list_list: list of list of strings.284a list of strings for each bounding box.285The reason to pass a list of strings for a286bounding box is that it might contain287multiple labels.288289Raises:290ValueError: if boxes is not a [N, 4] array291"""292boxes_shape = boxes.shape293if not boxes_shape:294return295if len(boxes_shape) != 2 or boxes_shape[1] != 4:296raise ValueError('Input must be of size [N, 4]')297for i in range(boxes_shape[0]):298display_str_list = ()299if display_str_list_list:300display_str_list = display_str_list_list[i]301draw_bounding_box_on_image(image, boxes[i, 0], boxes[i, 1], boxes[i, 2],302boxes[i, 3], color, thickness, display_str_list)303304305def create_visualization_fn(category_index,306include_masks=False,307include_keypoints=False,308include_keypoint_scores=False,309include_track_ids=False,310**kwargs):311"""Constructs a visualization function that can be wrapped in a py_func.312313py_funcs only accept positional arguments. This function returns a suitable314function with the correct positional argument mapping. The positional315arguments in order are:3160: image3171: boxes3182: classes3193: scores320[4]: masks (optional)321[4-5]: keypoints (optional)322[4-6]: keypoint_scores (optional)323[4-7]: track_ids (optional)324325-- Example 1 --326vis_only_masks_fn = create_visualization_fn(category_index,327include_masks=True, include_keypoints=False, include_track_ids=False,328**kwargs)329image = tf.py_func(vis_only_masks_fn,330inp=[image, boxes, classes, scores, masks],331Tout=tf.uint8)332333-- Example 2 --334vis_masks_and_track_ids_fn = create_visualization_fn(category_index,335include_masks=True, include_keypoints=False, include_track_ids=True,336**kwargs)337image = tf.py_func(vis_masks_and_track_ids_fn,338inp=[image, boxes, classes, scores, masks, track_ids],339Tout=tf.uint8)340341Args:342category_index: a dict that maps integer ids to category dicts. e.g.343{1: {1: 'dog'}, 2: {2: 'cat'}, ...}344include_masks: Whether masks should be expected as a positional argument in345the returned function.346include_keypoints: Whether keypoints should be expected as a positional347argument in the returned function.348include_keypoint_scores: Whether keypoint scores should be expected as a349positional argument in the returned function.350include_track_ids: Whether track ids should be expected as a positional351argument in the returned function.352**kwargs: Additional kwargs that will be passed to353visualize_boxes_and_labels_on_image_array.354355Returns:356Returns a function that only takes tensors as positional arguments.357"""358359def visualization_py_func_fn(*args):360"""Visualization function that can be wrapped in a tf.py_func.361362Args:363*args: First 4 positional arguments must be:364image - uint8 numpy array with shape (img_height, img_width, 3).365boxes - a numpy array of shape [N, 4].366classes - a numpy array of shape [N].367scores - a numpy array of shape [N] or None.368-- Optional positional arguments --369instance_masks - a numpy array of shape [N, image_height, image_width].370keypoints - a numpy array of shape [N, num_keypoints, 2].371keypoint_scores - a numpy array of shape [N, num_keypoints].372track_ids - a numpy array of shape [N] with unique track ids.373374Returns:375uint8 numpy array with shape (img_height, img_width, 3) with overlaid376boxes.377"""378image = args[0]379boxes = args[1]380classes = args[2]381scores = args[3]382masks = keypoints = keypoint_scores = track_ids = None383pos_arg_ptr = 4 # Positional argument for first optional tensor (masks).384if include_masks:385masks = args[pos_arg_ptr]386pos_arg_ptr += 1387if include_keypoints:388keypoints = args[pos_arg_ptr]389pos_arg_ptr += 1390if include_keypoint_scores:391keypoint_scores = args[pos_arg_ptr]392pos_arg_ptr += 1393if include_track_ids:394track_ids = args[pos_arg_ptr]395396return visualize_boxes_and_labels_on_image_array(397image,398boxes,399classes,400scores,401category_index=category_index,402instance_masks=masks,403keypoints=keypoints,404keypoint_scores=keypoint_scores,405track_ids=track_ids,406**kwargs)407return visualization_py_func_fn408409410def draw_heatmaps_on_image(image, heatmaps):411"""Draws heatmaps on an image.412413The heatmaps are handled channel by channel and different colors are used to414paint different heatmap channels.415416Args:417image: a PIL.Image object.418heatmaps: a numpy array with shape [image_height, image_width, channel].419Note that the image_height and image_width should match the size of input420image.421"""422draw = ImageDraw.Draw(image)423channel = heatmaps.shape[2]424for c in range(channel):425heatmap = heatmaps[:, :, c] * 255426heatmap = heatmap.astype('uint8')427bitmap = Image.fromarray(heatmap, 'L')428bitmap.convert('1')429draw.bitmap(430xy=[(0, 0)],431bitmap=bitmap,432fill=STANDARD_COLORS[c])433434435def draw_heatmaps_on_image_array(image, heatmaps):436"""Overlays heatmaps to an image (numpy array).437438The function overlays the heatmaps on top of image. The heatmap values will be439painted with different colors depending on the channels. Similar to440"draw_heatmaps_on_image_array" function except the inputs are numpy arrays.441442Args:443image: a numpy array with shape [height, width, 3].444heatmaps: a numpy array with shape [height, width, channel].445446Returns:447An uint8 numpy array representing the input image painted with heatmap448colors.449"""450if not isinstance(image, np.ndarray):451image = image.numpy()452if not isinstance(heatmaps, np.ndarray):453heatmaps = heatmaps.numpy()454image_pil = Image.fromarray(np.uint8(image)).convert('RGB')455draw_heatmaps_on_image(image_pil, heatmaps)456return np.array(image_pil)457458459def draw_heatmaps_on_image_tensors(images,460heatmaps,461apply_sigmoid=False):462"""Draws heatmaps on batch of image tensors.463464Args:465images: A 4D uint8 image tensor of shape [N, H, W, C]. If C > 3, additional466channels will be ignored. If C = 1, then we convert the images to RGB467images.468heatmaps: [N, h, w, channel] float32 tensor of heatmaps. Note that the469heatmaps will be resized to match the input image size before overlaying470the heatmaps with input images. Theoretically the heatmap height width471should have the same aspect ratio as the input image to avoid potential472misalignment introduced by the image resize.473apply_sigmoid: Whether to apply a sigmoid layer on top of the heatmaps. If474the heatmaps come directly from the prediction logits, then we should475apply the sigmoid layer to make sure the values are in between [0.0, 1.0].476477Returns:4784D image tensor of type uint8, with heatmaps overlaid on top.479"""480# Additional channels are being ignored.481if images.shape[3] > 3:482images = images[:, :, :, 0:3]483elif images.shape[3] == 1:484images = tf.image.grayscale_to_rgb(images)485486_, height, width, _ = shape_utils.combined_static_and_dynamic_shape(images)487if apply_sigmoid:488heatmaps = tf.math.sigmoid(heatmaps)489resized_heatmaps = tf.image.resize(heatmaps, size=[height, width])490491elems = [images, resized_heatmaps]492493def draw_heatmaps(image_and_heatmaps):494"""Draws heatmaps on image."""495image_with_heatmaps = tf.py_function(496draw_heatmaps_on_image_array,497image_and_heatmaps,498tf.uint8)499return image_with_heatmaps500images = tf.map_fn(draw_heatmaps, elems, dtype=tf.uint8, back_prop=False)501return images502503504def _resize_original_image(image, image_shape):505image = tf.expand_dims(image, 0)506image = tf.image.resize_images(507image,508image_shape,509method=tf.image.ResizeMethod.NEAREST_NEIGHBOR,510align_corners=True)511return tf.cast(tf.squeeze(image, 0), tf.uint8)512513514def draw_bounding_boxes_on_image_tensors(images,515boxes,516classes,517scores,518category_index,519original_image_spatial_shape=None,520true_image_shape=None,521instance_masks=None,522keypoints=None,523keypoint_scores=None,524keypoint_edges=None,525track_ids=None,526max_boxes_to_draw=20,527min_score_thresh=0.2,528use_normalized_coordinates=True):529"""Draws bounding boxes, masks, and keypoints on batch of image tensors.530531Args:532images: A 4D uint8 image tensor of shape [N, H, W, C]. If C > 3, additional533channels will be ignored. If C = 1, then we convert the images to RGB534images.535boxes: [N, max_detections, 4] float32 tensor of detection boxes.536classes: [N, max_detections] int tensor of detection classes. Note that537classes are 1-indexed.538scores: [N, max_detections] float32 tensor of detection scores.539category_index: a dict that maps integer ids to category dicts. e.g.540{1: {1: 'dog'}, 2: {2: 'cat'}, ...}541original_image_spatial_shape: [N, 2] tensor containing the spatial size of542the original image.543true_image_shape: [N, 3] tensor containing the spatial size of unpadded544original_image.545instance_masks: A 4D uint8 tensor of shape [N, max_detection, H, W] with546instance masks.547keypoints: A 4D float32 tensor of shape [N, max_detection, num_keypoints, 2]548with keypoints.549keypoint_scores: A 3D float32 tensor of shape [N, max_detection,550num_keypoints] with keypoint scores.551keypoint_edges: A list of tuples with keypoint indices that specify which552keypoints should be connected by an edge, e.g. [(0, 1), (2, 4)] draws553edges from keypoint 0 to 1 and from keypoint 2 to 4.554track_ids: [N, max_detections] int32 tensor of unique tracks ids (i.e.555instance ids for each object). If provided, the color-coding of boxes is556dictated by these ids, and not classes.557max_boxes_to_draw: Maximum number of boxes to draw on an image. Default 20.558min_score_thresh: Minimum score threshold for visualization. Default 0.2.559use_normalized_coordinates: Whether to assume boxes and kepoints are in560normalized coordinates (as opposed to absolute coordiantes).561Default is True.562563Returns:5644D image tensor of type uint8, with boxes drawn on top.565"""566# Additional channels are being ignored.567if images.shape[3] > 3:568images = images[:, :, :, 0:3]569elif images.shape[3] == 1:570images = tf.image.grayscale_to_rgb(images)571visualization_keyword_args = {572'use_normalized_coordinates': use_normalized_coordinates,573'max_boxes_to_draw': max_boxes_to_draw,574'min_score_thresh': min_score_thresh,575'agnostic_mode': False,576'line_thickness': 4,577'keypoint_edges': keypoint_edges578}579if true_image_shape is None:580true_shapes = tf.constant(-1, shape=[images.shape.as_list()[0], 3])581else:582true_shapes = true_image_shape583if original_image_spatial_shape is None:584original_shapes = tf.constant(-1, shape=[images.shape.as_list()[0], 2])585else:586original_shapes = original_image_spatial_shape587588visualize_boxes_fn = create_visualization_fn(589category_index,590include_masks=instance_masks is not None,591include_keypoints=keypoints is not None,592include_keypoint_scores=keypoint_scores is not None,593include_track_ids=track_ids is not None,594**visualization_keyword_args)595596elems = [true_shapes, original_shapes, images, boxes, classes, scores]597if instance_masks is not None:598elems.append(instance_masks)599if keypoints is not None:600elems.append(keypoints)601if keypoint_scores is not None:602elems.append(keypoint_scores)603if track_ids is not None:604elems.append(track_ids)605606def draw_boxes(image_and_detections):607"""Draws boxes on image."""608true_shape = image_and_detections[0]609original_shape = image_and_detections[1]610if true_image_shape is not None:611image = shape_utils.pad_or_clip_nd(image_and_detections[2],612[true_shape[0], true_shape[1], 3])613if original_image_spatial_shape is not None:614image_and_detections[2] = _resize_original_image(image, original_shape)615616image_with_boxes = tf.py_func(visualize_boxes_fn, image_and_detections[2:],617tf.uint8)618return image_with_boxes619620images = tf.map_fn(draw_boxes, elems, dtype=tf.uint8, back_prop=False)621return images622623624def draw_side_by_side_evaluation_image(eval_dict,625category_index,626max_boxes_to_draw=20,627min_score_thresh=0.2,628use_normalized_coordinates=True,629keypoint_edges=None):630"""Creates a side-by-side image with detections and groundtruth.631632Bounding boxes (and instance masks, if available) are visualized on both633subimages.634635Args:636eval_dict: The evaluation dictionary returned by637eval_util.result_dict_for_batched_example() or638eval_util.result_dict_for_single_example().639category_index: A category index (dictionary) produced from a labelmap.640max_boxes_to_draw: The maximum number of boxes to draw for detections.641min_score_thresh: The minimum score threshold for showing detections.642use_normalized_coordinates: Whether to assume boxes and keypoints are in643normalized coordinates (as opposed to absolute coordinates).644Default is True.645keypoint_edges: A list of tuples with keypoint indices that specify which646keypoints should be connected by an edge, e.g. [(0, 1), (2, 4)] draws647edges from keypoint 0 to 1 and from keypoint 2 to 4.648649Returns:650A list of [1, H, 2 * W, C] uint8 tensor. The subimage on the left651corresponds to detections, while the subimage on the right corresponds to652groundtruth.653"""654detection_fields = fields.DetectionResultFields()655input_data_fields = fields.InputDataFields()656657images_with_detections_list = []658659# Add the batch dimension if the eval_dict is for single example.660if len(eval_dict[detection_fields.detection_classes].shape) == 1:661for key in eval_dict:662if (key != input_data_fields.original_image and663key != input_data_fields.image_additional_channels):664eval_dict[key] = tf.expand_dims(eval_dict[key], 0)665666for indx in range(eval_dict[input_data_fields.original_image].shape[0]):667instance_masks = None668if detection_fields.detection_masks in eval_dict:669instance_masks = tf.cast(670tf.expand_dims(671eval_dict[detection_fields.detection_masks][indx], axis=0),672tf.uint8)673keypoints = None674keypoint_scores = None675if detection_fields.detection_keypoints in eval_dict:676keypoints = tf.expand_dims(677eval_dict[detection_fields.detection_keypoints][indx], axis=0)678if detection_fields.detection_keypoint_scores in eval_dict:679keypoint_scores = tf.expand_dims(680eval_dict[detection_fields.detection_keypoint_scores][indx], axis=0)681else:682keypoint_scores = tf.cast(keypoint_ops.set_keypoint_visibilities(683keypoints), dtype=tf.float32)684685groundtruth_instance_masks = None686if input_data_fields.groundtruth_instance_masks in eval_dict:687groundtruth_instance_masks = tf.cast(688tf.expand_dims(689eval_dict[input_data_fields.groundtruth_instance_masks][indx],690axis=0), tf.uint8)691groundtruth_keypoints = None692groundtruth_keypoint_scores = None693gt_kpt_vis_fld = input_data_fields.groundtruth_keypoint_visibilities694if input_data_fields.groundtruth_keypoints in eval_dict:695groundtruth_keypoints = tf.expand_dims(696eval_dict[input_data_fields.groundtruth_keypoints][indx], axis=0)697if gt_kpt_vis_fld in eval_dict:698groundtruth_keypoint_scores = tf.expand_dims(699tf.cast(eval_dict[gt_kpt_vis_fld][indx], dtype=tf.float32), axis=0)700else:701groundtruth_keypoint_scores = tf.cast(702keypoint_ops.set_keypoint_visibilities(703groundtruth_keypoints), dtype=tf.float32)704705images_with_detections = draw_bounding_boxes_on_image_tensors(706tf.expand_dims(707eval_dict[input_data_fields.original_image][indx], axis=0),708tf.expand_dims(709eval_dict[detection_fields.detection_boxes][indx], axis=0),710tf.expand_dims(711eval_dict[detection_fields.detection_classes][indx], axis=0),712tf.expand_dims(713eval_dict[detection_fields.detection_scores][indx], axis=0),714category_index,715original_image_spatial_shape=tf.expand_dims(716eval_dict[input_data_fields.original_image_spatial_shape][indx],717axis=0),718true_image_shape=tf.expand_dims(719eval_dict[input_data_fields.true_image_shape][indx], axis=0),720instance_masks=instance_masks,721keypoints=keypoints,722keypoint_scores=keypoint_scores,723keypoint_edges=keypoint_edges,724max_boxes_to_draw=max_boxes_to_draw,725min_score_thresh=min_score_thresh,726use_normalized_coordinates=use_normalized_coordinates)727images_with_groundtruth = draw_bounding_boxes_on_image_tensors(728tf.expand_dims(729eval_dict[input_data_fields.original_image][indx], axis=0),730tf.expand_dims(731eval_dict[input_data_fields.groundtruth_boxes][indx], axis=0),732tf.expand_dims(733eval_dict[input_data_fields.groundtruth_classes][indx], axis=0),734tf.expand_dims(735tf.ones_like(736eval_dict[input_data_fields.groundtruth_classes][indx],737dtype=tf.float32),738axis=0),739category_index,740original_image_spatial_shape=tf.expand_dims(741eval_dict[input_data_fields.original_image_spatial_shape][indx],742axis=0),743true_image_shape=tf.expand_dims(744eval_dict[input_data_fields.true_image_shape][indx], axis=0),745instance_masks=groundtruth_instance_masks,746keypoints=groundtruth_keypoints,747keypoint_scores=groundtruth_keypoint_scores,748keypoint_edges=keypoint_edges,749max_boxes_to_draw=None,750min_score_thresh=0.0,751use_normalized_coordinates=use_normalized_coordinates)752images_to_visualize = tf.concat([images_with_detections,753images_with_groundtruth], axis=2)754755if input_data_fields.image_additional_channels in eval_dict:756images_with_additional_channels_groundtruth = (757draw_bounding_boxes_on_image_tensors(758tf.expand_dims(759eval_dict[input_data_fields.image_additional_channels][indx],760axis=0),761tf.expand_dims(762eval_dict[input_data_fields.groundtruth_boxes][indx], axis=0),763tf.expand_dims(764eval_dict[input_data_fields.groundtruth_classes][indx],765axis=0),766tf.expand_dims(767tf.ones_like(768eval_dict[input_data_fields.groundtruth_classes][indx],769dtype=tf.float32),770axis=0),771category_index,772original_image_spatial_shape=tf.expand_dims(773eval_dict[input_data_fields.original_image_spatial_shape]774[indx],775axis=0),776true_image_shape=tf.expand_dims(777eval_dict[input_data_fields.true_image_shape][indx], axis=0),778instance_masks=groundtruth_instance_masks,779keypoints=None,780keypoint_edges=None,781max_boxes_to_draw=None,782min_score_thresh=0.0,783use_normalized_coordinates=use_normalized_coordinates))784images_to_visualize = tf.concat(785[images_to_visualize, images_with_additional_channels_groundtruth],786axis=2)787images_with_detections_list.append(images_to_visualize)788789return images_with_detections_list790791792def draw_densepose_visualizations(eval_dict,793max_boxes_to_draw=20,794min_score_thresh=0.2,795num_parts=24,796dp_coord_to_visualize=0):797"""Draws DensePose visualizations.798799Args:800eval_dict: The evaluation dictionary returned by801eval_util.result_dict_for_batched_example().802max_boxes_to_draw: The maximum number of boxes to draw for detections.803min_score_thresh: The minimum score threshold for showing detections.804num_parts: The number of different densepose parts.805dp_coord_to_visualize: Whether to visualize v-coordinates (0) or806u-coordinates (0) overlaid on the person masks.807808Returns:809A list of [1, H, W, C] uint8 tensor, each element corresponding to an image810in the batch.811812Raises:813ValueError: If `dp_coord_to_visualize` is not 0 or 1.814"""815if dp_coord_to_visualize not in (0, 1):816raise ValueError('`dp_coord_to_visualize` must be either 0 for v '817'coordinates), or 1 for u coordinates, but instead got '818'{}'.format(dp_coord_to_visualize))819detection_fields = fields.DetectionResultFields()820input_data_fields = fields.InputDataFields()821822if detection_fields.detection_masks not in eval_dict:823raise ValueError('Expected `detection_masks` in `eval_dict`.')824if detection_fields.detection_surface_coords not in eval_dict:825raise ValueError('Expected `detection_surface_coords` in `eval_dict`.')826827images_with_detections_list = []828for indx in range(eval_dict[input_data_fields.original_image].shape[0]):829# Note that detection masks have already been resized to the original image830# shapes, but `original_image` has not.831# TODO(ronnyvotel): Consider resizing `original_image` in832# eval_util.result_dict_for_batched_example().833true_shape = eval_dict[input_data_fields.true_image_shape][indx]834original_shape = eval_dict[835input_data_fields.original_image_spatial_shape][indx]836image = eval_dict[input_data_fields.original_image][indx]837image = shape_utils.pad_or_clip_nd(image, [true_shape[0], true_shape[1], 3])838image = _resize_original_image(image, original_shape)839840scores = eval_dict[detection_fields.detection_scores][indx]841detection_masks = eval_dict[detection_fields.detection_masks][indx]842surface_coords = eval_dict[detection_fields.detection_surface_coords][indx]843844def draw_densepose_py_func(image, detection_masks, surface_coords, scores):845"""Overlays part masks and surface coords on original images."""846surface_coord_image = np.copy(image)847for i, (score, surface_coord, mask) in enumerate(848zip(scores, surface_coords, detection_masks)):849if i == max_boxes_to_draw:850break851if score > min_score_thresh:852draw_part_mask_on_image_array(image, mask, num_parts=num_parts)853draw_float_channel_on_image_array(854surface_coord_image, surface_coord[:, :, dp_coord_to_visualize],855mask)856return np.concatenate([image, surface_coord_image], axis=1)857858image_with_densepose = tf.py_func(859draw_densepose_py_func,860[image, detection_masks, surface_coords, scores],861tf.uint8)862images_with_detections_list.append(863image_with_densepose[tf.newaxis, :, :, :])864return images_with_detections_list865866867def draw_keypoints_on_image_array(image,868keypoints,869keypoint_scores=None,870min_score_thresh=0.5,871color='red',872radius=2,873use_normalized_coordinates=True,874keypoint_edges=None,875keypoint_edge_color='green',876keypoint_edge_width=2):877"""Draws keypoints on an image (numpy array).878879Args:880image: a numpy array with shape [height, width, 3].881keypoints: a numpy array with shape [num_keypoints, 2].882keypoint_scores: a numpy array with shape [num_keypoints]. If provided, only883those keypoints with a score above score_threshold will be visualized.884min_score_thresh: A scalar indicating the minimum keypoint score required885for a keypoint to be visualized. Note that keypoint_scores must be886provided for this threshold to take effect.887color: color to draw the keypoints with. Default is red.888radius: keypoint radius. Default value is 2.889use_normalized_coordinates: if True (default), treat keypoint values as890relative to the image. Otherwise treat them as absolute.891keypoint_edges: A list of tuples with keypoint indices that specify which892keypoints should be connected by an edge, e.g. [(0, 1), (2, 4)] draws893edges from keypoint 0 to 1 and from keypoint 2 to 4.894keypoint_edge_color: color to draw the keypoint edges with. Default is red.895keypoint_edge_width: width of the edges drawn between keypoints. Default896value is 2.897"""898image_pil = Image.fromarray(np.uint8(image)).convert('RGB')899draw_keypoints_on_image(image_pil,900keypoints,901keypoint_scores=keypoint_scores,902min_score_thresh=min_score_thresh,903color=color,904radius=radius,905use_normalized_coordinates=use_normalized_coordinates,906keypoint_edges=keypoint_edges,907keypoint_edge_color=keypoint_edge_color,908keypoint_edge_width=keypoint_edge_width)909np.copyto(image, np.array(image_pil))910911912def draw_keypoints_on_image(image,913keypoints,914keypoint_scores=None,915min_score_thresh=0.5,916color='red',917radius=2,918use_normalized_coordinates=True,919keypoint_edges=None,920keypoint_edge_color='green',921keypoint_edge_width=2):922"""Draws keypoints on an image.923924Args:925image: a PIL.Image object.926keypoints: a numpy array with shape [num_keypoints, 2].927keypoint_scores: a numpy array with shape [num_keypoints].928min_score_thresh: a score threshold for visualizing keypoints. Only used if929keypoint_scores is provided.930color: color to draw the keypoints with. Default is red.931radius: keypoint radius. Default value is 2.932use_normalized_coordinates: if True (default), treat keypoint values as933relative to the image. Otherwise treat them as absolute.934keypoint_edges: A list of tuples with keypoint indices that specify which935keypoints should be connected by an edge, e.g. [(0, 1), (2, 4)] draws936edges from keypoint 0 to 1 and from keypoint 2 to 4.937keypoint_edge_color: color to draw the keypoint edges with. Default is red.938keypoint_edge_width: width of the edges drawn between keypoints. Default939value is 2.940"""941draw = ImageDraw.Draw(image)942im_width, im_height = image.size943keypoints = np.array(keypoints)944keypoints_x = [k[1] for k in keypoints]945keypoints_y = [k[0] for k in keypoints]946if use_normalized_coordinates:947keypoints_x = tuple([im_width * x for x in keypoints_x])948keypoints_y = tuple([im_height * y for y in keypoints_y])949if keypoint_scores is not None:950keypoint_scores = np.array(keypoint_scores)951valid_kpt = np.greater(keypoint_scores, min_score_thresh)952else:953valid_kpt = np.where(np.any(np.isnan(keypoints), axis=1),954np.zeros_like(keypoints[:, 0]),955np.ones_like(keypoints[:, 0]))956valid_kpt = [v for v in valid_kpt]957958for keypoint_x, keypoint_y, valid in zip(keypoints_x, keypoints_y, valid_kpt):959if valid:960draw.ellipse([(keypoint_x - radius, keypoint_y - radius),961(keypoint_x + radius, keypoint_y + radius)],962outline=color, fill=color)963if keypoint_edges is not None:964for keypoint_start, keypoint_end in keypoint_edges:965if (keypoint_start < 0 or keypoint_start >= len(keypoints) or966keypoint_end < 0 or keypoint_end >= len(keypoints)):967continue968if not (valid_kpt[keypoint_start] and valid_kpt[keypoint_end]):969continue970edge_coordinates = [971keypoints_x[keypoint_start], keypoints_y[keypoint_start],972keypoints_x[keypoint_end], keypoints_y[keypoint_end]973]974draw.line(975edge_coordinates, fill=keypoint_edge_color, width=keypoint_edge_width)976977978def draw_mask_on_image_array(image, mask, color='red', alpha=0.4):979"""Draws mask on an image.980981Args:982image: uint8 numpy array with shape (img_height, img_height, 3)983mask: a uint8 numpy array of shape (img_height, img_height) with984values between either 0 or 1.985color: color to draw the keypoints with. Default is red.986alpha: transparency value between 0 and 1. (default: 0.4)987988Raises:989ValueError: On incorrect data type for image or masks.990"""991if image.dtype != np.uint8:992raise ValueError('`image` not of type np.uint8')993if mask.dtype != np.uint8:994raise ValueError('`mask` not of type np.uint8')995if image.shape[:2] != mask.shape:996raise ValueError('The image has spatial dimensions %s but the mask has '997'dimensions %s' % (image.shape[:2], mask.shape))998rgb = ImageColor.getrgb(color)999pil_image = Image.fromarray(image)10001001solid_color = np.expand_dims(1002np.ones_like(mask), axis=2) * np.reshape(list(rgb), [1, 1, 3])1003pil_solid_color = Image.fromarray(np.uint8(solid_color)).convert('RGBA')1004pil_mask = Image.fromarray(np.uint8(255.0*alpha*(mask > 0))).convert('L')1005pil_image = Image.composite(pil_solid_color, pil_image, pil_mask)1006np.copyto(image, np.array(pil_image.convert('RGB')))100710081009def draw_part_mask_on_image_array(image, mask, alpha=0.4, num_parts=24):1010"""Draws part mask on an image.10111012Args:1013image: uint8 numpy array with shape (img_height, img_height, 3)1014mask: a uint8 numpy array of shape (img_height, img_height) with10151-indexed parts (0 for background).1016alpha: transparency value between 0 and 1 (default: 0.4)1017num_parts: the maximum number of parts that may exist in the image (default101824 for DensePose).10191020Raises:1021ValueError: On incorrect data type for image or masks.1022"""1023if image.dtype != np.uint8:1024raise ValueError('`image` not of type np.uint8')1025if mask.dtype != np.uint8:1026raise ValueError('`mask` not of type np.uint8')1027if image.shape[:2] != mask.shape:1028raise ValueError('The image has spatial dimensions %s but the mask has '1029'dimensions %s' % (image.shape[:2], mask.shape))10301031pil_image = Image.fromarray(image)1032part_colors = np.zeros_like(image)1033mask_1_channel = mask[:, :, np.newaxis]1034for i, color in enumerate(STANDARD_COLORS[:num_parts]):1035rgb = np.array(ImageColor.getrgb(color), dtype=np.uint8)1036part_colors += (mask_1_channel == i + 1) * rgb[np.newaxis, np.newaxis, :]1037pil_part_colors = Image.fromarray(np.uint8(part_colors)).convert('RGBA')1038pil_mask = Image.fromarray(np.uint8(255.0 * alpha * (mask > 0))).convert('L')1039pil_image = Image.composite(pil_part_colors, pil_image, pil_mask)1040np.copyto(image, np.array(pil_image.convert('RGB')))104110421043def draw_float_channel_on_image_array(image, channel, mask, alpha=0.9,1044cmap='YlGn'):1045"""Draws a floating point channel on an image array.10461047Args:1048image: uint8 numpy array with shape (img_height, img_height, 3)1049channel: float32 numpy array with shape (img_height, img_height). The values1050should be in the range [0, 1], and will be mapped to colors using the1051provided colormap `cmap` argument.1052mask: a uint8 numpy array of shape (img_height, img_height) with10531-indexed parts (0 for background).1054alpha: transparency value between 0 and 1 (default: 0.9)1055cmap: string with the colormap to use.10561057Raises:1058ValueError: On incorrect data type for image or masks.1059"""1060if image.dtype != np.uint8:1061raise ValueError('`image` not of type np.uint8')1062if channel.dtype != np.float32:1063raise ValueError('`channel` not of type np.float32')1064if mask.dtype != np.uint8:1065raise ValueError('`mask` not of type np.uint8')1066if image.shape[:2] != channel.shape:1067raise ValueError('The image has spatial dimensions %s but the channel has '1068'dimensions %s' % (image.shape[:2], channel.shape))1069if image.shape[:2] != mask.shape:1070raise ValueError('The image has spatial dimensions %s but the mask has '1071'dimensions %s' % (image.shape[:2], mask.shape))10721073cm = plt.get_cmap(cmap)1074pil_image = Image.fromarray(image)1075colored_channel = cm(channel)[:, :, :3]1076pil_colored_channel = Image.fromarray(1077np.uint8(colored_channel * 255)).convert('RGBA')1078pil_mask = Image.fromarray(np.uint8(255.0 * alpha * (mask > 0))).convert('L')1079pil_image = Image.composite(pil_colored_channel, pil_image, pil_mask)1080np.copyto(image, np.array(pil_image.convert('RGB')))108110821083def visualize_boxes_and_labels_on_image_array(1084image,1085boxes,1086classes,1087scores,1088category_index,1089instance_masks=None,1090instance_boundaries=None,1091keypoints=None,1092keypoint_scores=None,1093keypoint_edges=None,1094track_ids=None,1095use_normalized_coordinates=False,1096max_boxes_to_draw=20,1097min_score_thresh=.5,1098agnostic_mode=False,1099line_thickness=4,1100groundtruth_box_visualization_color='black',1101skip_boxes=False,1102skip_scores=False,1103skip_labels=False,1104skip_track_ids=False):1105"""Overlay labeled boxes on an image with formatted scores and label names.11061107This function groups boxes that correspond to the same location1108and creates a display string for each detection and overlays these1109on the image. Note that this function modifies the image in place, and returns1110that same image.11111112Args:1113image: uint8 numpy array with shape (img_height, img_width, 3)1114boxes: a numpy array of shape [N, 4]1115classes: a numpy array of shape [N]. Note that class indices are 1-based,1116and match the keys in the label map.1117scores: a numpy array of shape [N] or None. If scores=None, then1118this function assumes that the boxes to be plotted are groundtruth1119boxes and plot all boxes as black with no classes or scores.1120category_index: a dict containing category dictionaries (each holding1121category index `id` and category name `name`) keyed by category indices.1122instance_masks: a uint8 numpy array of shape [N, image_height, image_width],1123can be None.1124instance_boundaries: a numpy array of shape [N, image_height, image_width]1125with values ranging between 0 and 1, can be None.1126keypoints: a numpy array of shape [N, num_keypoints, 2], can1127be None.1128keypoint_scores: a numpy array of shape [N, num_keypoints], can be None.1129keypoint_edges: A list of tuples with keypoint indices that specify which1130keypoints should be connected by an edge, e.g. [(0, 1), (2, 4)] draws1131edges from keypoint 0 to 1 and from keypoint 2 to 4.1132track_ids: a numpy array of shape [N] with unique track ids. If provided,1133color-coding of boxes will be determined by these ids, and not the class1134indices.1135use_normalized_coordinates: whether boxes is to be interpreted as1136normalized coordinates or not.1137max_boxes_to_draw: maximum number of boxes to visualize. If None, draw1138all boxes.1139min_score_thresh: minimum score threshold for a box or keypoint to be1140visualized.1141agnostic_mode: boolean (default: False) controlling whether to evaluate in1142class-agnostic mode or not. This mode will display scores but ignore1143classes.1144line_thickness: integer (default: 4) controlling line width of the boxes.1145groundtruth_box_visualization_color: box color for visualizing groundtruth1146boxes1147skip_boxes: whether to skip the drawing of bounding boxes.1148skip_scores: whether to skip score when drawing a single detection1149skip_labels: whether to skip label when drawing a single detection1150skip_track_ids: whether to skip track id when drawing a single detection11511152Returns:1153uint8 numpy array with shape (img_height, img_width, 3) with overlaid boxes.1154"""1155# Create a display string (and color) for every box location, group any boxes1156# that correspond to the same location.1157box_to_display_str_map = collections.defaultdict(list)1158box_to_color_map = collections.defaultdict(str)1159box_to_instance_masks_map = {}1160box_to_instance_boundaries_map = {}1161box_to_keypoints_map = collections.defaultdict(list)1162box_to_keypoint_scores_map = collections.defaultdict(list)1163box_to_track_ids_map = {}1164if not max_boxes_to_draw:1165max_boxes_to_draw = boxes.shape[0]1166for i in range(boxes.shape[0]):1167if max_boxes_to_draw == len(box_to_color_map):1168break1169if scores is None or scores[i] > min_score_thresh:1170box = tuple(boxes[i].tolist())1171if instance_masks is not None:1172box_to_instance_masks_map[box] = instance_masks[i]1173if instance_boundaries is not None:1174box_to_instance_boundaries_map[box] = instance_boundaries[i]1175if keypoints is not None:1176box_to_keypoints_map[box].extend(keypoints[i])1177if keypoint_scores is not None:1178box_to_keypoint_scores_map[box].extend(keypoint_scores[i])1179if track_ids is not None:1180box_to_track_ids_map[box] = track_ids[i]1181if scores is None:1182box_to_color_map[box] = groundtruth_box_visualization_color1183else:1184display_str = ''1185if not skip_labels:1186if not agnostic_mode:1187if classes[i] in six.viewkeys(category_index):1188class_name = category_index[classes[i]]['name']1189else:1190class_name = 'N/A'1191display_str = str(class_name)1192if not skip_scores:1193if not display_str:1194display_str = '{}%'.format(round(100*scores[i]))1195else:1196display_str = '{}: {}%'.format(display_str, round(100*scores[i]))1197if not skip_track_ids and track_ids is not None:1198if not display_str:1199display_str = 'ID {}'.format(track_ids[i])1200else:1201display_str = '{}: ID {}'.format(display_str, track_ids[i])1202box_to_display_str_map[box].append(display_str)1203if agnostic_mode:1204box_to_color_map[box] = 'DarkOrange'1205elif track_ids is not None:1206prime_multipler = _get_multiplier_for_color_randomness()1207box_to_color_map[box] = STANDARD_COLORS[1208(prime_multipler * track_ids[i]) % len(STANDARD_COLORS)]1209else:1210box_to_color_map[box] = STANDARD_COLORS[1211classes[i] % len(STANDARD_COLORS)]12121213# Draw all boxes onto image.1214for box, color in box_to_color_map.items():1215ymin, xmin, ymax, xmax = box1216if instance_masks is not None:1217draw_mask_on_image_array(1218image,1219box_to_instance_masks_map[box],1220color=color1221)1222if instance_boundaries is not None:1223draw_mask_on_image_array(1224image,1225box_to_instance_boundaries_map[box],1226color='red',1227alpha=1.01228)1229draw_bounding_box_on_image_array(1230image,1231ymin,1232xmin,1233ymax,1234xmax,1235color=color,1236thickness=0 if skip_boxes else line_thickness,1237display_str_list=box_to_display_str_map[box],1238use_normalized_coordinates=use_normalized_coordinates)1239if keypoints is not None:1240keypoint_scores_for_box = None1241if box_to_keypoint_scores_map:1242keypoint_scores_for_box = box_to_keypoint_scores_map[box]1243draw_keypoints_on_image_array(1244image,1245box_to_keypoints_map[box],1246keypoint_scores_for_box,1247min_score_thresh=min_score_thresh,1248color=color,1249radius=line_thickness / 2,1250use_normalized_coordinates=use_normalized_coordinates,1251keypoint_edges=keypoint_edges,1252keypoint_edge_color=color,1253keypoint_edge_width=line_thickness // 2)12541255return image125612571258def add_cdf_image_summary(values, name):1259"""Adds a tf.summary.image for a CDF plot of the values.12601261Normalizes `values` such that they sum to 1, plots the cumulative distribution1262function and creates a tf image summary.12631264Args:1265values: a 1-D float32 tensor containing the values.1266name: name for the image summary.1267"""1268def cdf_plot(values):1269"""Numpy function to plot CDF."""1270normalized_values = values / np.sum(values)1271sorted_values = np.sort(normalized_values)1272cumulative_values = np.cumsum(sorted_values)1273fraction_of_examples = (np.arange(cumulative_values.size, dtype=np.float32)1274/ cumulative_values.size)1275fig = plt.figure(frameon=False)1276ax = fig.add_subplot('111')1277ax.plot(fraction_of_examples, cumulative_values)1278ax.set_ylabel('cumulative normalized values')1279ax.set_xlabel('fraction of examples')1280fig.canvas.draw()1281width, height = fig.get_size_inches() * fig.get_dpi()1282image = np.fromstring(fig.canvas.tostring_rgb(), dtype='uint8').reshape(12831, int(height), int(width), 3)1284return image1285cdf_plot = tf.py_func(cdf_plot, [values], tf.uint8)1286tf.summary.image(name, cdf_plot)128712881289def add_hist_image_summary(values, bins, name):1290"""Adds a tf.summary.image for a histogram plot of the values.12911292Plots the histogram of values and creates a tf image summary.12931294Args:1295values: a 1-D float32 tensor containing the values.1296bins: bin edges which will be directly passed to np.histogram.1297name: name for the image summary.1298"""12991300def hist_plot(values, bins):1301"""Numpy function to plot hist."""1302fig = plt.figure(frameon=False)1303ax = fig.add_subplot('111')1304y, x = np.histogram(values, bins=bins)1305ax.plot(x[:-1], y)1306ax.set_ylabel('count')1307ax.set_xlabel('value')1308fig.canvas.draw()1309width, height = fig.get_size_inches() * fig.get_dpi()1310image = np.fromstring(1311fig.canvas.tostring_rgb(), dtype='uint8').reshape(13121, int(height), int(width), 3)1313return image1314hist_plot = tf.py_func(hist_plot, [values, bins], tf.uint8)1315tf.summary.image(name, hist_plot)131613171318class EvalMetricOpsVisualization(six.with_metaclass(abc.ABCMeta, object)):1319"""Abstract base class responsible for visualizations during evaluation.13201321Currently, summary images are not run during evaluation. One way to produce1322evaluation images in Tensorboard is to provide tf.summary.image strings as1323`value_ops` in tf.estimator.EstimatorSpec's `eval_metric_ops`. This class is1324responsible for accruing images (with overlaid detections and groundtruth)1325and returning a dictionary that can be passed to `eval_metric_ops`.1326"""13271328def __init__(self,1329category_index,1330max_examples_to_draw=5,1331max_boxes_to_draw=20,1332min_score_thresh=0.2,1333use_normalized_coordinates=True,1334summary_name_prefix='evaluation_image',1335keypoint_edges=None):1336"""Creates an EvalMetricOpsVisualization.13371338Args:1339category_index: A category index (dictionary) produced from a labelmap.1340max_examples_to_draw: The maximum number of example summaries to produce.1341max_boxes_to_draw: The maximum number of boxes to draw for detections.1342min_score_thresh: The minimum score threshold for showing detections.1343use_normalized_coordinates: Whether to assume boxes and keypoints are in1344normalized coordinates (as opposed to absolute coordinates).1345Default is True.1346summary_name_prefix: A string prefix for each image summary.1347keypoint_edges: A list of tuples with keypoint indices that specify which1348keypoints should be connected by an edge, e.g. [(0, 1), (2, 4)] draws1349edges from keypoint 0 to 1 and from keypoint 2 to 4.1350"""13511352self._category_index = category_index1353self._max_examples_to_draw = max_examples_to_draw1354self._max_boxes_to_draw = max_boxes_to_draw1355self._min_score_thresh = min_score_thresh1356self._use_normalized_coordinates = use_normalized_coordinates1357self._summary_name_prefix = summary_name_prefix1358self._keypoint_edges = keypoint_edges1359self._images = []13601361def clear(self):1362self._images = []13631364def add_images(self, images):1365"""Store a list of images, each with shape [1, H, W, C]."""1366if len(self._images) >= self._max_examples_to_draw:1367return13681369# Store images and clip list if necessary.1370self._images.extend(images)1371if len(self._images) > self._max_examples_to_draw:1372self._images[self._max_examples_to_draw:] = []13731374def get_estimator_eval_metric_ops(self, eval_dict):1375"""Returns metric ops for use in tf.estimator.EstimatorSpec.13761377Args:1378eval_dict: A dictionary that holds an image, groundtruth, and detections1379for a batched example. Note that, we use only the first example for1380visualization. See eval_util.result_dict_for_batched_example() for a1381convenient method for constructing such a dictionary. The dictionary1382contains1383fields.InputDataFields.original_image: [batch_size, H, W, 3] image.1384fields.InputDataFields.original_image_spatial_shape: [batch_size, 2]1385tensor containing the size of the original image.1386fields.InputDataFields.true_image_shape: [batch_size, 3]1387tensor containing the spatial size of the upadded original image.1388fields.InputDataFields.groundtruth_boxes - [batch_size, num_boxes, 4]1389float32 tensor with groundtruth boxes in range [0.0, 1.0].1390fields.InputDataFields.groundtruth_classes - [batch_size, num_boxes]1391int64 tensor with 1-indexed groundtruth classes.1392fields.InputDataFields.groundtruth_instance_masks - (optional)1393[batch_size, num_boxes, H, W] int64 tensor with instance masks.1394fields.InputDataFields.groundtruth_keypoints - (optional)1395[batch_size, num_boxes, num_keypoints, 2] float32 tensor with1396keypoint coordinates in format [y, x].1397fields.InputDataFields.groundtruth_keypoint_visibilities - (optional)1398[batch_size, num_boxes, num_keypoints] bool tensor with1399keypoint visibilities.1400fields.DetectionResultFields.detection_boxes - [batch_size,1401max_num_boxes, 4] float32 tensor with detection boxes in range [0.0,14021.0].1403fields.DetectionResultFields.detection_classes - [batch_size,1404max_num_boxes] int64 tensor with 1-indexed detection classes.1405fields.DetectionResultFields.detection_scores - [batch_size,1406max_num_boxes] float32 tensor with detection scores.1407fields.DetectionResultFields.detection_masks - (optional) [batch_size,1408max_num_boxes, H, W] float32 tensor of binarized masks.1409fields.DetectionResultFields.detection_keypoints - (optional)1410[batch_size, max_num_boxes, num_keypoints, 2] float32 tensor with1411keypoints.1412fields.DetectionResultFields.detection_keypoint_scores - (optional)1413[batch_size, max_num_boxes, num_keypoints] float32 tensor with1414keypoints scores.14151416Returns:1417A dictionary of image summary names to tuple of (value_op, update_op). The1418`update_op` is the same for all items in the dictionary, and is1419responsible for saving a single side-by-side image with detections and1420groundtruth. Each `value_op` holds the tf.summary.image string for a given1421image.1422"""1423if self._max_examples_to_draw == 0:1424return {}1425images = self.images_from_evaluation_dict(eval_dict)14261427def get_images():1428"""Returns a list of images, padded to self._max_images_to_draw."""1429images = self._images1430while len(images) < self._max_examples_to_draw:1431images.append(np.array(0, dtype=np.uint8))1432self.clear()1433return images14341435def image_summary_or_default_string(summary_name, image):1436"""Returns image summaries for non-padded elements."""1437return tf.cond(1438tf.equal(tf.size(tf.shape(image)), 4),1439lambda: tf.summary.image(summary_name, image),1440lambda: tf.constant(''))14411442if tf.executing_eagerly():1443update_op = self.add_images([[images[0]]])1444image_tensors = get_images()1445else:1446update_op = tf.py_func(self.add_images, [[images[0]]], [])1447image_tensors = tf.py_func(1448get_images, [], [tf.uint8] * self._max_examples_to_draw)1449eval_metric_ops = {}1450for i, image in enumerate(image_tensors):1451summary_name = self._summary_name_prefix + '/' + str(i)1452value_op = image_summary_or_default_string(summary_name, image)1453eval_metric_ops[summary_name] = (value_op, update_op)1454return eval_metric_ops14551456@abc.abstractmethod1457def images_from_evaluation_dict(self, eval_dict):1458"""Converts evaluation dictionary into a list of image tensors.14591460To be overridden by implementations.14611462Args:1463eval_dict: A dictionary with all the necessary information for producing1464visualizations.14651466Returns:1467A list of [1, H, W, C] uint8 tensors.1468"""1469raise NotImplementedError147014711472class VisualizeSingleFrameDetections(EvalMetricOpsVisualization):1473"""Class responsible for single-frame object detection visualizations."""14741475def __init__(self,1476category_index,1477max_examples_to_draw=5,1478max_boxes_to_draw=20,1479min_score_thresh=0.2,1480use_normalized_coordinates=True,1481summary_name_prefix='Detections_Left_Groundtruth_Right',1482keypoint_edges=None):1483super(VisualizeSingleFrameDetections, self).__init__(1484category_index=category_index,1485max_examples_to_draw=max_examples_to_draw,1486max_boxes_to_draw=max_boxes_to_draw,1487min_score_thresh=min_score_thresh,1488use_normalized_coordinates=use_normalized_coordinates,1489summary_name_prefix=summary_name_prefix,1490keypoint_edges=keypoint_edges)14911492def images_from_evaluation_dict(self, eval_dict):1493return draw_side_by_side_evaluation_image(eval_dict, self._category_index,1494self._max_boxes_to_draw,1495self._min_score_thresh,1496self._use_normalized_coordinates,1497self._keypoint_edges)149814991500