stm32-modelzoo-app / object_detection /tf /src /utils /bounding_boxes_utils.py
FBAGSTM's picture
STM32 AI Experimentation Hub
747451d
# /*---------------------------------------------------------------------------------------------
# * Copyright (c) 2022 STMicroelectronics.
# * All rights reserved.
# * This software is licensed under terms that can be found in the LICENSE file in
# * the root directory of this software component.
# * If no LICENSE file comes with this software, it is provided AS-IS.
# *--------------------------------------------------------------------------------------------*/
import random
import matplotlib.patches as patches
import numpy as np
import tensorflow as tf
def bbox_center_to_corners_coords(
boxes: tf.Tensor,
image_size: tuple = None,
normalize: bool = None,
clip_boxes: bool = False) -> tf.Tensor:
"""
Converts coordinates of bounding boxes in (x, y, w, h) system
to coordinates in (x1, y1, x2, y2) system. (x1, y1) and (x2, y2)
are pairs of diagonally opposite corners.
Input coordinates must be normalized (lying in the interval
[0, 1]). Output coordinates can be normalized or absolute.
Arguments:
boxes:
Bounding boxes in (x, y, w, h) coordinates system with
normalized values.
Shape: [batch_size, 4]
image_size:
A tuple specifying the size of the image: (width, height).
Required only when `normalize` is False.
normalize:
A boolean. If True, the output coordinates are normalized.
If False, they are absolute.
clip_boxes:
A boolean. If True, the output coordinates of the bounding
boxes are clipped to fit the image. If False, they are
left as is. Defaults to False.
Output coordinates:
Bounding boxes in (x1, y1, x2, y2) coordinates system, with
normalized or absolute values.
Shape:[batch_size, 4]
"""
x = boxes[..., 0]
y = boxes[..., 1]
w = boxes[..., 2]
h = boxes[..., 3]
x1 = x - w/2
y1 = y - h/2
x2 = x + w/2
y2 = y + h/2
if normalize:
if clip_boxes:
x1 = tf.clip_by_value(x1, 0, 1)
y1 = tf.clip_by_value(y1, 0, 1)
x2 = tf.clip_by_value(x2, 0, 1)
y2 = tf.clip_by_value(y2, 0, 1)
else:
image_size = tf.cast(image_size, tf.float32)
width = image_size[0]
height = image_size[1]
x1 = tf.round(x1 * width)
y1 = tf.round(y1 * height)
x2 = tf.round(x2 * width)
y2 = tf.round(y2 * height)
if clip_boxes:
x1 = tf.clip_by_value(x1, 0, width - 1)
y1 = tf.clip_by_value(y1, 0, height - 1)
x2 = tf.clip_by_value(x2, 0, width - 1)
y2 = tf.clip_by_value(y2, 0, height - 1)
return tf.stack([x1, y1, x2, y2], axis=-1)
def bbox_corners_to_center_coords(
boxes: tf.Tensor,
image_size: tuple = None,
abs_corners: bool = None,
clip_boxes: bool = False) -> tf.Tensor:
"""
Converts coordinates of bounding boxes in (x1, y1, x2, y2) system
to coordinates in (x, y, w, h) system. (x1, y1) and (x2, y2)
are pairs of diagonally opposite corners.
Input coordinates must be normalized (lying in the interval
[0, 1]). Output coordinates can be normalized or absolute.
Arguments:
boxes:
Bounding boxes in (x, y, w, h) coordinates system with
normalized values.
Shape: [batch_size, 4]
image_size:
A tuple specifying the size of the image: (width, height).
Required only when `normalize` is False.
normalize:
A boolean. If True, the output coordinates are normalized.
If False, they are absolute.
clip_boxes:
A boolean. If True, the output coordinates of the bounding
boxes are clipped to fit the image. If False, they are
left as is. Defaults to False.
Output coordinates:
Bounding boxes in (x1, y1, x2, y2) coordinates system, with
normalized or absolute values.
Shape:[batch_size, 4]
"""
x1 = boxes[..., 0]
y1 = boxes[..., 1]
x2 = boxes[..., 2]
y2 = boxes[..., 3]
# If the input corners coordinates are absolute, normalize them.
if abs_corners:
image_size = tf.cast(image_size, tf.float32)
width = image_size[0]
height = image_size[1]
x1 /= width
y1 /= height
x2 /= width
y2 /= height
w = x2 - x1
h = y2 - y1
x = x1 + w / 2
y = y1 + h / 2
if clip_boxes:
x = tf.clip_by_value(x, 0, 1)
y = tf.clip_by_value(y, 0, 1)
w = tf.clip_by_value(w, 0, 1)
h = tf.clip_by_value(h, 0, 1)
return tf.stack([x, y, w, h], axis=-1)
def bbox_normalized_to_abs_coords(
boxes: tf.Tensor,
image_size: tuple = None,
clip_boxes: bool = False):
"""
Converts coordinate values of bounding boxes in (x1, y1, x2, y2)
system from normalized to absolute. (x1, y1) and (x2, y2)
are pairs of diagonally opposite corners.
Arguments:
boxes:
Bounding boxes in (x1, y1, x2, y2) coordinates system
with normalized values.
Shape: [batch_size, 4]
image_size:
A tuple specifying the size of the image: (width, height).
clip_boxes:
A boolean. If True, the output coordinates of the bounding
boxes are clipped to fit the image. If False, they are
left as is. Defaults to False.
Output coordinates:
Bounding boxes in (x1, y1, x2, y2) coordinates system with
absolute values.
Shape:[batch_size, 4]
"""
x1 = boxes[..., 0]
y1 = boxes[..., 1]
x2 = boxes[..., 2]
y2 = boxes[..., 3]
image_size = tf.cast(image_size, tf.float32)
width = image_size[0]
height = image_size[1]
x1 = tf.round(x1 * width)
y1 = tf.round(y1 * height)
x2 = tf.round(x2 * width)
y2 = tf.round(y2 * height)
if clip_boxes:
x1 = tf.clip_by_value(x1, 0, width - 1)
y1 = tf.clip_by_value(y1, 0, height - 1)
x2 = tf.clip_by_value(x2, 0, width - 1)
y2 = tf.clip_by_value(y2, 0, height - 1)
return tf.stack([x1, y1, x2, y2], axis=-1)
def bbox_abs_to_normalized_coords(
boxes: tf.Tensor,
image_size: tuple = None,
clip_boxes: bool = False) -> tf.Tensor:
"""
Converts coordinate values of bounding boxes in (x1, y1, x2, y2)
system from absolute to normalized. (x1, y1) and (x2, y2)
are pairs of diagonally opposite corners.
Arguments:
boxes:
Bounding boxes in (x1, y1, x2, y2) coordinates system
with absolute values.
Shape: [batch_size, 4]
image_size:
A tuple specifying the size of the image: (width, height).
clip_boxes:
A boolean. If True, the output coordinates of the bounding
boxes are clipped to fit the image. If False, they are
left as is. Defaults to False.
Output coordinates:
Bounding boxes in (x1, y1, x2, y2) coordinates system with
normalized values.
Shape:[batch_size, 4]
"""
x1 = boxes[..., 0]
y1 = boxes[..., 1]
x2 = boxes[..., 2]
y2 = boxes[..., 3]
image_size = tf.cast(image_size, tf.float32)
width = image_size[0]
height = image_size[1]
x1 /= width
y1 /= height
x2 /= width
y2 /= height
if clip_boxes:
x1 = tf.clip_by_value(x1, 0, 1)
y1 = tf.clip_by_value(y1, 0, 1)
x2 = tf.clip_by_value(x2, 0, 1)
y2 = tf.clip_by_value(y2, 0, 1)
return tf.stack([x1, y1, x2, y2], axis=-1)
def calculate_box_wise_iou(
box1: tf.Tensor,
box2: tf.Tensor,
abs_coords: bool = None) -> tf.Tensor:
"""
Calculates the IOUs of two sets of bounding boxes in an
element-wise fashion (box to box). The coordinates of
bounding boxes must be in the (x1, y1, x2, y2) system, with
normalized or absolute values. (x1, y1) and (x2, y2) are
pairs of diagonally opposite corners.
Arguments:
box1:
First set of bounding boxes with coordinates in
(x1, y1, x2, y2) system.
Shape: [num_boxes, 4]
box2:
Second set of bounding boxes with coordinates in
(x1, y1, x2, y2) system.
Shape: [num_boxes, 4]
abs_coords:
Boolean. If True, coordinate values are absolute.
If False, they are normalized.
Returns:
A tf.Tensor with shape [num_boxes], the box-to-box IOU values.
"""
box1_x1 = box1[:, 0]
box1_y1 = box1[:, 1]
box1_x2 = box1[:, 2]
box1_y2 = box1[:, 3]
box2_x1 = box2[:, 0]
box2_y1 = box2[:, 1]
box2_x2 = box2[:, 2]
box2_y2 = box2[:, 3]
# Calculate the coordinates of diagonally opposite
# corners of the intersection of box1 and box2
inter_x1 = tf.math.maximum(box1_x1, box2_x1)
inter_y1 = tf.math.maximum(box1_y1, box2_y1)
inter_x2 = tf.math.minimum(box1_x2, box2_x2)
inter_y2 = tf.math.minimum(box1_y2, box2_y2)
if abs_coords:
inter_x = inter_x2 - inter_x1 + 1
inter_y = inter_y2 - inter_y1 + 1
else:
inter_x = inter_x2 - inter_x1
inter_y = inter_y2 - inter_y1
inter_area = tf.math.maximum(inter_x, 0.) * tf.math.maximum(inter_y, 0.)
if abs_coords:
box1_area = (box1_x2 - box1_x1 + 1) * (box1_y2 - box1_y1 + 1)
box2_area = (box2_x2 - box2_x1 + 1) * (box2_y2 - box2_y1 + 1)
else:
box1_area = (box1_x2 - box1_x1) * (box1_y2 - box1_y1)
box2_area = (box2_x2 - box2_x1) * (box2_y2 - box2_y1)
union_area = box1_area + box2_area - inter_area
iou = tf.where(union_area > 0, inter_area / union_area, 0.)
return iou
def plot_bounding_boxes(ax, boxes, classes=None, scores=None, class_names=None, colors=None):
# The function may be called with tensors.
# Make sure we have numpy arguments.
boxes = np.array(boxes, dtype=np.float32)
if classes is not None:
classes = np.array(classes, dtype=np.int32)
if scores is not None:
scores = np.array(scores, dtype=np.float32)
# Sample the box and text colors if they are not provided
num_boxes = np.shape(boxes)[0]
if colors is None:
colors = []
for i in range(num_boxes):
colors.append(random.choice(['b', 'g', 'r', 'c', 'm', 'y', 'w']))
for i in range(num_boxes):
# Skip padding boxes
if np.sum(boxes[i]) == 0:
continue
# Draw the box
x1, y1, x2, y2 = boxes[i]
rec = patches.Rectangle((x1, y1), x2 - x1, y2 - y1,
linewidth=2, edgecolor=colors[i], facecolor='none')
ax.add_patch(rec)
## Add the class name and score to the box
if (classes is not None) or (scores is not None):
class_txt = ''
if classes is not None:
class_txt = class_names[classes[i]]
if scores is not None:
class_txt += ': '
score_txt = ''
if scores is not None:
score_txt = "{:.4f}".format(scores[i])
legend = class_txt + score_txt
ax.text(x1 + 2, y1 - 3,
legend,
color='black', fontsize = 6, weight='bold',
bbox = dict(facecolor=colors[i], color=colors[i], alpha=0.75))