YMmim's picture
Object detection from scratch: Faster R-CNN + YOLO comparison
017c046 verified
Raw
History Blame Contribute Delete
5.36 kB
"""
Faster R-CNN ๋ฐ‘๋ฐ”๋‹ฅ ๊ตฌํ˜„ โ€” [1/5] ๋ฐ์ดํ„ฐ์…‹ (Pascal VOC)
=========================================================
Pascal VOC 2007/2012 ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด (์ด๋ฏธ์ง€, ๋ฐ•์Šค, ๋ผ๋ฒจ)์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
VOC ์–ด๋…ธํ…Œ์ด์…˜์€ XML ํ˜•์‹์ด๋ฉฐ, ๊ฐ ๊ฐ์ฒด๋งˆ๋‹ค ๋‹ค์Œ์„ ๋‹ด๋Š”๋‹ค:
- name : ํด๋ž˜์Šค ์ด๋ฆ„ (์˜ˆ: 'person', 'car')
- bndbox : xmin, ymin, xmax, ymax (์ขŒ์ƒ๋‹จยท์šฐํ•˜๋‹จ ํ”ฝ์…€ ์ขŒํ‘œ)
- difficult : ํŒ๋ณ„ ์–ด๋ ค์šด ๊ฐ์ฒด ํ‘œ์‹œ(ํ•™์Šต ์‹œ ๋ณดํ†ต ์ œ์™ธ)
ํ•ต์‹ฌ ๊ฐœ๋…:
- ๋ฐ•์Šค ์ขŒํ‘œ๋Š” [x1, y1, x2, y2] ์ ˆ๋Œ€ ํ”ฝ์…€ ์ขŒํ‘œ๋กœ ํ†ต์ผํ•œ๋‹ค.
- ์ด๋ฏธ์ง€๋ฅผ ๋ฆฌ์‚ฌ์ด์ฆˆํ•˜๋ฉด ๋ฐ•์Šค๋„ ๊ฐ™์€ ๋น„์œจ๋กœ ์Šค์ผ€์ผํ•ด์•ผ ํ•œ๋‹ค.
"""
import os
import xml.etree.ElementTree as ET
import torch
from torch.utils.data import Dataset
from PIL import Image
import torchvision.transforms.functional as F
# Pascal VOC 20๊ฐœ ํด๋ž˜์Šค (์ธ๋ฑ์Šค 0์€ ๋ฐฐ๊ฒฝ์œผ๋กœ ์˜ˆ์•ฝ โ†’ ํด๋ž˜์Šค๋Š” 1๋ถ€ํ„ฐ)
VOC_CLASSES = [
"aeroplane", "bicycle", "bird", "boat", "bottle",
"bus", "car", "cat", "chair", "cow",
"diningtable", "dog", "horse", "motorbike", "person",
"pottedplant", "sheep", "sofa", "train", "tvmonitor",
]
# ์ด๋ฆ„ โ†’ ์ธ๋ฑ์Šค (๋ฐฐ๊ฒฝ=0 ์ด๋ฏ€๋กœ +1)
CLASS_TO_IDX = {name: i + 1 for i, name in enumerate(VOC_CLASSES)}
NUM_CLASSES = len(VOC_CLASSES) + 1 # +1 = ๋ฐฐ๊ฒฝ(background)
class VOCDataset(Dataset):
"""
Pascal VOC ๊ฐ์ฒดํƒ์ง€ ๋ฐ์ดํ„ฐ์…‹.
Args:
root: VOCdevkit/VOC2007 (๋˜๋Š” VOC2012) ๊ฒฝ๋กœ
split: 'train' | 'val' | 'trainval' | 'test'
min_size: ๋ฆฌ์‚ฌ์ด์ฆˆ ์‹œ ์ด๋ฏธ์ง€ ์งง์€ ๋ณ€์˜ ๋ชฉํ‘œ ๊ธธ์ด
max_size: ๊ธด ๋ณ€์˜ ์ตœ๋Œ€ ๊ธธ์ด(๋น„์œจ ์œ ์ง€ํ•˜๋ฉฐ ์ƒํ•œ ์ ์šฉ)
keep_difficult: difficult=1 ๊ฐ์ฒด๋ฅผ ํฌํ•จํ• ์ง€ ์—ฌ๋ถ€(ํ•™์Šต ์‹œ False ๊ถŒ์žฅ)
"""
def __init__(self, root, split="trainval", min_size=600, max_size=1000,
keep_difficult=False):
self.root = root
self.min_size = min_size
self.max_size = max_size
self.keep_difficult = keep_difficult
# ImageSets/Main/<split>.txt ์— ์ด๋ฏธ์ง€ ID ๋ชฉ๋ก์ด ์žˆ๋‹ค.
split_file = os.path.join(root, "ImageSets", "Main", f"{split}.txt")
with open(split_file) as f:
self.ids = [line.strip() for line in f if line.strip()]
def __len__(self):
return len(self.ids)
def _load_annotation(self, img_id):
"""XML์„ ํŒŒ์‹ฑํ•ด ๋ฐ•์Šค์™€ ๋ผ๋ฒจ์„ ๋ฝ‘๋Š”๋‹ค."""
ann_path = os.path.join(self.root, "Annotations", f"{img_id}.xml")
tree = ET.parse(ann_path)
boxes, labels = [], []
for obj in tree.findall("object"):
difficult = int(obj.findtext("difficult", "0"))
if difficult and not self.keep_difficult:
continue
name = obj.findtext("name").strip().lower()
if name not in CLASS_TO_IDX:
continue
bnd = obj.find("bndbox")
# VOC ์ขŒํ‘œ๋Š” 1๋ถ€ํ„ฐ ์‹œ์ž‘ โ†’ 0-๊ธฐ๋ฐ˜์œผ๋กœ ๋ณด์ •(-1)
x1 = float(bnd.findtext("xmin")) - 1
y1 = float(bnd.findtext("ymin")) - 1
x2 = float(bnd.findtext("xmax")) - 1
y2 = float(bnd.findtext("ymax")) - 1
boxes.append([x1, y1, x2, y2])
labels.append(CLASS_TO_IDX[name])
boxes = torch.as_tensor(boxes, dtype=torch.float32).reshape(-1, 4)
labels = torch.as_tensor(labels, dtype=torch.int64)
return boxes, labels
def _resize(self, img, boxes):
"""
์งง์€ ๋ณ€์„ min_size๋กœ ๋งž์ถ”๋˜, ๊ธด ๋ณ€์ด max_size๋ฅผ ๋„˜์ง€ ์•Š๋„๋ก ์Šค์ผ€์ผ.
๋ฐ•์Šค๋„ ๊ฐ™์€ ๋น„์œจ๋กœ ์กฐ์ •ํ•œ๋‹ค. (Faster R-CNN ์›๋…ผ๋ฌธ ๋ฐฉ์‹)
"""
w, h = img.size
short, long = min(w, h), max(w, h)
scale = self.min_size / short
if long * scale > self.max_size:
scale = self.max_size / long
new_w, new_h = int(round(w * scale)), int(round(h * scale))
img = img.resize((new_w, new_h), Image.BILINEAR)
if boxes.numel() > 0:
boxes = boxes * scale # ๋ฐ•์Šค๋„ ๋™์ผ ๋ฐฐ์œจ ์ ์šฉ
return img, boxes, scale
def __getitem__(self, idx):
img_id = self.ids[idx]
img_path = os.path.join(self.root, "JPEGImages", f"{img_id}.jpg")
img = Image.open(img_path).convert("RGB")
boxes, labels = self._load_annotation(img_id)
img, boxes, scale = self._resize(img, boxes)
# ํ…์„œ ๋ณ€ํ™˜ + ImageNet ์ •๊ทœํ™”(๋ฐฑ๋ณธ์ด ImageNet ์‚ฌ์ „ํ•™์Šต์ด๋ฏ€๋กœ)
img = F.to_tensor(img)
img = F.normalize(img,
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
target = {
"boxes": boxes, # [N,4] ์ ˆ๋Œ€ ํ”ฝ์…€ (๋ฆฌ์‚ฌ์ด์ฆˆ ํ›„)
"labels": labels, # [N] 1..20 (0=๋ฐฐ๊ฒฝ)
"image_id": img_id,
"scale": scale, # ํ‰๊ฐ€ ์‹œ ์›๋ณธ ์ขŒํ‘œ๋กœ ๋˜๋Œ๋ฆด ๋•Œ ์‚ฌ์šฉ
}
return img, target
def collate_fn(batch):
"""
์ด๋ฏธ์ง€๋งˆ๋‹ค ํฌ๊ธฐ๊ฐ€ ๋‹ฌ๋ผ ๊ธฐ๋ณธ collate๋กœ ๋ชป ๋ฌถ๋Š”๋‹ค.
๋ฆฌ์ŠคํŠธ ํ˜•ํƒœ๋กœ ๊ทธ๋Œ€๋กœ ๋„˜๊ธฐ๊ณ , ๋ชจ๋ธ ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.
(๊ฐ„๋‹จํ™”๋ฅผ ์œ„ํ•ด batch_size=1 ์‚ฌ์šฉ์„ ๊ถŒ์žฅ)
"""
imgs, targets = list(zip(*batch))
return list(imgs), list(targets)