| |
| |
| |
| |
| |
| |
| |
|
|
| 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 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] |
| |
| |
| |
| 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): |
| |
| |
| |
| 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) |
|
|
| |
| 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): |
| |
| if np.sum(boxes[i]) == 0: |
| continue |
| |
| |
| 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) |
| |
| |
| 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)) |
|
|
|
|