AtLeastOneBBoxRandomCrop

Targets:
image
mask
bboxes
keypoints
volume
mask3d
Image Types:uint8, float32

Crop an area from image while ensuring at least one bounding box is present in the crop.

Similar to BBoxSafeRandomCrop, but with a key difference:

  • BBoxSafeRandomCrop ensures ALL bounding boxes are preserved in the crop
  • AtLeastOneBBoxRandomCrop ensures AT LEAST ONE bounding box is present in the crop

This makes AtLeastOneBBoxRandomCrop more flexible for scenarios where:

  • You want to focus on individual objects rather than all objects
  • You're willing to lose some bounding boxes to get more varied crops
  • The image has many bounding boxes and keeping all of them would be too restrictive

The algorithm:

  1. If bounding boxes exist:
    • Randomly selects a reference bounding box from available boxes
    • Computes an eroded version of this box (shrunk by erosion_factor)
    • Calculates valid crop bounds that ensure overlap with the eroded box
    • Randomly samples crop coordinates within these bounds
  2. If no bounding boxes exist:
    • Uses full image dimensions as valid bounds
    • Randomly samples crop coordinates within these bounds
Arguments
height
int

Fixed height of the crop

width
int

Fixed width of the crop

erosion_factor
float
0

Factor by which to erode (shrink) the reference bounding box when computing valid crop regions. Must be in range [0.0, 1.0].

  • 0.0 means no erosion (crop must fully contain the reference box)
  • 1.0 means maximum erosion (crop can be anywhere that intersects the reference box) Defaults to 0.0.
p
float
1

Probability of applying the transform. Defaults to 1.0.

Examples
>>> import numpy as np
>>> import albumentations as A
>>> import cv2
>>>
>>> # Prepare sample data
>>> image = np.random.randint(0, 256, (300, 300, 3), dtype=np.uint8)
>>> mask = np.random.randint(0, 2, (300, 300), dtype=np.uint8)
>>> # Create multiple bounding boxes - the transform will ensure at least one is in the crop
>>> bboxes = np.array([
...     [30, 50, 100, 140],   # first box
...     [150, 120, 270, 250], # second box
...     [200, 30, 280, 90]    # third box
... ], dtype=np.float32)
>>> bbox_labels = [1, 2, 3]
>>> keypoints = np.array([
...     [50, 70],    # keypoint inside first box
...     [190, 170],  # keypoint inside second box
...     [240, 60]    # keypoint inside third box
... ], dtype=np.float32)
>>> keypoint_labels = [0, 1, 2]
>>>
>>> # Define transform with different erosion_factor values
>>> transform = A.Compose([
...     A.AtLeastOneBBoxRandomCrop(
...         height=200,
...         width=200,
...         erosion_factor=0.2,  # Allows moderate flexibility in crop placement
...         p=1.0
...     ),
... ], bbox_params=A.BboxParams(coord_format='pascal_voc', label_fields=['bbox_labels']),
...    keypoint_params=A.KeypointParams(coord_format='xy', label_fields=['keypoint_labels']))
>>>
>>> # Apply the transform
>>> transformed = transform(
...     image=image,
...     mask=mask,
...     bboxes=bboxes,
...     bbox_labels=bbox_labels,
...     keypoints=keypoints,
...     keypoint_labels=keypoint_labels
... )
>>>
>>> # Get the transformed data
>>> transformed_image = transformed['image']       # Shape: (200, 200, 3)
>>> transformed_mask = transformed['mask']         # Shape: (200, 200)
>>> transformed_bboxes = transformed['bboxes']     # At least one bbox is guaranteed
>>> transformed_bbox_labels = transformed['bbox_labels']  # Labels for the preserved bboxes
>>> transformed_keypoints = transformed['keypoints']      # Only keypoints in crop are kept
>>> transformed_keypoint_labels = transformed['keypoint_labels']  # Their labels
>>>
>>> # Verify that at least one bounding box was preserved
>>> assert len(transformed_bboxes) > 0, "Should have at least one bbox in the crop"
>>>
>>> # With erosion_factor=0.0, the crop must fully contain the selected reference bbox
>>> conservative_transform = A.Compose([
...     A.AtLeastOneBBoxRandomCrop(
...         height=200,
...         width=200,
...         erosion_factor=0.0,  # No erosion - crop must fully contain a bbox
...         p=1.0
...     ),
... ], bbox_params=A.BboxParams(coord_format='pascal_voc', label_fields=['bbox_labels']))
>>>
>>> # With erosion_factor=1.0, the crop must only intersect with the selected reference bbox
>>> flexible_transform = A.Compose([
...     A.AtLeastOneBBoxRandomCrop(
...         height=200,
...         width=200,
...         erosion_factor=1.0,  # Maximum erosion - crop only needs to intersect a bbox
...         p=1.0
...     ),
... ], bbox_params=A.BboxParams(coord_format='pascal_voc', label_fields=['bbox_labels']))
Notes
  • Uses fixed crop dimensions (height and width)
  • Bounding boxes that end up partially outside the crop will be adjusted
  • Bounding boxes that end up completely outside the crop will be removed
  • If no bounding boxes are provided, acts as a regular random crop