Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.

Conversation

@i-aki-y
Copy link
Contributor

@i-aki-y i-aki-y commented Feb 5, 2023

This PR try to add rotate_method="ellipse" in the Affine transform.

close #1316
related issues #1388, #1373

Abount the implementation

As for the affine transform, we need to consider not only the rotation but also the effect of other parameters, such as the shear that also introduces additional space between the object and the bounding boxes.
Ex.

bbox_shear

Fortunately, a generalization looks to be straightforward.
The ellipse rotation assumes the object's shape is an ellipse and applies the rotation to the ellipse instead of the bbox, then reconstructs the rotated bbox from the rotated ellipse.
I think the same idea can be applied to the generic affine transform:

  • Assume the object shape as the ellipse.
  • Apply the affine transform to the ellipse.
  • Get the bbox from the transformed ellipse.

I have not analyzed as the original author of the ellipse rotation did, but I expect this generalized version to work.
See the example below.

Example

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import cv2
import albumentations as A

def visualize(ax, img, bboxes, grid=False):
    box_colors = ["grey", "black"]
    cmap = plt.cm.get_cmap("tab10")
    ax.imshow(img)
    for i, bbox in enumerate(bboxes):
        ax.add_patch(Rectangle(bbox[:2], bbox[2], bbox[3], fill=False, lw=2, color=box_colors[i]))
    ax.grid(grid)
    return ax

def get_data():    
    ## prepare sample image and bbox
    img = np.full((300, 300, 3), 255, dtype=np.uint8)
    cx, cy = (150, 150)
    rx = 50
    ry = 100
    cv2.rectangle(img, (cx-rx, cy-ry), (cx+rx, cy+ry), (255, 127, 127), -1)
    cv2.ellipse(img, (cx, cy), (rx, ry), 0, 0, 360, (127, 255, 127), -1)
    bboxes = [[cx-rx, cy-ry, 2 * rx, 2 * ry, "label"]]
    return img, bboxes

def get_affine_aug(rot=0, shear=(0, 0), rotate_method="largest_box"):
    shear_x, shear_y = shear
    aug = A.Compose([
        A.Affine(
            p=1, 
            scale=1,
            translate_percent=0,
            rotate=rot,
            shear={"x": (shear_x, shear_x), "y": (shear_y, shear_y)},
            rotate_method=rotate_method,
            mode=cv2.BORDER_CONSTANT,
            cval=(255, 255, 255)
        ),
    ], bbox_params=A.BboxParams(format='coco'))
    return aug

fig, axes = plt.subplots(1, 2, figsize=(10, 5), sharey=True)    

img, bboxes = get_data()
visualize(axes[0], img, bboxes)
aug1 = get_affine_aug(rot=15, shear=(50, 0), rotate_method="largest_box")
aug2 = get_affine_aug(rot=15, shear=(50, 0), rotate_method="ellipse")

data1 = aug1(image=img, bboxes=bboxes)
data2 = aug2(image=img, bboxes=bboxes)

img1 = data1["image"]
bboxes1 = data1["bboxes"]
bboxes2 = data2["bboxes"]

visualize(axes[1], img1, bboxes1 + bboxes2)
fig.savefig("./affine_ellipse.jpg", bbox_inches="tight")

affine_ellipse

Black bbox is a result of a generalized ellipse rotation. It looks much better than the gray box (largest_box).

@mikel-brostrom
Copy link

Black bbox is a result of a generalized ellipse rotation. It looks much better than the gray box (largest_box).

This looks fantastic @i-aki-y!

@Dipet Dipet merged commit 1eceb79 into albumentations-team:master Feb 6, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add rotate_method=ellipse for Affine

3 participants