0Curious0 commited on
Commit
d581287
·
verified ·
1 Parent(s): a1816f6

Upload inference.py

Browse files
Files changed (1) hide show
  1. src/inference.py +144 -0
src/inference.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ import torch
5
+ from PIL import ImageDraw, ImageFont
6
+
7
+ from src.backbone import Backbone, backbone_transform
8
+ from src.rpn import RPN_Head, RegionProposalNetwork
9
+ from src.roi import RoIPool
10
+ from src.detection_net import DetectionHead, DetectionNet
11
+ from src.dataset import VOC_CLASSES
12
+
13
+ from huggingface_hub import hf_hub_download
14
+
15
+ # One fixed, distinguishable color per VOC class, keyed by VOC_CLASSES order.
16
+ _PALETTE = [
17
+ "#e6194b", "#3cb44b", "#ffe119", "#4363d8", "#f58231",
18
+ "#911eb4", "#46f0f0", "#f032e6", "#bcf60c", "#fabebe",
19
+ "#008080", "#e6beff", "#9a6324", "#fffac8", "#800000",
20
+ "#aaffc3", "#808000", "#ffd8b1", "#000075", "#808080",
21
+ ]
22
+ _CLASS_COLORS = dict(zip(VOC_CLASSES, _PALETTE))
23
+
24
+
25
+ @dataclass
26
+ class Pipeline:
27
+ backbone: Backbone
28
+ rpn_network: RegionProposalNetwork
29
+ roi_pool: RoIPool
30
+ detection_network: DetectionNet
31
+ device: torch.device
32
+
33
+
34
+ _CHECKPOINT_NAME = "faster_rcnn_final.bin"
35
+
36
+
37
+ def load_pipeline(checkpoint_dir, device):
38
+ checkpoint_dir = Path(checkpoint_dir)
39
+ if not (checkpoint_dir / _CHECKPOINT_NAME).exists():
40
+ print(f"Missing checkpoint file in '{checkpoint_dir}': {_CHECKPOINT_NAME}. ")
41
+
42
+ checkpoint_path = hf_hub_download(repo_id="0Curious0/faster_rcnn_resnet50", filename="checkpoints/faster_rcnn_final.bin")
43
+ else:
44
+ checkpoint_path = checkpoint_dir / _CHECKPOINT_NAME
45
+
46
+ backbone = Backbone().to(device)
47
+ rpn_head = RPN_Head(in_channels=1024, mid_channels=512)
48
+ detection_head = DetectionHead()
49
+
50
+ unified_ckpt = torch.load(checkpoint_path, map_location=device)
51
+
52
+ backbone.load_state_dict(unified_ckpt["backbone_state_dict"])
53
+ rpn_head.load_state_dict(unified_ckpt["rpn_state_dict"])
54
+ detection_head.load_state_dict(unified_ckpt["detection_state_dict"])
55
+
56
+ rpn_network = RegionProposalNetwork(rpn_head=rpn_head).to(device)
57
+ roi_pool = RoIPool(output_size=(7, 7), pooling_mode="adaptive").to(device)
58
+ detection_network = DetectionNet(detection_head=detection_head).to(device)
59
+
60
+ for module in (backbone, rpn_network, roi_pool, detection_network):
61
+ module.eval()
62
+ for param in module.parameters():
63
+ param.requires_grad = False
64
+
65
+ return Pipeline(
66
+ backbone=backbone,
67
+ rpn_network=rpn_network,
68
+ roi_pool=roi_pool,
69
+ detection_network=detection_network,
70
+ device=device,
71
+ )
72
+
73
+
74
+ def predict(pipeline, pil_image, score_thresh, nms_iou_thresh):
75
+ # pil_image: original, un-resized PIL image (RGB). backbone_transform only reads target["size"] (and doesn't need it to hold anything), so an empty dict is enough
76
+ img_tensor, _ = backbone_transform(pil_image, {"size": {}})
77
+ batch_imgs = img_tensor.unsqueeze(0).to(pipeline.device)
78
+
79
+ tensor_height, tensor_width = batch_imgs.shape[2], batch_imgs.shape[3]
80
+ img_sizes_before_pad = [(tensor_height, tensor_width)]
81
+
82
+ pipeline.detection_network.score_thresh = score_thresh
83
+ pipeline.detection_network.nms_iou_thresh = nms_iou_thresh
84
+
85
+ with torch.inference_mode():
86
+ feature_map = pipeline.backbone(batch_imgs)
87
+ _, proposals = pipeline.rpn_network(
88
+ feature_map,
89
+ batch_img_height=tensor_height,
90
+ batch_img_width=tensor_width,
91
+ img_sizes_before_pad=img_sizes_before_pad,
92
+ pre_nms_top_n=6000,
93
+ post_nms_top_n=2000,
94
+ )
95
+ pooled = pipeline.roi_pool(feature_map, proposals, tensor_height, tensor_width)
96
+ labels_list, scores_list, boxes_list = pipeline.detection_network(
97
+ proposals, pooled, img_sizes_before_pad
98
+ )
99
+
100
+ labels, scores, boxes = labels_list[0], scores_list[0], boxes_list[0]
101
+
102
+ # Rescaling per-axis by the tensor's own dims maps back onto the original image regardless of that swap, since it's the exact inverse of whatever TF.resize did.
103
+ orig_width, orig_height = pil_image.size
104
+ scale_x = orig_width / tensor_width
105
+ scale_y = orig_height / tensor_height
106
+
107
+ detections = []
108
+ for box, label, score in zip(boxes, labels, scores):
109
+ x1, y1, x2, y2 = box.tolist()
110
+ rescaled_box = (x1 * scale_x, y1 * scale_y, x2 * scale_x, y2 * scale_y)
111
+ detections.append((rescaled_box, VOC_CLASSES[label.item()], score.item()))
112
+
113
+ return detections
114
+
115
+
116
+ def draw_boxes(pil_image, detections):
117
+ annotated = pil_image.convert("RGB").copy()
118
+ draw = ImageDraw.Draw(annotated)
119
+
120
+ # Scale font to image size so labels stay legible on both small thumbnails and
121
+ font_size = max(16, round(min(annotated.size) / 40)) # divide by 40 to make font size 2.5% of the smaller image dimension
122
+ font = ImageFont.load_default(size=font_size)
123
+
124
+ for (x1, y1, x2, y2), label, score in detections:
125
+ color = _CLASS_COLORS[label]
126
+ draw.rectangle((x1, y1, x2, y2), outline=color, width=3)
127
+
128
+ text = f"{label} {score:.2f}"
129
+ text_bbox = draw.textbbox((0, 0), text, font=font)
130
+ text_width = text_bbox[2] - text_bbox[0]
131
+ text_height = text_bbox[3] - text_bbox[1]
132
+ pad = 2
133
+
134
+ # Label goes above the box unless that would run off the top of the image,
135
+ # in which case it's drawn just inside the box instead.
136
+ label_top = y1 - text_height - 2 * pad
137
+ if label_top < 0:
138
+ label_top = y1
139
+ label_bg = (x1, label_top, x1 + text_width + 2 * pad, label_top + text_height + 2 * pad)
140
+
141
+ draw.rectangle(label_bg, fill=color)
142
+ draw.text((x1 + pad, label_top + pad - text_bbox[1]), text, fill="white", font=font)
143
+
144
+ return annotated