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