BBoxSafeRandomCrop
Targets:
image
mask
bboxes
keypoints
volume
mask3d
Image Types:uint8, float32
Crop an area from image while ensuring all bounding boxes are preserved in the crop.
Similar to AtLeastOneBboxRandomCrop, but with a key difference:
- BBoxSafeRandomCrop ensures ALL bounding boxes are preserved in the crop when erosion_rate=0.0
- AtLeastOneBboxRandomCrop ensures AT LEAST ONE bounding box is present in the crop
This makes BBoxSafeRandomCrop more suitable for scenarios where:
- You need to preserve all objects in the scene
- Losing any bounding box would be problematic (e.g., rare object classes)
- You're training a model that needs to detect multiple objects simultaneously
The algorithm:
- If bounding boxes exist:
- Computes the union of all bounding boxes
- Applies erosion based on erosion_rate to this union
- Clips the eroded union to valid image coordinates [0,1]
- Randomly samples crop coordinates within the clipped union area
- If no bounding boxes exist:
- Computes crop height based on erosion_rate
- Sets crop width to maintain original aspect ratio
- Randomly places the crop within the image
Arguments
erosion_ratefloat
0
Controls how much the valid crop region can deviate from the bbox union. Must be in range [0.0, 1.0].
- 0.0: crop must contain the exact bbox union (safest option that guarantees all boxes are preserved)
- 1.0: crop can deviate maximally from the bbox union (increases likelihood of cutting off some boxes) Defaults to 0.0.
pfloat
1
Probability of applying the transform. Defaults to 1.0.
Examples
>>> import numpy as np
>>> import albumentations as A
>>>
>>> # Prepare sample data
>>> image = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
>>> mask = np.random.randint(0, 2, (100, 100), dtype=np.uint8)
>>> bboxes = np.array([[10, 10, 50, 50], [40, 40, 80, 80]], dtype=np.float32)
>>> bbox_labels = [1, 2]
>>> keypoints = np.array([[20, 30], [60, 70]], dtype=np.float32)
>>> keypoint_labels = [0, 1]
>>>
>>> # Define transform with erosion_rate parameter
>>> transform = A.Compose([
... A.BBoxSafeRandomCrop(erosion_rate=0.2),
... ], 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
>>> result = transform(
... image=image,
... mask=mask,
... bboxes=bboxes,
... bbox_labels=bbox_labels,
... keypoints=keypoints,
... keypoint_labels=keypoint_labels
... )
>>>
>>> # Get the transformed data
>>> transformed_image = result['image'] # Cropped image containing all bboxes
>>> transformed_mask = result['mask'] # Cropped mask
>>> transformed_bboxes = result['bboxes'] # All bounding boxes preserved with adjusted coordinates
>>> transformed_bbox_labels = result['bbox_labels'] # Original labels preserved
>>> transformed_keypoints = result['keypoints'] # Keypoints with adjusted coordinates
>>> transformed_keypoint_labels = result['keypoint_labels'] # Original keypoint labels preserved
>>>
>>> # Example with a different erosion_rate
>>> transform_more_flexible = A.Compose([
... A.BBoxSafeRandomCrop(erosion_rate=0.5), # More flexibility in crop placement
... ], bbox_params=A.BboxParams(coord_format='pascal_voc', label_fields=['bbox_labels']))
>>>
>>> # Apply transform with only image and bboxes
>>> result_bboxes_only = transform_more_flexible(
... image=image,
... bboxes=bboxes,
... bbox_labels=bbox_labels
... )
>>> transformed_image = result_bboxes_only['image']
>>> transformed_bboxes = result_bboxes_only['bboxes'] # All bboxes still preservedNotes
- IMPORTANT: Using erosion_rate > 0.0 may result in some bounding boxes being cut off, particularly narrow boxes at the boundary of the union area. For guaranteed preservation of all bounding boxes, use erosion_rate=0.0.
- Aspect ratio is preserved only when no bounding boxes are present
- May be more restrictive in crop placement compared to AtLeastOneBboxRandomCrop
- The crop size is determined by the bounding boxes when present