From 0a40928012f983e1496a1c6521a1f3ff16c71cb6 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Sun, 11 Oct 2020 00:51:49 +0530 Subject: [PATCH 01/16] initital prototype --- test/test_utils.py | 10 +++++++++- torchvision/utils.py | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/test/test_utils.py b/test/test_utils.py index f1982130f75..e3e654b1147 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -6,7 +6,7 @@ import unittest from io import BytesIO import torchvision.transforms.functional as F -from PIL import Image +from PIL import Image, ImageDraw class Tester(unittest.TestCase): @@ -79,6 +79,14 @@ def test_save_image_single_pixel_file_object(self): self.assertTrue(torch.equal(F.to_tensor(img_orig), F.to_tensor(img_bytes)), 'Pixel Image not stored in file object') + def test_draw_boxes(self): + img = torch.rand(3, 226, 226) + boxes = torch.tensor([[0, 0, 100, 100], [0, 0, 0, 0], + [10, 15, 30, 35], [23, 35, 93, 95]], dtype=torch.float) + labels = torch.tensor([2, 1, 3, 5]) + utils.draw_bounding_boxes(img, boxes, labels) + + return True if __name__ == '__main__': unittest.main() diff --git a/torchvision/utils.py b/torchvision/utils.py index be373138c5f..815cb2ed4e4 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -1,8 +1,12 @@ -from typing import Union, Optional, List, Tuple, Text, BinaryIO +from typing import Union, Optional, List, Tuple, Text, BinaryIO, Dict import io import pathlib import torch import math +from PIL import Image, ImageDraw + +__all__ = ["make_grid", "save_image", "draw_bounding_boxes"] + irange = range @@ -121,10 +125,42 @@ def save_image( If a file object was used instead of a filename, this parameter should always be used. **kwargs: Other arguments are documented in ``make_grid``. """ - from PIL import Image + # from PIL import Image grid = make_grid(tensor, nrow=nrow, padding=padding, pad_value=pad_value, normalize=normalize, range=range, scale_each=scale_each) # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer ndarr = grid.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() im = Image.fromarray(ndarr) im.save(fp, format=format) + + +def draw_bounding_boxes( + image: torch.Tensor, + bboxes: torch.Tensor, + labels: torch.Tensor, + colors: Dict[int, str] = None, + draw_labels: bool = False, + width: int = 1 +) -> torch.Tensor: + + """ + Draws bounding boxes on given image. + + Args: + image (tensor): Tensor of shape (C x H x W) + bboxes (tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. + labels (tensor): Tensor of size (N) Labels for each bounding boxes. + colors (dict): Dict with key as label id and value as color name. + draw_labels (bool): If True draws label names on bounding boxes. + width (int): Width of bounding box. + """ + + # Code co-contributed by sumanthratna + # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer + ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() + im = Image.fromarray(ndarr) + draw = ImageDraw.Draw(im) + + return True + + From 42ab7aa3dbd13a7679610204e83248032b6d7fe0 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Sun, 11 Oct 2020 00:52:05 +0530 Subject: [PATCH 02/16] flake --- torchvision/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torchvision/utils.py b/torchvision/utils.py index 815cb2ed4e4..836ca46094c 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -160,7 +160,7 @@ def draw_bounding_boxes( ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() im = Image.fromarray(ndarr) draw = ImageDraw.Draw(im) - + return True From e229fc750a1ca8022baf722bd929986cffe5c059 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Sun, 11 Oct 2020 15:51:46 +0530 Subject: [PATCH 03/16] Adds documentation --- docs/source/utils.rst | 1 + torchvision/utils.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/utils.rst b/docs/source/utils.rst index ad2fc91c897..0ae450487e3 100644 --- a/docs/source/utils.rst +++ b/docs/source/utils.rst @@ -7,3 +7,4 @@ torchvision.utils .. autofunction:: save_image +.. autofunction:: draw_bounding_boxes \ No newline at end of file diff --git a/torchvision/utils.py b/torchvision/utils.py index 836ca46094c..d51955147bc 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -147,9 +147,9 @@ def draw_bounding_boxes( Draws bounding boxes on given image. Args: - image (tensor): Tensor of shape (C x H x W) - bboxes (tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. - labels (tensor): Tensor of size (N) Labels for each bounding boxes. + image (Tensor): Tensor of shape (C x H x W) + bboxes (Tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. + labels (Tensor): Tensor of size (N) Labels for each bounding boxes. colors (dict): Dict with key as label id and value as color name. draw_labels (bool): If True draws label names on bounding boxes. width (int): Width of bounding box. From 47acfad6949b76fa5e0478675f8a1980810943a4 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Sun, 11 Oct 2020 18:49:03 +0530 Subject: [PATCH 04/16] minimal working bboxes --- torchvision/utils.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/torchvision/utils.py b/torchvision/utils.py index d51955147bc..110ca736e84 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -3,6 +3,7 @@ import pathlib import torch import math +import numpy as np from PIL import Image, ImageDraw __all__ = ["make_grid", "save_image", "draw_bounding_boxes"] @@ -136,7 +137,7 @@ def save_image( def draw_bounding_boxes( image: torch.Tensor, - bboxes: torch.Tensor, + boxes: torch.Tensor, labels: torch.Tensor, colors: Dict[int, str] = None, draw_labels: bool = False, @@ -156,11 +157,26 @@ def draw_bounding_boxes( """ # Code co-contributed by sumanthratna + + # Currently works for (C x H x W) images, but I think we should extend. # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer + if not (torch.is_tensor(image)): + raise TypeError('tensor expected, got {}'.format(type(image))) + ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() - im = Image.fromarray(ndarr) - draw = ImageDraw.Draw(im) - return True + # Neceassary check since FRCNN returns boxes which have grad enabled. + if(boxes.requires_grad): + boxes = boxes.detach() + + boxes = boxes.to('cpu').numpy().astype('int').tolist() + + img_to_draw = Image.fromarray(ndarr) + draw = ImageDraw.Draw(img_to_draw) + + for i, bbox in enumerate(boxes): + draw.rectangle(bbox, width=width) + + return torch.from_numpy(np.array(img_to_draw)) From 86c0dc9441a494a859770c34a19e2f1235e1d7b3 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Sun, 11 Oct 2020 23:46:57 +0530 Subject: [PATCH 05/16] Adds label display --- torchvision/utils.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/torchvision/utils.py b/torchvision/utils.py index 110ca736e84..c855c58ba63 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -139,6 +139,7 @@ def draw_bounding_boxes( image: torch.Tensor, boxes: torch.Tensor, labels: torch.Tensor, + label_names: List[int] = None, colors: Dict[int, str] = None, draw_labels: bool = False, width: int = 1 @@ -151,6 +152,7 @@ def draw_bounding_boxes( image (Tensor): Tensor of shape (C x H x W) bboxes (Tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. labels (Tensor): Tensor of size (N) Labels for each bounding boxes. + label_names (List): List containing labels excluding background. colors (dict): Dict with key as label id and value as color name. draw_labels (bool): If True draws label names on bounding boxes. width (int): Width of bounding box. @@ -160,9 +162,14 @@ def draw_bounding_boxes( # Currently works for (C x H x W) images, but I think we should extend. # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer + if not (torch.is_tensor(image)): raise TypeError('tensor expected, got {}'.format(type(image))) + if label_names is not None: + # Since for our detection models class 0 is background + label_names.insert(0, "__background__") + ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() # Neceassary check since FRCNN returns boxes which have grad enabled. @@ -170,13 +177,18 @@ def draw_bounding_boxes( boxes = boxes.detach() boxes = boxes.to('cpu').numpy().astype('int').tolist() + labels = labels.to('cpu').numpy().astype('int').tolist() img_to_draw = Image.fromarray(ndarr) draw = ImageDraw.Draw(img_to_draw) - for i, bbox in enumerate(boxes): + for bbox, label in zip(boxes, labels): draw.rectangle(bbox, width=width) - return torch.from_numpy(np.array(img_to_draw)) - + if label_names is None: + draw.text((bbox[0], bbox[1]), str(label)) + else: + if draw_labels is True: + draw.text((bbox[0], bbox[1]), label_names[int(label)]) + return torch.from_numpy(np.array(img_to_draw)) From e6225e5f01b348d8b6c6fadde93797d42e757873 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Mon, 12 Oct 2020 00:24:22 +0530 Subject: [PATCH 06/16] adds colors :-) --- torchvision/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/torchvision/utils.py b/torchvision/utils.py index c855c58ba63..ebf3b8a1289 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -141,7 +141,7 @@ def draw_bounding_boxes( labels: torch.Tensor, label_names: List[int] = None, colors: Dict[int, str] = None, - draw_labels: bool = False, + draw_labels: bool = True, width: int = 1 ) -> torch.Tensor: @@ -183,7 +183,10 @@ def draw_bounding_boxes( draw = ImageDraw.Draw(img_to_draw) for bbox, label in zip(boxes, labels): - draw.rectangle(bbox, width=width) + if colors is None: + draw.rectangle(bbox, width=width) + else: + draw.rectangle(bbox, width=width, outline=colors[label]) if label_names is None: draw.text((bbox[0], bbox[1]), str(label)) From 124977fe43906e55cca98e32ba24ea7b8d1a5ba8 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Mon, 12 Oct 2020 14:40:27 +0530 Subject: [PATCH 07/16] adds suggestions and fixes CI --- test/test_utils.py | 1 + torchvision/utils.py | 14 +++++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/test/test_utils.py b/test/test_utils.py index e3e654b1147..0fb47b1c2ec 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -88,5 +88,6 @@ def test_draw_boxes(self): return True + if __name__ == '__main__': unittest.main() diff --git a/torchvision/utils.py b/torchvision/utils.py index ebf3b8a1289..ee770db71a9 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -126,7 +126,6 @@ def save_image( If a file object was used instead of a filename, this parameter should always be used. **kwargs: Other arguments are documented in ``make_grid``. """ - # from PIL import Image grid = make_grid(tensor, nrow=nrow, padding=padding, pad_value=pad_value, normalize=normalize, range=range, scale_each=scale_each) # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer @@ -139,7 +138,7 @@ def draw_bounding_boxes( image: torch.Tensor, boxes: torch.Tensor, labels: torch.Tensor, - label_names: List[int] = None, + label_names: List[str] = None, colors: Dict[int, str] = None, draw_labels: bool = True, width: int = 1 @@ -154,13 +153,11 @@ def draw_bounding_boxes( labels (Tensor): Tensor of size (N) Labels for each bounding boxes. label_names (List): List containing labels excluding background. colors (dict): Dict with key as label id and value as color name. - draw_labels (bool): If True draws label names on bounding boxes. + draw_labels (bool): If True (default) draws label names on bounding boxes. width (int): Width of bounding box. """ # Code co-contributed by sumanthratna - - # Currently works for (C x H x W) images, but I think we should extend. # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer if not (torch.is_tensor(image)): @@ -172,7 +169,7 @@ def draw_bounding_boxes( ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() - # Neceassary check since FRCNN returns boxes which have grad enabled. + # Neceassary check to remove grad if present if(boxes.requires_grad): boxes = boxes.detach() @@ -190,8 +187,7 @@ def draw_bounding_boxes( if label_names is None: draw.text((bbox[0], bbox[1]), str(label)) - else: - if draw_labels is True: - draw.text((bbox[0], bbox[1]), label_names[int(label)]) + elif draw_labels is True: + draw.text((bbox[0], bbox[1]), label_names[int(label)]) return torch.from_numpy(np.array(img_to_draw)) From 83524a55b2edd9695a80666ab53a3c9c57c6f656 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Mon, 12 Oct 2020 15:21:01 +0530 Subject: [PATCH 08/16] handles image of dim 4 --- torchvision/utils.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/torchvision/utils.py b/torchvision/utils.py index ee770db71a9..2d2b112cf8f 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -148,7 +148,7 @@ def draw_bounding_boxes( Draws bounding boxes on given image. Args: - image (Tensor): Tensor of shape (C x H x W) + image (Tensor): Tensor of shape (C x H x W) or (1 x C x H x W) bboxes (Tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. labels (Tensor): Tensor of size (N) Labels for each bounding boxes. label_names (List): List containing labels excluding background. @@ -158,15 +158,21 @@ def draw_bounding_boxes( """ # Code co-contributed by sumanthratna - # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer + + if(image.dim() == 4): + if(image.shape[0] == 1): + image = image.squeeze(0) + else: + raise ValueError("Batch size > 1 is not supported. Pass images with batch size 1 only") if not (torch.is_tensor(image)): - raise TypeError('tensor expected, got {}'.format(type(image))) + raise TypeError(f'tensor expected, got {type(image)}') if label_names is not None: # Since for our detection models class 0 is background label_names.insert(0, "__background__") + # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() # Neceassary check to remove grad if present From deccdab837108fe252331ba5f6fd96721686ce11 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Mon, 12 Oct 2020 15:22:07 +0530 Subject: [PATCH 09/16] fixes image handling --- torchvision/dev.py | 186 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 torchvision/dev.py diff --git a/torchvision/dev.py b/torchvision/dev.py new file mode 100644 index 00000000000..19f7f2bcb86 --- /dev/null +++ b/torchvision/dev.py @@ -0,0 +1,186 @@ +from PIL import Image, ImageDraw +import torch +from torchvision.models.detection import fasterrcnn_resnet50_fpn +from typing import Dict +import sys +from torchvision.io.image import read_image +import torchvision.transforms as T +from typing import List +import numpy as np + +img_path = "../test/assets/grace_hopper_517x606.jpg" + + +# def draw_bounding_boxes( +# image: torch.Tensor, +# boxes: torch.Tensor, +# labels: torch.Tensor, +# label_names: List[int] = None, +# colors: Dict[int, str] = None, +# draw_labels: bool = True, +# width: int = 1 +# ) -> torch.Tensor: + +# """ +# Draws bounding boxes on given image. + +# Args: +# image (Tensor): Tensor of shape (C x H x W) +# bboxes (Tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. +# labels (Tensor): Tensor of size (N) Labels for each bounding boxes. +# label_names (List): List containing labels excluding background. +# colors (dict): Dict with key as label id and value as color name. +# draw_labels (bool): If True draws label names on bounding boxes. +# width (int): Width of bounding box. +# """ + + # Code co-contributed by sumanthratna + + # Currently works for (C x H x W) images, but I think we should extend. + # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer + +# if not (torch.is_tensor(image)): +# raise TypeError('tensor expected, got {}'.format(type(image))) + +# if label_names is not None: +# # Since for our detection models class 0 is background +# label_names.insert(0, "__background__") + +# ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() + +# # Neceassary check since FRCNN returns boxes which have grad enabled. +# if(boxes.requires_grad): +# boxes = boxes.detach() + +# boxes = boxes.to('cpu').numpy().astype('int').tolist() +# labels = labels.to('cpu').numpy().astype('int').tolist() + +# img_to_draw = Image.fromarray(ndarr) +# draw = ImageDraw.Draw(img_to_draw) + +# for bbox, label in zip(boxes, labels): +# if colors is None: +# draw.rectangle(bbox, width=width) +# else: +# draw.rectangle(bbox, width=width, outline=colors[label]) + +# if label_names is None: +# draw.text((bbox[0], bbox[1]), str(label)) +# else: +# if draw_labels is True: +# draw.text((bbox[0], bbox[1]), label_names[int(label)]) + +# img_to_draw.show() +# return torch.from_numpy(np.array(img_to_draw)) + + +if __name__ == "__main__": +# # img = torch.rand(3, 3, 226, 226) +# # img = read_image(img_path) + +# label_names = [ +# 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', +# 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'N/A', 'stop sign', +# 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', +# 'elephant', 'bear', 'zebra', 'giraffe', 'N/A', 'backpack', 'umbrella', 'N/A', 'N/A', +# 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', +# 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', +# 'bottle', 'N/A', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', +# 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', +# 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'N/A', 'dining table', +# 'N/A', 'N/A', 'toilet', 'N/A', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', +# 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'N/A', 'book', +# 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'] + +# colors = {1: "blue", 32: "aqua", 84: "yellow", 16: "black", 38: "brown"} + +# # label_names.insert(0, "__background__") + +# # print(label_names) + + img = Image.open(img_path) + img = T.ToTensor()(img) + print(img.shape) + print(img.dim()) + img = torch.unsqueeze(img, 0) + print(img.shape) + print(img.dim()) + print(img.shape[0]) + + if(img.dim() == 4): + if(img.shape[0] == 1): + img = img.squeeze(0) + print(img.shape) + print(img.dim()) + print(img.shape[0]) + + +# print(img.shape) + +# model = fasterrcnn_resnet50_fpn(pretrained=True) +# model = model.eval() +# out = model(img) +# # print(out) + +# boxes = out[0]["boxes"] +# labels = out[0]["labels"] + +# print(out) + + # # print(boxes) + # print(boxes.shape) + + # # print(labels) + # print(labels.shape) + + # # draw_bounding_boxes(img, boxes, labels,) + + # # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer + # # print(img) + # # print(img.shape) + # img = img.squeeze(0) + # # print(img.shape) + + + # ndarr = img.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() + + # # print(ndarr) + + # print(ndarr.shape) + + # # img = Image.fromarray(ndarr) + # # draw = ImageDraw.Draw(img) + + # # print(boxes) + # if(boxes.requires_grad): + # boxes = boxes.detach() + + # boxes = boxes.to('cpu').numpy().astype('int').tolist() + # print(boxes) + + # labels = labels.to('cpu').numpy().astype('int').tolist() + # print(labels) + + # img_to_draw = Image.fromarray(ndarr) + # draw = ImageDraw.Draw(img_to_draw) + + # # print(img_to_draw.shape) + + # width = 1 + # for bbox, label in zip(boxes, labels): + # draw.rectangle(bbox, outline="red", width=5) + # # draw.text((bbox[0], bbox[1]), str(label)) + # draw.text((bbox[0], bbox[1]), label_names[label]) + # # draw.text + # # print("Draw Bbox") + # img_to_draw.show() + + # # out = Image.open(img_path) + # # draw = ImageDraw.Draw(out) + # # box = [12, 23, 45, 42] + # # draw.rectangle(box, outline="red", width=5) + # # out.show() + + # img = img.squeeze(0) + # img_drawn = draw_bounding_boxes(img, boxes, labels, label_names, colors=colors) + From 6886cacd3ad714fed607463caffaa08f1117562a Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Mon, 12 Oct 2020 15:23:46 +0530 Subject: [PATCH 10/16] removes dev file --- torchvision/dev.py | 186 ------------------------------------------- torchvision/utils.py | 6 +- 2 files changed, 3 insertions(+), 189 deletions(-) delete mode 100644 torchvision/dev.py diff --git a/torchvision/dev.py b/torchvision/dev.py deleted file mode 100644 index 19f7f2bcb86..00000000000 --- a/torchvision/dev.py +++ /dev/null @@ -1,186 +0,0 @@ -from PIL import Image, ImageDraw -import torch -from torchvision.models.detection import fasterrcnn_resnet50_fpn -from typing import Dict -import sys -from torchvision.io.image import read_image -import torchvision.transforms as T -from typing import List -import numpy as np - -img_path = "../test/assets/grace_hopper_517x606.jpg" - - -# def draw_bounding_boxes( -# image: torch.Tensor, -# boxes: torch.Tensor, -# labels: torch.Tensor, -# label_names: List[int] = None, -# colors: Dict[int, str] = None, -# draw_labels: bool = True, -# width: int = 1 -# ) -> torch.Tensor: - -# """ -# Draws bounding boxes on given image. - -# Args: -# image (Tensor): Tensor of shape (C x H x W) -# bboxes (Tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. -# labels (Tensor): Tensor of size (N) Labels for each bounding boxes. -# label_names (List): List containing labels excluding background. -# colors (dict): Dict with key as label id and value as color name. -# draw_labels (bool): If True draws label names on bounding boxes. -# width (int): Width of bounding box. -# """ - - # Code co-contributed by sumanthratna - - # Currently works for (C x H x W) images, but I think we should extend. - # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer - -# if not (torch.is_tensor(image)): -# raise TypeError('tensor expected, got {}'.format(type(image))) - -# if label_names is not None: -# # Since for our detection models class 0 is background -# label_names.insert(0, "__background__") - -# ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() - -# # Neceassary check since FRCNN returns boxes which have grad enabled. -# if(boxes.requires_grad): -# boxes = boxes.detach() - -# boxes = boxes.to('cpu').numpy().astype('int').tolist() -# labels = labels.to('cpu').numpy().astype('int').tolist() - -# img_to_draw = Image.fromarray(ndarr) -# draw = ImageDraw.Draw(img_to_draw) - -# for bbox, label in zip(boxes, labels): -# if colors is None: -# draw.rectangle(bbox, width=width) -# else: -# draw.rectangle(bbox, width=width, outline=colors[label]) - -# if label_names is None: -# draw.text((bbox[0], bbox[1]), str(label)) -# else: -# if draw_labels is True: -# draw.text((bbox[0], bbox[1]), label_names[int(label)]) - -# img_to_draw.show() -# return torch.from_numpy(np.array(img_to_draw)) - - -if __name__ == "__main__": -# # img = torch.rand(3, 3, 226, 226) -# # img = read_image(img_path) - -# label_names = [ -# 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', -# 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'N/A', 'stop sign', -# 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', -# 'elephant', 'bear', 'zebra', 'giraffe', 'N/A', 'backpack', 'umbrella', 'N/A', 'N/A', -# 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', -# 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', -# 'bottle', 'N/A', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', -# 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', -# 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'N/A', 'dining table', -# 'N/A', 'N/A', 'toilet', 'N/A', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', -# 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'N/A', 'book', -# 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'] - -# colors = {1: "blue", 32: "aqua", 84: "yellow", 16: "black", 38: "brown"} - -# # label_names.insert(0, "__background__") - -# # print(label_names) - - img = Image.open(img_path) - img = T.ToTensor()(img) - print(img.shape) - print(img.dim()) - img = torch.unsqueeze(img, 0) - print(img.shape) - print(img.dim()) - print(img.shape[0]) - - if(img.dim() == 4): - if(img.shape[0] == 1): - img = img.squeeze(0) - print(img.shape) - print(img.dim()) - print(img.shape[0]) - - -# print(img.shape) - -# model = fasterrcnn_resnet50_fpn(pretrained=True) -# model = model.eval() -# out = model(img) -# # print(out) - -# boxes = out[0]["boxes"] -# labels = out[0]["labels"] - -# print(out) - - # # print(boxes) - # print(boxes.shape) - - # # print(labels) - # print(labels.shape) - - # # draw_bounding_boxes(img, boxes, labels,) - - # # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer - # # print(img) - # # print(img.shape) - # img = img.squeeze(0) - # # print(img.shape) - - - # ndarr = img.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() - - # # print(ndarr) - - # print(ndarr.shape) - - # # img = Image.fromarray(ndarr) - # # draw = ImageDraw.Draw(img) - - # # print(boxes) - # if(boxes.requires_grad): - # boxes = boxes.detach() - - # boxes = boxes.to('cpu').numpy().astype('int').tolist() - # print(boxes) - - # labels = labels.to('cpu').numpy().astype('int').tolist() - # print(labels) - - # img_to_draw = Image.fromarray(ndarr) - # draw = ImageDraw.Draw(img_to_draw) - - # # print(img_to_draw.shape) - - # width = 1 - # for bbox, label in zip(boxes, labels): - # draw.rectangle(bbox, outline="red", width=5) - # # draw.text((bbox[0], bbox[1]), str(label)) - # draw.text((bbox[0], bbox[1]), label_names[label]) - # # draw.text - # # print("Draw Bbox") - # img_to_draw.show() - - # # out = Image.open(img_path) - # # draw = ImageDraw.Draw(out) - # # box = [12, 23, 45, 42] - # # draw.rectangle(box, outline="red", width=5) - # # out.show() - - # img = img.squeeze(0) - # img_drawn = draw_bounding_boxes(img, boxes, labels, label_names, colors=colors) - diff --git a/torchvision/utils.py b/torchvision/utils.py index 2d2b112cf8f..9fa29afcc7e 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -159,15 +159,15 @@ def draw_bounding_boxes( # Code co-contributed by sumanthratna + if not (torch.is_tensor(image)): + raise TypeError(f'tensor expected, got {type(image)}') + if(image.dim() == 4): if(image.shape[0] == 1): image = image.squeeze(0) else: raise ValueError("Batch size > 1 is not supported. Pass images with batch size 1 only") - if not (torch.is_tensor(image)): - raise TypeError(f'tensor expected, got {type(image)}') - if label_names is not None: # Since for our detection models class 0 is background label_names.insert(0, "__background__") From 396dc537f18c1a6dbe8623b12f35faeff625e638 Mon Sep 17 00:00:00 2001 From: Aditya Oke Date: Tue, 13 Oct 2020 15:21:41 +0530 Subject: [PATCH 11/16] adds suggested changes --- torchvision/utils.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/torchvision/utils.py b/torchvision/utils.py index 9fa29afcc7e..85d3fbaf5fd 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -140,7 +140,6 @@ def draw_bounding_boxes( labels: torch.Tensor, label_names: List[str] = None, colors: Dict[int, str] = None, - draw_labels: bool = True, width: int = 1 ) -> torch.Tensor: @@ -153,34 +152,31 @@ def draw_bounding_boxes( labels (Tensor): Tensor of size (N) Labels for each bounding boxes. label_names (List): List containing labels excluding background. colors (dict): Dict with key as label id and value as color name. - draw_labels (bool): If True (default) draws label names on bounding boxes. width (int): Width of bounding box. """ - # Code co-contributed by sumanthratna - - if not (torch.is_tensor(image)): + if not isinstance(image, torch.Tensor): raise TypeError(f'tensor expected, got {type(image)}') - if(image.dim() == 4): - if(image.shape[0] == 1): + if image.dim() == 4: + if image.shape[0] == 1: image = image.squeeze(0) else: raise ValueError("Batch size > 1 is not supported. Pass images with batch size 1 only") - if label_names is not None: - # Since for our detection models class 0 is background - label_names.insert(0, "__background__") + # if label_names is not None: + # # Since for our detection models class 0 is background + # label_names.insert(0, "__background__") # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() # Neceassary check to remove grad if present - if(boxes.requires_grad): + if boxes.requires_grad: boxes = boxes.detach() - boxes = boxes.to('cpu').numpy().astype('int').tolist() - labels = labels.to('cpu').numpy().astype('int').tolist() + boxes = boxes.to(torch.int64).tolist() + labels = labels.to(torch.int64).tolist() img_to_draw = Image.fromarray(ndarr) draw = ImageDraw.Draw(img_to_draw) @@ -193,7 +189,7 @@ def draw_bounding_boxes( if label_names is None: draw.text((bbox[0], bbox[1]), str(label)) - elif draw_labels is True: + else: draw.text((bbox[0], bbox[1]), label_names[int(label)]) return torch.from_numpy(np.array(img_to_draw)) From cbd5ee922b1cd336e47459017244ce6578e2c28e Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 20 Nov 2020 13:06:48 +0000 Subject: [PATCH 12/16] Updating the API. --- test/test_utils.py | 6 ++-- torchvision/utils.py | 68 +++++++++++++++++++------------------------- 2 files changed, 32 insertions(+), 42 deletions(-) diff --git a/test/test_utils.py b/test/test_utils.py index 0fb47b1c2ec..20a4101014c 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -80,11 +80,11 @@ def test_save_image_single_pixel_file_object(self): 'Pixel Image not stored in file object') def test_draw_boxes(self): - img = torch.rand(3, 226, 226) + img = torch.rand(3, 226, 226, dtype=torch.uint8) boxes = torch.tensor([[0, 0, 100, 100], [0, 0, 0, 0], [10, 15, 30, 35], [23, 35, 93, 95]], dtype=torch.float) - labels = torch.tensor([2, 1, 3, 5]) - utils.draw_bounding_boxes(img, boxes, labels) + labels = ['a', 'b', 'c', 'd'] + utils.draw_bounding_boxes(img, boxes, labels=labels) return True diff --git a/torchvision/utils.py b/torchvision/utils.py index af362b8f22a..8009c182ead 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -1,10 +1,10 @@ -from typing import Union, Optional, List, Tuple, Text, BinaryIO, Dict -import io +from typing import Union, Optional, List, Tuple, Text, BinaryIO import pathlib import torch import math import numpy as np from PIL import Image, ImageDraw +from PIL.ImageFont import ImageFont __all__ = ["make_grid", "save_image", "draw_bounding_boxes"] @@ -137,59 +137,49 @@ def save_image( def draw_bounding_boxes( image: torch.Tensor, boxes: torch.Tensor, - labels: torch.Tensor, - label_names: List[str] = None, - colors: Dict[int, str] = None, - width: int = 1 + colors: Optional[List[str]] = None, + labels: Optional[List[str]] = None, + width: int = 1, + font: Optional[ImageFont] = None ) -> torch.Tensor: """ Draws bounding boxes on given image. + The values of the input image should be uint8 between 0 and 255. Args: - image (Tensor): Tensor of shape (C x H x W) or (1 x C x H x W) + image (Tensor): Tensor of shape (C x H x W) bboxes (Tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. - labels (Tensor): Tensor of size (N) Labels for each bounding boxes. - label_names (List): List containing labels excluding background. - colors (dict): Dict with key as label id and value as color name. + colors (List): List containing the colors of bounding boxes excluding background. + labels (List): List containing the labels of bounding boxes excluding background. width (int): Width of bounding box. + font (ImageFont): The PIL ImageFont object used to for drawing the labels. """ if not isinstance(image, torch.Tensor): - raise TypeError(f'tensor expected, got {type(image)}') - - if image.dim() == 4: - if image.shape[0] == 1: - image = image.squeeze(0) - else: - raise ValueError("Batch size > 1 is not supported. Pass images with batch size 1 only") - - # if label_names is not None: - # # Since for our detection models class 0 is background - # label_names.insert(0, "__background__") - - # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer - ndarr = image.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() - - # Neceassary check to remove grad if present + raise TypeError(f"Tensor expected, got {type(image)}") + elif image.dtype != torch.uint8: + raise ValueError(f"Tensor uint8 expected, got {image.dtype}") + elif image.dim() != 3: + raise ValueError("Pass individual images, not batches") + + if image.requires_grad: + image = image.detach() if boxes.requires_grad: boxes = boxes.detach() - boxes = boxes.to(torch.int64).tolist() - labels = labels.to(torch.int64).tolist() - + ndarr = image.permute(1, 2, 0).numpy() img_to_draw = Image.fromarray(ndarr) + + img_boxes = boxes.to(torch.int64).tolist() + draw = ImageDraw.Draw(img_to_draw) - for bbox, label in zip(boxes, labels): - if colors is None: - draw.rectangle(bbox, width=width) - else: - draw.rectangle(bbox, width=width, outline=colors[label]) + for i, bbox in enumerate(img_boxes): + color = None if colors is None else colors[i] + draw.rectangle(bbox, width=width, outline=color) - if label_names is None: - draw.text((bbox[0], bbox[1]), str(label)) - else: - draw.text((bbox[0], bbox[1]), label_names[int(label)]) + if labels is not None: + draw.text((bbox[0], bbox[1]), labels[i], fill=color, font=font) - return torch.from_numpy(np.array(img_to_draw)) + return torch.from_numpy(np.array(img_to_draw)).permute(2, 0, 1) From 35f6951166a6208a404fcbe69eed7b007a1d8f9d Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 20 Nov 2020 13:08:28 +0000 Subject: [PATCH 13/16] Update test. --- test/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_utils.py b/test/test_utils.py index 20a4101014c..7092eec16ae 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -80,7 +80,7 @@ def test_save_image_single_pixel_file_object(self): 'Pixel Image not stored in file object') def test_draw_boxes(self): - img = torch.rand(3, 226, 226, dtype=torch.uint8) + img = torch.randint(0, 255, (3, 226, 226), dtype=torch.uint8) boxes = torch.tensor([[0, 0, 100, 100], [0, 0, 0, 0], [10, 15, 30, 35], [23, 35, 93, 95]], dtype=torch.float) labels = ['a', 'b', 'c', 'd'] From 07274f1790261cd99e3d7f93f750ee6e01ed51a2 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 20 Nov 2020 17:11:47 +0000 Subject: [PATCH 14/16] Implementing code review improvements. --- test/test_utils.py | 12 ++++++------ torchvision/utils.py | 17 ++++++++--------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/test/test_utils.py b/test/test_utils.py index 7092eec16ae..8b8d8af741a 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -80,13 +80,13 @@ def test_save_image_single_pixel_file_object(self): 'Pixel Image not stored in file object') def test_draw_boxes(self): - img = torch.randint(0, 255, (3, 226, 226), dtype=torch.uint8) - boxes = torch.tensor([[0, 0, 100, 100], [0, 0, 0, 0], + img = torch.randint(0, 255, (3, 100, 100), dtype=torch.uint8) + boxes = torch.tensor([[0, 0, 20, 20], [0, 0, 0, 0], [10, 15, 30, 35], [23, 35, 93, 95]], dtype=torch.float) - labels = ['a', 'b', 'c', 'd'] - utils.draw_bounding_boxes(img, boxes, labels=labels) - - return True + labels = ["a", "b", "c", "d"] + colors = ["green", "#FF00FF", (0, 255, 0), "red"] + result = utils.draw_bounding_boxes(img, boxes, labels=labels, colors=colors) + #Image.fromarray(result.permute(1, 2, 0).numpy()).show() if __name__ == '__main__': diff --git a/torchvision/utils.py b/torchvision/utils.py index 8009c182ead..3fbe57083b3 100644 --- a/torchvision/utils.py +++ b/torchvision/utils.py @@ -134,11 +134,12 @@ def save_image( im.save(fp, format=format) +@torch.no_grad() def draw_bounding_boxes( image: torch.Tensor, boxes: torch.Tensor, - colors: Optional[List[str]] = None, labels: Optional[List[str]] = None, + colors: Optional[List[Union[str, Tuple[int, int, int]]]] = None, width: int = 1, font: Optional[ImageFont] = None ) -> torch.Tensor: @@ -149,9 +150,12 @@ def draw_bounding_boxes( Args: image (Tensor): Tensor of shape (C x H x W) - bboxes (Tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. - colors (List): List containing the colors of bounding boxes excluding background. - labels (List): List containing the labels of bounding boxes excluding background. + bboxes (Tensor): Tensor of size (N, 4) containing bounding boxes in (xmin, ymin, xmax, ymax) format. Note that + the boxes are absolute coordinates with respect to the image. In other words: `0 <= xmin < xmax < W` and + `0 <= ymin < ymax < H`. + labels (List[str]): List containing the labels of bounding boxes. + colors (List[Union[str, Tuple[int, int, int]]]): List containing the colors of bounding boxes. The colors can + be represented as `str` or `Tuple[int, int, int]`. width (int): Width of bounding box. font (ImageFont): The PIL ImageFont object used to for drawing the labels. """ @@ -163,11 +167,6 @@ def draw_bounding_boxes( elif image.dim() != 3: raise ValueError("Pass individual images, not batches") - if image.requires_grad: - image = image.detach() - if boxes.requires_grad: - boxes = boxes.detach() - ndarr = image.permute(1, 2, 0).numpy() img_to_draw = Image.fromarray(ndarr) From 30905e9ca38e1f99bee460ff07a04c493722386c Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 20 Nov 2020 17:37:54 +0000 Subject: [PATCH 15/16] Further refactoring and adding test. --- test/assets/fakedata/draw_boxes_util.png | Bin 0 -> 29547 bytes test/common_utils.py | 7 +++++++ test/test_models.py | 10 +--------- test/test_utils.py | 16 ++++++++++++---- torchvision/utils.py | 13 +++++++++---- 5 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 test/assets/fakedata/draw_boxes_util.png diff --git a/test/assets/fakedata/draw_boxes_util.png b/test/assets/fakedata/draw_boxes_util.png new file mode 100644 index 0000000000000000000000000000000000000000..8cb3df92e0a25cd482fe07ee42cfe06c13afde1c GIT binary patch literal 29547 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 a0{;ai$L>9INR6=o0000 torch.Tensor: """ @@ -157,7 +158,10 @@ def draw_bounding_boxes( colors (List[Union[str, Tuple[int, int, int]]]): List containing the colors of bounding boxes. The colors can be represented as `str` or `Tuple[int, int, int]`. width (int): Width of bounding box. - font (ImageFont): The PIL ImageFont object used to for drawing the labels. + font (str): A filename containing a TrueType font. If the file is not found in this filename, the loader may + also search in other directories, such as the `fonts/` directory on Windows or `/Library/Fonts/`, + `/System/Library/Fonts/` and `~/Library/Fonts/` on macOS. + font_size (int): The requested font size in points. """ if not isinstance(image, torch.Tensor): @@ -179,6 +183,7 @@ def draw_bounding_boxes( draw.rectangle(bbox, width=width, outline=color) if labels is not None: - draw.text((bbox[0], bbox[1]), labels[i], fill=color, font=font) + txt_font = None if font is None else ImageFont.truetype(font=font, size=font_size) + draw.text((bbox[0], bbox[1]), labels[i], fill=color, font=txt_font) return torch.from_numpy(np.array(img_to_draw)).permute(2, 0, 1) From 1568b54b69cd274f1a02fc740054c9310dd77dd3 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 20 Nov 2020 18:19:36 +0000 Subject: [PATCH 16/16] Replace random to white to reduce size and change font on tests. --- test/assets/fakedata/draw_boxes_util.png | Bin 29547 -> 490 bytes test/test_utils.py | 7 +++---- torchvision/utils.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/test/assets/fakedata/draw_boxes_util.png b/test/assets/fakedata/draw_boxes_util.png index 8cb3df92e0a25cd482fe07ee42cfe06c13afde1c..e6b9286bf922d5dac64e5a083b902efa80bf857b 100644 GIT binary patch literal 490 zcmeAS@N?(olHy`uVBq!ia0vp^DIm)5Lqq9HXkK(J$-XJd7zm^-g?Fas5-}T~k&b z*%?=~|8%^4qjF?&sztnS_L|h}o2y^Rz0O&^>%8r?m3|C&=aq%I_`U21t+7s@TCgw6 z#){z>QxB^Iw}Du}+WT|9I{#f;*nY5X&o=edE_PlG?CWMO> z_5O0i^o-!_R9pbKkK*s{I#-Q_kPXn+mo+;W@Y2v zeycQ{_tuNV{U@u;w>KAVV(8~<*w_%8YJOBUy literal 29547 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 a0{;ai$L>9INR6=o0000