| 
 | 1 | +"""  | 
 | 2 | +===============================================================  | 
 | 3 | +Transforms on Rotated Bounding Boxes  | 
 | 4 | +===============================================================  | 
 | 5 | +
  | 
 | 6 | +This example illustrates how to define and use rotated bounding boxes.  | 
 | 7 | +
  | 
 | 8 | +.. note::  | 
 | 9 | +    Support for rotated bounding boxes was released in TorchVision 0.23 and is  | 
 | 10 | +    currently a BETA feature. We don't expect the API to change, but there may  | 
 | 11 | +    be some rare edge-cases. If you find any issues, please report them on  | 
 | 12 | +    our bug tracker: https://github.com/pytorch/vision/issues?q=is:open+is:issue  | 
 | 13 | +
  | 
 | 14 | +First, a bit of setup code:  | 
 | 15 | +"""  | 
 | 16 | + | 
 | 17 | +# %%  | 
 | 18 | +from PIL import Image  | 
 | 19 | +from pathlib import Path  | 
 | 20 | +import matplotlib.pyplot as plt  | 
 | 21 | + | 
 | 22 | + | 
 | 23 | +import torch  | 
 | 24 | +from torchvision.tv_tensors import BoundingBoxes  | 
 | 25 | +from torchvision.transforms import v2  | 
 | 26 | +from helpers import plot  | 
 | 27 | + | 
 | 28 | +plt.rcParams["figure.figsize"] = [10, 5]  | 
 | 29 | +plt.rcParams["savefig.bbox"] = "tight"  | 
 | 30 | + | 
 | 31 | +# if you change the seed, make sure that the randomly-applied transforms  | 
 | 32 | +# properly show that the image can be both transformed and *not* transformed!  | 
 | 33 | +torch.manual_seed(0)  | 
 | 34 | + | 
 | 35 | +# If you're trying to run that on Colab, you can download the assets and the  | 
 | 36 | +# helpers from https://github.com/pytorch/vision/tree/main/gallery/  | 
 | 37 | +orig_img = Image.open(Path('../assets') / 'leaning_tower.jpg')  | 
 | 38 | + | 
 | 39 | +# %%  | 
 | 40 | +# Creating a Rotated Bounding Box  | 
 | 41 | +# -------------------------------  | 
 | 42 | +# Rotated bounding boxes are created by instantiating the  | 
 | 43 | +# :class:`~torchvision.tv_tensors.BoundingBoxes` class. It's the ``format``  | 
 | 44 | +# parameter of the constructor that determines if a bounding box is rotated or  | 
 | 45 | +# not. In this instance, we use the CXCYWHR  | 
 | 46 | +# :attr:`~torchvision.tv_tensors.BoundingBoxFormat`. The first two values are  | 
 | 47 | +# the X and Y coordinates of the center of the bounding box.  The next two  | 
 | 48 | +# values are the width and height of the bounding box, and the last value is the  | 
 | 49 | +# rotation of the bounding box, in degrees.  | 
 | 50 | + | 
 | 51 | + | 
 | 52 | +orig_box = BoundingBoxes(  | 
 | 53 | +    [  | 
 | 54 | +        [860.0, 1100, 570, 1840, -7],  | 
 | 55 | +    ],  | 
 | 56 | +    format="CXCYWHR",  | 
 | 57 | +    canvas_size=(orig_img.size[1], orig_img.size[0]),  | 
 | 58 | +)  | 
 | 59 | + | 
 | 60 | +plot([(orig_img, orig_box)], bbox_width=10)  | 
 | 61 | + | 
 | 62 | +# %%  | 
 | 63 | +# Transforms illustrations  | 
 | 64 | +# ------------------------  | 
 | 65 | +#  | 
 | 66 | +# Using :class:`~torchvision.transforms.RandomRotation`:  | 
 | 67 | +rotater = v2.RandomRotation(degrees=(0, 180), expand=True)  | 
 | 68 | +rotated_imgs = [rotater((orig_img, orig_box)) for _ in range(4)]  | 
 | 69 | +plot([(orig_img, orig_box)] + rotated_imgs, bbox_width=10)  | 
 | 70 | + | 
 | 71 | +# %%  | 
 | 72 | +# Using :class:`~torchvision.transforms.Pad`:  | 
 | 73 | +padded_imgs_and_boxes = [  | 
 | 74 | +    v2.Pad(padding=padding)(orig_img, orig_box)  | 
 | 75 | +    for padding in (30, 50, 100, 200)  | 
 | 76 | +]  | 
 | 77 | +plot([(orig_img, orig_box)] + padded_imgs_and_boxes, bbox_width=10)  | 
 | 78 | + | 
 | 79 | +# %%  | 
 | 80 | +# Using :class:`~torchvision.transforms.Resize`:  | 
 | 81 | +resized_imgs = [  | 
 | 82 | +    v2.Resize(size=size)(orig_img, orig_box)  | 
 | 83 | +    for size in (30, 50, 100, orig_img.size)  | 
 | 84 | +]  | 
 | 85 | +plot([(orig_img, orig_box)] + resized_imgs, bbox_width=5)  | 
 | 86 | + | 
 | 87 | +# %%  | 
 | 88 | +# Note that the bounding box looking bigger in the images with less pixels is  | 
 | 89 | +# an artifact, not reality. That is merely the rasterised representation of the  | 
 | 90 | +# bounding box's boundaries appearing bigger because we specify a fixed width of  | 
 | 91 | +# that rasterized line. When the image is, say, only 30 pixels wide, a  | 
 | 92 | +# line that is 3 pixels wide is relatively large.  | 
 | 93 | +#  | 
 | 94 | +# .. _clamping_mode_tuto:  | 
 | 95 | +#  | 
 | 96 | +# Clamping Mode, and its effect on transforms  | 
 | 97 | +# -------------------------------------------  | 
 | 98 | +#  | 
 | 99 | +# Some transforms, such as :class:`~torchvision.transforms.CenterCrop`, may  | 
 | 100 | +# result in having the transformed bounding box partially outside of the  | 
 | 101 | +# transformed (cropped) image. In general, this may happen on most of the  | 
 | 102 | +# :ref:`geometric transforms <v2_api_ref>`.  | 
 | 103 | +#  | 
 | 104 | +# In such cases, the bounding box is clamped to the transformed image size based  | 
 | 105 | +# on its ``clamping_mode`` attribute.  There are three values for  | 
 | 106 | +# ``clamping_mode``, which determines how the box is clamped after a  | 
 | 107 | +# transformation:  | 
 | 108 | +#  | 
 | 109 | +#  - ``None``: No clamping is applied, and the bounding box may be partially  | 
 | 110 | +#    outside of the image.  | 
 | 111 | +#  - `"hard"`:  The box is clamped to the image size, such that all its corners  | 
 | 112 | +#    are within the image canvas. This potentially results in a loss of  | 
 | 113 | +#    information, and it can lead to unintuitive resuts. But may be necessary  | 
 | 114 | +#    for some applications e.g. if the model doesn't support boxes outside of  | 
 | 115 | +#    their image.  | 
 | 116 | +#  - `"soft"`: . This is an intermediate mode between ``None`` and "hard": the  | 
 | 117 | +#    box is clamped, but not as strictly as in "hard" mode. Some box dimensions  | 
 | 118 | +#    may still be outside of the image. This is the default when constucting  | 
 | 119 | +#    :class:`~torchvision.tv_tensors.BoundingBoxes`.  | 
 | 120 | +#  | 
 | 121 | +# .. note::  | 
 | 122 | +#  | 
 | 123 | +#       For axis-aligned bounding boxes, the `"soft"` and `"hard"` modes behave  | 
 | 124 | +#       the same, as the bounding box is always clamped to the image size.  | 
 | 125 | +#  | 
 | 126 | +# Let's illustrate the clamping modes with  | 
 | 127 | +# :class:`~torchvision.transforms.CenterCrop` transform:  | 
 | 128 | + | 
 | 129 | +assert orig_box.clamping_mode == "soft"  | 
 | 130 | + | 
 | 131 | +box_hard_clamping = BoundingBoxes(orig_box, format=orig_box.format, canvas_size=orig_box.canvas_size, clamping_mode="hard")  | 
 | 132 | + | 
 | 133 | +box_no_clamping = BoundingBoxes(orig_box, format=orig_box.format, canvas_size=orig_box.canvas_size, clamping_mode=None)  | 
 | 134 | + | 
 | 135 | +crop_sizes = (800, 1200, 2000, orig_img.size)  | 
 | 136 | +soft_center_crops_and_boxes = [  | 
 | 137 | +    v2.CenterCrop(size=size)(orig_img, orig_box)  | 
 | 138 | +    for size in crop_sizes  | 
 | 139 | +]  | 
 | 140 | + | 
 | 141 | +hard_center_crops_and_boxes = [  | 
 | 142 | +    v2.CenterCrop(size=size)(orig_img, box_hard_clamping)  | 
 | 143 | +    for size in crop_sizes  | 
 | 144 | +]  | 
 | 145 | + | 
 | 146 | +no_clamping_center_crops_and_boxes = [  | 
 | 147 | +    v2.CenterCrop(size=size)(orig_img, box_no_clamping)  | 
 | 148 | +    for size in crop_sizes  | 
 | 149 | +]  | 
 | 150 | + | 
 | 151 | +plot([[(orig_img, box_hard_clamping)] + hard_center_crops_and_boxes,  | 
 | 152 | +      [(orig_img, orig_box)] + soft_center_crops_and_boxes,  | 
 | 153 | +      [(orig_img, box_no_clamping)] + no_clamping_center_crops_and_boxes],  | 
 | 154 | +     bbox_width=10)  | 
 | 155 | + | 
 | 156 | +# %%  | 
 | 157 | +# The plot above shows the "hard" clamping mode, "soft" and ``None``, in this  | 
 | 158 | +# order. While "soft" and ``None`` result in similar plots, they do not lead to  | 
 | 159 | +# the exact same clamped boxes. The non-clamped boxes will show dimensions that are further away from the image:  | 
 | 160 | +print("boxes with soft clamping:")  | 
 | 161 | +print(soft_center_crops_and_boxes)  | 
 | 162 | +print()  | 
 | 163 | +print("boxes with no clamping:")  | 
 | 164 | +print(no_clamping_center_crops_and_boxes)  | 
 | 165 | + | 
 | 166 | +# %%  | 
 | 167 | +#  | 
 | 168 | +# Setting the clamping mode  | 
 | 169 | +# --------------------------  | 
 | 170 | +#  | 
 | 171 | +# The ``clamping_mode`` attribute, which determines the clamping strategy that  | 
 | 172 | +# is applied to a box, can be set in different ways:  | 
 | 173 | +#  | 
 | 174 | +# - When constructing the bounding box with its  | 
 | 175 | +#   :class:`~torchvision.tv_tensors.BoundingBoxes` constructor, as done in the example above.  | 
 | 176 | +# - By directly setting the attribute on an existing instance, e.g. ``boxes.clamping_mode = "hard"``.  | 
 | 177 | +# - By calling the :class:`~torchvision.transforms.v2.SetClampingMode` transform.  | 
 | 178 | +#  | 
 | 179 | +# Also, remember that you can always clamp the bounding box manually by  | 
 | 180 | +# calling the :meth:`~torchvision.transforms.v2.ClampBoundingBoxes` transform!  | 
 | 181 | +# Here's an example illustrating all of these option:  | 
 | 182 | + | 
 | 183 | +t = v2.Compose([  | 
 | 184 | +    v2.CenterCrop(size=(800,)),  # clamps according to the current clamping_mode  | 
 | 185 | +                                 # attribute, in this case set by the constructor  | 
 | 186 | +    v2.SetClampingMode(None),  # sets the clamping_mode attribute for future transforms  | 
 | 187 | +    v2.Pad(padding=3),  # clamps according to the current clamping_mode  | 
 | 188 | +                        # i.e. ``None``  | 
 | 189 | +    v2.ClampBoundingBoxes(clamping_mode="soft"),  # clamps with "soft" mode.  | 
 | 190 | +])  | 
 | 191 | + | 
 | 192 | +out_img, out_box = t(orig_img, orig_box)  | 
 | 193 | +plot([(orig_img, orig_box), (out_img, out_box)], bbox_width=10)  | 
 | 194 | + | 
 | 195 | +# %%  | 
0 commit comments