Source code for albumentations.augmentations.bbox_utils

from __future__ import division

import numpy as np

__all__ = ['normalize_bbox', 'denormalize_bbox', 'normalize_bboxes', 'denormalize_bboxes', 'calculate_bbox_area',
           'filter_bboxes_by_visibility', 'convert_bbox_to_albumentations', 'convert_bbox_from_albumentations',
           'convert_bboxes_to_albumentations', 'convert_bboxes_from_albumentations']


[docs]def normalize_bbox(bbox, rows, cols): """Normalize coordinates of a bounding box. Divide x-coordinates by image width and y-coordinates by image height. """ if rows == 0: raise ValueError('Argument rows cannot be zero') if cols == 0: raise ValueError('Argument cols cannot be zero') x_min, y_min, x_max, y_max = bbox[:4] normalized_bbox = [x_min / cols, y_min / rows, x_max / cols, y_max / rows] return normalized_bbox + list(bbox[4:])
[docs]def denormalize_bbox(bbox, rows, cols): """Denormalize coordinates of a bounding box. Multiply x-coordinates by image width and y-coordinates by image height. This is an inverse operation for :func:`~albumentations.augmentations.bbox.normalize_bbox`. """ if rows == 0: raise ValueError('Argument rows cannot be zero') if cols == 0: raise ValueError('Argument cols cannot be zero') x_min, y_min, x_max, y_max = bbox[:4] denormalized_bbox = [x_min * cols, y_min * rows, x_max * cols, y_max * rows] return denormalized_bbox + list(bbox[4:])
[docs]def normalize_bboxes(bboxes, rows, cols): """Normalize a list of bounding boxes.""" return [normalize_bbox(bbox, rows, cols) for bbox in bboxes]
[docs]def denormalize_bboxes(bboxes, rows, cols): """Denormalize a list of bounding boxes.""" return [denormalize_bbox(bbox, rows, cols) for bbox in bboxes]
[docs]def calculate_bbox_area(bbox, rows, cols): """Calculate the area of a bounding box in pixels.""" bbox = denormalize_bbox(bbox, rows, cols) x_min, y_min, x_max, y_max = bbox[:4] area = (x_max - x_min) * (y_max - y_min) return area
[docs]def filter_bboxes_by_visibility(original_shape, bboxes, transformed_shape, transformed_bboxes, threshold=0., min_area=0.): """Filter bounding boxes and return only those boxes whose visibility after transformation is above the threshold and minimal area of bounding box in pixels is more then min_area. Args: original_shape (tuple): original image shape bboxes (list): original bounding boxes transformed_shape(tuple): transformed image transformed_bboxes (list): transformed bounding boxes threshold (float): visibility threshold. Should be a value in the range [0.0, 1.0]. min_area (float): Minimal area threshold. """ img_height, img_width = original_shape[:2] transformed_img_height, transformed_img_width = transformed_shape[:2] visible_bboxes = [] for bbox, transformed_bbox in zip(bboxes, transformed_bboxes): if not all(0.0 <= value <= 1.0 for value in transformed_bbox[:4]): continue bbox_area = calculate_bbox_area(bbox, img_height, img_width) transformed_bbox_area = calculate_bbox_area(transformed_bbox, transformed_img_height, transformed_img_width) if transformed_bbox_area < min_area: continue visibility = transformed_bbox_area / bbox_area if visibility >= threshold: visible_bboxes.append(transformed_bbox) return visible_bboxes
[docs]def convert_bbox_to_albumentations(bbox, source_format, rows, cols, check_validity=False): """Convert a bounding box from a format specified in `source_format` to the format used by albumentations: normalized coordinates of bottom-left and top-right corners of the bounding box in a form of `[x_min, y_min, x_max, y_max]` e.g. `[0.15, 0.27, 0.67, 0.5]`. Args: bbox (list): bounding box source_format (str): format of the bounding box. Should be 'coco' or 'pascal_voc'. check_validity (bool): check if all boxes are valid boxes rows (int): image height cols (int): image width Note: The `coco` format of a bounding box looks like `[x_min, y_min, width, height]`, e.g. [97, 12, 150, 200]. The `pascal_voc` format of a bounding box looks like `[x_min, y_min, x_max, y_max]`, e.g. [97, 12, 247, 212]. Raises: ValueError: if `target_format` is not equal to `coco` or `pascal_voc`. """ if source_format not in {'coco', 'pascal_voc'}: raise ValueError( "Unknown source_format {}. Supported formats are: 'coco' and 'pascal_voc'".format(source_format) ) if source_format == 'coco': x_min, y_min, width, height = bbox[:4] x_max = x_min + width y_max = y_min + height else: x_min, y_min, x_max, y_max = bbox[:4] bbox = [x_min, y_min, x_max, y_max] + list(bbox[4:]) bbox = normalize_bbox(bbox, rows, cols) if check_validity: check_bbox(bbox) return bbox
[docs]def convert_bbox_from_albumentations(bbox, target_format, rows, cols, check_validity=False): """Convert a bounding box from the format used by albumentations to a format, specified in `target_format`. Args: bbox (list): bounding box with coordinates in the format used by albumentations target_format (str): required format of the output bounding box. Should be 'coco' or 'pascal_voc'. rows (int): image height cols (int): image width check_validity (bool): check if all boxes are valid boxes Note: The `coco` format of a bounding box looks like `[x_min, y_min, width, height]`, e.g. [97, 12, 150, 200]. The `pascal_voc` format of a bounding box looks like `[x_min, y_min, x_max, y_max]`, e.g. [97, 12, 247, 212]. Raises: ValueError: if `target_format` is not equal to `coco` or `pascal_voc`. """ if target_format not in {'coco', 'pascal_voc'}: raise ValueError( "Unknown target_format {}. Supported formats are: 'coco' and 'pascal_voc'".format(target_format) ) if check_validity: check_bbox(bbox) bbox = denormalize_bbox(bbox, rows, cols) if target_format == 'coco': x_min, y_min, x_max, y_max = bbox[:4] width = x_max - x_min height = y_max - y_min bbox = [x_min, y_min, width, height] + list(bbox[4:]) return bbox
[docs]def convert_bboxes_to_albumentations(bboxes, source_format, rows, cols, check_validity=False): """Convert a list bounding boxes from a format specified in `source_format` to the format used by albumentations """ return [convert_bbox_to_albumentations(bbox, source_format, rows, cols, check_validity) for bbox in bboxes]
[docs]def convert_bboxes_from_albumentations(bboxes, target_format, rows, cols, check_validity=False): """Convert a list of bounding boxes from the format used by albumentations to a format, specified in `target_format`. Args: bboxes (list): List of bounding box with coordinates in the format used by albumentations target_format (str): required format of the output bounding box. Should be 'coco' or 'pascal_voc'. rows (int): image height cols (int): image width check_validity (bool): check if all boxes are valid boxes """ return [convert_bbox_from_albumentations(bbox, target_format, rows, cols, check_validity) for bbox in bboxes]
def check_bbox(bbox): """Check if bbox boundaries are in range 0, 1 and minimums are lesser then maximums""" for name, value in zip(['x_min', 'y_min', 'x_max', 'y_max'], bbox[:4]): if not 0 <= value <= 1: raise ValueError( 'Expected {name} for bbox {bbox} ' 'to be in the range [0.0, 1.0], got {value}.'.format( bbox=bbox, name=name, value=value, ) ) x_min, y_min, x_max, y_max = bbox[:4] if x_max <= x_min: raise ValueError('x_max is less than or equal to x_min for bbox {bbox}.'.format( bbox=bbox, )) if y_max <= y_min: raise ValueError('y_max is less than or equal to y_min for bbox {bbox}.'.format( bbox=bbox, )) def check_bboxes(bboxes): """Check if bboxes boundaries are in range 0, 1 and minimums are lesser then maximums""" for bbox in bboxes: check_bbox(bbox) def filter_bboxes(bboxes, rows, cols, min_area=0., min_visibility=0.): """Remove bounding boxes that either lie outside of the visible area by more then min_visibility or whose area in pixels is under the threshold set by `min_area`. Also it crops boxes to final image size. Args: bboxes (list): List of bounding box with coordinates in the format used by albumentations rows (int): Image rows. cols (int): Image cols. min_area (float): minimum area of a bounding box. All bounding boxes whose visible area in pixels is less than this value will be removed. Default: 0.0. min_visibility (float): minimum fraction of area for a bounding box to remain this box in list. Default: 0.0. """ resulting_boxes = [] for bbox in bboxes: transformed_box_area = calculate_bbox_area(bbox, rows, cols) bbox[:4] = np.clip(bbox[:4], 0, 1.) clipped_box_area = calculate_bbox_area(bbox, rows, cols) if not transformed_box_area or clipped_box_area / transformed_box_area <= min_visibility: continue else: bbox[:4] = np.clip(bbox[:4], 0, 1.) if calculate_bbox_area(bbox, rows, cols) <= min_area: continue resulting_boxes.append(bbox) return resulting_boxes def union_of_bboxes(height, width, bboxes, erosion_rate=0.0, to_int=False): """Calculate union of bounding boxes. Args: height (float): Height of image or space. width (float): Width of image or space. bboxes (list): List like bounding boxes. Format is `[x_min, y_min, x_max, y_max]`. erosion_rate (float): How much each bounding box can be shrinked, useful for erosive cropping. Set this in range [0, 1]. 0 will not be erosive at all, 1.0 can make any bbox to lose its volume. """ x1, y1 = width, height x2, y2 = 0, 0 for b in bboxes: w, h = b[2] - b[0], b[3] - b[1] lim_x1, lim_y1 = b[0] + erosion_rate * w, b[1] + erosion_rate * h lim_x2, lim_y2 = b[2] - erosion_rate * w, b[3] - erosion_rate * h x1, y1 = np.min([x1, lim_x1]), np.min([y1, lim_y1]) x2, y2 = np.max([x2, lim_x2]), np.max([y2, lim_y2]) return x1, y1, x2, y2