File size: 5,899 Bytes
017c046 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | """
Faster R-CNN ๋ฐ๋ฐ๋ฅ ๊ตฌํ โ [4/5] ์์ค ๊ณ์ฐ
=============================================
Faster R-CNN์ ๋ ๊ณณ์์ ์์ค์ด ๋ฐ์ํ๋ค.
RPN ์์ค : ์ต์ปค๊ฐ ๊ฐ์ฒด์ธ๊ฐ(๋ถ๋ฅ) + ์ต์ปคโ์ ๋ต ๋ฐ์ค ๋ณด์ (ํ๊ท)
RoI ์์ค : ํ๋ณด์์ญ์ ํด๋์ค(๋ถ๋ฅ) + ํด๋์ค๋ณ ๋ฐ์ค ๋ณด์ (ํ๊ท)
ํต์ฌ์ "ํ๊น ํ ๋น(target assignment)":
- ๊ฐ ์ต์ปค/ํ๋ณด์์ญ์ด ์ด๋ค ์ ๋ต ๋ฐ์ค๋ฅผ ๋ด๋นํ๋์ง IoU๋ก ์ ํ๋ค.
- IoU ๋์ผ๋ฉด positive(๊ฐ์ฒด), ๋ฎ์ผ๋ฉด negative(๋ฐฐ๊ฒฝ), ์ ๋งคํ๋ฉด ๋ฌด์.
- ๊ทธ ๋ค์ positive์๋ง ํ๊ท ์์ค, pos+neg์ ๋ถ๋ฅ ์์ค์ ๊ฑด๋ค.
"""
import torch
import torch.nn.functional as F
from box_utils import box_iou, encode_boxes, decode_boxes, clip_boxes
def _sample(pos_idx, neg_idx, num, pos_frac):
"""pos/neg๋ฅผ ์ ํด์ง ๊ฐ์ยท๋น์จ๋ก ๋ฌด์์ ์ํ๋ง(ํด๋์ค ๋ถ๊ท ํ ๋ฐฉ์ง)."""
num_pos = min(int(num * pos_frac), pos_idx.numel())
num_neg = min(num - num_pos, neg_idx.numel())
pos = pos_idx[torch.randperm(pos_idx.numel())[:num_pos]]
neg = neg_idx[torch.randperm(neg_idx.numel())[:num_neg]]
return pos, neg
# ---------------------------------------------------------------
# RPN ์์ค
# ---------------------------------------------------------------
def rpn_loss(rpn_logits, rpn_deltas, anchors, gt_boxes, img_hw,
pos_iou=0.7, neg_iou=0.3, num_samples=256, pos_frac=0.5):
device = rpn_logits.device
# ์ด๋ฏธ์ง ๋ฐ์ผ๋ก ๋๊ฐ ์ต์ปค๋ ํ์ต์์ ์ ์ธ
inside = ((anchors[:, 0] >= 0) & (anchors[:, 1] >= 0) &
(anchors[:, 2] <= img_hw[1]) & (anchors[:, 3] <= img_hw[0]))
idx_inside = torch.where(inside)[0]
anc = anchors[idx_inside]
labels = torch.full((anc.shape[0],), -1, dtype=torch.float32, device=device) # -1=๋ฌด์
if gt_boxes.numel() > 0:
ious = box_iou(anc, gt_boxes) # [A, G]
max_iou, argmax = ious.max(dim=1) # ๊ฐ ์ต์ปค์ ์ต๊ณ IoU ์ ๋ต
labels[max_iou < neg_iou] = 0 # ๋ฐฐ๊ฒฝ
labels[max_iou >= pos_iou] = 1 # ๊ฐ์ฒด
# ๊ฐ ์ ๋ต ๋ฐ์ค์ ๋ํด IoU ์ต๋์ธ ์ต์ปค๋ ๊ฐ์ ๋ก positive
gt_best = ious.argmax(dim=0)
labels[gt_best] = 1
matched_gt = gt_boxes[argmax]
else:
labels[:] = 0
matched_gt = torch.zeros_like(anc)
pos = torch.where(labels == 1)[0]
neg = torch.where(labels == 0)[0]
pos, neg = _sample(pos, neg, num_samples, pos_frac)
samp = torch.cat([pos, neg])
# --- ๋ถ๋ฅ ์์ค (๊ฐ์ฒด/๋ฐฐ๊ฒฝ) ---
logits_inside = rpn_logits[idx_inside]
cls_loss = F.binary_cross_entropy_with_logits(
logits_inside[samp], labels[samp])
# --- ํ๊ท ์์ค (positive ์ต์ปค๋ง) ---
if pos.numel() > 0:
deltas_inside = rpn_deltas[idx_inside]
reg_targets = encode_boxes(matched_gt[pos], anc[pos])
reg_loss = F.smooth_l1_loss(deltas_inside[pos], reg_targets,
beta=1.0 / 9.0)
else:
reg_loss = torch.tensor(0.0, device=device)
return cls_loss + reg_loss
# ---------------------------------------------------------------
# RoI ํ๊น ํ ๋น (ํ๋ณด์์ญ โ ํ์ต ์ํ)
# ---------------------------------------------------------------
def assign_roi_targets(proposals, gt_boxes, gt_labels,
pos_iou=0.5, neg_iou_hi=0.5, neg_iou_lo=0.0,
num_samples=128, pos_frac=0.25):
"""ํ๋ณด์์ญ์ ํด๋์ค ๋ผ๋ฒจ๊ณผ ํ๊ท ํ๊น์ ๋ถ์ด๊ณ ์ํ๋ง."""
device = proposals.device
# ์ ๋ต ๋ฐ์ค๋ ํ๋ณด์ ์ถ๊ฐ(ํ์ต ์ด๊ธฐ ์์ ํ)
if gt_boxes.numel() > 0:
proposals = torch.cat([proposals, gt_boxes], dim=0)
if gt_boxes.numel() == 0:
# ์ ๋ต์ด ์์ผ๋ฉด ์ ๋ถ ๋ฐฐ๊ฒฝ
n = min(num_samples, proposals.shape[0])
sel = proposals[:n]
labels = torch.zeros((n,), dtype=torch.int64, device=device)
reg_t = torch.zeros((n, 4), device=device)
return sel, labels, reg_t, torch.zeros((n,), dtype=torch.bool, device=device)
ious = box_iou(proposals, gt_boxes)
max_iou, argmax = ious.max(dim=1)
gt_for_prop = gt_labels[argmax]
matched_gt = gt_boxes[argmax]
labels = torch.zeros_like(gt_for_prop) # 0=๋ฐฐ๊ฒฝ ๊ธฐ๋ณธ
pos_mask = max_iou >= pos_iou
labels[pos_mask] = gt_for_prop[pos_mask] # ๊ฐ์ฒด ํด๋์ค ๋ถ์ฌ
pos = torch.where(pos_mask)[0]
neg = torch.where((max_iou < neg_iou_hi) & (max_iou >= neg_iou_lo))[0]
pos, neg = _sample(pos, neg, num_samples, pos_frac)
samp = torch.cat([pos, neg])
sel_prop = proposals[samp]
sel_labels = labels[samp]
reg_targets = encode_boxes(matched_gt[samp], sel_prop)
is_pos = torch.zeros((samp.numel(),), dtype=torch.bool, device=device)
is_pos[:pos.numel()] = True
return sel_prop, sel_labels, reg_targets, is_pos
# ---------------------------------------------------------------
# RoI ์์ค
# ---------------------------------------------------------------
def roi_loss(head, feat, stride, proposals, gt_boxes, gt_labels):
sel_prop, labels, reg_targets, is_pos = assign_roi_targets(
proposals, gt_boxes, gt_labels)
cls_logits, reg = head(feat, sel_prop, stride) # [S,C], [S,C*4]
# --- ๋ถ๋ฅ ์์ค (์ ์ฒด ์ํ) ---
cls_loss = F.cross_entropy(cls_logits, labels)
# --- ํ๊ท ์์ค (positive๋ง, ํด๋น ํด๋์ค์ 4๊ฐ ์ขํ๋ง) ---
if is_pos.sum() > 0:
S = reg.shape[0]
reg = reg.reshape(S, -1, 4)
pos_idx = torch.where(is_pos)[0]
pos_labels = labels[pos_idx]
reg_pos = reg[pos_idx, pos_labels] # ํด๋น ํด๋์ค ์ขํ
reg_loss = F.smooth_l1_loss(reg_pos, reg_targets[pos_idx], beta=1.0)
else:
reg_loss = torch.tensor(0.0, device=feat.device)
return cls_loss + reg_loss
|