Skip to content

Commit 9024472

Browse files
authored
Skeleton for rotated bounding box tutorial (#9140)
1 parent d247de8 commit 9024472

File tree

4 files changed

+165
-2
lines changed

4 files changed

+165
-2
lines changed

docs/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def __init__(self, src_dir):
8787
"plot_transforms_illustrations.py",
8888
"plot_transforms_e2e.py",
8989
"plot_cutmix_mixup.py",
90+
"plot_rotated_box_transforms.py",
9091
"plot_custom_transforms.py",
9192
"plot_tv_tensors.py",
9293
"plot_custom_tv_tensors.py",

gallery/assets/leaning_tower.jpg

1.25 MB
Loading

gallery/transforms/helpers.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import torch
33
from torchvision.utils import draw_bounding_boxes, draw_segmentation_masks
44
from torchvision import tv_tensors
5+
from torchvision.transforms import v2
56
from torchvision.transforms.v2 import functional as F
67

78

8-
def plot(imgs, row_title=None, **imshow_kwargs):
9+
def plot(imgs, row_title=None, bbox_width=3, **imshow_kwargs):
910
if not isinstance(imgs[0], list):
1011
# Make a 2d grid even if there's just 1 row
1112
imgs = [imgs]
@@ -24,6 +25,11 @@ def plot(imgs, row_title=None, **imshow_kwargs):
2425
masks = target.get("masks")
2526
elif isinstance(target, tv_tensors.BoundingBoxes):
2627
boxes = target
28+
29+
# Conversion necessary because draw_bounding_boxes() only
30+
# work with this specific format.
31+
if tv_tensors.is_rotated_bounding_format(boxes.format):
32+
boxes = v2.ConvertBoundingBoxFormat("xyxyxyxy")(boxes)
2733
else:
2834
raise ValueError(f"Unexpected target type: {type(target)}")
2935
img = F.to_image(img)
@@ -35,7 +41,7 @@ def plot(imgs, row_title=None, **imshow_kwargs):
3541

3642
img = F.to_dtype(img, torch.uint8, scale=True)
3743
if boxes is not None:
38-
img = draw_bounding_boxes(img, boxes, colors="yellow", width=3)
44+
img = draw_bounding_boxes(img, boxes, colors="yellow", width=bbox_width)
3945
if masks is not None:
4046
img = draw_segmentation_masks(img, masks.to(torch.bool), colors=["green"] * masks.shape[0], alpha=.65)
4147

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
"""
2+
===============================================================
3+
Transforms on Rotated Bounding Boxes
4+
===============================================================
5+
6+
This example illustrates how to define and use rotated bounding boxes. We'll
7+
cover how to define them, demonstrate their usage with some of the existing
8+
transforms, and finally some of their unique behavior in comparision to
9+
standard bounding boxes.
10+
11+
First, a bit of setup code:
12+
"""
13+
14+
# %%
15+
from PIL import Image
16+
from pathlib import Path
17+
import matplotlib.pyplot as plt
18+
19+
20+
import torch
21+
from torchvision import tv_tensors
22+
from torchvision.transforms import v2
23+
from helpers import plot
24+
25+
plt.rcParams["figure.figsize"] = [10, 5]
26+
plt.rcParams["savefig.bbox"] = "tight"
27+
28+
# if you change the seed, make sure that the randomly-applied transforms
29+
# properly show that the image can be both transformed and *not* transformed!
30+
torch.manual_seed(0)
31+
32+
# If you're trying to run that on Colab, you can download the assets and the
33+
# helpers from https://github.com/pytorch/vision/tree/main/gallery/
34+
orig_img = Image.open(Path('../assets') / 'leaning_tower.jpg')
35+
36+
# %%
37+
# Creating a Rotated Bounding Box
38+
# -------------------------------
39+
# Rotated bounding boxes are created by instantiating the
40+
# :class:`~torchvision.tv_tensors.BoundingBoxes` class. It's the `format`
41+
# parameter of the constructor that determines if a bounding box is rotated or
42+
# not. In this instance, we use the
43+
# :attr:`~torchvision.tv_tensors.BoundingBoxFormat` kind `CXCYWHR`. The first
44+
# two values are the `x` and `y` coordinates of the center of the bounding box.
45+
# The next two values are the `width` and `height` of the bounding box, and the
46+
# last value is the `rotation` of the bounding box.
47+
48+
49+
orig_box = tv_tensors.BoundingBoxes(
50+
[
51+
[860.0, 1100, 570, 1840, -7],
52+
],
53+
format="CXCYWHR",
54+
canvas_size=(orig_img.size[1], orig_img.size[0]),
55+
)
56+
57+
plot([(orig_img, orig_box)], bbox_width=10)
58+
59+
# %%
60+
# Rotation
61+
# --------
62+
# Rotated bounding boxes maintain their rotation with respect to the image even
63+
# when the image itself is rotated through the
64+
# :class:`~torchvision.transforms.RandomRotation` transform.
65+
rotater = v2.RandomRotation(degrees=(0, 180), expand=True)
66+
rotated_imgs = [rotater((orig_img, orig_box)) for _ in range(4)]
67+
plot([(orig_img, orig_box)] + rotated_imgs, bbox_width=10)
68+
69+
# %%
70+
# Padding
71+
# -------
72+
# Rotated bounding boxes also maintain their properties when the image is padded using
73+
# :class:`~torchvision.transforms.Pad`.
74+
padded_imgs_and_boxes = [
75+
v2.Pad(padding=padding)(orig_img, orig_box)
76+
for padding in (30, 50, 100, 200)
77+
]
78+
plot([(orig_img, orig_box)] + padded_imgs_and_boxes, bbox_width=10)
79+
80+
# %%
81+
# Resizing
82+
# --------
83+
# Rotated bounding boxes are also resized along with an image in the
84+
# :class:`~torchvision.transforms.Resize` transform.
85+
#
86+
# Note that the bounding box looking bigger in the images with less pixels is
87+
# an artifact, not reality. That is merely the rasterised representation of the
88+
# bounding box's boundaries appearing bigger because we specify a fixed width of
89+
# that rasterized line. When the image is, say, only 30 pixels wide, a
90+
# line that is 3 pixels wide is relatively large.
91+
resized_imgs = [
92+
v2.Resize(size=size)(orig_img, orig_box)
93+
for size in (30, 50, 100, orig_img.size)
94+
]
95+
plot([(orig_img, orig_box)] + resized_imgs, bbox_width=5)
96+
97+
# %%
98+
# Perspective
99+
# -----------
100+
# The rotated bounding box is also transformed along with the image when the
101+
# perspective is transformed with :class:`~torchvision.transforms.RandomPerspective`.
102+
perspective_transformer = v2.RandomPerspective(distortion_scale=0.6, p=1.0)
103+
perspective_imgs = [perspective_transformer(orig_img, orig_box) for _ in range(4)]
104+
plot([(orig_img, orig_box)] + perspective_imgs, bbox_width=10)
105+
106+
# %%
107+
# Elastic Transform
108+
# -----------------
109+
# The rotated bounding box is appropriately unchanged when going through the
110+
# :class:`~torchvision.transforms.ElasticTransform`.
111+
elastic_imgs = [
112+
v2.ElasticTransform(alpha=alpha)(orig_img, orig_box)
113+
for alpha in (100.0, 500.0, 1000.0, 2000.0)
114+
]
115+
plot([(orig_img, orig_box)] + elastic_imgs, bbox_width=10)
116+
117+
# %%
118+
# Crop & Clamping Modes
119+
# ---------------------
120+
# The :class:`~torchvision.transforms.CenterCrop` transform selectively crops
121+
# the image on a center location. The behavior of the rotated bounding box
122+
# depends on its `clamping_mode`. We can set the `clamping_mode` in the
123+
# :class:`~torchvision.tv_tensors.BoundingBoxes` constructur, or by directly
124+
# setting it after construction as we do in the example below.
125+
#
126+
# There are two values for `clamping_mode`:
127+
#
128+
# - `"soft"`: The default when constucting
129+
# :class:`~torchvision.tv_tensors.BoundingBoxes`. <Insert semantic
130+
# description for soft mode.>
131+
# - `"hard"`: <Insert semantic description for hard mode.>
132+
#
133+
# For standard bounding boxes, both modes behave the same. We also need to
134+
# document:
135+
#
136+
# - `clamping_mode` for individual kernels.
137+
# - `clamping_mode` in :class:`~torchvision.transforms.v2.ClampBoundingBoxes`.
138+
# - the new :class:`~torchvision.transforms.v2.SetClampingMode` transform.
139+
#
140+
assert orig_box.clamping_mode == "soft"
141+
hard_box = orig_box.clone()
142+
hard_box.clamping_mode = "hard"
143+
144+
soft_center_crops_and_boxes = [
145+
v2.CenterCrop(size=size)(orig_img, orig_box)
146+
for size in (800, 1200, 2000, orig_img.size)
147+
]
148+
149+
hard_center_crops_and_boxes = [
150+
v2.CenterCrop(size=size)(orig_img, hard_box)
151+
for size in (800, 1200, 2000, orig_img.size)
152+
]
153+
154+
plot([[(orig_img, orig_box)] + soft_center_crops_and_boxes,
155+
[(orig_img, hard_box)] + hard_center_crops_and_boxes],
156+
bbox_width=10)

0 commit comments

Comments
 (0)