object-detection-scratch / box_utils.py
YMmim's picture
Object detection from scratch: Faster R-CNN + YOLO comparison
017c046 verified
Raw
History Blame Contribute Delete
5.85 kB
"""
Faster R-CNN ๋ฐ‘๋ฐ”๋‹ฅ ๊ตฌํ˜„ โ€” [2/5] ๋ฐ•์Šค ์—ฐ์‚ฐ ์œ ํ‹ธ
==================================================
๊ฐ์ฒดํƒ์ง€์˜ ์ˆ˜ํ•™์  ํ•ต์‹ฌ์ด ๋ชจ๋‘ ์—ฌ๊ธฐ์— ์žˆ๋‹ค.
1) ์•ต์ปค(anchor) ์ƒ์„ฑ : ๊ฒฉ์ž๋งˆ๋‹ค ์—ฌ๋Ÿฌ ํฌ๊ธฐยท๋น„์œจ์˜ ๊ธฐ์ค€ ๋ฐ•์Šค๋ฅผ ๊น๋‹ค
2) IoU : ๋‘ ๋ฐ•์Šค๊ฐ€ ์–ผ๋งˆ๋‚˜ ๊ฒน์น˜๋Š”๊ฐ€
3) ๋ฐ•์Šค ์ธ์ฝ”๋”ฉ/๋””์ฝ”๋”ฉ : (๋ฐ•์Šค โ†’ ํšŒ๊ท€ ํƒ€๊นƒ) / (์˜ˆ์ธก๊ฐ’ โ†’ ๋ฐ•์Šค)
4) NMS : ๊ฒน์น˜๋Š” ์ค‘๋ณต ์˜ˆ์ธก์„ ์ œ๊ฑฐ
์ด ํŒŒ์ผ๋งŒ ์ดํ•ดํ•˜๋ฉด Faster R-CNN์˜ ์ ˆ๋ฐ˜์„ ์ดํ•ดํ•œ ๊ฒƒ์ด๋‹ค.
"""
import torch
# ---------------------------------------------------------------
# 1) ์•ต์ปค ์ƒ์„ฑ
# ---------------------------------------------------------------
def generate_anchors(base_size=16, ratios=(0.5, 1.0, 2.0),
scales=(8, 16, 32)):
"""
ํ•œ ๊ฒฉ์ž์ (cell)์— ๋†“์„ ๊ธฐ์ค€ ์•ต์ปค๋“ค์„ ๋งŒ๋“ ๋‹ค.
ratios(๊ฐ€๋กœ์„ธ๋กœ๋น„) ร— scales(ํฌ๊ธฐ) ์กฐํ•ฉ โ†’ ๋ณดํ†ต 9๊ฐœ ์•ต์ปค.
๋ฐ˜ํ™˜: [num_anchors, 4] ํ˜•ํƒœ์˜ (x1,y1,x2,y2), ์ค‘์‹ฌ์ด ์›์  ๊ธฐ์ค€.
"""
anchors = []
for scale in scales:
area = (base_size * scale) ** 2
for ratio in ratios:
# ๋„“์ด๋Š” ์œ ์ง€ํ•˜๊ณ  ๊ฐ€๋กœ์„ธ๋กœ๋น„๋งŒ ๋ฐ”๊พผ๋‹ค
w = round((area / ratio) ** 0.5)
h = round(w * ratio)
anchors.append([-w / 2, -h / 2, w / 2, h / 2])
return torch.tensor(anchors, dtype=torch.float32)
def shift_anchors(base_anchors, feat_h, feat_w, stride):
"""
๊ธฐ์ค€ ์•ต์ปค๋ฅผ ํŠน์ง•๋งต ์ „์ฒด ๊ฒฉ์ž์— ๋ณต์ œยท์ด๋™์‹œ์ผœ
๋ชจ๋“  ์œ„์น˜์˜ ์•ต์ปค๋ฅผ ๋งŒ๋“ ๋‹ค.
feat_h, feat_w : ํŠน์ง•๋งต ํฌ๊ธฐ
stride : ์›๋ณธ ์ด๋ฏธ์ง€ ๋Œ€๋น„ ํŠน์ง•๋งต ์ถ•์†Œ ๋ฐฐ์œจ(์˜ˆ: 16)
๋ฐ˜ํ™˜: [feat_h*feat_w*num_anchors, 4] (์›๋ณธ ์ด๋ฏธ์ง€ ์ขŒํ‘œ๊ณ„)
"""
# ๊ฐ ๊ฒฉ์ž์ ์˜ ์ด๋ฏธ์ง€์ƒ ์ค‘์‹ฌ ์ขŒํ‘œ
shift_x = (torch.arange(feat_w) + 0.5) * stride
shift_y = (torch.arange(feat_h) + 0.5) * stride
sy, sx = torch.meshgrid(shift_y, shift_x, indexing="ij")
shifts = torch.stack([sx.reshape(-1), sy.reshape(-1),
sx.reshape(-1), sy.reshape(-1)], dim=1) # [K,4]
# [K,1,4] + [1,A,4] โ†’ [K,A,4] โ†’ [K*A,4]
anchors = shifts[:, None, :] + base_anchors[None, :, :]
return anchors.reshape(-1, 4)
# ---------------------------------------------------------------
# 2) IoU (Intersection over Union)
# ---------------------------------------------------------------
def box_iou(boxes1, boxes2):
"""
[N,4], [M,4] โ†’ [N,M] IoU ํ–‰๋ ฌ.
IoU = ๊ต์ง‘ํ•ฉ ๋„“์ด / ํ•ฉ์ง‘ํ•ฉ ๋„“์ด. 0(์•ˆ ๊ฒน์นจ)~1(์™„์ „ ์ผ์น˜).
"""
area1 = (boxes1[:, 2] - boxes1[:, 0]) * (boxes1[:, 3] - boxes1[:, 1])
area2 = (boxes2[:, 2] - boxes2[:, 0]) * (boxes2[:, 3] - boxes2[:, 1])
lt = torch.max(boxes1[:, None, :2], boxes2[None, :, :2]) # ๊ต์ง‘ํ•ฉ ์ขŒ์ƒ๋‹จ
rb = torch.min(boxes1[:, None, 2:], boxes2[None, :, 2:]) # ๊ต์ง‘ํ•ฉ ์šฐํ•˜๋‹จ
wh = (rb - lt).clamp(min=0)
inter = wh[:, :, 0] * wh[:, :, 1]
union = area1[:, None] + area2[None, :] - inter
return inter / union.clamp(min=1e-6)
# ---------------------------------------------------------------
# 3) ๋ฐ•์Šค ์ธ์ฝ”๋”ฉ / ๋””์ฝ”๋”ฉ
# ---------------------------------------------------------------
def encode_boxes(gt, anchors):
"""
์ •๋‹ต ๋ฐ•์Šค(gt)๋ฅผ ์•ต์ปค ๊ธฐ์ค€ ํšŒ๊ท€ ํƒ€๊นƒ (dx,dy,dw,dh)์œผ๋กœ ๋ณ€ํ™˜.
๋„คํŠธ์›Œํฌ๋Š” ์ ˆ๋Œ€ ์ขŒํ‘œ๊ฐ€ ์•„๋‹ˆ๋ผ "์•ต์ปค๋กœ๋ถ€ํ„ฐ์˜ ์ƒ๋Œ€ ๋ณ€ํ˜•"์„ ๋ฐฐ์šด๋‹ค.
"""
aw = anchors[:, 2] - anchors[:, 0]
ah = anchors[:, 3] - anchors[:, 1]
ax = anchors[:, 0] + 0.5 * aw
ay = anchors[:, 1] + 0.5 * ah
gw = gt[:, 2] - gt[:, 0]
gh = gt[:, 3] - gt[:, 1]
gx = gt[:, 0] + 0.5 * gw
gy = gt[:, 1] + 0.5 * gh
dx = (gx - ax) / aw
dy = (gy - ay) / ah
dw = torch.log(gw / aw)
dh = torch.log(gh / ah)
return torch.stack([dx, dy, dw, dh], dim=1)
def decode_boxes(deltas, anchors):
"""
๋„คํŠธ์›Œํฌ๊ฐ€ ์˜ˆ์ธกํ•œ (dx,dy,dw,dh)๋ฅผ ์‹ค์ œ ๋ฐ•์Šค ์ขŒํ‘œ๋กœ ๋ณต์›.
encode_boxes์˜ ์—ญ์—ฐ์‚ฐ.
"""
aw = anchors[:, 2] - anchors[:, 0]
ah = anchors[:, 3] - anchors[:, 1]
ax = anchors[:, 0] + 0.5 * aw
ay = anchors[:, 1] + 0.5 * ah
dx, dy, dw, dh = deltas[:, 0], deltas[:, 1], deltas[:, 2], deltas[:, 3]
# dw,dh ํญ์ฃผ ๋ฐฉ์ง€ ํด๋žจํ”„
dw = torch.clamp(dw, max=4.135)
dh = torch.clamp(dh, max=4.135)
px = dx * aw + ax
py = dy * ah + ay
pw = torch.exp(dw) * aw
ph = torch.exp(dh) * ah
x1 = px - 0.5 * pw
y1 = py - 0.5 * ph
x2 = px + 0.5 * pw
y2 = py + 0.5 * ph
return torch.stack([x1, y1, x2, y2], dim=1)
def clip_boxes(boxes, img_h, img_w):
"""๋ฐ•์Šค๋ฅผ ์ด๋ฏธ์ง€ ๊ฒฝ๊ณ„ ์•ˆ์œผ๋กœ ์ž๋ฅธ๋‹ค."""
boxes[:, 0].clamp_(min=0, max=img_w)
boxes[:, 1].clamp_(min=0, max=img_h)
boxes[:, 2].clamp_(min=0, max=img_w)
boxes[:, 3].clamp_(min=0, max=img_h)
return boxes
# ---------------------------------------------------------------
# 4) NMS (Non-Maximum Suppression)
# ---------------------------------------------------------------
def nms(boxes, scores, iou_thresh=0.7):
"""
์ ์ˆ˜ ๋†’์€ ๋ฐ•์Šค๋ถ€ํ„ฐ ๋‚จ๊ธฐ๊ณ , ๊ทธ์™€ ๋งŽ์ด ๊ฒน์น˜๋Š” ๋ฐ•์Šค๋Š” ์ œ๊ฑฐ.
torchvision.ops.nms ๋ฅผ ์จ๋„ ๋˜์ง€๋งŒ, ์›๋ฆฌ ํ•™์Šต์šฉ์œผ๋กœ ์ง์ ‘ ๊ตฌํ˜„.
๋ฐ˜ํ™˜: ๋‚จ๊ธธ ์ธ๋ฑ์Šค.
"""
if boxes.numel() == 0:
return torch.empty((0,), dtype=torch.int64)
order = scores.argsort(descending=True)
keep = []
while order.numel() > 0:
i = order[0].item()
keep.append(i)
if order.numel() == 1:
break
ious = box_iou(boxes[i].unsqueeze(0), boxes[order[1:]]).squeeze(0)
# ์ž„๊ณ„๊ฐ’ ์ดํ•˜๋งŒ ๋‚จ๊ธด๋‹ค
order = order[1:][ious <= iou_thresh]
return torch.tensor(keep, dtype=torch.int64)