| """ |
| Faster R-CNN ๋ฐ๋ฐ๋ฅ ๊ตฌํ โ [3/5] ๋ชจ๋ธ |
| ========================================== |
| ์ ์ฒด ๊ตฌ์กฐ: |
| |
| ์ด๋ฏธ์ง |
| โ [๋ฐฑ๋ณธ: ResNet50 (ImageNet ์ฌ์ ํ์ต)] |
| โผ |
| ํน์ง๋งต (stride 16) |
| โ |
| โโโถ [RPN] โโ ์ต์ปค๋ง๋ค (๊ฐ์ฒด ์ฌ๋ถ, ๋ฐ์ค ๋ณด์ ) ์์ธก โโ ํ๋ณด์์ญ(proposal) ์์ฑ |
| โ |
| โผ |
| [RoI Align] โโ ๊ฐ ํ๋ณด์์ญ์ ๊ณ ์ ํฌ๊ธฐ(7x7) ํน์ง์ผ๋ก ์ถ์ถ |
| โ |
| โผ |
| [RoI Head] โโ (ํด๋์ค ๋ถ๋ฅ, ํด๋์ค๋ณ ๋ฐ์ค ๋ณด์ ) ์ต์ข
์์ธก |
| |
| 2-stage ํ์ง๊ธฐ์ ์ ์ ๊ตฌ์กฐ. RPN์ด "์ด๋์ ๋ญ๊ฐ ์๋ค"๋ฅผ, |
| RoI Head๊ฐ "๊ทธ๊ฒ ๋ฌด์์ด๊ณ ์ ํํ ์์น๋ ์ด๋"๋ฅผ ๋ด๋นํ๋ค. |
| """ |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| import torchvision |
| from torchvision.ops import roi_align |
|
|
| from box_utils import (generate_anchors, shift_anchors, box_iou, |
| encode_boxes, decode_boxes, clip_boxes, nms) |
|
|
|
|
| |
| |
| |
| class Backbone(nn.Module): |
| def __init__(self): |
| super().__init__() |
| resnet = torchvision.models.resnet50(weights="IMAGENET1K_V1") |
| |
| self.body = nn.Sequential( |
| resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, |
| resnet.layer1, resnet.layer2, resnet.layer3, |
| ) |
| self.out_channels = 1024 |
| self.stride = 16 |
|
|
| |
| for p in self.body[:5].parameters(): |
| p.requires_grad = False |
|
|
| def forward(self, x): |
| return self.body(x) |
|
|
|
|
| |
| |
| |
| class RPN(nn.Module): |
| def __init__(self, in_channels=1024, num_anchors=9): |
| super().__init__() |
| self.conv = nn.Conv2d(in_channels, 512, 3, padding=1) |
| |
| self.cls = nn.Conv2d(512, num_anchors, 1) |
| self.reg = nn.Conv2d(512, num_anchors * 4, 1) |
| self.num_anchors = num_anchors |
|
|
| for layer in [self.conv, self.cls, self.reg]: |
| nn.init.normal_(layer.weight, std=0.01) |
| nn.init.constant_(layer.bias, 0) |
|
|
| def forward(self, feat): |
| t = F.relu(self.conv(feat)) |
| logits = self.cls(t) |
| deltas = self.reg(t) |
|
|
| B, _, H, W = logits.shape |
| |
| logits = logits.permute(0, 2, 3, 1).reshape(B, -1) |
| deltas = deltas.permute(0, 2, 3, 1).reshape(B, -1, 4) |
| return logits, deltas, (H, W) |
|
|
|
|
| |
| |
| |
| class RoIHead(nn.Module): |
| def __init__(self, num_classes, in_channels=1024, roi_size=7): |
| super().__init__() |
| |
| self.roi_size = roi_size |
| flat = in_channels * roi_size * roi_size |
| self.fc = nn.Sequential( |
| nn.Linear(flat, 1024), nn.ReLU(inplace=True), |
| nn.Linear(1024, 1024), nn.ReLU(inplace=True), |
| ) |
| self.cls = nn.Linear(1024, num_classes) |
| self.reg = nn.Linear(1024, num_classes * 4) |
| self.num_classes = num_classes |
|
|
| nn.init.normal_(self.cls.weight, std=0.01) |
| nn.init.normal_(self.reg.weight, std=0.001) |
| nn.init.constant_(self.cls.bias, 0) |
| nn.init.constant_(self.reg.bias, 0) |
|
|
| def forward(self, feat, rois, stride): |
| """ |
| feat : ๋ฐฑ๋ณธ ํน์ง๋งต [B,C,H,W] |
| rois : ํ๋ณด์์ญ [N,4] (์ด๋ฏธ์ง ์ขํ) |
| """ |
| |
| |
| batch_idx = torch.zeros((rois.shape[0], 1), device=rois.device) |
| rois_b = torch.cat([batch_idx, rois], dim=1) |
| pooled = roi_align(feat, rois_b, |
| output_size=(self.roi_size, self.roi_size), |
| spatial_scale=1.0 / stride, |
| sampling_ratio=2) |
| x = pooled.flatten(1) |
| x = self.fc(x) |
| return self.cls(x), self.reg(x) |
|
|
|
|
| |
| |
| |
| class FasterRCNN(nn.Module): |
| def __init__(self, num_classes): |
| super().__init__() |
| self.backbone = Backbone() |
| self.rpn = RPN(self.backbone.out_channels) |
| self.head = RoIHead(num_classes, self.backbone.out_channels) |
| self.num_classes = num_classes |
|
|
| self.base_anchors = generate_anchors() |
| self.stride = self.backbone.stride |
|
|
| |
| self.rpn_pre_nms = 12000 |
| self.rpn_post_nms_train = 2000 |
| self.rpn_post_nms_test = 300 |
| self.rpn_nms_thresh = 0.7 |
|
|
| def _anchors_for(self, feat_h, feat_w, device): |
| a = shift_anchors(self.base_anchors, feat_h, feat_w, self.stride) |
| return a.to(device) |
|
|
| def _proposals(self, rpn_logits, rpn_deltas, anchors, img_hw, training): |
| """RPN ์ถ๋ ฅ โ NMS ๊ฑฐ์น ํ๋ณด์์ญ(proposal) ์์ฑ.""" |
| scores = torch.sigmoid(rpn_logits[0]) |
| deltas = rpn_deltas[0] |
| proposals = decode_boxes(deltas, anchors) |
| proposals = clip_boxes(proposals, img_hw[0], img_hw[1]) |
|
|
| |
| n_pre = min(self.rpn_pre_nms, scores.numel()) |
| top = scores.topk(n_pre).indices |
| proposals, scores = proposals[top], scores[top] |
|
|
| keep = nms(proposals, scores, self.rpn_nms_thresh) |
| n_post = self.rpn_post_nms_train if training else self.rpn_post_nms_test |
| keep = keep[:n_post] |
| return proposals[keep], scores[keep] |
|
|
| def forward(self, image, target=None): |
| """ |
| image : [1,3,H,W] ๋จ์ผ ์ด๋ฏธ์ง(batch_size=1) |
| target : ํ์ต ์ {'boxes','labels'}, ์ถ๋ก ์ None |
| """ |
| device = image.device |
| img_h, img_w = image.shape[-2:] |
| feat = self.backbone(image) |
| _, _, fh, fw = feat.shape |
|
|
| anchors = self._anchors_for(fh, fw, device) |
| rpn_logits, rpn_deltas, _ = self.rpn(feat) |
|
|
| proposals, _ = self._proposals( |
| rpn_logits, rpn_deltas, anchors, (img_h, img_w), |
| training=self.training) |
|
|
| if self.training: |
| |
| return { |
| "feat": feat, "anchors": anchors, |
| "rpn_logits": rpn_logits[0], "rpn_deltas": rpn_deltas[0], |
| "proposals": proposals, "stride": self.stride, |
| } |
| else: |
| |
| cls_logits, reg = self.head(feat, proposals, self.stride) |
| return self._postprocess(cls_logits, reg, proposals, (img_h, img_w)) |
|
|
| @torch.no_grad() |
| def _postprocess(self, cls_logits, reg, proposals, img_hw, |
| score_thresh=0.05, nms_thresh=0.3, max_det=100): |
| """RoI Head ์ถ๋ ฅ โ ํด๋์ค๋ณ NMS โ ์ต์ข
๋ฐ์ค/๋ผ๋ฒจ/์ ์.""" |
| probs = F.softmax(cls_logits, dim=1) |
| N, C = probs.shape |
| reg = reg.reshape(N, C, 4) |
|
|
| all_boxes, all_scores, all_labels = [], [], [] |
| for c in range(1, C): |
| scores_c = probs[:, c] |
| mask = scores_c > score_thresh |
| if mask.sum() == 0: |
| continue |
| boxes_c = decode_boxes(reg[mask, c], proposals[mask]) |
| boxes_c = clip_boxes(boxes_c, img_hw[0], img_hw[1]) |
| scores_c = scores_c[mask] |
|
|
| keep = nms(boxes_c, scores_c, nms_thresh) |
| all_boxes.append(boxes_c[keep]) |
| all_scores.append(scores_c[keep]) |
| all_labels.append(torch.full((keep.numel(),), c, |
| dtype=torch.int64, device=boxes_c.device)) |
|
|
| if not all_boxes: |
| return {"boxes": torch.empty((0, 4)), "labels": torch.empty((0,), dtype=torch.int64), |
| "scores": torch.empty((0,))} |
|
|
| boxes = torch.cat(all_boxes) |
| scores = torch.cat(all_scores) |
| labels = torch.cat(all_labels) |
|
|
| |
| if scores.numel() > max_det: |
| top = scores.topk(max_det).indices |
| boxes, scores, labels = boxes[top], scores[top], labels[top] |
| return {"boxes": boxes, "labels": labels, "scores": scores} |
|
|