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:
- 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
- If no bounding boxes exist:
- Uses full image dimensions as valid bounds
- Randomly samples crop coordinates within these bounds
Arguments
heightint
Fixed height of the crop
widthint
Fixed width of the crop
erosion_factorfloat
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.
pfloat
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