File size: 5,358 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
"""
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)