YMmim's picture
Object detection from scratch: Faster R-CNN + YOLO comparison
017c046 verified
Raw
History Blame Contribute Delete
5.9 kB
"""
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