-
Notifications
You must be signed in to change notification settings - Fork 31
Add Visualization utilities article #397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| --- | ||
| title: "Visualization utilities" | ||
| type: docs | ||
| --- | ||
|
|
||
| ```{r, echo = FALSE} | ||
| knitr::opts_chunk$set(eval = FALSE) | ||
| ``` | ||
|
|
||
| This article walks through the drawing helpers shipped with torchvision | ||
| (`vision_make_grid()`, `draw_bounding_boxes()`, `draw_segmentation_masks()` and | ||
| `draw_keypoints()`) and shows how to feed them the output of a pretrained | ||
| detection or segmentation model. It follows the | ||
| [Visualization utilities](https://docs.pytorch.org/vision/stable/auto_examples/others/plot_visualization_utils.html) | ||
| tutorial from PyTorch. | ||
|
|
||
| The code below is not evaluated when the website is built, because it downloads | ||
| around 250 MB of model weights. Every figure was produced by running it. | ||
|
|
||
| ```{r setup} | ||
| library(torchvision) | ||
| library(torch) | ||
|
|
||
| gallery <- "https://raw.githubusercontent.com/pytorch/vision/main/gallery/assets/" | ||
| dog1 <- base_loader(paste0(gallery, "dog1.jpg")) %>% transform_to_tensor() | ||
| dog2 <- base_loader(paste0(gallery, "dog2.jpg")) %>% transform_to_tensor() | ||
| ``` | ||
|
|
||
| ## A grid of images | ||
|
|
||
| `vision_make_grid()` tiles a batch of images into a single tensor, which is a | ||
| quick way to eyeball a batch coming out of a `dataloader()`. | ||
|
|
||
| ```{r} | ||
| grid <- vision_make_grid(torch_stack(list(dog1, dog2)), num_rows = 2) | ||
| tensor_image_browse(grid) | ||
| ``` | ||
|
|
||
|  | ||
|
|
||
| ## Bounding boxes | ||
|
|
||
| `draw_bounding_boxes()` expects an `(N, 4)` tensor of boxes given as | ||
| `(xmin, ymin, xmax, ymax)` in absolute pixel coordinates. | ||
|
|
||
| ```{r} | ||
| boxes <- torch_tensor(rbind( | ||
| c(50, 50, 100, 200), | ||
| c(210, 150, 350, 430) | ||
| ), dtype = torch_float()) | ||
|
|
||
| boxed <- draw_bounding_boxes(dog1, boxes, colors = c("red", "yellow"), width = 5) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion could we add labels = c("label_1", ...) to visually explain all the features of the function ? |
||
| tensor_image_browse(boxed) | ||
| ``` | ||
|
|
||
|  | ||
|
|
||
| ## Object detection with RF-DETR | ||
|
|
||
| Boxes are usually not hand written but predicted. RF-DETR expects | ||
| ImageNet-normalized inputs, so normalize before batching. The model still finds | ||
| the dogs on unnormalized input, but far less confidently: the corgi below scores | ||
| 0.91 with normalization and 0.69 without. | ||
|
|
||
| ```{r} | ||
| norm_mean <- c(0.485, 0.456, 0.406) | ||
| norm_std <- c(0.229, 0.224, 0.225) | ||
|
|
||
| batch <- torch_stack(list( | ||
| transform_normalize(dog1, norm_mean, norm_std), | ||
| transform_normalize(dog2, norm_mean, norm_std) | ||
| )) | ||
|
Comment on lines
+69
to
+72
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion if I'm not wrong, transform_normalize() accepts batches, so I would demonstrate it and I would apply it once after torch_strack() |
||
|
|
||
| model <- model_rfdetr_base(pretrained = TRUE) | ||
| model$eval() | ||
| detections <- with_no_grad(model(batch))$detections | ||
| ``` | ||
|
|
||
| RF-DETR resizes the batch to its own working resolution internally and maps the | ||
| boxes back to the coordinates of the images you passed in, so they can be drawn | ||
| straight onto the original tensors. Each element of `detections` holds `boxes`, | ||
| `labels` and `scores` sorted by decreasing score. Keep the confident ones, and | ||
| turn the label indices into readable names with `coco_classes()`. | ||
|
|
||
| ```{r} | ||
| draw_detection <- function(image, detection) { | ||
| keep <- as.logical(as.array(detection$scores > 0.5)) | ||
| draw_bounding_boxes( | ||
| image, | ||
| detection$boxes[keep, , drop = FALSE], | ||
| labels = coco_classes(as.integer(detection$labels[keep])), | ||
| colors = "yellow", | ||
| width = 5, | ||
| font_size = 30 | ||
| ) | ||
| } | ||
|
Comment on lines
+86
to
+96
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thought I'm not in favor of defining new functions wrapping visualization utilities in an article where the intent is to demonstrate how good the visualization utilities are. That said, I've no better idea to not have a lot of boilerplate code. |
||
|
|
||
| tensor_image_browse(draw_detection(dog1, detections[[1]])) | ||
| tensor_image_browse(draw_detection(dog2, detections[[2]])) | ||
| ``` | ||
|
|
||
|  | ||
|  | ||
|
|
||
| ## Semantic segmentation masks | ||
|
|
||
| `model_fcn_resnet50()` scores every pixel against the 21 PASCAL VOC classes. Its | ||
| pretrained weights are evaluated at 520 pixels, so resize before normalizing, | ||
| and keep the resized images around to draw on: masks and image must share their | ||
| height and width. | ||
|
|
||
| ```{r} | ||
| resized <- lapply(list(dog1, dog2), transform_resize, size = c(520, 520)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. todo simplification clarity transform_resize is also accepting batches. So please demonstrate the piping of transforms after stacking the images. |
||
| batch <- torch_stack(list( | ||
| transform_normalize(resized[[1]], norm_mean, norm_std), | ||
| transform_normalize(resized[[2]], norm_mean, norm_std) | ||
| )) | ||
|
|
||
| model <- model_fcn_resnet50(pretrained = TRUE) | ||
| model$eval() | ||
| scores <- with_no_grad(model(batch))$out | ||
| ``` | ||
|
|
||
| `scores` is a `(2, 21, 520, 520)` float tensor. Handed such a tensor, | ||
| `draw_segmentation_masks()` takes the argmax over the class dimension and | ||
| colors each class that appears. | ||
|
|
||
| ```{r} | ||
| segmented <- draw_segmentation_masks(resized[[1]], scores[1, , , ], alpha = 0.6) | ||
| tensor_image_browse(segmented) | ||
| ``` | ||
|
|
||
|  | ||
|
|
||
| To highlight a single class instead, build the boolean mask yourself. `dog` is | ||
| the 13th of the 21 classes, and softmax turns the scores into per-pixel | ||
| probabilities that can be thresholded. | ||
|
|
||
| ```{r} | ||
| dog_masks <- nnf_softmax(scores, dim = 2)[, 13, , ] > 0.7 | ||
|
|
||
| tensor_image_browse(draw_segmentation_masks(resized[[1]], dog_masks[1, , ], | ||
| alpha = 0.6, colors = "blue")) | ||
| tensor_image_browse(draw_segmentation_masks(resized[[2]], dog_masks[2, , ], | ||
| alpha = 0.6, colors = "blue")) | ||
| ``` | ||
|
|
||
|  | ||
|  | ||
|
|
||
| ## Keypoints | ||
|
|
||
| `draw_keypoints()` takes an `(N, K, 2)` tensor holding the `(x, y)` location of | ||
| `K` keypoints for each of `N` instances. torchvision has no pose estimation | ||
| model to feed it (`model_mtcnn()` predicts five landmarks, but only on human | ||
| faces), so the keypoints here are annotated by hand: the two ear tips, the two | ||
| eyes and the nose of the corgi. `connectivity` lists the pairs of keypoint | ||
| indices to join with a line. Each line is drawn in the color of the keypoint it | ||
| starts from, so leave `colors` at its default or pass one color per keypoint. | ||
|
|
||
| ```{r} | ||
| keypoints <- torch_tensor(rbind( | ||
| c(220, 171), | ||
| c(296, 176), | ||
| c(239, 222), | ||
| c(262, 226), | ||
| c(242, 250) | ||
| ), dtype = torch_float())$reshape(c(1, 5, 2)) | ||
|
|
||
| connectivity <- list(c(1, 3), c(2, 4), c(3, 4), c(3, 5), c(4, 5)) | ||
|
|
||
| posed <- draw_keypoints(dog1, keypoints, connectivity = connectivity, | ||
| radius = 6, width = 3) | ||
| tensor_image_browse(posed) | ||
| ``` | ||
|
|
||
|  | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought Should we wait for #394 Solution 2 available in #398 and have a nice looking
grid <- vision_make_grid(dog1, dog2, num_rows = 2)?