Upload 581 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- SCHP/__init__.py +241 -0
- SCHP/__pycache__/__init__.cpython-310.pyc +0 -0
- SCHP/networks/AugmentCE2P.py +480 -0
- SCHP/networks/__init__.py +13 -0
- SCHP/networks/__pycache__/AugmentCE2P.cpython-310.pyc +0 -0
- SCHP/networks/__pycache__/__init__.cpython-310.pyc +0 -0
- SCHP/utils/__pycache__/transforms.cpython-310.pyc +0 -0
- SCHP/utils/transforms.py +174 -0
- app.py +141 -0
- densepose/__init__.py +20 -0
- densepose/__pycache__/__init__.cpython-310.pyc +0 -0
- densepose/__pycache__/config.cpython-310.pyc +0 -0
- densepose/config.py +277 -0
- densepose/converters/__init__.py +15 -0
- densepose/converters/__pycache__/__init__.cpython-310.pyc +0 -0
- densepose/converters/__pycache__/base.cpython-310.pyc +0 -0
- densepose/converters/__pycache__/builtin.cpython-310.pyc +0 -0
- densepose/converters/__pycache__/chart_output_hflip.cpython-310.pyc +0 -0
- densepose/converters/__pycache__/chart_output_to_chart_result.cpython-310.pyc +0 -0
- densepose/converters/__pycache__/hflip.cpython-310.pyc +0 -0
- densepose/converters/__pycache__/segm_to_mask.cpython-310.pyc +0 -0
- densepose/converters/__pycache__/to_chart_result.cpython-310.pyc +0 -0
- densepose/converters/__pycache__/to_mask.cpython-310.pyc +0 -0
- densepose/converters/base.py +93 -0
- densepose/converters/builtin.py +31 -0
- densepose/converters/chart_output_hflip.py +71 -0
- densepose/converters/chart_output_to_chart_result.py +188 -0
- densepose/converters/hflip.py +34 -0
- densepose/converters/segm_to_mask.py +150 -0
- densepose/converters/to_chart_result.py +70 -0
- densepose/converters/to_mask.py +49 -0
- densepose/data/__init__.py +25 -0
- densepose/data/__pycache__/__init__.cpython-310.pyc +0 -0
- densepose/data/__pycache__/build.cpython-310.pyc +0 -0
- densepose/data/__pycache__/combined_loader.cpython-310.pyc +0 -0
- densepose/data/__pycache__/dataset_mapper.cpython-310.pyc +0 -0
- densepose/data/__pycache__/image_list_dataset.cpython-310.pyc +0 -0
- densepose/data/__pycache__/inference_based_loader.cpython-310.pyc +0 -0
- densepose/data/__pycache__/utils.cpython-310.pyc +0 -0
- densepose/data/build.py +736 -0
- densepose/data/combined_loader.py +44 -0
- densepose/data/dataset_mapper.py +168 -0
- densepose/data/datasets/__init__.py +5 -0
- densepose/data/datasets/__pycache__/__init__.cpython-310.pyc +0 -0
- densepose/data/datasets/__pycache__/builtin.cpython-310.pyc +0 -0
- densepose/data/datasets/__pycache__/chimpnsee.cpython-310.pyc +0 -0
- densepose/data/datasets/__pycache__/coco.cpython-310.pyc +0 -0
- densepose/data/datasets/__pycache__/dataset_type.cpython-310.pyc +0 -0
- densepose/data/datasets/__pycache__/lvis.cpython-310.pyc +0 -0
- densepose/data/datasets/builtin.py +16 -0
SCHP/__init__.py
ADDED
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from collections import OrderedDict
|
2 |
+
|
3 |
+
import cv2
|
4 |
+
import numpy as np
|
5 |
+
import torch
|
6 |
+
from PIL import Image
|
7 |
+
from SCHP import networks
|
8 |
+
from SCHP.utils.transforms import get_affine_transform, transform_logits
|
9 |
+
from torchvision import transforms
|
10 |
+
|
11 |
+
|
12 |
+
def get_palette(num_cls):
|
13 |
+
"""Returns the color map for visualizing the segmentation mask.
|
14 |
+
Args:
|
15 |
+
num_cls: Number of classes
|
16 |
+
Returns:
|
17 |
+
The color map
|
18 |
+
"""
|
19 |
+
n = num_cls
|
20 |
+
palette = [0] * (n * 3)
|
21 |
+
for j in range(0, n):
|
22 |
+
lab = j
|
23 |
+
palette[j * 3 + 0] = 0
|
24 |
+
palette[j * 3 + 1] = 0
|
25 |
+
palette[j * 3 + 2] = 0
|
26 |
+
i = 0
|
27 |
+
while lab:
|
28 |
+
palette[j * 3 + 0] |= ((lab >> 0) & 1) << (7 - i)
|
29 |
+
palette[j * 3 + 1] |= ((lab >> 1) & 1) << (7 - i)
|
30 |
+
palette[j * 3 + 2] |= ((lab >> 2) & 1) << (7 - i)
|
31 |
+
i += 1
|
32 |
+
lab >>= 3
|
33 |
+
return palette
|
34 |
+
|
35 |
+
|
36 |
+
dataset_settings = {
|
37 |
+
"lip": {
|
38 |
+
"input_size": [473, 473],
|
39 |
+
"num_classes": 20,
|
40 |
+
"label": [
|
41 |
+
"Background",
|
42 |
+
"Hat",
|
43 |
+
"Hair",
|
44 |
+
"Glove",
|
45 |
+
"Sunglasses",
|
46 |
+
"Upper-clothes",
|
47 |
+
"Dress",
|
48 |
+
"Coat",
|
49 |
+
"Socks",
|
50 |
+
"Pants",
|
51 |
+
"Jumpsuits",
|
52 |
+
"Scarf",
|
53 |
+
"Skirt",
|
54 |
+
"Face",
|
55 |
+
"Left-arm",
|
56 |
+
"Right-arm",
|
57 |
+
"Left-leg",
|
58 |
+
"Right-leg",
|
59 |
+
"Left-shoe",
|
60 |
+
"Right-shoe",
|
61 |
+
],
|
62 |
+
},
|
63 |
+
"atr": {
|
64 |
+
"input_size": [512, 512],
|
65 |
+
"num_classes": 18,
|
66 |
+
"label": [
|
67 |
+
"Background",
|
68 |
+
"Hat",
|
69 |
+
"Hair",
|
70 |
+
"Sunglasses",
|
71 |
+
"Upper-clothes",
|
72 |
+
"Skirt",
|
73 |
+
"Pants",
|
74 |
+
"Dress",
|
75 |
+
"Belt",
|
76 |
+
"Left-shoe",
|
77 |
+
"Right-shoe",
|
78 |
+
"Face",
|
79 |
+
"Left-leg",
|
80 |
+
"Right-leg",
|
81 |
+
"Left-arm",
|
82 |
+
"Right-arm",
|
83 |
+
"Bag",
|
84 |
+
"Scarf",
|
85 |
+
],
|
86 |
+
},
|
87 |
+
"pascal": {
|
88 |
+
"input_size": [512, 512],
|
89 |
+
"num_classes": 7,
|
90 |
+
"label": [
|
91 |
+
"Background",
|
92 |
+
"Head",
|
93 |
+
"Torso",
|
94 |
+
"Upper Arms",
|
95 |
+
"Lower Arms",
|
96 |
+
"Upper Legs",
|
97 |
+
"Lower Legs",
|
98 |
+
],
|
99 |
+
},
|
100 |
+
}
|
101 |
+
|
102 |
+
|
103 |
+
class SCHP:
|
104 |
+
def __init__(self, ckpt_path, device):
|
105 |
+
dataset_type = None
|
106 |
+
if "lip" in ckpt_path:
|
107 |
+
dataset_type = "lip"
|
108 |
+
elif "atr" in ckpt_path:
|
109 |
+
dataset_type = "atr"
|
110 |
+
elif "pascal" in ckpt_path:
|
111 |
+
dataset_type = "pascal"
|
112 |
+
assert dataset_type is not None, "Dataset type not found in checkpoint path"
|
113 |
+
self.device = device
|
114 |
+
self.num_classes = dataset_settings[dataset_type]["num_classes"]
|
115 |
+
self.input_size = dataset_settings[dataset_type]["input_size"]
|
116 |
+
self.aspect_ratio = self.input_size[1] * 1.0 / self.input_size[0]
|
117 |
+
self.palette = get_palette(self.num_classes)
|
118 |
+
|
119 |
+
self.label = dataset_settings[dataset_type]["label"]
|
120 |
+
self.model = networks.init_model(
|
121 |
+
"resnet101", num_classes=self.num_classes, pretrained=None
|
122 |
+
).to(device)
|
123 |
+
self.load_ckpt(ckpt_path)
|
124 |
+
self.model.eval()
|
125 |
+
|
126 |
+
self.transform = transforms.Compose(
|
127 |
+
[
|
128 |
+
transforms.ToTensor(),
|
129 |
+
transforms.Normalize(
|
130 |
+
mean=[0.406, 0.456, 0.485], std=[0.225, 0.224, 0.229]
|
131 |
+
),
|
132 |
+
]
|
133 |
+
)
|
134 |
+
self.upsample = torch.nn.Upsample(
|
135 |
+
size=self.input_size, mode="bilinear", align_corners=True
|
136 |
+
)
|
137 |
+
|
138 |
+
def load_ckpt(self, ckpt_path):
|
139 |
+
rename_map = {
|
140 |
+
"decoder.conv3.2.weight": "decoder.conv3.3.weight",
|
141 |
+
"decoder.conv3.3.weight": "decoder.conv3.4.weight",
|
142 |
+
"decoder.conv3.3.bias": "decoder.conv3.4.bias",
|
143 |
+
"decoder.conv3.3.running_mean": "decoder.conv3.4.running_mean",
|
144 |
+
"decoder.conv3.3.running_var": "decoder.conv3.4.running_var",
|
145 |
+
"fushion.3.weight": "fushion.4.weight",
|
146 |
+
"fushion.3.bias": "fushion.4.bias",
|
147 |
+
}
|
148 |
+
state_dict = torch.load(ckpt_path, map_location="cpu")["state_dict"]
|
149 |
+
new_state_dict = OrderedDict()
|
150 |
+
for k, v in state_dict.items():
|
151 |
+
name = k[7:] # remove `module.`
|
152 |
+
new_state_dict[name] = v
|
153 |
+
new_state_dict_ = OrderedDict()
|
154 |
+
for k, v in list(new_state_dict.items()):
|
155 |
+
if k in rename_map:
|
156 |
+
new_state_dict_[rename_map[k]] = v
|
157 |
+
else:
|
158 |
+
new_state_dict_[k] = v
|
159 |
+
self.model.load_state_dict(new_state_dict_, strict=False)
|
160 |
+
|
161 |
+
def _box2cs(self, box):
|
162 |
+
x, y, w, h = box[:4]
|
163 |
+
return self._xywh2cs(x, y, w, h)
|
164 |
+
|
165 |
+
def _xywh2cs(self, x, y, w, h):
|
166 |
+
center = np.zeros((2), dtype=np.float32)
|
167 |
+
center[0] = x + w * 0.5
|
168 |
+
center[1] = y + h * 0.5
|
169 |
+
if w > self.aspect_ratio * h:
|
170 |
+
h = w * 1.0 / self.aspect_ratio
|
171 |
+
elif w < self.aspect_ratio * h:
|
172 |
+
w = h * self.aspect_ratio
|
173 |
+
scale = np.array([w, h], dtype=np.float32)
|
174 |
+
return center, scale
|
175 |
+
|
176 |
+
def preprocess(self, image):
|
177 |
+
if isinstance(image, str):
|
178 |
+
img = cv2.imread(image, cv2.IMREAD_COLOR)
|
179 |
+
elif isinstance(image, Image.Image):
|
180 |
+
# to cv2 format
|
181 |
+
img = np.array(image)
|
182 |
+
|
183 |
+
h, w, _ = img.shape
|
184 |
+
# Get person center and scale
|
185 |
+
person_center, s = self._box2cs([0, 0, w - 1, h - 1])
|
186 |
+
r = 0
|
187 |
+
trans = get_affine_transform(person_center, s, r, self.input_size)
|
188 |
+
input = cv2.warpAffine(
|
189 |
+
img,
|
190 |
+
trans,
|
191 |
+
(int(self.input_size[1]), int(self.input_size[0])),
|
192 |
+
flags=cv2.INTER_LINEAR,
|
193 |
+
borderMode=cv2.BORDER_CONSTANT,
|
194 |
+
borderValue=(0, 0, 0),
|
195 |
+
)
|
196 |
+
|
197 |
+
input = self.transform(input).to(self.device).unsqueeze(0)
|
198 |
+
meta = {
|
199 |
+
"center": person_center,
|
200 |
+
"height": h,
|
201 |
+
"width": w,
|
202 |
+
"scale": s,
|
203 |
+
"rotation": r,
|
204 |
+
}
|
205 |
+
return input, meta
|
206 |
+
|
207 |
+
def __call__(self, image_or_path):
|
208 |
+
if isinstance(image_or_path, list):
|
209 |
+
image_list = []
|
210 |
+
meta_list = []
|
211 |
+
for image in image_or_path:
|
212 |
+
image, meta = self.preprocess(image)
|
213 |
+
image_list.append(image)
|
214 |
+
meta_list.append(meta)
|
215 |
+
image = torch.cat(image_list, dim=0)
|
216 |
+
else:
|
217 |
+
image, meta = self.preprocess(image_or_path)
|
218 |
+
meta_list = [meta]
|
219 |
+
|
220 |
+
output = self.model(image)
|
221 |
+
# upsample_outputs = self.upsample(output[0][-1])
|
222 |
+
upsample_outputs = self.upsample(output)
|
223 |
+
upsample_outputs = upsample_outputs.permute(0, 2, 3, 1) # BCHW -> BHWC
|
224 |
+
|
225 |
+
output_img_list = []
|
226 |
+
for upsample_output, meta in zip(upsample_outputs, meta_list):
|
227 |
+
c, s, w, h = meta["center"], meta["scale"], meta["width"], meta["height"]
|
228 |
+
logits_result = transform_logits(
|
229 |
+
upsample_output.data.cpu().numpy(),
|
230 |
+
c,
|
231 |
+
s,
|
232 |
+
w,
|
233 |
+
h,
|
234 |
+
input_size=self.input_size,
|
235 |
+
)
|
236 |
+
parsing_result = np.argmax(logits_result, axis=2)
|
237 |
+
output_img = Image.fromarray(np.asarray(parsing_result, dtype=np.uint8))
|
238 |
+
output_img.putpalette(self.palette)
|
239 |
+
output_img_list.append(output_img)
|
240 |
+
|
241 |
+
return output_img_list[0] if len(output_img_list) == 1 else output_img_list
|
SCHP/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (5.42 kB). View file
|
|
SCHP/networks/AugmentCE2P.py
ADDED
@@ -0,0 +1,480 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python
|
2 |
+
# -*- encoding: utf-8 -*-
|
3 |
+
|
4 |
+
"""
|
5 |
+
@Author : Peike Li
|
6 |
+
@Contact : [email protected]
|
7 |
+
@File : AugmentCE2P.py
|
8 |
+
@Time : 8/4/19 3:35 PM
|
9 |
+
@Desc :
|
10 |
+
@License : This source code is licensed under the license found in the
|
11 |
+
LICENSE file in the root directory of this source tree.
|
12 |
+
"""
|
13 |
+
|
14 |
+
import torch
|
15 |
+
import torch.nn as nn
|
16 |
+
|
17 |
+
from torch.nn import BatchNorm2d, functional as F, LeakyReLU
|
18 |
+
|
19 |
+
affine_par = True
|
20 |
+
pretrained_settings = {
|
21 |
+
"resnet101": {
|
22 |
+
"imagenet": {
|
23 |
+
"input_space": "BGR",
|
24 |
+
"input_size": [3, 224, 224],
|
25 |
+
"input_range": [0, 1],
|
26 |
+
"mean": [0.406, 0.456, 0.485],
|
27 |
+
"std": [0.225, 0.224, 0.229],
|
28 |
+
"num_classes": 1000,
|
29 |
+
}
|
30 |
+
},
|
31 |
+
}
|
32 |
+
|
33 |
+
|
34 |
+
def conv3x3(in_planes, out_planes, stride=1):
|
35 |
+
"3x3 convolution with padding"
|
36 |
+
return nn.Conv2d(
|
37 |
+
in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False
|
38 |
+
)
|
39 |
+
|
40 |
+
|
41 |
+
class Bottleneck(nn.Module):
|
42 |
+
expansion = 4
|
43 |
+
|
44 |
+
def __init__(
|
45 |
+
self,
|
46 |
+
inplanes,
|
47 |
+
planes,
|
48 |
+
stride=1,
|
49 |
+
dilation=1,
|
50 |
+
downsample=None,
|
51 |
+
fist_dilation=1,
|
52 |
+
multi_grid=1,
|
53 |
+
):
|
54 |
+
super(Bottleneck, self).__init__()
|
55 |
+
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
|
56 |
+
self.bn1 = BatchNorm2d(planes)
|
57 |
+
self.conv2 = nn.Conv2d(
|
58 |
+
planes,
|
59 |
+
planes,
|
60 |
+
kernel_size=3,
|
61 |
+
stride=stride,
|
62 |
+
padding=dilation * multi_grid,
|
63 |
+
dilation=dilation * multi_grid,
|
64 |
+
bias=False,
|
65 |
+
)
|
66 |
+
self.bn2 = BatchNorm2d(planes)
|
67 |
+
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
|
68 |
+
self.bn3 = BatchNorm2d(planes * 4)
|
69 |
+
self.relu = nn.ReLU(inplace=False)
|
70 |
+
self.relu_inplace = nn.ReLU(inplace=True)
|
71 |
+
self.downsample = downsample
|
72 |
+
self.dilation = dilation
|
73 |
+
self.stride = stride
|
74 |
+
|
75 |
+
def forward(self, x):
|
76 |
+
residual = x
|
77 |
+
|
78 |
+
out = self.conv1(x)
|
79 |
+
out = self.bn1(out)
|
80 |
+
out = self.relu(out)
|
81 |
+
|
82 |
+
out = self.conv2(out)
|
83 |
+
out = self.bn2(out)
|
84 |
+
out = self.relu(out)
|
85 |
+
|
86 |
+
out = self.conv3(out)
|
87 |
+
out = self.bn3(out)
|
88 |
+
|
89 |
+
if self.downsample is not None:
|
90 |
+
residual = self.downsample(x)
|
91 |
+
|
92 |
+
out = out + residual
|
93 |
+
out = self.relu_inplace(out)
|
94 |
+
|
95 |
+
return out
|
96 |
+
|
97 |
+
|
98 |
+
class PSPModule(nn.Module):
|
99 |
+
"""
|
100 |
+
Reference:
|
101 |
+
Zhao, Hengshuang, et al. *"Pyramid scene parsing network."*
|
102 |
+
"""
|
103 |
+
|
104 |
+
def __init__(self, features, out_features=512, sizes=(1, 2, 3, 6)):
|
105 |
+
super(PSPModule, self).__init__()
|
106 |
+
|
107 |
+
self.stages = []
|
108 |
+
self.stages = nn.ModuleList(
|
109 |
+
[self._make_stage(features, out_features, size) for size in sizes]
|
110 |
+
)
|
111 |
+
self.bottleneck = nn.Sequential(
|
112 |
+
nn.Conv2d(
|
113 |
+
features + len(sizes) * out_features,
|
114 |
+
out_features,
|
115 |
+
kernel_size=3,
|
116 |
+
padding=1,
|
117 |
+
dilation=1,
|
118 |
+
bias=False,
|
119 |
+
),
|
120 |
+
BatchNorm2d(out_features),
|
121 |
+
LeakyReLU(),
|
122 |
+
)
|
123 |
+
|
124 |
+
def _make_stage(self, features, out_features, size):
|
125 |
+
prior = nn.AdaptiveAvgPool2d(output_size=(size, size))
|
126 |
+
conv = nn.Conv2d(features, out_features, kernel_size=1, bias=False)
|
127 |
+
return nn.Sequential(
|
128 |
+
prior,
|
129 |
+
conv,
|
130 |
+
# bn
|
131 |
+
BatchNorm2d(out_features),
|
132 |
+
LeakyReLU(),
|
133 |
+
)
|
134 |
+
|
135 |
+
def forward(self, feats):
|
136 |
+
h, w = feats.size(2), feats.size(3)
|
137 |
+
priors = [
|
138 |
+
F.interpolate(
|
139 |
+
input=stage(feats), size=(h, w), mode="bilinear", align_corners=True
|
140 |
+
)
|
141 |
+
for stage in self.stages
|
142 |
+
] + [feats]
|
143 |
+
bottle = self.bottleneck(torch.cat(priors, 1))
|
144 |
+
return bottle
|
145 |
+
|
146 |
+
|
147 |
+
class ASPPModule(nn.Module):
|
148 |
+
"""
|
149 |
+
Reference:
|
150 |
+
Chen, Liang-Chieh, et al. *"Rethinking Atrous Convolution for Semantic Image Segmentation."*
|
151 |
+
"""
|
152 |
+
|
153 |
+
def __init__(
|
154 |
+
self, features, inner_features=256, out_features=512, dilations=(12, 24, 36)
|
155 |
+
):
|
156 |
+
super(ASPPModule, self).__init__()
|
157 |
+
|
158 |
+
self.conv1 = nn.Sequential(
|
159 |
+
nn.AdaptiveAvgPool2d((1, 1)),
|
160 |
+
nn.Conv2d(
|
161 |
+
features,
|
162 |
+
inner_features,
|
163 |
+
kernel_size=1,
|
164 |
+
padding=0,
|
165 |
+
dilation=1,
|
166 |
+
bias=False,
|
167 |
+
),
|
168 |
+
# InPlaceABNSync(inner_features)
|
169 |
+
BatchNorm2d(inner_features),
|
170 |
+
LeakyReLU(),
|
171 |
+
)
|
172 |
+
self.conv2 = nn.Sequential(
|
173 |
+
nn.Conv2d(
|
174 |
+
features,
|
175 |
+
inner_features,
|
176 |
+
kernel_size=1,
|
177 |
+
padding=0,
|
178 |
+
dilation=1,
|
179 |
+
bias=False,
|
180 |
+
),
|
181 |
+
BatchNorm2d(inner_features),
|
182 |
+
LeakyReLU(),
|
183 |
+
)
|
184 |
+
self.conv3 = nn.Sequential(
|
185 |
+
nn.Conv2d(
|
186 |
+
features,
|
187 |
+
inner_features,
|
188 |
+
kernel_size=3,
|
189 |
+
padding=dilations[0],
|
190 |
+
dilation=dilations[0],
|
191 |
+
bias=False,
|
192 |
+
),
|
193 |
+
BatchNorm2d(inner_features),
|
194 |
+
LeakyReLU(),
|
195 |
+
)
|
196 |
+
self.conv4 = nn.Sequential(
|
197 |
+
nn.Conv2d(
|
198 |
+
features,
|
199 |
+
inner_features,
|
200 |
+
kernel_size=3,
|
201 |
+
padding=dilations[1],
|
202 |
+
dilation=dilations[1],
|
203 |
+
bias=False,
|
204 |
+
),
|
205 |
+
BatchNorm2d(inner_features),
|
206 |
+
LeakyReLU(),
|
207 |
+
)
|
208 |
+
self.conv5 = nn.Sequential(
|
209 |
+
nn.Conv2d(
|
210 |
+
features,
|
211 |
+
inner_features,
|
212 |
+
kernel_size=3,
|
213 |
+
padding=dilations[2],
|
214 |
+
dilation=dilations[2],
|
215 |
+
bias=False,
|
216 |
+
),
|
217 |
+
BatchNorm2d(inner_features),
|
218 |
+
LeakyReLU(),
|
219 |
+
)
|
220 |
+
|
221 |
+
self.bottleneck = nn.Sequential(
|
222 |
+
nn.Conv2d(
|
223 |
+
inner_features * 5,
|
224 |
+
out_features,
|
225 |
+
kernel_size=1,
|
226 |
+
padding=0,
|
227 |
+
dilation=1,
|
228 |
+
bias=False,
|
229 |
+
),
|
230 |
+
BatchNorm2d(inner_features),
|
231 |
+
LeakyReLU(),
|
232 |
+
nn.Dropout2d(0.1),
|
233 |
+
)
|
234 |
+
|
235 |
+
def forward(self, x):
|
236 |
+
_, _, h, w = x.size()
|
237 |
+
|
238 |
+
feat1 = F.interpolate(
|
239 |
+
self.conv1(x), size=(h, w), mode="bilinear", align_corners=True
|
240 |
+
)
|
241 |
+
|
242 |
+
feat2 = self.conv2(x)
|
243 |
+
feat3 = self.conv3(x)
|
244 |
+
feat4 = self.conv4(x)
|
245 |
+
feat5 = self.conv5(x)
|
246 |
+
out = torch.cat((feat1, feat2, feat3, feat4, feat5), 1)
|
247 |
+
|
248 |
+
bottle = self.bottleneck(out)
|
249 |
+
return bottle
|
250 |
+
|
251 |
+
|
252 |
+
class Edge_Module(nn.Module):
|
253 |
+
"""
|
254 |
+
Edge Learning Branch
|
255 |
+
"""
|
256 |
+
|
257 |
+
def __init__(self, in_fea=[256, 512, 1024], mid_fea=256, out_fea=2):
|
258 |
+
super(Edge_Module, self).__init__()
|
259 |
+
|
260 |
+
self.conv1 = nn.Sequential(
|
261 |
+
nn.Conv2d(
|
262 |
+
in_fea[0], mid_fea, kernel_size=1, padding=0, dilation=1, bias=False
|
263 |
+
),
|
264 |
+
BatchNorm2d(mid_fea),
|
265 |
+
LeakyReLU(),
|
266 |
+
)
|
267 |
+
self.conv2 = nn.Sequential(
|
268 |
+
nn.Conv2d(
|
269 |
+
in_fea[1], mid_fea, kernel_size=1, padding=0, dilation=1, bias=False
|
270 |
+
),
|
271 |
+
BatchNorm2d(mid_fea),
|
272 |
+
LeakyReLU(),
|
273 |
+
)
|
274 |
+
self.conv3 = nn.Sequential(
|
275 |
+
nn.Conv2d(
|
276 |
+
in_fea[2], mid_fea, kernel_size=1, padding=0, dilation=1, bias=False
|
277 |
+
),
|
278 |
+
BatchNorm2d(mid_fea),
|
279 |
+
LeakyReLU(),
|
280 |
+
)
|
281 |
+
self.conv4 = nn.Conv2d(
|
282 |
+
mid_fea, out_fea, kernel_size=3, padding=1, dilation=1, bias=True
|
283 |
+
)
|
284 |
+
# self.conv5 = nn.Conv2d(out_fea * 3, out_fea, kernel_size=1, padding=0, dilation=1, bias=True)
|
285 |
+
|
286 |
+
def forward(self, x1, x2, x3):
|
287 |
+
_, _, h, w = x1.size()
|
288 |
+
|
289 |
+
edge1_fea = self.conv1(x1)
|
290 |
+
# edge1 = self.conv4(edge1_fea)
|
291 |
+
edge2_fea = self.conv2(x2)
|
292 |
+
edge2 = self.conv4(edge2_fea)
|
293 |
+
edge3_fea = self.conv3(x3)
|
294 |
+
edge3 = self.conv4(edge3_fea)
|
295 |
+
|
296 |
+
edge2_fea = F.interpolate(
|
297 |
+
edge2_fea, size=(h, w), mode="bilinear", align_corners=True
|
298 |
+
)
|
299 |
+
edge3_fea = F.interpolate(
|
300 |
+
edge3_fea, size=(h, w), mode="bilinear", align_corners=True
|
301 |
+
)
|
302 |
+
edge2 = F.interpolate(edge2, size=(h, w), mode="bilinear", align_corners=True)
|
303 |
+
edge3 = F.interpolate(edge3, size=(h, w), mode="bilinear", align_corners=True)
|
304 |
+
|
305 |
+
# edge = torch.cat([edge1, edge2, edge3], dim=1)
|
306 |
+
edge_fea = torch.cat([edge1_fea, edge2_fea, edge3_fea], dim=1)
|
307 |
+
# edge = self.conv5(edge)
|
308 |
+
|
309 |
+
# return edge, edge_fea
|
310 |
+
return edge_fea
|
311 |
+
|
312 |
+
|
313 |
+
class Decoder_Module(nn.Module):
|
314 |
+
"""
|
315 |
+
Parsing Branch Decoder Module.
|
316 |
+
"""
|
317 |
+
|
318 |
+
def __init__(self, num_classes):
|
319 |
+
super(Decoder_Module, self).__init__()
|
320 |
+
self.conv1 = nn.Sequential(
|
321 |
+
nn.Conv2d(512, 256, kernel_size=1, padding=0, dilation=1, bias=False),
|
322 |
+
BatchNorm2d(256),
|
323 |
+
LeakyReLU(),
|
324 |
+
)
|
325 |
+
self.conv2 = nn.Sequential(
|
326 |
+
nn.Conv2d(
|
327 |
+
256, 48, kernel_size=1, stride=1, padding=0, dilation=1, bias=False
|
328 |
+
),
|
329 |
+
BatchNorm2d(48),
|
330 |
+
LeakyReLU(),
|
331 |
+
)
|
332 |
+
self.conv3 = nn.Sequential(
|
333 |
+
nn.Conv2d(304, 256, kernel_size=1, padding=0, dilation=1, bias=False),
|
334 |
+
BatchNorm2d(256),
|
335 |
+
LeakyReLU(),
|
336 |
+
nn.Conv2d(256, 256, kernel_size=1, padding=0, dilation=1, bias=False),
|
337 |
+
BatchNorm2d(256),
|
338 |
+
LeakyReLU(),
|
339 |
+
)
|
340 |
+
|
341 |
+
# self.conv4 = nn.Conv2d(256, num_classes, kernel_size=1, padding=0, dilation=1, bias=True)
|
342 |
+
|
343 |
+
def forward(self, xt, xl):
|
344 |
+
_, _, h, w = xl.size()
|
345 |
+
xt = F.interpolate(
|
346 |
+
self.conv1(xt), size=(h, w), mode="bilinear", align_corners=True
|
347 |
+
)
|
348 |
+
xl = self.conv2(xl)
|
349 |
+
x = torch.cat([xt, xl], dim=1)
|
350 |
+
x = self.conv3(x)
|
351 |
+
# seg = self.conv4(x)
|
352 |
+
# return seg, x
|
353 |
+
return x
|
354 |
+
|
355 |
+
|
356 |
+
class ResNet(nn.Module):
|
357 |
+
def __init__(self, block, layers, num_classes):
|
358 |
+
self.inplanes = 128
|
359 |
+
super(ResNet, self).__init__()
|
360 |
+
self.conv1 = conv3x3(3, 64, stride=2)
|
361 |
+
self.bn1 = BatchNorm2d(64)
|
362 |
+
self.relu1 = nn.ReLU(inplace=False)
|
363 |
+
self.conv2 = conv3x3(64, 64)
|
364 |
+
self.bn2 = BatchNorm2d(64)
|
365 |
+
self.relu2 = nn.ReLU(inplace=False)
|
366 |
+
self.conv3 = conv3x3(64, 128)
|
367 |
+
self.bn3 = BatchNorm2d(128)
|
368 |
+
self.relu3 = nn.ReLU(inplace=False)
|
369 |
+
|
370 |
+
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
371 |
+
|
372 |
+
self.layer1 = self._make_layer(block, 64, layers[0])
|
373 |
+
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
|
374 |
+
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
|
375 |
+
self.layer4 = self._make_layer(
|
376 |
+
block, 512, layers[3], stride=1, dilation=2, multi_grid=(1, 1, 1)
|
377 |
+
)
|
378 |
+
|
379 |
+
self.context_encoding = PSPModule(2048, 512)
|
380 |
+
|
381 |
+
self.edge = Edge_Module()
|
382 |
+
self.decoder = Decoder_Module(num_classes)
|
383 |
+
|
384 |
+
self.fushion = nn.Sequential(
|
385 |
+
nn.Conv2d(1024, 256, kernel_size=1, padding=0, dilation=1, bias=False),
|
386 |
+
BatchNorm2d(256),
|
387 |
+
LeakyReLU(),
|
388 |
+
nn.Dropout2d(0.1),
|
389 |
+
nn.Conv2d(
|
390 |
+
256, num_classes, kernel_size=1, padding=0, dilation=1, bias=True
|
391 |
+
),
|
392 |
+
)
|
393 |
+
|
394 |
+
def _make_layer(self, block, planes, blocks, stride=1, dilation=1, multi_grid=1):
|
395 |
+
downsample = None
|
396 |
+
if stride != 1 or self.inplanes != planes * block.expansion:
|
397 |
+
downsample = nn.Sequential(
|
398 |
+
nn.Conv2d(
|
399 |
+
self.inplanes,
|
400 |
+
planes * block.expansion,
|
401 |
+
kernel_size=1,
|
402 |
+
stride=stride,
|
403 |
+
bias=False,
|
404 |
+
),
|
405 |
+
BatchNorm2d(planes * block.expansion, affine=affine_par),
|
406 |
+
)
|
407 |
+
|
408 |
+
layers = []
|
409 |
+
generate_multi_grid = lambda index, grids: (
|
410 |
+
grids[index % len(grids)] if isinstance(grids, tuple) else 1
|
411 |
+
)
|
412 |
+
layers.append(
|
413 |
+
block(
|
414 |
+
self.inplanes,
|
415 |
+
planes,
|
416 |
+
stride,
|
417 |
+
dilation=dilation,
|
418 |
+
downsample=downsample,
|
419 |
+
multi_grid=generate_multi_grid(0, multi_grid),
|
420 |
+
)
|
421 |
+
)
|
422 |
+
self.inplanes = planes * block.expansion
|
423 |
+
for i in range(1, blocks):
|
424 |
+
layers.append(
|
425 |
+
block(
|
426 |
+
self.inplanes,
|
427 |
+
planes,
|
428 |
+
dilation=dilation,
|
429 |
+
multi_grid=generate_multi_grid(i, multi_grid),
|
430 |
+
)
|
431 |
+
)
|
432 |
+
|
433 |
+
return nn.Sequential(*layers)
|
434 |
+
|
435 |
+
def forward(self, x):
|
436 |
+
x = self.relu1(self.bn1(self.conv1(x)))
|
437 |
+
x = self.relu2(self.bn2(self.conv2(x)))
|
438 |
+
x = self.relu3(self.bn3(self.conv3(x)))
|
439 |
+
x = self.maxpool(x)
|
440 |
+
x2 = self.layer1(x)
|
441 |
+
x3 = self.layer2(x2)
|
442 |
+
x4 = self.layer3(x3)
|
443 |
+
x5 = self.layer4(x4)
|
444 |
+
x = self.context_encoding(x5)
|
445 |
+
# parsing_result, parsing_fea = self.decoder(x, x2)
|
446 |
+
parsing_fea = self.decoder(x, x2)
|
447 |
+
# Edge Branch
|
448 |
+
# edge_result, edge_fea = self.edge(x2, x3, x4)
|
449 |
+
edge_fea = self.edge(x2, x3, x4)
|
450 |
+
# Fusion Branch
|
451 |
+
x = torch.cat([parsing_fea, edge_fea], dim=1)
|
452 |
+
fusion_result = self.fushion(x)
|
453 |
+
# return [[parsing_result, fusion_result], [edge_result]]
|
454 |
+
return fusion_result
|
455 |
+
|
456 |
+
|
457 |
+
def initialize_pretrained_model(
|
458 |
+
model, settings, pretrained="./models/resnet101-imagenet.pth"
|
459 |
+
):
|
460 |
+
model.input_space = settings["input_space"]
|
461 |
+
model.input_size = settings["input_size"]
|
462 |
+
model.input_range = settings["input_range"]
|
463 |
+
model.mean = settings["mean"]
|
464 |
+
model.std = settings["std"]
|
465 |
+
|
466 |
+
if pretrained is not None:
|
467 |
+
saved_state_dict = torch.load(pretrained)
|
468 |
+
new_params = model.state_dict().copy()
|
469 |
+
for i in saved_state_dict:
|
470 |
+
i_parts = i.split(".")
|
471 |
+
if not i_parts[0] == "fc":
|
472 |
+
new_params[".".join(i_parts[0:])] = saved_state_dict[i]
|
473 |
+
model.load_state_dict(new_params)
|
474 |
+
|
475 |
+
|
476 |
+
def resnet101(num_classes=20, pretrained="./models/resnet101-imagenet.pth"):
|
477 |
+
model = ResNet(Bottleneck, [3, 4, 23, 3], num_classes)
|
478 |
+
settings = pretrained_settings["resnet101"]["imagenet"]
|
479 |
+
initialize_pretrained_model(model, settings, pretrained)
|
480 |
+
return model
|
SCHP/networks/__init__.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import absolute_import
|
2 |
+
|
3 |
+
from SCHP.networks.AugmentCE2P import resnet101
|
4 |
+
|
5 |
+
__factory = {
|
6 |
+
"resnet101": resnet101,
|
7 |
+
}
|
8 |
+
|
9 |
+
|
10 |
+
def init_model(name, *args, **kwargs):
|
11 |
+
if name not in __factory.keys():
|
12 |
+
raise KeyError("Unknown model arch: {}".format(name))
|
13 |
+
return __factory[name](*args, **kwargs)
|
SCHP/networks/__pycache__/AugmentCE2P.cpython-310.pyc
ADDED
Binary file (10.4 kB). View file
|
|
SCHP/networks/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (511 Bytes). View file
|
|
SCHP/utils/__pycache__/transforms.cpython-310.pyc
ADDED
Binary file (4.84 kB). View file
|
|
SCHP/utils/transforms.py
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ------------------------------------------------------------------------------
|
2 |
+
# Copyright (c) Microsoft
|
3 |
+
# Licensed under the MIT License.
|
4 |
+
# Written by Bin Xiao ([email protected])
|
5 |
+
# ------------------------------------------------------------------------------
|
6 |
+
|
7 |
+
from __future__ import absolute_import, division, print_function
|
8 |
+
|
9 |
+
import cv2
|
10 |
+
|
11 |
+
import numpy as np
|
12 |
+
import torch
|
13 |
+
|
14 |
+
|
15 |
+
class BRG2Tensor_transform(object):
|
16 |
+
def __call__(self, pic):
|
17 |
+
img = torch.from_numpy(pic.transpose((2, 0, 1)))
|
18 |
+
if isinstance(img, torch.ByteTensor):
|
19 |
+
return img.float()
|
20 |
+
else:
|
21 |
+
return img
|
22 |
+
|
23 |
+
|
24 |
+
class BGR2RGB_transform(object):
|
25 |
+
def __call__(self, tensor):
|
26 |
+
return tensor[[2, 1, 0], :, :]
|
27 |
+
|
28 |
+
|
29 |
+
def flip_back(output_flipped, matched_parts):
|
30 |
+
"""
|
31 |
+
ouput_flipped: numpy.ndarray(batch_size, num_joints, height, width)
|
32 |
+
"""
|
33 |
+
assert (
|
34 |
+
output_flipped.ndim == 4
|
35 |
+
), "output_flipped should be [batch_size, num_joints, height, width]"
|
36 |
+
|
37 |
+
output_flipped = output_flipped[:, :, :, ::-1]
|
38 |
+
|
39 |
+
for pair in matched_parts:
|
40 |
+
tmp = output_flipped[:, pair[0], :, :].copy()
|
41 |
+
output_flipped[:, pair[0], :, :] = output_flipped[:, pair[1], :, :]
|
42 |
+
output_flipped[:, pair[1], :, :] = tmp
|
43 |
+
|
44 |
+
return output_flipped
|
45 |
+
|
46 |
+
|
47 |
+
def fliplr_joints(joints, joints_vis, width, matched_parts):
|
48 |
+
"""
|
49 |
+
flip coords
|
50 |
+
"""
|
51 |
+
# Flip horizontal
|
52 |
+
joints[:, 0] = width - joints[:, 0] - 1
|
53 |
+
|
54 |
+
# Change left-right parts
|
55 |
+
for pair in matched_parts:
|
56 |
+
joints[pair[0], :], joints[pair[1], :] = (
|
57 |
+
joints[pair[1], :],
|
58 |
+
joints[pair[0], :].copy(),
|
59 |
+
)
|
60 |
+
joints_vis[pair[0], :], joints_vis[pair[1], :] = (
|
61 |
+
joints_vis[pair[1], :],
|
62 |
+
joints_vis[pair[0], :].copy(),
|
63 |
+
)
|
64 |
+
|
65 |
+
return joints * joints_vis, joints_vis
|
66 |
+
|
67 |
+
|
68 |
+
def transform_preds(coords, center, scale, input_size):
|
69 |
+
target_coords = np.zeros(coords.shape)
|
70 |
+
trans = get_affine_transform(center, scale, 0, input_size, inv=1)
|
71 |
+
for p in range(coords.shape[0]):
|
72 |
+
target_coords[p, 0:2] = affine_transform(coords[p, 0:2], trans)
|
73 |
+
return target_coords
|
74 |
+
|
75 |
+
|
76 |
+
def transform_parsing(pred, center, scale, width, height, input_size):
|
77 |
+
|
78 |
+
trans = get_affine_transform(center, scale, 0, input_size, inv=1)
|
79 |
+
target_pred = cv2.warpAffine(
|
80 |
+
pred,
|
81 |
+
trans,
|
82 |
+
(int(width), int(height)), # (int(width), int(height)),
|
83 |
+
flags=cv2.INTER_NEAREST,
|
84 |
+
borderMode=cv2.BORDER_CONSTANT,
|
85 |
+
borderValue=(0),
|
86 |
+
)
|
87 |
+
|
88 |
+
return target_pred
|
89 |
+
|
90 |
+
|
91 |
+
def transform_logits(logits, center, scale, width, height, input_size):
|
92 |
+
|
93 |
+
trans = get_affine_transform(center, scale, 0, input_size, inv=1)
|
94 |
+
channel = logits.shape[2]
|
95 |
+
target_logits = []
|
96 |
+
for i in range(channel):
|
97 |
+
target_logit = cv2.warpAffine(
|
98 |
+
logits[:, :, i],
|
99 |
+
trans,
|
100 |
+
(int(width), int(height)), # (int(width), int(height)),
|
101 |
+
flags=cv2.INTER_LINEAR,
|
102 |
+
borderMode=cv2.BORDER_CONSTANT,
|
103 |
+
borderValue=(0),
|
104 |
+
)
|
105 |
+
target_logits.append(target_logit)
|
106 |
+
target_logits = np.stack(target_logits, axis=2)
|
107 |
+
|
108 |
+
return target_logits
|
109 |
+
|
110 |
+
|
111 |
+
def get_affine_transform(
|
112 |
+
center, scale, rot, output_size, shift=np.array([0, 0], dtype=np.float32), inv=0
|
113 |
+
):
|
114 |
+
if not isinstance(scale, np.ndarray) and not isinstance(scale, list):
|
115 |
+
print(scale)
|
116 |
+
scale = np.array([scale, scale])
|
117 |
+
|
118 |
+
scale_tmp = scale
|
119 |
+
|
120 |
+
src_w = scale_tmp[0]
|
121 |
+
dst_w = output_size[1]
|
122 |
+
dst_h = output_size[0]
|
123 |
+
|
124 |
+
rot_rad = np.pi * rot / 180
|
125 |
+
src_dir = get_dir([0, src_w * -0.5], rot_rad)
|
126 |
+
dst_dir = np.array([0, (dst_w - 1) * -0.5], np.float32)
|
127 |
+
|
128 |
+
src = np.zeros((3, 2), dtype=np.float32)
|
129 |
+
dst = np.zeros((3, 2), dtype=np.float32)
|
130 |
+
src[0, :] = center + scale_tmp * shift
|
131 |
+
src[1, :] = center + src_dir + scale_tmp * shift
|
132 |
+
dst[0, :] = [(dst_w - 1) * 0.5, (dst_h - 1) * 0.5]
|
133 |
+
dst[1, :] = np.array([(dst_w - 1) * 0.5, (dst_h - 1) * 0.5]) + dst_dir
|
134 |
+
|
135 |
+
src[2:, :] = get_3rd_point(src[0, :], src[1, :])
|
136 |
+
dst[2:, :] = get_3rd_point(dst[0, :], dst[1, :])
|
137 |
+
|
138 |
+
if inv:
|
139 |
+
trans = cv2.getAffineTransform(np.float32(dst), np.float32(src))
|
140 |
+
else:
|
141 |
+
trans = cv2.getAffineTransform(np.float32(src), np.float32(dst))
|
142 |
+
|
143 |
+
return trans
|
144 |
+
|
145 |
+
|
146 |
+
def affine_transform(pt, t):
|
147 |
+
new_pt = np.array([pt[0], pt[1], 1.0]).T
|
148 |
+
new_pt = np.dot(t, new_pt)
|
149 |
+
return new_pt[:2]
|
150 |
+
|
151 |
+
|
152 |
+
def get_3rd_point(a, b):
|
153 |
+
direct = a - b
|
154 |
+
return b + np.array([-direct[1], direct[0]], dtype=np.float32)
|
155 |
+
|
156 |
+
|
157 |
+
def get_dir(src_point, rot_rad):
|
158 |
+
sn, cs = np.sin(rot_rad), np.cos(rot_rad)
|
159 |
+
|
160 |
+
src_result = [0, 0]
|
161 |
+
src_result[0] = src_point[0] * cs - src_point[1] * sn
|
162 |
+
src_result[1] = src_point[0] * sn + src_point[1] * cs
|
163 |
+
|
164 |
+
return src_result
|
165 |
+
|
166 |
+
|
167 |
+
def crop(img, center, scale, output_size, rot=0):
|
168 |
+
trans = get_affine_transform(center, scale, rot, output_size)
|
169 |
+
|
170 |
+
dst_img = cv2.warpAffine(
|
171 |
+
img, trans, (int(output_size[1]), int(output_size[0])), flags=cv2.INTER_LINEAR
|
172 |
+
)
|
173 |
+
|
174 |
+
return dst_img
|
app.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, File, UploadFile, HTTPException
|
2 |
+
from fastapi.responses import JSONResponse, FileResponse
|
3 |
+
from typing import Optional
|
4 |
+
from PIL import Image
|
5 |
+
import numpy as np
|
6 |
+
import os
|
7 |
+
import uuid
|
8 |
+
import requests
|
9 |
+
from leffa.transform import LeffaTransform
|
10 |
+
from leffa.model import LeffaModel
|
11 |
+
from leffa.inference import LeffaInference
|
12 |
+
from utils.garment_agnostic_mask_predictor import AutoMasker
|
13 |
+
from utils.densepose_predictor import DensePosePredictor
|
14 |
+
from utils.utils import resize_and_center
|
15 |
+
|
16 |
+
# Initialize FastAPI
|
17 |
+
app = FastAPI()
|
18 |
+
|
19 |
+
# Temporary folder to save uploaded and generated images
|
20 |
+
UPLOAD_FOLDER = "./uploads"
|
21 |
+
GENERATED_FOLDER = "./generated"
|
22 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
23 |
+
os.makedirs(GENERATED_FOLDER, exist_ok=True)
|
24 |
+
|
25 |
+
|
26 |
+
class LeffaPredictor:
|
27 |
+
def __init__(self):
|
28 |
+
self.mask_predictor = AutoMasker(
|
29 |
+
densepose_path="./ckpts/densepose",
|
30 |
+
schp_path="./ckpts/schp",
|
31 |
+
)
|
32 |
+
self.densepose_predictor = DensePosePredictor(
|
33 |
+
config_path="./ckpts/densepose/densepose_rcnn_R_50_FPN_s1x.yaml",
|
34 |
+
weights_path="./ckpts/densepose/model_final_162be9.pkl",
|
35 |
+
)
|
36 |
+
vt_model = LeffaModel(
|
37 |
+
pretrained_model_name_or_path="./ckpts/stable-diffusion-inpainting",
|
38 |
+
pretrained_model="./ckpts/virtual_tryon.pth",
|
39 |
+
)
|
40 |
+
self.vt_inference = LeffaInference(model=vt_model)
|
41 |
+
|
42 |
+
def leffa_predict(self, src_image: Image.Image, ref_image: Image.Image, step: int = 50, scale: float = 2.5, seed: int = 42):
|
43 |
+
src_image = resize_and_center(src_image, 768, 1024)
|
44 |
+
ref_image = resize_and_center(ref_image, 768, 1024)
|
45 |
+
|
46 |
+
src_image_array = np.array(src_image)
|
47 |
+
mask = self.mask_predictor(src_image, "upper")["mask"]
|
48 |
+
src_image_seg_array = self.densepose_predictor.predict_seg(src_image_array)
|
49 |
+
densepose = Image.fromarray(src_image_seg_array)
|
50 |
+
|
51 |
+
transform = LeffaTransform()
|
52 |
+
data = {
|
53 |
+
"src_image": [src_image],
|
54 |
+
"ref_image": [ref_image],
|
55 |
+
"mask": [mask],
|
56 |
+
"densepose": [densepose],
|
57 |
+
}
|
58 |
+
data = transform(data)
|
59 |
+
|
60 |
+
output = self.vt_inference(
|
61 |
+
data,
|
62 |
+
num_inference_steps=step,
|
63 |
+
guidance_scale=scale,
|
64 |
+
seed=seed,
|
65 |
+
)
|
66 |
+
return output["generated_image"][0]
|
67 |
+
|
68 |
+
|
69 |
+
# Initialize the predictor
|
70 |
+
leffa_predictor = LeffaPredictor()
|
71 |
+
|
72 |
+
|
73 |
+
@app.post("/predict/")
|
74 |
+
async def predict(
|
75 |
+
src_image: UploadFile = File(...),
|
76 |
+
ref_image: UploadFile = File(...),
|
77 |
+
prompt: str = "Add a beautiful background adapted to the image",
|
78 |
+
step: Optional[int] = 50,
|
79 |
+
scale: Optional[float] = 2.5,
|
80 |
+
seed: Optional[int] = 42,
|
81 |
+
):
|
82 |
+
try:
|
83 |
+
# Save uploaded files
|
84 |
+
src_path = os.path.join(UPLOAD_FOLDER, f"src_{uuid.uuid4().hex}.png")
|
85 |
+
ref_path = os.path.join(UPLOAD_FOLDER, f"ref_{uuid.uuid4().hex}.png")
|
86 |
+
|
87 |
+
with open(src_path, "wb") as src_file:
|
88 |
+
src_file.write(await src_image.read())
|
89 |
+
|
90 |
+
with open(ref_path, "wb") as ref_file:
|
91 |
+
ref_file.write(await ref_image.read())
|
92 |
+
|
93 |
+
# Open images
|
94 |
+
src_img = Image.open(src_path).convert("RGB")
|
95 |
+
ref_img = Image.open(ref_path).convert("RGB")
|
96 |
+
|
97 |
+
# Generate image
|
98 |
+
generated_image = leffa_predictor.leffa_predict(src_img, ref_img, step, scale, seed)
|
99 |
+
|
100 |
+
# Save generated image
|
101 |
+
output_path = os.path.join(GENERATED_FOLDER, f"gen_{uuid.uuid4().hex}.png")
|
102 |
+
generated_image.save(output_path)
|
103 |
+
|
104 |
+
# Process generated image using external API
|
105 |
+
api_key = "5757076ffef10db778e0c1331607ac35e1c8b284"
|
106 |
+
url = "https://image-api.photoroom.com/v2/edit"
|
107 |
+
processed_output_path = os.path.join(GENERATED_FOLDER, f"processed_{uuid.uuid4().hex}.png")
|
108 |
+
|
109 |
+
with open(output_path, "rb") as image_file:
|
110 |
+
files = {
|
111 |
+
"imageFile": image_file,
|
112 |
+
}
|
113 |
+
data = {
|
114 |
+
"referenceBox": "originalImage",
|
115 |
+
"background.prompt": prompt,
|
116 |
+
}
|
117 |
+
headers = {
|
118 |
+
"x-api-key": api_key,
|
119 |
+
}
|
120 |
+
|
121 |
+
# Call the external API
|
122 |
+
response = requests.post(url, headers=headers, files=files, data=data)
|
123 |
+
|
124 |
+
if response.status_code == 200:
|
125 |
+
with open(processed_output_path, "wb") as output_file:
|
126 |
+
output_file.write(response.content)
|
127 |
+
else:
|
128 |
+
raise HTTPException(
|
129 |
+
status_code=response.status_code,
|
130 |
+
detail=f"External API call failed: {response.text}",
|
131 |
+
)
|
132 |
+
|
133 |
+
# Return processed image
|
134 |
+
return FileResponse(processed_output_path, media_type="image/png")
|
135 |
+
|
136 |
+
except Exception as e:
|
137 |
+
raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")
|
138 |
+
|
139 |
+
@app.get("/")
|
140 |
+
def read_root():
|
141 |
+
return {"message": "Welcome to the Leffa Virtual Try-on API"}
|
densepose/__init__.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
from .data.datasets import builtin # just to register data
|
3 |
+
from .converters import builtin as builtin_converters # register converters
|
4 |
+
from .config import (
|
5 |
+
add_densepose_config,
|
6 |
+
add_densepose_head_config,
|
7 |
+
add_hrnet_config,
|
8 |
+
add_dataset_category_config,
|
9 |
+
add_bootstrap_config,
|
10 |
+
load_bootstrap_config,
|
11 |
+
)
|
12 |
+
from .structures import DensePoseDataRelative, DensePoseList, DensePoseTransformData
|
13 |
+
from .evaluation import DensePoseCOCOEvaluator
|
14 |
+
from .modeling.roi_heads import DensePoseROIHeads
|
15 |
+
from .modeling.test_time_augmentation import (
|
16 |
+
DensePoseGeneralizedRCNNWithTTA,
|
17 |
+
DensePoseDatasetMapperTTA,
|
18 |
+
)
|
19 |
+
from .utils.transform import load_from_cfg
|
20 |
+
from .modeling.hrfpn import build_hrfpn_backbone
|
densepose/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (925 Bytes). View file
|
|
densepose/__pycache__/config.cpython-310.pyc
ADDED
Binary file (5.83 kB). View file
|
|
densepose/config.py
ADDED
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding = utf-8 -*-
|
2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
3 |
+
# pyre-ignore-all-errors
|
4 |
+
|
5 |
+
from detectron2.config import CfgNode as CN
|
6 |
+
|
7 |
+
|
8 |
+
def add_dataset_category_config(cfg: CN) -> None:
|
9 |
+
"""
|
10 |
+
Add config for additional category-related dataset options
|
11 |
+
- category whitelisting
|
12 |
+
- category mapping
|
13 |
+
"""
|
14 |
+
_C = cfg
|
15 |
+
_C.DATASETS.CATEGORY_MAPS = CN(new_allowed=True)
|
16 |
+
_C.DATASETS.WHITELISTED_CATEGORIES = CN(new_allowed=True)
|
17 |
+
# class to mesh mapping
|
18 |
+
_C.DATASETS.CLASS_TO_MESH_NAME_MAPPING = CN(new_allowed=True)
|
19 |
+
|
20 |
+
|
21 |
+
def add_evaluation_config(cfg: CN) -> None:
|
22 |
+
_C = cfg
|
23 |
+
_C.DENSEPOSE_EVALUATION = CN()
|
24 |
+
# evaluator type, possible values:
|
25 |
+
# - "iou": evaluator for models that produce iou data
|
26 |
+
# - "cse": evaluator for models that produce cse data
|
27 |
+
_C.DENSEPOSE_EVALUATION.TYPE = "iou"
|
28 |
+
# storage for DensePose results, possible values:
|
29 |
+
# - "none": no explicit storage, all the results are stored in the
|
30 |
+
# dictionary with predictions, memory intensive;
|
31 |
+
# historically the default storage type
|
32 |
+
# - "ram": RAM storage, uses per-process RAM storage, which is
|
33 |
+
# reduced to a single process storage on later stages,
|
34 |
+
# less memory intensive
|
35 |
+
# - "file": file storage, uses per-process file-based storage,
|
36 |
+
# the least memory intensive, but may create bottlenecks
|
37 |
+
# on file system accesses
|
38 |
+
_C.DENSEPOSE_EVALUATION.STORAGE = "none"
|
39 |
+
# minimum threshold for IOU values: the lower its values is,
|
40 |
+
# the more matches are produced (and the higher the AP score)
|
41 |
+
_C.DENSEPOSE_EVALUATION.MIN_IOU_THRESHOLD = 0.5
|
42 |
+
# Non-distributed inference is slower (at inference time) but can avoid RAM OOM
|
43 |
+
_C.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE = True
|
44 |
+
# evaluate mesh alignment based on vertex embeddings, only makes sense in CSE context
|
45 |
+
_C.DENSEPOSE_EVALUATION.EVALUATE_MESH_ALIGNMENT = False
|
46 |
+
# meshes to compute mesh alignment for
|
47 |
+
_C.DENSEPOSE_EVALUATION.MESH_ALIGNMENT_MESH_NAMES = []
|
48 |
+
|
49 |
+
|
50 |
+
def add_bootstrap_config(cfg: CN) -> None:
|
51 |
+
""" """
|
52 |
+
_C = cfg
|
53 |
+
_C.BOOTSTRAP_DATASETS = []
|
54 |
+
_C.BOOTSTRAP_MODEL = CN()
|
55 |
+
_C.BOOTSTRAP_MODEL.WEIGHTS = ""
|
56 |
+
_C.BOOTSTRAP_MODEL.DEVICE = "cuda"
|
57 |
+
|
58 |
+
|
59 |
+
def get_bootstrap_dataset_config() -> CN:
|
60 |
+
_C = CN()
|
61 |
+
_C.DATASET = ""
|
62 |
+
# ratio used to mix data loaders
|
63 |
+
_C.RATIO = 0.1
|
64 |
+
# image loader
|
65 |
+
_C.IMAGE_LOADER = CN(new_allowed=True)
|
66 |
+
_C.IMAGE_LOADER.TYPE = ""
|
67 |
+
_C.IMAGE_LOADER.BATCH_SIZE = 4
|
68 |
+
_C.IMAGE_LOADER.NUM_WORKERS = 4
|
69 |
+
_C.IMAGE_LOADER.CATEGORIES = []
|
70 |
+
_C.IMAGE_LOADER.MAX_COUNT_PER_CATEGORY = 1_000_000
|
71 |
+
_C.IMAGE_LOADER.CATEGORY_TO_CLASS_MAPPING = CN(new_allowed=True)
|
72 |
+
# inference
|
73 |
+
_C.INFERENCE = CN()
|
74 |
+
# batch size for model inputs
|
75 |
+
_C.INFERENCE.INPUT_BATCH_SIZE = 4
|
76 |
+
# batch size to group model outputs
|
77 |
+
_C.INFERENCE.OUTPUT_BATCH_SIZE = 2
|
78 |
+
# sampled data
|
79 |
+
_C.DATA_SAMPLER = CN(new_allowed=True)
|
80 |
+
_C.DATA_SAMPLER.TYPE = ""
|
81 |
+
_C.DATA_SAMPLER.USE_GROUND_TRUTH_CATEGORIES = False
|
82 |
+
# filter
|
83 |
+
_C.FILTER = CN(new_allowed=True)
|
84 |
+
_C.FILTER.TYPE = ""
|
85 |
+
return _C
|
86 |
+
|
87 |
+
|
88 |
+
def load_bootstrap_config(cfg: CN) -> None:
|
89 |
+
"""
|
90 |
+
Bootstrap datasets are given as a list of `dict` that are not automatically
|
91 |
+
converted into CfgNode. This method processes all bootstrap dataset entries
|
92 |
+
and ensures that they are in CfgNode format and comply with the specification
|
93 |
+
"""
|
94 |
+
if not cfg.BOOTSTRAP_DATASETS:
|
95 |
+
return
|
96 |
+
|
97 |
+
bootstrap_datasets_cfgnodes = []
|
98 |
+
for dataset_cfg in cfg.BOOTSTRAP_DATASETS:
|
99 |
+
_C = get_bootstrap_dataset_config().clone()
|
100 |
+
_C.merge_from_other_cfg(CN(dataset_cfg))
|
101 |
+
bootstrap_datasets_cfgnodes.append(_C)
|
102 |
+
cfg.BOOTSTRAP_DATASETS = bootstrap_datasets_cfgnodes
|
103 |
+
|
104 |
+
|
105 |
+
def add_densepose_head_cse_config(cfg: CN) -> None:
|
106 |
+
"""
|
107 |
+
Add configuration options for Continuous Surface Embeddings (CSE)
|
108 |
+
"""
|
109 |
+
_C = cfg
|
110 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE = CN()
|
111 |
+
# Dimensionality D of the embedding space
|
112 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_SIZE = 16
|
113 |
+
# Embedder specifications for various mesh IDs
|
114 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDERS = CN(new_allowed=True)
|
115 |
+
# normalization coefficient for embedding distances
|
116 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDING_DIST_GAUSS_SIGMA = 0.01
|
117 |
+
# normalization coefficient for geodesic distances
|
118 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.GEODESIC_DIST_GAUSS_SIGMA = 0.01
|
119 |
+
# embedding loss weight
|
120 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_LOSS_WEIGHT = 0.6
|
121 |
+
# embedding loss name, currently the following options are supported:
|
122 |
+
# - EmbeddingLoss: cross-entropy on vertex labels
|
123 |
+
# - SoftEmbeddingLoss: cross-entropy on vertex label combined with
|
124 |
+
# Gaussian penalty on distance between vertices
|
125 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBED_LOSS_NAME = "EmbeddingLoss"
|
126 |
+
# optimizer hyperparameters
|
127 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.FEATURES_LR_FACTOR = 1.0
|
128 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.EMBEDDING_LR_FACTOR = 1.0
|
129 |
+
# Shape to shape cycle consistency loss parameters:
|
130 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS = CN({"ENABLED": False})
|
131 |
+
# shape to shape cycle consistency loss weight
|
132 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.WEIGHT = 0.025
|
133 |
+
# norm type used for loss computation
|
134 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.NORM_P = 2
|
135 |
+
# normalization term for embedding similarity matrices
|
136 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.TEMPERATURE = 0.05
|
137 |
+
# maximum number of vertices to include into shape to shape cycle loss
|
138 |
+
# if negative or zero, all vertices are considered
|
139 |
+
# if positive, random subset of vertices of given size is considered
|
140 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.SHAPE_TO_SHAPE_CYCLE_LOSS.MAX_NUM_VERTICES = 4936
|
141 |
+
# Pixel to shape cycle consistency loss parameters:
|
142 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS = CN({"ENABLED": False})
|
143 |
+
# pixel to shape cycle consistency loss weight
|
144 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.WEIGHT = 0.0001
|
145 |
+
# norm type used for loss computation
|
146 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.NORM_P = 2
|
147 |
+
# map images to all meshes and back (if false, use only gt meshes from the batch)
|
148 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.USE_ALL_MESHES_NOT_GT_ONLY = False
|
149 |
+
# Randomly select at most this number of pixels from every instance
|
150 |
+
# if negative or zero, all vertices are considered
|
151 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.NUM_PIXELS_TO_SAMPLE = 100
|
152 |
+
# normalization factor for pixel to pixel distances (higher value = smoother distribution)
|
153 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.PIXEL_SIGMA = 5.0
|
154 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.TEMPERATURE_PIXEL_TO_VERTEX = 0.05
|
155 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CSE.PIX_TO_SHAPE_CYCLE_LOSS.TEMPERATURE_VERTEX_TO_PIXEL = 0.05
|
156 |
+
|
157 |
+
|
158 |
+
def add_densepose_head_config(cfg: CN) -> None:
|
159 |
+
"""
|
160 |
+
Add config for densepose head.
|
161 |
+
"""
|
162 |
+
_C = cfg
|
163 |
+
|
164 |
+
_C.MODEL.DENSEPOSE_ON = True
|
165 |
+
|
166 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD = CN()
|
167 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.NAME = ""
|
168 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.NUM_STACKED_CONVS = 8
|
169 |
+
# Number of parts used for point labels
|
170 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.NUM_PATCHES = 24
|
171 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.DECONV_KERNEL = 4
|
172 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CONV_HEAD_DIM = 512
|
173 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.CONV_HEAD_KERNEL = 3
|
174 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.UP_SCALE = 2
|
175 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.HEATMAP_SIZE = 112
|
176 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.POOLER_TYPE = "ROIAlignV2"
|
177 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.POOLER_RESOLUTION = 28
|
178 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.POOLER_SAMPLING_RATIO = 2
|
179 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.NUM_COARSE_SEGM_CHANNELS = 2 # 15 or 2
|
180 |
+
# Overlap threshold for an RoI to be considered foreground (if >= FG_IOU_THRESHOLD)
|
181 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.FG_IOU_THRESHOLD = 0.7
|
182 |
+
# Loss weights for annotation masks.(14 Parts)
|
183 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.INDEX_WEIGHTS = 5.0
|
184 |
+
# Loss weights for surface parts. (24 Parts)
|
185 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.PART_WEIGHTS = 1.0
|
186 |
+
# Loss weights for UV regression.
|
187 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.POINT_REGRESSION_WEIGHTS = 0.01
|
188 |
+
# Coarse segmentation is trained using instance segmentation task data
|
189 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.COARSE_SEGM_TRAINED_BY_MASKS = False
|
190 |
+
# For Decoder
|
191 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_ON = True
|
192 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_NUM_CLASSES = 256
|
193 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_CONV_DIMS = 256
|
194 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_NORM = ""
|
195 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.DECODER_COMMON_STRIDE = 4
|
196 |
+
# For DeepLab head
|
197 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.DEEPLAB = CN()
|
198 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.DEEPLAB.NORM = "GN"
|
199 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.DEEPLAB.NONLOCAL_ON = 0
|
200 |
+
# Predictor class name, must be registered in DENSEPOSE_PREDICTOR_REGISTRY
|
201 |
+
# Some registered predictors:
|
202 |
+
# "DensePoseChartPredictor": predicts segmentation and UV coordinates for predefined charts
|
203 |
+
# "DensePoseChartWithConfidencePredictor": predicts segmentation, UV coordinates
|
204 |
+
# and associated confidences for predefined charts (default)
|
205 |
+
# "DensePoseEmbeddingWithConfidencePredictor": predicts segmentation, embeddings
|
206 |
+
# and associated confidences for CSE
|
207 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.PREDICTOR_NAME = "DensePoseChartWithConfidencePredictor"
|
208 |
+
# Loss class name, must be registered in DENSEPOSE_LOSS_REGISTRY
|
209 |
+
# Some registered losses:
|
210 |
+
# "DensePoseChartLoss": loss for chart-based models that estimate
|
211 |
+
# segmentation and UV coordinates
|
212 |
+
# "DensePoseChartWithConfidenceLoss": loss for chart-based models that estimate
|
213 |
+
# segmentation, UV coordinates and the corresponding confidences (default)
|
214 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.LOSS_NAME = "DensePoseChartWithConfidenceLoss"
|
215 |
+
# Confidences
|
216 |
+
# Enable learning UV confidences (variances) along with the actual values
|
217 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.UV_CONFIDENCE = CN({"ENABLED": False})
|
218 |
+
# UV confidence lower bound
|
219 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.UV_CONFIDENCE.EPSILON = 0.01
|
220 |
+
# Enable learning segmentation confidences (variances) along with the actual values
|
221 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.SEGM_CONFIDENCE = CN({"ENABLED": False})
|
222 |
+
# Segmentation confidence lower bound
|
223 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.SEGM_CONFIDENCE.EPSILON = 0.01
|
224 |
+
# Statistical model type for confidence learning, possible values:
|
225 |
+
# - "iid_iso": statistically independent identically distributed residuals
|
226 |
+
# with isotropic covariance
|
227 |
+
# - "indep_aniso": statistically independent residuals with anisotropic
|
228 |
+
# covariances
|
229 |
+
_C.MODEL.ROI_DENSEPOSE_HEAD.UV_CONFIDENCE.TYPE = "iid_iso"
|
230 |
+
# List of angles for rotation in data augmentation during training
|
231 |
+
_C.INPUT.ROTATION_ANGLES = [0]
|
232 |
+
_C.TEST.AUG.ROTATION_ANGLES = () # Rotation TTA
|
233 |
+
|
234 |
+
add_densepose_head_cse_config(cfg)
|
235 |
+
|
236 |
+
|
237 |
+
def add_hrnet_config(cfg: CN) -> None:
|
238 |
+
"""
|
239 |
+
Add config for HRNet backbone.
|
240 |
+
"""
|
241 |
+
_C = cfg
|
242 |
+
|
243 |
+
# For HigherHRNet w32
|
244 |
+
_C.MODEL.HRNET = CN()
|
245 |
+
_C.MODEL.HRNET.STEM_INPLANES = 64
|
246 |
+
_C.MODEL.HRNET.STAGE2 = CN()
|
247 |
+
_C.MODEL.HRNET.STAGE2.NUM_MODULES = 1
|
248 |
+
_C.MODEL.HRNET.STAGE2.NUM_BRANCHES = 2
|
249 |
+
_C.MODEL.HRNET.STAGE2.BLOCK = "BASIC"
|
250 |
+
_C.MODEL.HRNET.STAGE2.NUM_BLOCKS = [4, 4]
|
251 |
+
_C.MODEL.HRNET.STAGE2.NUM_CHANNELS = [32, 64]
|
252 |
+
_C.MODEL.HRNET.STAGE2.FUSE_METHOD = "SUM"
|
253 |
+
_C.MODEL.HRNET.STAGE3 = CN()
|
254 |
+
_C.MODEL.HRNET.STAGE3.NUM_MODULES = 4
|
255 |
+
_C.MODEL.HRNET.STAGE3.NUM_BRANCHES = 3
|
256 |
+
_C.MODEL.HRNET.STAGE3.BLOCK = "BASIC"
|
257 |
+
_C.MODEL.HRNET.STAGE3.NUM_BLOCKS = [4, 4, 4]
|
258 |
+
_C.MODEL.HRNET.STAGE3.NUM_CHANNELS = [32, 64, 128]
|
259 |
+
_C.MODEL.HRNET.STAGE3.FUSE_METHOD = "SUM"
|
260 |
+
_C.MODEL.HRNET.STAGE4 = CN()
|
261 |
+
_C.MODEL.HRNET.STAGE4.NUM_MODULES = 3
|
262 |
+
_C.MODEL.HRNET.STAGE4.NUM_BRANCHES = 4
|
263 |
+
_C.MODEL.HRNET.STAGE4.BLOCK = "BASIC"
|
264 |
+
_C.MODEL.HRNET.STAGE4.NUM_BLOCKS = [4, 4, 4, 4]
|
265 |
+
_C.MODEL.HRNET.STAGE4.NUM_CHANNELS = [32, 64, 128, 256]
|
266 |
+
_C.MODEL.HRNET.STAGE4.FUSE_METHOD = "SUM"
|
267 |
+
|
268 |
+
_C.MODEL.HRNET.HRFPN = CN()
|
269 |
+
_C.MODEL.HRNET.HRFPN.OUT_CHANNELS = 256
|
270 |
+
|
271 |
+
|
272 |
+
def add_densepose_config(cfg: CN) -> None:
|
273 |
+
add_densepose_head_config(cfg)
|
274 |
+
add_hrnet_config(cfg)
|
275 |
+
add_bootstrap_config(cfg)
|
276 |
+
add_dataset_category_config(cfg)
|
277 |
+
add_evaluation_config(cfg)
|
densepose/converters/__init__.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from .hflip import HFlipConverter
|
4 |
+
from .to_mask import ToMaskConverter
|
5 |
+
from .to_chart_result import ToChartResultConverter, ToChartResultConverterWithConfidences
|
6 |
+
from .segm_to_mask import (
|
7 |
+
predictor_output_with_fine_and_coarse_segm_to_mask,
|
8 |
+
predictor_output_with_coarse_segm_to_mask,
|
9 |
+
resample_fine_and_coarse_segm_to_bbox,
|
10 |
+
)
|
11 |
+
from .chart_output_to_chart_result import (
|
12 |
+
densepose_chart_predictor_output_to_result,
|
13 |
+
densepose_chart_predictor_output_to_result_with_confidences,
|
14 |
+
)
|
15 |
+
from .chart_output_hflip import densepose_chart_predictor_output_hflip
|
densepose/converters/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (799 Bytes). View file
|
|
densepose/converters/__pycache__/base.cpython-310.pyc
ADDED
Binary file (3.68 kB). View file
|
|
densepose/converters/__pycache__/builtin.cpython-310.pyc
ADDED
Binary file (804 Bytes). View file
|
|
densepose/converters/__pycache__/chart_output_hflip.cpython-310.pyc
ADDED
Binary file (1.95 kB). View file
|
|
densepose/converters/__pycache__/chart_output_to_chart_result.cpython-310.pyc
ADDED
Binary file (6.08 kB). View file
|
|
densepose/converters/__pycache__/hflip.cpython-310.pyc
ADDED
Binary file (1.35 kB). View file
|
|
densepose/converters/__pycache__/segm_to_mask.cpython-310.pyc
ADDED
Binary file (5.79 kB). View file
|
|
densepose/converters/__pycache__/to_chart_result.cpython-310.pyc
ADDED
Binary file (2.68 kB). View file
|
|
densepose/converters/__pycache__/to_mask.cpython-310.pyc
ADDED
Binary file (1.77 kB). View file
|
|
densepose/converters/base.py
ADDED
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from typing import Any, Tuple, Type
|
4 |
+
import torch
|
5 |
+
|
6 |
+
|
7 |
+
class BaseConverter:
|
8 |
+
"""
|
9 |
+
Converter base class to be reused by various converters.
|
10 |
+
Converter allows one to convert data from various source types to a particular
|
11 |
+
destination type. Each source type needs to register its converter. The
|
12 |
+
registration for each source type is valid for all descendants of that type.
|
13 |
+
"""
|
14 |
+
|
15 |
+
@classmethod
|
16 |
+
def register(cls, from_type: Type, converter: Any = None):
|
17 |
+
"""
|
18 |
+
Registers a converter for the specified type.
|
19 |
+
Can be used as a decorator (if converter is None), or called as a method.
|
20 |
+
|
21 |
+
Args:
|
22 |
+
from_type (type): type to register the converter for;
|
23 |
+
all instances of this type will use the same converter
|
24 |
+
converter (callable): converter to be registered for the given
|
25 |
+
type; if None, this method is assumed to be a decorator for the converter
|
26 |
+
"""
|
27 |
+
|
28 |
+
if converter is not None:
|
29 |
+
cls._do_register(from_type, converter)
|
30 |
+
|
31 |
+
def wrapper(converter: Any) -> Any:
|
32 |
+
cls._do_register(from_type, converter)
|
33 |
+
return converter
|
34 |
+
|
35 |
+
return wrapper
|
36 |
+
|
37 |
+
@classmethod
|
38 |
+
def _do_register(cls, from_type: Type, converter: Any):
|
39 |
+
cls.registry[from_type] = converter # pyre-ignore[16]
|
40 |
+
|
41 |
+
@classmethod
|
42 |
+
def _lookup_converter(cls, from_type: Type) -> Any:
|
43 |
+
"""
|
44 |
+
Perform recursive lookup for the given type
|
45 |
+
to find registered converter. If a converter was found for some base
|
46 |
+
class, it gets registered for this class to save on further lookups.
|
47 |
+
|
48 |
+
Args:
|
49 |
+
from_type: type for which to find a converter
|
50 |
+
Return:
|
51 |
+
callable or None - registered converter or None
|
52 |
+
if no suitable entry was found in the registry
|
53 |
+
"""
|
54 |
+
if from_type in cls.registry: # pyre-ignore[16]
|
55 |
+
return cls.registry[from_type]
|
56 |
+
for base in from_type.__bases__:
|
57 |
+
converter = cls._lookup_converter(base)
|
58 |
+
if converter is not None:
|
59 |
+
cls._do_register(from_type, converter)
|
60 |
+
return converter
|
61 |
+
return None
|
62 |
+
|
63 |
+
@classmethod
|
64 |
+
def convert(cls, instance: Any, *args, **kwargs):
|
65 |
+
"""
|
66 |
+
Convert an instance to the destination type using some registered
|
67 |
+
converter. Does recursive lookup for base classes, so there's no need
|
68 |
+
for explicit registration for derived classes.
|
69 |
+
|
70 |
+
Args:
|
71 |
+
instance: source instance to convert to the destination type
|
72 |
+
Return:
|
73 |
+
An instance of the destination type obtained from the source instance
|
74 |
+
Raises KeyError, if no suitable converter found
|
75 |
+
"""
|
76 |
+
instance_type = type(instance)
|
77 |
+
converter = cls._lookup_converter(instance_type)
|
78 |
+
if converter is None:
|
79 |
+
if cls.dst_type is None: # pyre-ignore[16]
|
80 |
+
output_type_str = "itself"
|
81 |
+
else:
|
82 |
+
output_type_str = cls.dst_type
|
83 |
+
raise KeyError(f"Could not find converter from {instance_type} to {output_type_str}")
|
84 |
+
return converter(instance, *args, **kwargs)
|
85 |
+
|
86 |
+
|
87 |
+
IntTupleBox = Tuple[int, int, int, int]
|
88 |
+
|
89 |
+
|
90 |
+
def make_int_box(box: torch.Tensor) -> IntTupleBox:
|
91 |
+
int_box = [0, 0, 0, 0]
|
92 |
+
int_box[0], int_box[1], int_box[2], int_box[3] = tuple(box.long().tolist())
|
93 |
+
return int_box[0], int_box[1], int_box[2], int_box[3]
|
densepose/converters/builtin.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from ..structures import DensePoseChartPredictorOutput, DensePoseEmbeddingPredictorOutput
|
4 |
+
from . import (
|
5 |
+
HFlipConverter,
|
6 |
+
ToChartResultConverter,
|
7 |
+
ToChartResultConverterWithConfidences,
|
8 |
+
ToMaskConverter,
|
9 |
+
densepose_chart_predictor_output_hflip,
|
10 |
+
densepose_chart_predictor_output_to_result,
|
11 |
+
densepose_chart_predictor_output_to_result_with_confidences,
|
12 |
+
predictor_output_with_coarse_segm_to_mask,
|
13 |
+
predictor_output_with_fine_and_coarse_segm_to_mask,
|
14 |
+
)
|
15 |
+
|
16 |
+
ToMaskConverter.register(
|
17 |
+
DensePoseChartPredictorOutput, predictor_output_with_fine_and_coarse_segm_to_mask
|
18 |
+
)
|
19 |
+
ToMaskConverter.register(
|
20 |
+
DensePoseEmbeddingPredictorOutput, predictor_output_with_coarse_segm_to_mask
|
21 |
+
)
|
22 |
+
|
23 |
+
ToChartResultConverter.register(
|
24 |
+
DensePoseChartPredictorOutput, densepose_chart_predictor_output_to_result
|
25 |
+
)
|
26 |
+
|
27 |
+
ToChartResultConverterWithConfidences.register(
|
28 |
+
DensePoseChartPredictorOutput, densepose_chart_predictor_output_to_result_with_confidences
|
29 |
+
)
|
30 |
+
|
31 |
+
HFlipConverter.register(DensePoseChartPredictorOutput, densepose_chart_predictor_output_hflip)
|
densepose/converters/chart_output_hflip.py
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
from dataclasses import fields
|
3 |
+
import torch
|
4 |
+
|
5 |
+
from densepose.structures import DensePoseChartPredictorOutput, DensePoseTransformData
|
6 |
+
|
7 |
+
|
8 |
+
def densepose_chart_predictor_output_hflip(
|
9 |
+
densepose_predictor_output: DensePoseChartPredictorOutput,
|
10 |
+
transform_data: DensePoseTransformData,
|
11 |
+
) -> DensePoseChartPredictorOutput:
|
12 |
+
"""
|
13 |
+
Change to take into account a Horizontal flip.
|
14 |
+
"""
|
15 |
+
if len(densepose_predictor_output) > 0:
|
16 |
+
|
17 |
+
PredictorOutput = type(densepose_predictor_output)
|
18 |
+
output_dict = {}
|
19 |
+
|
20 |
+
for field in fields(densepose_predictor_output):
|
21 |
+
field_value = getattr(densepose_predictor_output, field.name)
|
22 |
+
# flip tensors
|
23 |
+
if isinstance(field_value, torch.Tensor):
|
24 |
+
setattr(densepose_predictor_output, field.name, torch.flip(field_value, [3]))
|
25 |
+
|
26 |
+
densepose_predictor_output = _flip_iuv_semantics_tensor(
|
27 |
+
densepose_predictor_output, transform_data
|
28 |
+
)
|
29 |
+
densepose_predictor_output = _flip_segm_semantics_tensor(
|
30 |
+
densepose_predictor_output, transform_data
|
31 |
+
)
|
32 |
+
|
33 |
+
for field in fields(densepose_predictor_output):
|
34 |
+
output_dict[field.name] = getattr(densepose_predictor_output, field.name)
|
35 |
+
|
36 |
+
return PredictorOutput(**output_dict)
|
37 |
+
else:
|
38 |
+
return densepose_predictor_output
|
39 |
+
|
40 |
+
|
41 |
+
def _flip_iuv_semantics_tensor(
|
42 |
+
densepose_predictor_output: DensePoseChartPredictorOutput,
|
43 |
+
dp_transform_data: DensePoseTransformData,
|
44 |
+
) -> DensePoseChartPredictorOutput:
|
45 |
+
point_label_symmetries = dp_transform_data.point_label_symmetries
|
46 |
+
uv_symmetries = dp_transform_data.uv_symmetries
|
47 |
+
|
48 |
+
N, C, H, W = densepose_predictor_output.u.shape
|
49 |
+
u_loc = (densepose_predictor_output.u[:, 1:, :, :].clamp(0, 1) * 255).long()
|
50 |
+
v_loc = (densepose_predictor_output.v[:, 1:, :, :].clamp(0, 1) * 255).long()
|
51 |
+
Iindex = torch.arange(C - 1, device=densepose_predictor_output.u.device)[
|
52 |
+
None, :, None, None
|
53 |
+
].expand(N, C - 1, H, W)
|
54 |
+
densepose_predictor_output.u[:, 1:, :, :] = uv_symmetries["U_transforms"][Iindex, v_loc, u_loc]
|
55 |
+
densepose_predictor_output.v[:, 1:, :, :] = uv_symmetries["V_transforms"][Iindex, v_loc, u_loc]
|
56 |
+
|
57 |
+
for el in ["fine_segm", "u", "v"]:
|
58 |
+
densepose_predictor_output.__dict__[el] = densepose_predictor_output.__dict__[el][
|
59 |
+
:, point_label_symmetries, :, :
|
60 |
+
]
|
61 |
+
return densepose_predictor_output
|
62 |
+
|
63 |
+
|
64 |
+
def _flip_segm_semantics_tensor(
|
65 |
+
densepose_predictor_output: DensePoseChartPredictorOutput, dp_transform_data
|
66 |
+
):
|
67 |
+
if densepose_predictor_output.coarse_segm.shape[1] > 2:
|
68 |
+
densepose_predictor_output.coarse_segm = densepose_predictor_output.coarse_segm[
|
69 |
+
:, dp_transform_data.mask_label_symmetries, :, :
|
70 |
+
]
|
71 |
+
return densepose_predictor_output
|
densepose/converters/chart_output_to_chart_result.py
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from typing import Dict
|
4 |
+
import torch
|
5 |
+
from torch.nn import functional as F
|
6 |
+
|
7 |
+
from detectron2.structures.boxes import Boxes, BoxMode
|
8 |
+
|
9 |
+
from ..structures import (
|
10 |
+
DensePoseChartPredictorOutput,
|
11 |
+
DensePoseChartResult,
|
12 |
+
DensePoseChartResultWithConfidences,
|
13 |
+
)
|
14 |
+
from . import resample_fine_and_coarse_segm_to_bbox
|
15 |
+
from .base import IntTupleBox, make_int_box
|
16 |
+
|
17 |
+
|
18 |
+
def resample_uv_tensors_to_bbox(
|
19 |
+
u: torch.Tensor,
|
20 |
+
v: torch.Tensor,
|
21 |
+
labels: torch.Tensor,
|
22 |
+
box_xywh_abs: IntTupleBox,
|
23 |
+
) -> torch.Tensor:
|
24 |
+
"""
|
25 |
+
Resamples U and V coordinate estimates for the given bounding box
|
26 |
+
|
27 |
+
Args:
|
28 |
+
u (tensor [1, C, H, W] of float): U coordinates
|
29 |
+
v (tensor [1, C, H, W] of float): V coordinates
|
30 |
+
labels (tensor [H, W] of long): labels obtained by resampling segmentation
|
31 |
+
outputs for the given bounding box
|
32 |
+
box_xywh_abs (tuple of 4 int): bounding box that corresponds to predictor outputs
|
33 |
+
Return:
|
34 |
+
Resampled U and V coordinates - a tensor [2, H, W] of float
|
35 |
+
"""
|
36 |
+
x, y, w, h = box_xywh_abs
|
37 |
+
w = max(int(w), 1)
|
38 |
+
h = max(int(h), 1)
|
39 |
+
u_bbox = F.interpolate(u, (h, w), mode="bilinear", align_corners=False)
|
40 |
+
v_bbox = F.interpolate(v, (h, w), mode="bilinear", align_corners=False)
|
41 |
+
uv = torch.zeros([2, h, w], dtype=torch.float32, device=u.device)
|
42 |
+
for part_id in range(1, u_bbox.size(1)):
|
43 |
+
uv[0][labels == part_id] = u_bbox[0, part_id][labels == part_id]
|
44 |
+
uv[1][labels == part_id] = v_bbox[0, part_id][labels == part_id]
|
45 |
+
return uv
|
46 |
+
|
47 |
+
|
48 |
+
def resample_uv_to_bbox(
|
49 |
+
predictor_output: DensePoseChartPredictorOutput,
|
50 |
+
labels: torch.Tensor,
|
51 |
+
box_xywh_abs: IntTupleBox,
|
52 |
+
) -> torch.Tensor:
|
53 |
+
"""
|
54 |
+
Resamples U and V coordinate estimates for the given bounding box
|
55 |
+
|
56 |
+
Args:
|
57 |
+
predictor_output (DensePoseChartPredictorOutput): DensePose predictor
|
58 |
+
output to be resampled
|
59 |
+
labels (tensor [H, W] of long): labels obtained by resampling segmentation
|
60 |
+
outputs for the given bounding box
|
61 |
+
box_xywh_abs (tuple of 4 int): bounding box that corresponds to predictor outputs
|
62 |
+
Return:
|
63 |
+
Resampled U and V coordinates - a tensor [2, H, W] of float
|
64 |
+
"""
|
65 |
+
return resample_uv_tensors_to_bbox(
|
66 |
+
predictor_output.u,
|
67 |
+
predictor_output.v,
|
68 |
+
labels,
|
69 |
+
box_xywh_abs,
|
70 |
+
)
|
71 |
+
|
72 |
+
|
73 |
+
def densepose_chart_predictor_output_to_result(
|
74 |
+
predictor_output: DensePoseChartPredictorOutput, boxes: Boxes
|
75 |
+
) -> DensePoseChartResult:
|
76 |
+
"""
|
77 |
+
Convert densepose chart predictor outputs to results
|
78 |
+
|
79 |
+
Args:
|
80 |
+
predictor_output (DensePoseChartPredictorOutput): DensePose predictor
|
81 |
+
output to be converted to results, must contain only 1 output
|
82 |
+
boxes (Boxes): bounding box that corresponds to the predictor output,
|
83 |
+
must contain only 1 bounding box
|
84 |
+
Return:
|
85 |
+
DensePose chart-based result (DensePoseChartResult)
|
86 |
+
"""
|
87 |
+
assert len(predictor_output) == 1 and len(boxes) == 1, (
|
88 |
+
f"Predictor output to result conversion can operate only single outputs"
|
89 |
+
f", got {len(predictor_output)} predictor outputs and {len(boxes)} boxes"
|
90 |
+
)
|
91 |
+
|
92 |
+
boxes_xyxy_abs = boxes.tensor.clone()
|
93 |
+
boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
|
94 |
+
box_xywh = make_int_box(boxes_xywh_abs[0])
|
95 |
+
|
96 |
+
labels = resample_fine_and_coarse_segm_to_bbox(predictor_output, box_xywh).squeeze(0)
|
97 |
+
uv = resample_uv_to_bbox(predictor_output, labels, box_xywh)
|
98 |
+
return DensePoseChartResult(labels=labels, uv=uv)
|
99 |
+
|
100 |
+
|
101 |
+
def resample_confidences_to_bbox(
|
102 |
+
predictor_output: DensePoseChartPredictorOutput,
|
103 |
+
labels: torch.Tensor,
|
104 |
+
box_xywh_abs: IntTupleBox,
|
105 |
+
) -> Dict[str, torch.Tensor]:
|
106 |
+
"""
|
107 |
+
Resamples confidences for the given bounding box
|
108 |
+
|
109 |
+
Args:
|
110 |
+
predictor_output (DensePoseChartPredictorOutput): DensePose predictor
|
111 |
+
output to be resampled
|
112 |
+
labels (tensor [H, W] of long): labels obtained by resampling segmentation
|
113 |
+
outputs for the given bounding box
|
114 |
+
box_xywh_abs (tuple of 4 int): bounding box that corresponds to predictor outputs
|
115 |
+
Return:
|
116 |
+
Resampled confidences - a dict of [H, W] tensors of float
|
117 |
+
"""
|
118 |
+
|
119 |
+
x, y, w, h = box_xywh_abs
|
120 |
+
w = max(int(w), 1)
|
121 |
+
h = max(int(h), 1)
|
122 |
+
|
123 |
+
confidence_names = [
|
124 |
+
"sigma_1",
|
125 |
+
"sigma_2",
|
126 |
+
"kappa_u",
|
127 |
+
"kappa_v",
|
128 |
+
"fine_segm_confidence",
|
129 |
+
"coarse_segm_confidence",
|
130 |
+
]
|
131 |
+
confidence_results = {key: None for key in confidence_names}
|
132 |
+
confidence_names = [
|
133 |
+
key for key in confidence_names if getattr(predictor_output, key) is not None
|
134 |
+
]
|
135 |
+
confidence_base = torch.zeros([h, w], dtype=torch.float32, device=predictor_output.u.device)
|
136 |
+
|
137 |
+
# assign data from channels that correspond to the labels
|
138 |
+
for key in confidence_names:
|
139 |
+
resampled_confidence = F.interpolate(
|
140 |
+
getattr(predictor_output, key),
|
141 |
+
(h, w),
|
142 |
+
mode="bilinear",
|
143 |
+
align_corners=False,
|
144 |
+
)
|
145 |
+
result = confidence_base.clone()
|
146 |
+
for part_id in range(1, predictor_output.u.size(1)):
|
147 |
+
if resampled_confidence.size(1) != predictor_output.u.size(1):
|
148 |
+
# confidence is not part-based, don't try to fill it part by part
|
149 |
+
continue
|
150 |
+
result[labels == part_id] = resampled_confidence[0, part_id][labels == part_id]
|
151 |
+
|
152 |
+
if resampled_confidence.size(1) != predictor_output.u.size(1):
|
153 |
+
# confidence is not part-based, fill the data with the first channel
|
154 |
+
# (targeted for segmentation confidences that have only 1 channel)
|
155 |
+
result = resampled_confidence[0, 0]
|
156 |
+
|
157 |
+
confidence_results[key] = result
|
158 |
+
|
159 |
+
return confidence_results # pyre-ignore[7]
|
160 |
+
|
161 |
+
|
162 |
+
def densepose_chart_predictor_output_to_result_with_confidences(
|
163 |
+
predictor_output: DensePoseChartPredictorOutput, boxes: Boxes
|
164 |
+
) -> DensePoseChartResultWithConfidences:
|
165 |
+
"""
|
166 |
+
Convert densepose chart predictor outputs to results
|
167 |
+
|
168 |
+
Args:
|
169 |
+
predictor_output (DensePoseChartPredictorOutput): DensePose predictor
|
170 |
+
output with confidences to be converted to results, must contain only 1 output
|
171 |
+
boxes (Boxes): bounding box that corresponds to the predictor output,
|
172 |
+
must contain only 1 bounding box
|
173 |
+
Return:
|
174 |
+
DensePose chart-based result with confidences (DensePoseChartResultWithConfidences)
|
175 |
+
"""
|
176 |
+
assert len(predictor_output) == 1 and len(boxes) == 1, (
|
177 |
+
f"Predictor output to result conversion can operate only single outputs"
|
178 |
+
f", got {len(predictor_output)} predictor outputs and {len(boxes)} boxes"
|
179 |
+
)
|
180 |
+
|
181 |
+
boxes_xyxy_abs = boxes.tensor.clone()
|
182 |
+
boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
|
183 |
+
box_xywh = make_int_box(boxes_xywh_abs[0])
|
184 |
+
|
185 |
+
labels = resample_fine_and_coarse_segm_to_bbox(predictor_output, box_xywh).squeeze(0)
|
186 |
+
uv = resample_uv_to_bbox(predictor_output, labels, box_xywh)
|
187 |
+
confidences = resample_confidences_to_bbox(predictor_output, labels, box_xywh)
|
188 |
+
return DensePoseChartResultWithConfidences(labels=labels, uv=uv, **confidences)
|
densepose/converters/hflip.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from typing import Any
|
4 |
+
|
5 |
+
from .base import BaseConverter
|
6 |
+
|
7 |
+
|
8 |
+
class HFlipConverter(BaseConverter):
|
9 |
+
"""
|
10 |
+
Converts various DensePose predictor outputs to DensePose results.
|
11 |
+
Each DensePose predictor output type has to register its convertion strategy.
|
12 |
+
"""
|
13 |
+
|
14 |
+
registry = {}
|
15 |
+
dst_type = None
|
16 |
+
|
17 |
+
@classmethod
|
18 |
+
# pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
|
19 |
+
# inconsistently.
|
20 |
+
def convert(cls, predictor_outputs: Any, transform_data: Any, *args, **kwargs):
|
21 |
+
"""
|
22 |
+
Performs an horizontal flip on DensePose predictor outputs.
|
23 |
+
Does recursive lookup for base classes, so there's no need
|
24 |
+
for explicit registration for derived classes.
|
25 |
+
|
26 |
+
Args:
|
27 |
+
predictor_outputs: DensePose predictor output to be converted to BitMasks
|
28 |
+
transform_data: Anything useful for the flip
|
29 |
+
Return:
|
30 |
+
An instance of the same type as predictor_outputs
|
31 |
+
"""
|
32 |
+
return super(HFlipConverter, cls).convert(
|
33 |
+
predictor_outputs, transform_data, *args, **kwargs
|
34 |
+
)
|
densepose/converters/segm_to_mask.py
ADDED
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from typing import Any
|
4 |
+
import torch
|
5 |
+
from torch.nn import functional as F
|
6 |
+
|
7 |
+
from detectron2.structures import BitMasks, Boxes, BoxMode
|
8 |
+
|
9 |
+
from .base import IntTupleBox, make_int_box
|
10 |
+
from .to_mask import ImageSizeType
|
11 |
+
|
12 |
+
|
13 |
+
def resample_coarse_segm_tensor_to_bbox(coarse_segm: torch.Tensor, box_xywh_abs: IntTupleBox):
|
14 |
+
"""
|
15 |
+
Resample coarse segmentation tensor to the given
|
16 |
+
bounding box and derive labels for each pixel of the bounding box
|
17 |
+
|
18 |
+
Args:
|
19 |
+
coarse_segm: float tensor of shape [1, K, Hout, Wout]
|
20 |
+
box_xywh_abs (tuple of 4 int): bounding box given by its upper-left
|
21 |
+
corner coordinates, width (W) and height (H)
|
22 |
+
Return:
|
23 |
+
Labels for each pixel of the bounding box, a long tensor of size [1, H, W]
|
24 |
+
"""
|
25 |
+
x, y, w, h = box_xywh_abs
|
26 |
+
w = max(int(w), 1)
|
27 |
+
h = max(int(h), 1)
|
28 |
+
labels = F.interpolate(coarse_segm, (h, w), mode="bilinear", align_corners=False).argmax(dim=1)
|
29 |
+
return labels
|
30 |
+
|
31 |
+
|
32 |
+
def resample_fine_and_coarse_segm_tensors_to_bbox(
|
33 |
+
fine_segm: torch.Tensor, coarse_segm: torch.Tensor, box_xywh_abs: IntTupleBox
|
34 |
+
):
|
35 |
+
"""
|
36 |
+
Resample fine and coarse segmentation tensors to the given
|
37 |
+
bounding box and derive labels for each pixel of the bounding box
|
38 |
+
|
39 |
+
Args:
|
40 |
+
fine_segm: float tensor of shape [1, C, Hout, Wout]
|
41 |
+
coarse_segm: float tensor of shape [1, K, Hout, Wout]
|
42 |
+
box_xywh_abs (tuple of 4 int): bounding box given by its upper-left
|
43 |
+
corner coordinates, width (W) and height (H)
|
44 |
+
Return:
|
45 |
+
Labels for each pixel of the bounding box, a long tensor of size [1, H, W]
|
46 |
+
"""
|
47 |
+
x, y, w, h = box_xywh_abs
|
48 |
+
w = max(int(w), 1)
|
49 |
+
h = max(int(h), 1)
|
50 |
+
# coarse segmentation
|
51 |
+
coarse_segm_bbox = F.interpolate(
|
52 |
+
coarse_segm,
|
53 |
+
(h, w),
|
54 |
+
mode="bilinear",
|
55 |
+
align_corners=False,
|
56 |
+
).argmax(dim=1)
|
57 |
+
# combined coarse and fine segmentation
|
58 |
+
labels = (
|
59 |
+
F.interpolate(fine_segm, (h, w), mode="bilinear", align_corners=False).argmax(dim=1)
|
60 |
+
* (coarse_segm_bbox > 0).long()
|
61 |
+
)
|
62 |
+
return labels
|
63 |
+
|
64 |
+
|
65 |
+
def resample_fine_and_coarse_segm_to_bbox(predictor_output: Any, box_xywh_abs: IntTupleBox):
|
66 |
+
"""
|
67 |
+
Resample fine and coarse segmentation outputs from a predictor to the given
|
68 |
+
bounding box and derive labels for each pixel of the bounding box
|
69 |
+
|
70 |
+
Args:
|
71 |
+
predictor_output: DensePose predictor output that contains segmentation
|
72 |
+
results to be resampled
|
73 |
+
box_xywh_abs (tuple of 4 int): bounding box given by its upper-left
|
74 |
+
corner coordinates, width (W) and height (H)
|
75 |
+
Return:
|
76 |
+
Labels for each pixel of the bounding box, a long tensor of size [1, H, W]
|
77 |
+
"""
|
78 |
+
return resample_fine_and_coarse_segm_tensors_to_bbox(
|
79 |
+
predictor_output.fine_segm,
|
80 |
+
predictor_output.coarse_segm,
|
81 |
+
box_xywh_abs,
|
82 |
+
)
|
83 |
+
|
84 |
+
|
85 |
+
def predictor_output_with_coarse_segm_to_mask(
|
86 |
+
predictor_output: Any, boxes: Boxes, image_size_hw: ImageSizeType
|
87 |
+
) -> BitMasks:
|
88 |
+
"""
|
89 |
+
Convert predictor output with coarse and fine segmentation to a mask.
|
90 |
+
Assumes that predictor output has the following attributes:
|
91 |
+
- coarse_segm (tensor of size [N, D, H, W]): coarse segmentation
|
92 |
+
unnormalized scores for N instances; D is the number of coarse
|
93 |
+
segmentation labels, H and W is the resolution of the estimate
|
94 |
+
|
95 |
+
Args:
|
96 |
+
predictor_output: DensePose predictor output to be converted to mask
|
97 |
+
boxes (Boxes): bounding boxes that correspond to the DensePose
|
98 |
+
predictor outputs
|
99 |
+
image_size_hw (tuple [int, int]): image height Himg and width Wimg
|
100 |
+
Return:
|
101 |
+
BitMasks that contain a bool tensor of size [N, Himg, Wimg] with
|
102 |
+
a mask of the size of the image for each instance
|
103 |
+
"""
|
104 |
+
H, W = image_size_hw
|
105 |
+
boxes_xyxy_abs = boxes.tensor.clone()
|
106 |
+
boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
|
107 |
+
N = len(boxes_xywh_abs)
|
108 |
+
masks = torch.zeros((N, H, W), dtype=torch.bool, device=boxes.tensor.device)
|
109 |
+
for i in range(len(boxes_xywh_abs)):
|
110 |
+
box_xywh = make_int_box(boxes_xywh_abs[i])
|
111 |
+
box_mask = resample_coarse_segm_tensor_to_bbox(predictor_output[i].coarse_segm, box_xywh)
|
112 |
+
x, y, w, h = box_xywh
|
113 |
+
masks[i, y : y + h, x : x + w] = box_mask
|
114 |
+
|
115 |
+
return BitMasks(masks)
|
116 |
+
|
117 |
+
|
118 |
+
def predictor_output_with_fine_and_coarse_segm_to_mask(
|
119 |
+
predictor_output: Any, boxes: Boxes, image_size_hw: ImageSizeType
|
120 |
+
) -> BitMasks:
|
121 |
+
"""
|
122 |
+
Convert predictor output with coarse and fine segmentation to a mask.
|
123 |
+
Assumes that predictor output has the following attributes:
|
124 |
+
- coarse_segm (tensor of size [N, D, H, W]): coarse segmentation
|
125 |
+
unnormalized scores for N instances; D is the number of coarse
|
126 |
+
segmentation labels, H and W is the resolution of the estimate
|
127 |
+
- fine_segm (tensor of size [N, C, H, W]): fine segmentation
|
128 |
+
unnormalized scores for N instances; C is the number of fine
|
129 |
+
segmentation labels, H and W is the resolution of the estimate
|
130 |
+
|
131 |
+
Args:
|
132 |
+
predictor_output: DensePose predictor output to be converted to mask
|
133 |
+
boxes (Boxes): bounding boxes that correspond to the DensePose
|
134 |
+
predictor outputs
|
135 |
+
image_size_hw (tuple [int, int]): image height Himg and width Wimg
|
136 |
+
Return:
|
137 |
+
BitMasks that contain a bool tensor of size [N, Himg, Wimg] with
|
138 |
+
a mask of the size of the image for each instance
|
139 |
+
"""
|
140 |
+
H, W = image_size_hw
|
141 |
+
boxes_xyxy_abs = boxes.tensor.clone()
|
142 |
+
boxes_xywh_abs = BoxMode.convert(boxes_xyxy_abs, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
|
143 |
+
N = len(boxes_xywh_abs)
|
144 |
+
masks = torch.zeros((N, H, W), dtype=torch.bool, device=boxes.tensor.device)
|
145 |
+
for i in range(len(boxes_xywh_abs)):
|
146 |
+
box_xywh = make_int_box(boxes_xywh_abs[i])
|
147 |
+
labels_i = resample_fine_and_coarse_segm_to_bbox(predictor_output[i], box_xywh)
|
148 |
+
x, y, w, h = box_xywh
|
149 |
+
masks[i, y : y + h, x : x + w] = labels_i > 0
|
150 |
+
return BitMasks(masks)
|
densepose/converters/to_chart_result.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from typing import Any
|
4 |
+
|
5 |
+
from detectron2.structures import Boxes
|
6 |
+
|
7 |
+
from ..structures import DensePoseChartResult, DensePoseChartResultWithConfidences
|
8 |
+
from .base import BaseConverter
|
9 |
+
|
10 |
+
|
11 |
+
class ToChartResultConverter(BaseConverter):
|
12 |
+
"""
|
13 |
+
Converts various DensePose predictor outputs to DensePose results.
|
14 |
+
Each DensePose predictor output type has to register its convertion strategy.
|
15 |
+
"""
|
16 |
+
|
17 |
+
registry = {}
|
18 |
+
dst_type = DensePoseChartResult
|
19 |
+
|
20 |
+
@classmethod
|
21 |
+
# pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
|
22 |
+
# inconsistently.
|
23 |
+
def convert(cls, predictor_outputs: Any, boxes: Boxes, *args, **kwargs) -> DensePoseChartResult:
|
24 |
+
"""
|
25 |
+
Convert DensePose predictor outputs to DensePoseResult using some registered
|
26 |
+
converter. Does recursive lookup for base classes, so there's no need
|
27 |
+
for explicit registration for derived classes.
|
28 |
+
|
29 |
+
Args:
|
30 |
+
densepose_predictor_outputs: DensePose predictor output to be
|
31 |
+
converted to BitMasks
|
32 |
+
boxes (Boxes): bounding boxes that correspond to the DensePose
|
33 |
+
predictor outputs
|
34 |
+
Return:
|
35 |
+
An instance of DensePoseResult. If no suitable converter was found, raises KeyError
|
36 |
+
"""
|
37 |
+
return super(ToChartResultConverter, cls).convert(predictor_outputs, boxes, *args, **kwargs)
|
38 |
+
|
39 |
+
|
40 |
+
class ToChartResultConverterWithConfidences(BaseConverter):
|
41 |
+
"""
|
42 |
+
Converts various DensePose predictor outputs to DensePose results.
|
43 |
+
Each DensePose predictor output type has to register its convertion strategy.
|
44 |
+
"""
|
45 |
+
|
46 |
+
registry = {}
|
47 |
+
dst_type = DensePoseChartResultWithConfidences
|
48 |
+
|
49 |
+
@classmethod
|
50 |
+
# pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
|
51 |
+
# inconsistently.
|
52 |
+
def convert(
|
53 |
+
cls, predictor_outputs: Any, boxes: Boxes, *args, **kwargs
|
54 |
+
) -> DensePoseChartResultWithConfidences:
|
55 |
+
"""
|
56 |
+
Convert DensePose predictor outputs to DensePoseResult with confidences
|
57 |
+
using some registered converter. Does recursive lookup for base classes,
|
58 |
+
so there's no need for explicit registration for derived classes.
|
59 |
+
|
60 |
+
Args:
|
61 |
+
densepose_predictor_outputs: DensePose predictor output with confidences
|
62 |
+
to be converted to BitMasks
|
63 |
+
boxes (Boxes): bounding boxes that correspond to the DensePose
|
64 |
+
predictor outputs
|
65 |
+
Return:
|
66 |
+
An instance of DensePoseResult. If no suitable converter was found, raises KeyError
|
67 |
+
"""
|
68 |
+
return super(ToChartResultConverterWithConfidences, cls).convert(
|
69 |
+
predictor_outputs, boxes, *args, **kwargs
|
70 |
+
)
|
densepose/converters/to_mask.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from typing import Any, Tuple
|
4 |
+
|
5 |
+
from detectron2.structures import BitMasks, Boxes
|
6 |
+
|
7 |
+
from .base import BaseConverter
|
8 |
+
|
9 |
+
ImageSizeType = Tuple[int, int]
|
10 |
+
|
11 |
+
|
12 |
+
class ToMaskConverter(BaseConverter):
|
13 |
+
"""
|
14 |
+
Converts various DensePose predictor outputs to masks
|
15 |
+
in bit mask format (see `BitMasks`). Each DensePose predictor output type
|
16 |
+
has to register its convertion strategy.
|
17 |
+
"""
|
18 |
+
|
19 |
+
registry = {}
|
20 |
+
dst_type = BitMasks
|
21 |
+
|
22 |
+
@classmethod
|
23 |
+
# pyre-fixme[14]: `convert` overrides method defined in `BaseConverter`
|
24 |
+
# inconsistently.
|
25 |
+
def convert(
|
26 |
+
cls,
|
27 |
+
densepose_predictor_outputs: Any,
|
28 |
+
boxes: Boxes,
|
29 |
+
image_size_hw: ImageSizeType,
|
30 |
+
*args,
|
31 |
+
**kwargs
|
32 |
+
) -> BitMasks:
|
33 |
+
"""
|
34 |
+
Convert DensePose predictor outputs to BitMasks using some registered
|
35 |
+
converter. Does recursive lookup for base classes, so there's no need
|
36 |
+
for explicit registration for derived classes.
|
37 |
+
|
38 |
+
Args:
|
39 |
+
densepose_predictor_outputs: DensePose predictor output to be
|
40 |
+
converted to BitMasks
|
41 |
+
boxes (Boxes): bounding boxes that correspond to the DensePose
|
42 |
+
predictor outputs
|
43 |
+
image_size_hw (tuple [int, int]): image height and width
|
44 |
+
Return:
|
45 |
+
An instance of `BitMasks`. If no suitable converter was found, raises KeyError
|
46 |
+
"""
|
47 |
+
return super(ToMaskConverter, cls).convert(
|
48 |
+
densepose_predictor_outputs, boxes, image_size_hw, *args, **kwargs
|
49 |
+
)
|
densepose/data/__init__.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from .meshes import builtin
|
4 |
+
from .build import (
|
5 |
+
build_detection_test_loader,
|
6 |
+
build_detection_train_loader,
|
7 |
+
build_combined_loader,
|
8 |
+
build_frame_selector,
|
9 |
+
build_inference_based_loaders,
|
10 |
+
has_inference_based_loaders,
|
11 |
+
BootstrapDatasetFactoryCatalog,
|
12 |
+
)
|
13 |
+
from .combined_loader import CombinedDataLoader
|
14 |
+
from .dataset_mapper import DatasetMapper
|
15 |
+
from .inference_based_loader import InferenceBasedLoader, ScoreBasedFilter
|
16 |
+
from .image_list_dataset import ImageListDataset
|
17 |
+
from .utils import is_relative_local_path, maybe_prepend_base_path
|
18 |
+
|
19 |
+
# ensure the builtin datasets are registered
|
20 |
+
from . import datasets
|
21 |
+
|
22 |
+
# ensure the bootstrap datasets builders are registered
|
23 |
+
from . import build
|
24 |
+
|
25 |
+
__all__ = [k for k in globals().keys() if not k.startswith("_")]
|
densepose/data/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (1.05 kB). View file
|
|
densepose/data/__pycache__/build.cpython-310.pyc
ADDED
Binary file (23.8 kB). View file
|
|
densepose/data/__pycache__/combined_loader.cpython-310.pyc
ADDED
Binary file (1.84 kB). View file
|
|
densepose/data/__pycache__/dataset_mapper.cpython-310.pyc
ADDED
Binary file (5.44 kB). View file
|
|
densepose/data/__pycache__/image_list_dataset.cpython-310.pyc
ADDED
Binary file (2.67 kB). View file
|
|
densepose/data/__pycache__/inference_based_loader.cpython-310.pyc
ADDED
Binary file (5.87 kB). View file
|
|
densepose/data/__pycache__/utils.cpython-310.pyc
ADDED
Binary file (1.61 kB). View file
|
|
densepose/data/build.py
ADDED
@@ -0,0 +1,736 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
import itertools
|
4 |
+
import logging
|
5 |
+
import numpy as np
|
6 |
+
from collections import UserDict, defaultdict
|
7 |
+
from dataclasses import dataclass
|
8 |
+
from typing import Any, Callable, Collection, Dict, Iterable, List, Optional, Sequence, Tuple
|
9 |
+
import torch
|
10 |
+
from torch.utils.data.dataset import Dataset
|
11 |
+
|
12 |
+
from detectron2.config import CfgNode
|
13 |
+
from detectron2.data.build import build_detection_test_loader as d2_build_detection_test_loader
|
14 |
+
from detectron2.data.build import build_detection_train_loader as d2_build_detection_train_loader
|
15 |
+
from detectron2.data.build import (
|
16 |
+
load_proposals_into_dataset,
|
17 |
+
print_instances_class_histogram,
|
18 |
+
trivial_batch_collator,
|
19 |
+
worker_init_reset_seed,
|
20 |
+
)
|
21 |
+
from detectron2.data.catalog import DatasetCatalog, Metadata, MetadataCatalog
|
22 |
+
from detectron2.data.samplers import TrainingSampler
|
23 |
+
from detectron2.utils.comm import get_world_size
|
24 |
+
|
25 |
+
from densepose.config import get_bootstrap_dataset_config
|
26 |
+
from densepose.modeling import build_densepose_embedder
|
27 |
+
|
28 |
+
from .combined_loader import CombinedDataLoader, Loader
|
29 |
+
from .dataset_mapper import DatasetMapper
|
30 |
+
from .datasets.coco import DENSEPOSE_CSE_KEYS_WITHOUT_MASK, DENSEPOSE_IUV_KEYS_WITHOUT_MASK
|
31 |
+
from .datasets.dataset_type import DatasetType
|
32 |
+
from .inference_based_loader import InferenceBasedLoader, ScoreBasedFilter
|
33 |
+
from .samplers import (
|
34 |
+
DensePoseConfidenceBasedSampler,
|
35 |
+
DensePoseCSEConfidenceBasedSampler,
|
36 |
+
DensePoseCSEUniformSampler,
|
37 |
+
DensePoseUniformSampler,
|
38 |
+
MaskFromDensePoseSampler,
|
39 |
+
PredictionToGroundTruthSampler,
|
40 |
+
)
|
41 |
+
from .transform import ImageResizeTransform
|
42 |
+
from .utils import get_category_to_class_mapping, get_class_to_mesh_name_mapping
|
43 |
+
from .video import (
|
44 |
+
FirstKFramesSelector,
|
45 |
+
FrameSelectionStrategy,
|
46 |
+
LastKFramesSelector,
|
47 |
+
RandomKFramesSelector,
|
48 |
+
VideoKeyframeDataset,
|
49 |
+
video_list_from_file,
|
50 |
+
)
|
51 |
+
|
52 |
+
__all__ = ["build_detection_train_loader", "build_detection_test_loader"]
|
53 |
+
|
54 |
+
|
55 |
+
Instance = Dict[str, Any]
|
56 |
+
InstancePredicate = Callable[[Instance], bool]
|
57 |
+
|
58 |
+
|
59 |
+
def _compute_num_images_per_worker(cfg: CfgNode) -> int:
|
60 |
+
num_workers = get_world_size()
|
61 |
+
images_per_batch = cfg.SOLVER.IMS_PER_BATCH
|
62 |
+
assert (
|
63 |
+
images_per_batch % num_workers == 0
|
64 |
+
), "SOLVER.IMS_PER_BATCH ({}) must be divisible by the number of workers ({}).".format(
|
65 |
+
images_per_batch, num_workers
|
66 |
+
)
|
67 |
+
assert (
|
68 |
+
images_per_batch >= num_workers
|
69 |
+
), "SOLVER.IMS_PER_BATCH ({}) must be larger than the number of workers ({}).".format(
|
70 |
+
images_per_batch, num_workers
|
71 |
+
)
|
72 |
+
images_per_worker = images_per_batch // num_workers
|
73 |
+
return images_per_worker
|
74 |
+
|
75 |
+
|
76 |
+
def _map_category_id_to_contiguous_id(dataset_name: str, dataset_dicts: Iterable[Instance]) -> None:
|
77 |
+
meta = MetadataCatalog.get(dataset_name)
|
78 |
+
for dataset_dict in dataset_dicts:
|
79 |
+
for ann in dataset_dict["annotations"]:
|
80 |
+
ann["category_id"] = meta.thing_dataset_id_to_contiguous_id[ann["category_id"]]
|
81 |
+
|
82 |
+
|
83 |
+
@dataclass
|
84 |
+
class _DatasetCategory:
|
85 |
+
"""
|
86 |
+
Class representing category data in a dataset:
|
87 |
+
- id: category ID, as specified in the dataset annotations file
|
88 |
+
- name: category name, as specified in the dataset annotations file
|
89 |
+
- mapped_id: category ID after applying category maps (DATASETS.CATEGORY_MAPS config option)
|
90 |
+
- mapped_name: category name after applying category maps
|
91 |
+
- dataset_name: dataset in which the category is defined
|
92 |
+
|
93 |
+
For example, when training models in a class-agnostic manner, one could take LVIS 1.0
|
94 |
+
dataset and map the animal categories to the same category as human data from COCO:
|
95 |
+
id = 225
|
96 |
+
name = "cat"
|
97 |
+
mapped_id = 1
|
98 |
+
mapped_name = "person"
|
99 |
+
dataset_name = "lvis_v1_animals_dp_train"
|
100 |
+
"""
|
101 |
+
|
102 |
+
id: int
|
103 |
+
name: str
|
104 |
+
mapped_id: int
|
105 |
+
mapped_name: str
|
106 |
+
dataset_name: str
|
107 |
+
|
108 |
+
|
109 |
+
_MergedCategoriesT = Dict[int, List[_DatasetCategory]]
|
110 |
+
|
111 |
+
|
112 |
+
def _add_category_id_to_contiguous_id_maps_to_metadata(
|
113 |
+
merged_categories: _MergedCategoriesT,
|
114 |
+
) -> None:
|
115 |
+
merged_categories_per_dataset = {}
|
116 |
+
for contiguous_cat_id, cat_id in enumerate(sorted(merged_categories.keys())):
|
117 |
+
for cat in merged_categories[cat_id]:
|
118 |
+
if cat.dataset_name not in merged_categories_per_dataset:
|
119 |
+
merged_categories_per_dataset[cat.dataset_name] = defaultdict(list)
|
120 |
+
merged_categories_per_dataset[cat.dataset_name][cat_id].append(
|
121 |
+
(
|
122 |
+
contiguous_cat_id,
|
123 |
+
cat,
|
124 |
+
)
|
125 |
+
)
|
126 |
+
|
127 |
+
logger = logging.getLogger(__name__)
|
128 |
+
for dataset_name, merged_categories in merged_categories_per_dataset.items():
|
129 |
+
meta = MetadataCatalog.get(dataset_name)
|
130 |
+
if not hasattr(meta, "thing_classes"):
|
131 |
+
meta.thing_classes = []
|
132 |
+
meta.thing_dataset_id_to_contiguous_id = {}
|
133 |
+
meta.thing_dataset_id_to_merged_id = {}
|
134 |
+
else:
|
135 |
+
meta.thing_classes.clear()
|
136 |
+
meta.thing_dataset_id_to_contiguous_id.clear()
|
137 |
+
meta.thing_dataset_id_to_merged_id.clear()
|
138 |
+
logger.info(f"Dataset {dataset_name}: category ID to contiguous ID mapping:")
|
139 |
+
for _cat_id, categories in sorted(merged_categories.items()):
|
140 |
+
added_to_thing_classes = False
|
141 |
+
for contiguous_cat_id, cat in categories:
|
142 |
+
if not added_to_thing_classes:
|
143 |
+
meta.thing_classes.append(cat.mapped_name)
|
144 |
+
added_to_thing_classes = True
|
145 |
+
meta.thing_dataset_id_to_contiguous_id[cat.id] = contiguous_cat_id
|
146 |
+
meta.thing_dataset_id_to_merged_id[cat.id] = cat.mapped_id
|
147 |
+
logger.info(f"{cat.id} ({cat.name}) -> {contiguous_cat_id}")
|
148 |
+
|
149 |
+
|
150 |
+
def _maybe_create_general_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
|
151 |
+
def has_annotations(instance: Instance) -> bool:
|
152 |
+
return "annotations" in instance
|
153 |
+
|
154 |
+
def has_only_crowd_anotations(instance: Instance) -> bool:
|
155 |
+
for ann in instance["annotations"]:
|
156 |
+
if ann.get("is_crowd", 0) == 0:
|
157 |
+
return False
|
158 |
+
return True
|
159 |
+
|
160 |
+
def general_keep_instance_predicate(instance: Instance) -> bool:
|
161 |
+
return has_annotations(instance) and not has_only_crowd_anotations(instance)
|
162 |
+
|
163 |
+
if not cfg.DATALOADER.FILTER_EMPTY_ANNOTATIONS:
|
164 |
+
return None
|
165 |
+
return general_keep_instance_predicate
|
166 |
+
|
167 |
+
|
168 |
+
def _maybe_create_keypoints_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
|
169 |
+
|
170 |
+
min_num_keypoints = cfg.MODEL.ROI_KEYPOINT_HEAD.MIN_KEYPOINTS_PER_IMAGE
|
171 |
+
|
172 |
+
def has_sufficient_num_keypoints(instance: Instance) -> bool:
|
173 |
+
num_kpts = sum(
|
174 |
+
(np.array(ann["keypoints"][2::3]) > 0).sum()
|
175 |
+
for ann in instance["annotations"]
|
176 |
+
if "keypoints" in ann
|
177 |
+
)
|
178 |
+
return num_kpts >= min_num_keypoints
|
179 |
+
|
180 |
+
if cfg.MODEL.KEYPOINT_ON and (min_num_keypoints > 0):
|
181 |
+
return has_sufficient_num_keypoints
|
182 |
+
return None
|
183 |
+
|
184 |
+
|
185 |
+
def _maybe_create_mask_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
|
186 |
+
if not cfg.MODEL.MASK_ON:
|
187 |
+
return None
|
188 |
+
|
189 |
+
def has_mask_annotations(instance: Instance) -> bool:
|
190 |
+
return any("segmentation" in ann for ann in instance["annotations"])
|
191 |
+
|
192 |
+
return has_mask_annotations
|
193 |
+
|
194 |
+
|
195 |
+
def _maybe_create_densepose_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
|
196 |
+
if not cfg.MODEL.DENSEPOSE_ON:
|
197 |
+
return None
|
198 |
+
|
199 |
+
use_masks = cfg.MODEL.ROI_DENSEPOSE_HEAD.COARSE_SEGM_TRAINED_BY_MASKS
|
200 |
+
|
201 |
+
def has_densepose_annotations(instance: Instance) -> bool:
|
202 |
+
for ann in instance["annotations"]:
|
203 |
+
if all(key in ann for key in DENSEPOSE_IUV_KEYS_WITHOUT_MASK) or all(
|
204 |
+
key in ann for key in DENSEPOSE_CSE_KEYS_WITHOUT_MASK
|
205 |
+
):
|
206 |
+
return True
|
207 |
+
if use_masks and "segmentation" in ann:
|
208 |
+
return True
|
209 |
+
return False
|
210 |
+
|
211 |
+
return has_densepose_annotations
|
212 |
+
|
213 |
+
|
214 |
+
def _maybe_create_specific_keep_instance_predicate(cfg: CfgNode) -> Optional[InstancePredicate]:
|
215 |
+
specific_predicate_creators = [
|
216 |
+
_maybe_create_keypoints_keep_instance_predicate,
|
217 |
+
_maybe_create_mask_keep_instance_predicate,
|
218 |
+
_maybe_create_densepose_keep_instance_predicate,
|
219 |
+
]
|
220 |
+
predicates = [creator(cfg) for creator in specific_predicate_creators]
|
221 |
+
predicates = [p for p in predicates if p is not None]
|
222 |
+
if not predicates:
|
223 |
+
return None
|
224 |
+
|
225 |
+
def combined_predicate(instance: Instance) -> bool:
|
226 |
+
return any(p(instance) for p in predicates)
|
227 |
+
|
228 |
+
return combined_predicate
|
229 |
+
|
230 |
+
|
231 |
+
def _get_train_keep_instance_predicate(cfg: CfgNode):
|
232 |
+
general_keep_predicate = _maybe_create_general_keep_instance_predicate(cfg)
|
233 |
+
combined_specific_keep_predicate = _maybe_create_specific_keep_instance_predicate(cfg)
|
234 |
+
|
235 |
+
def combined_general_specific_keep_predicate(instance: Instance) -> bool:
|
236 |
+
return general_keep_predicate(instance) and combined_specific_keep_predicate(instance)
|
237 |
+
|
238 |
+
if (general_keep_predicate is None) and (combined_specific_keep_predicate is None):
|
239 |
+
return None
|
240 |
+
if general_keep_predicate is None:
|
241 |
+
return combined_specific_keep_predicate
|
242 |
+
if combined_specific_keep_predicate is None:
|
243 |
+
return general_keep_predicate
|
244 |
+
return combined_general_specific_keep_predicate
|
245 |
+
|
246 |
+
|
247 |
+
def _get_test_keep_instance_predicate(cfg: CfgNode):
|
248 |
+
general_keep_predicate = _maybe_create_general_keep_instance_predicate(cfg)
|
249 |
+
return general_keep_predicate
|
250 |
+
|
251 |
+
|
252 |
+
def _maybe_filter_and_map_categories(
|
253 |
+
dataset_name: str, dataset_dicts: List[Instance]
|
254 |
+
) -> List[Instance]:
|
255 |
+
meta = MetadataCatalog.get(dataset_name)
|
256 |
+
category_id_map = meta.thing_dataset_id_to_contiguous_id
|
257 |
+
filtered_dataset_dicts = []
|
258 |
+
for dataset_dict in dataset_dicts:
|
259 |
+
anns = []
|
260 |
+
for ann in dataset_dict["annotations"]:
|
261 |
+
cat_id = ann["category_id"]
|
262 |
+
if cat_id not in category_id_map:
|
263 |
+
continue
|
264 |
+
ann["category_id"] = category_id_map[cat_id]
|
265 |
+
anns.append(ann)
|
266 |
+
dataset_dict["annotations"] = anns
|
267 |
+
filtered_dataset_dicts.append(dataset_dict)
|
268 |
+
return filtered_dataset_dicts
|
269 |
+
|
270 |
+
|
271 |
+
def _add_category_whitelists_to_metadata(cfg: CfgNode) -> None:
|
272 |
+
for dataset_name, whitelisted_cat_ids in cfg.DATASETS.WHITELISTED_CATEGORIES.items():
|
273 |
+
meta = MetadataCatalog.get(dataset_name)
|
274 |
+
meta.whitelisted_categories = whitelisted_cat_ids
|
275 |
+
logger = logging.getLogger(__name__)
|
276 |
+
logger.info(
|
277 |
+
"Whitelisted categories for dataset {}: {}".format(
|
278 |
+
dataset_name, meta.whitelisted_categories
|
279 |
+
)
|
280 |
+
)
|
281 |
+
|
282 |
+
|
283 |
+
def _add_category_maps_to_metadata(cfg: CfgNode) -> None:
|
284 |
+
for dataset_name, category_map in cfg.DATASETS.CATEGORY_MAPS.items():
|
285 |
+
category_map = {
|
286 |
+
int(cat_id_src): int(cat_id_dst) for cat_id_src, cat_id_dst in category_map.items()
|
287 |
+
}
|
288 |
+
meta = MetadataCatalog.get(dataset_name)
|
289 |
+
meta.category_map = category_map
|
290 |
+
logger = logging.getLogger(__name__)
|
291 |
+
logger.info("Category maps for dataset {}: {}".format(dataset_name, meta.category_map))
|
292 |
+
|
293 |
+
|
294 |
+
def _add_category_info_to_bootstrapping_metadata(dataset_name: str, dataset_cfg: CfgNode) -> None:
|
295 |
+
meta = MetadataCatalog.get(dataset_name)
|
296 |
+
meta.category_to_class_mapping = get_category_to_class_mapping(dataset_cfg)
|
297 |
+
meta.categories = dataset_cfg.CATEGORIES
|
298 |
+
meta.max_count_per_category = dataset_cfg.MAX_COUNT_PER_CATEGORY
|
299 |
+
logger = logging.getLogger(__name__)
|
300 |
+
logger.info(
|
301 |
+
"Category to class mapping for dataset {}: {}".format(
|
302 |
+
dataset_name, meta.category_to_class_mapping
|
303 |
+
)
|
304 |
+
)
|
305 |
+
|
306 |
+
|
307 |
+
def _maybe_add_class_to_mesh_name_map_to_metadata(dataset_names: List[str], cfg: CfgNode) -> None:
|
308 |
+
for dataset_name in dataset_names:
|
309 |
+
meta = MetadataCatalog.get(dataset_name)
|
310 |
+
if not hasattr(meta, "class_to_mesh_name"):
|
311 |
+
meta.class_to_mesh_name = get_class_to_mesh_name_mapping(cfg)
|
312 |
+
|
313 |
+
|
314 |
+
def _merge_categories(dataset_names: Collection[str]) -> _MergedCategoriesT:
|
315 |
+
merged_categories = defaultdict(list)
|
316 |
+
category_names = {}
|
317 |
+
for dataset_name in dataset_names:
|
318 |
+
meta = MetadataCatalog.get(dataset_name)
|
319 |
+
whitelisted_categories = meta.get("whitelisted_categories")
|
320 |
+
category_map = meta.get("category_map", {})
|
321 |
+
cat_ids = (
|
322 |
+
whitelisted_categories if whitelisted_categories is not None else meta.categories.keys()
|
323 |
+
)
|
324 |
+
for cat_id in cat_ids:
|
325 |
+
cat_name = meta.categories[cat_id]
|
326 |
+
cat_id_mapped = category_map.get(cat_id, cat_id)
|
327 |
+
if cat_id_mapped == cat_id or cat_id_mapped in cat_ids:
|
328 |
+
category_names[cat_id] = cat_name
|
329 |
+
else:
|
330 |
+
category_names[cat_id] = str(cat_id_mapped)
|
331 |
+
# assign temporary mapped category name, this name can be changed
|
332 |
+
# during the second pass, since mapped ID can correspond to a category
|
333 |
+
# from a different dataset
|
334 |
+
cat_name_mapped = meta.categories[cat_id_mapped]
|
335 |
+
merged_categories[cat_id_mapped].append(
|
336 |
+
_DatasetCategory(
|
337 |
+
id=cat_id,
|
338 |
+
name=cat_name,
|
339 |
+
mapped_id=cat_id_mapped,
|
340 |
+
mapped_name=cat_name_mapped,
|
341 |
+
dataset_name=dataset_name,
|
342 |
+
)
|
343 |
+
)
|
344 |
+
# second pass to assign proper mapped category names
|
345 |
+
for cat_id, categories in merged_categories.items():
|
346 |
+
for cat in categories:
|
347 |
+
if cat_id in category_names and cat.mapped_name != category_names[cat_id]:
|
348 |
+
cat.mapped_name = category_names[cat_id]
|
349 |
+
|
350 |
+
return merged_categories
|
351 |
+
|
352 |
+
|
353 |
+
def _warn_if_merged_different_categories(merged_categories: _MergedCategoriesT) -> None:
|
354 |
+
logger = logging.getLogger(__name__)
|
355 |
+
for cat_id in merged_categories:
|
356 |
+
merged_categories_i = merged_categories[cat_id]
|
357 |
+
first_cat_name = merged_categories_i[0].name
|
358 |
+
if len(merged_categories_i) > 1 and not all(
|
359 |
+
cat.name == first_cat_name for cat in merged_categories_i[1:]
|
360 |
+
):
|
361 |
+
cat_summary_str = ", ".join(
|
362 |
+
[f"{cat.id} ({cat.name}) from {cat.dataset_name}" for cat in merged_categories_i]
|
363 |
+
)
|
364 |
+
logger.warning(
|
365 |
+
f"Merged category {cat_id} corresponds to the following categories: "
|
366 |
+
f"{cat_summary_str}"
|
367 |
+
)
|
368 |
+
|
369 |
+
|
370 |
+
def combine_detection_dataset_dicts(
|
371 |
+
dataset_names: Collection[str],
|
372 |
+
keep_instance_predicate: Optional[InstancePredicate] = None,
|
373 |
+
proposal_files: Optional[Collection[str]] = None,
|
374 |
+
) -> List[Instance]:
|
375 |
+
"""
|
376 |
+
Load and prepare dataset dicts for training / testing
|
377 |
+
|
378 |
+
Args:
|
379 |
+
dataset_names (Collection[str]): a list of dataset names
|
380 |
+
keep_instance_predicate (Callable: Dict[str, Any] -> bool): predicate
|
381 |
+
applied to instance dicts which defines whether to keep the instance
|
382 |
+
proposal_files (Collection[str]): if given, a list of object proposal files
|
383 |
+
that match each dataset in `dataset_names`.
|
384 |
+
"""
|
385 |
+
assert len(dataset_names)
|
386 |
+
if proposal_files is None:
|
387 |
+
proposal_files = [None] * len(dataset_names)
|
388 |
+
assert len(dataset_names) == len(proposal_files)
|
389 |
+
# load datasets and metadata
|
390 |
+
dataset_name_to_dicts = {}
|
391 |
+
for dataset_name in dataset_names:
|
392 |
+
dataset_name_to_dicts[dataset_name] = DatasetCatalog.get(dataset_name)
|
393 |
+
assert len(dataset_name_to_dicts), f"Dataset '{dataset_name}' is empty!"
|
394 |
+
# merge categories, requires category metadata to be loaded
|
395 |
+
# cat_id -> [(orig_cat_id, cat_name, dataset_name)]
|
396 |
+
merged_categories = _merge_categories(dataset_names)
|
397 |
+
_warn_if_merged_different_categories(merged_categories)
|
398 |
+
merged_category_names = [
|
399 |
+
merged_categories[cat_id][0].mapped_name for cat_id in sorted(merged_categories)
|
400 |
+
]
|
401 |
+
# map to contiguous category IDs
|
402 |
+
_add_category_id_to_contiguous_id_maps_to_metadata(merged_categories)
|
403 |
+
# load annotations and dataset metadata
|
404 |
+
for dataset_name, proposal_file in zip(dataset_names, proposal_files):
|
405 |
+
dataset_dicts = dataset_name_to_dicts[dataset_name]
|
406 |
+
assert len(dataset_dicts), f"Dataset '{dataset_name}' is empty!"
|
407 |
+
if proposal_file is not None:
|
408 |
+
dataset_dicts = load_proposals_into_dataset(dataset_dicts, proposal_file)
|
409 |
+
dataset_dicts = _maybe_filter_and_map_categories(dataset_name, dataset_dicts)
|
410 |
+
print_instances_class_histogram(dataset_dicts, merged_category_names)
|
411 |
+
dataset_name_to_dicts[dataset_name] = dataset_dicts
|
412 |
+
|
413 |
+
if keep_instance_predicate is not None:
|
414 |
+
all_datasets_dicts_plain = [
|
415 |
+
d
|
416 |
+
for d in itertools.chain.from_iterable(dataset_name_to_dicts.values())
|
417 |
+
if keep_instance_predicate(d)
|
418 |
+
]
|
419 |
+
else:
|
420 |
+
all_datasets_dicts_plain = list(
|
421 |
+
itertools.chain.from_iterable(dataset_name_to_dicts.values())
|
422 |
+
)
|
423 |
+
return all_datasets_dicts_plain
|
424 |
+
|
425 |
+
|
426 |
+
def build_detection_train_loader(cfg: CfgNode, mapper=None):
|
427 |
+
"""
|
428 |
+
A data loader is created in a way similar to that of Detectron2.
|
429 |
+
The main differences are:
|
430 |
+
- it allows to combine datasets with different but compatible object category sets
|
431 |
+
|
432 |
+
The data loader is created by the following steps:
|
433 |
+
1. Use the dataset names in config to query :class:`DatasetCatalog`, and obtain a list of dicts.
|
434 |
+
2. Start workers to work on the dicts. Each worker will:
|
435 |
+
* Map each metadata dict into another format to be consumed by the model.
|
436 |
+
* Batch them by simply putting dicts into a list.
|
437 |
+
The batched ``list[mapped_dict]`` is what this dataloader will return.
|
438 |
+
|
439 |
+
Args:
|
440 |
+
cfg (CfgNode): the config
|
441 |
+
mapper (callable): a callable which takes a sample (dict) from dataset and
|
442 |
+
returns the format to be consumed by the model.
|
443 |
+
By default it will be `DatasetMapper(cfg, True)`.
|
444 |
+
|
445 |
+
Returns:
|
446 |
+
an infinite iterator of training data
|
447 |
+
"""
|
448 |
+
|
449 |
+
_add_category_whitelists_to_metadata(cfg)
|
450 |
+
_add_category_maps_to_metadata(cfg)
|
451 |
+
_maybe_add_class_to_mesh_name_map_to_metadata(cfg.DATASETS.TRAIN, cfg)
|
452 |
+
dataset_dicts = combine_detection_dataset_dicts(
|
453 |
+
cfg.DATASETS.TRAIN,
|
454 |
+
keep_instance_predicate=_get_train_keep_instance_predicate(cfg),
|
455 |
+
proposal_files=cfg.DATASETS.PROPOSAL_FILES_TRAIN if cfg.MODEL.LOAD_PROPOSALS else None,
|
456 |
+
)
|
457 |
+
if mapper is None:
|
458 |
+
mapper = DatasetMapper(cfg, True)
|
459 |
+
return d2_build_detection_train_loader(cfg, dataset=dataset_dicts, mapper=mapper)
|
460 |
+
|
461 |
+
|
462 |
+
def build_detection_test_loader(cfg, dataset_name, mapper=None):
|
463 |
+
"""
|
464 |
+
Similar to `build_detection_train_loader`.
|
465 |
+
But this function uses the given `dataset_name` argument (instead of the names in cfg),
|
466 |
+
and uses batch size 1.
|
467 |
+
|
468 |
+
Args:
|
469 |
+
cfg: a detectron2 CfgNode
|
470 |
+
dataset_name (str): a name of the dataset that's available in the DatasetCatalog
|
471 |
+
mapper (callable): a callable which takes a sample (dict) from dataset
|
472 |
+
and returns the format to be consumed by the model.
|
473 |
+
By default it will be `DatasetMapper(cfg, False)`.
|
474 |
+
|
475 |
+
Returns:
|
476 |
+
DataLoader: a torch DataLoader, that loads the given detection
|
477 |
+
dataset, with test-time transformation and batching.
|
478 |
+
"""
|
479 |
+
_add_category_whitelists_to_metadata(cfg)
|
480 |
+
_add_category_maps_to_metadata(cfg)
|
481 |
+
_maybe_add_class_to_mesh_name_map_to_metadata([dataset_name], cfg)
|
482 |
+
dataset_dicts = combine_detection_dataset_dicts(
|
483 |
+
[dataset_name],
|
484 |
+
keep_instance_predicate=_get_test_keep_instance_predicate(cfg),
|
485 |
+
proposal_files=[
|
486 |
+
cfg.DATASETS.PROPOSAL_FILES_TEST[list(cfg.DATASETS.TEST).index(dataset_name)]
|
487 |
+
]
|
488 |
+
if cfg.MODEL.LOAD_PROPOSALS
|
489 |
+
else None,
|
490 |
+
)
|
491 |
+
sampler = None
|
492 |
+
if not cfg.DENSEPOSE_EVALUATION.DISTRIBUTED_INFERENCE:
|
493 |
+
sampler = torch.utils.data.SequentialSampler(dataset_dicts)
|
494 |
+
if mapper is None:
|
495 |
+
mapper = DatasetMapper(cfg, False)
|
496 |
+
return d2_build_detection_test_loader(
|
497 |
+
dataset_dicts, mapper=mapper, num_workers=cfg.DATALOADER.NUM_WORKERS, sampler=sampler
|
498 |
+
)
|
499 |
+
|
500 |
+
|
501 |
+
def build_frame_selector(cfg: CfgNode):
|
502 |
+
strategy = FrameSelectionStrategy(cfg.STRATEGY)
|
503 |
+
if strategy == FrameSelectionStrategy.RANDOM_K:
|
504 |
+
frame_selector = RandomKFramesSelector(cfg.NUM_IMAGES)
|
505 |
+
elif strategy == FrameSelectionStrategy.FIRST_K:
|
506 |
+
frame_selector = FirstKFramesSelector(cfg.NUM_IMAGES)
|
507 |
+
elif strategy == FrameSelectionStrategy.LAST_K:
|
508 |
+
frame_selector = LastKFramesSelector(cfg.NUM_IMAGES)
|
509 |
+
elif strategy == FrameSelectionStrategy.ALL:
|
510 |
+
frame_selector = None
|
511 |
+
# pyre-fixme[61]: `frame_selector` may not be initialized here.
|
512 |
+
return frame_selector
|
513 |
+
|
514 |
+
|
515 |
+
def build_transform(cfg: CfgNode, data_type: str):
|
516 |
+
if cfg.TYPE == "resize":
|
517 |
+
if data_type == "image":
|
518 |
+
return ImageResizeTransform(cfg.MIN_SIZE, cfg.MAX_SIZE)
|
519 |
+
raise ValueError(f"Unknown transform {cfg.TYPE} for data type {data_type}")
|
520 |
+
|
521 |
+
|
522 |
+
def build_combined_loader(cfg: CfgNode, loaders: Collection[Loader], ratios: Sequence[float]):
|
523 |
+
images_per_worker = _compute_num_images_per_worker(cfg)
|
524 |
+
return CombinedDataLoader(loaders, images_per_worker, ratios)
|
525 |
+
|
526 |
+
|
527 |
+
def build_bootstrap_dataset(dataset_name: str, cfg: CfgNode) -> Sequence[torch.Tensor]:
|
528 |
+
"""
|
529 |
+
Build dataset that provides data to bootstrap on
|
530 |
+
|
531 |
+
Args:
|
532 |
+
dataset_name (str): Name of the dataset, needs to have associated metadata
|
533 |
+
to load the data
|
534 |
+
cfg (CfgNode): bootstrapping config
|
535 |
+
Returns:
|
536 |
+
Sequence[Tensor] - dataset that provides image batches, Tensors of size
|
537 |
+
[N, C, H, W] of type float32
|
538 |
+
"""
|
539 |
+
logger = logging.getLogger(__name__)
|
540 |
+
_add_category_info_to_bootstrapping_metadata(dataset_name, cfg)
|
541 |
+
meta = MetadataCatalog.get(dataset_name)
|
542 |
+
factory = BootstrapDatasetFactoryCatalog.get(meta.dataset_type)
|
543 |
+
dataset = None
|
544 |
+
if factory is not None:
|
545 |
+
dataset = factory(meta, cfg)
|
546 |
+
if dataset is None:
|
547 |
+
logger.warning(f"Failed to create dataset {dataset_name} of type {meta.dataset_type}")
|
548 |
+
return dataset
|
549 |
+
|
550 |
+
|
551 |
+
def build_data_sampler(cfg: CfgNode, sampler_cfg: CfgNode, embedder: Optional[torch.nn.Module]):
|
552 |
+
if sampler_cfg.TYPE == "densepose_uniform":
|
553 |
+
data_sampler = PredictionToGroundTruthSampler()
|
554 |
+
# transform densepose pred -> gt
|
555 |
+
data_sampler.register_sampler(
|
556 |
+
"pred_densepose",
|
557 |
+
"gt_densepose",
|
558 |
+
DensePoseUniformSampler(count_per_class=sampler_cfg.COUNT_PER_CLASS),
|
559 |
+
)
|
560 |
+
data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
|
561 |
+
return data_sampler
|
562 |
+
elif sampler_cfg.TYPE == "densepose_UV_confidence":
|
563 |
+
data_sampler = PredictionToGroundTruthSampler()
|
564 |
+
# transform densepose pred -> gt
|
565 |
+
data_sampler.register_sampler(
|
566 |
+
"pred_densepose",
|
567 |
+
"gt_densepose",
|
568 |
+
DensePoseConfidenceBasedSampler(
|
569 |
+
confidence_channel="sigma_2",
|
570 |
+
count_per_class=sampler_cfg.COUNT_PER_CLASS,
|
571 |
+
search_proportion=0.5,
|
572 |
+
),
|
573 |
+
)
|
574 |
+
data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
|
575 |
+
return data_sampler
|
576 |
+
elif sampler_cfg.TYPE == "densepose_fine_segm_confidence":
|
577 |
+
data_sampler = PredictionToGroundTruthSampler()
|
578 |
+
# transform densepose pred -> gt
|
579 |
+
data_sampler.register_sampler(
|
580 |
+
"pred_densepose",
|
581 |
+
"gt_densepose",
|
582 |
+
DensePoseConfidenceBasedSampler(
|
583 |
+
confidence_channel="fine_segm_confidence",
|
584 |
+
count_per_class=sampler_cfg.COUNT_PER_CLASS,
|
585 |
+
search_proportion=0.5,
|
586 |
+
),
|
587 |
+
)
|
588 |
+
data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
|
589 |
+
return data_sampler
|
590 |
+
elif sampler_cfg.TYPE == "densepose_coarse_segm_confidence":
|
591 |
+
data_sampler = PredictionToGroundTruthSampler()
|
592 |
+
# transform densepose pred -> gt
|
593 |
+
data_sampler.register_sampler(
|
594 |
+
"pred_densepose",
|
595 |
+
"gt_densepose",
|
596 |
+
DensePoseConfidenceBasedSampler(
|
597 |
+
confidence_channel="coarse_segm_confidence",
|
598 |
+
count_per_class=sampler_cfg.COUNT_PER_CLASS,
|
599 |
+
search_proportion=0.5,
|
600 |
+
),
|
601 |
+
)
|
602 |
+
data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
|
603 |
+
return data_sampler
|
604 |
+
elif sampler_cfg.TYPE == "densepose_cse_uniform":
|
605 |
+
assert embedder is not None
|
606 |
+
data_sampler = PredictionToGroundTruthSampler()
|
607 |
+
# transform densepose pred -> gt
|
608 |
+
data_sampler.register_sampler(
|
609 |
+
"pred_densepose",
|
610 |
+
"gt_densepose",
|
611 |
+
DensePoseCSEUniformSampler(
|
612 |
+
cfg=cfg,
|
613 |
+
use_gt_categories=sampler_cfg.USE_GROUND_TRUTH_CATEGORIES,
|
614 |
+
embedder=embedder,
|
615 |
+
count_per_class=sampler_cfg.COUNT_PER_CLASS,
|
616 |
+
),
|
617 |
+
)
|
618 |
+
data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
|
619 |
+
return data_sampler
|
620 |
+
elif sampler_cfg.TYPE == "densepose_cse_coarse_segm_confidence":
|
621 |
+
assert embedder is not None
|
622 |
+
data_sampler = PredictionToGroundTruthSampler()
|
623 |
+
# transform densepose pred -> gt
|
624 |
+
data_sampler.register_sampler(
|
625 |
+
"pred_densepose",
|
626 |
+
"gt_densepose",
|
627 |
+
DensePoseCSEConfidenceBasedSampler(
|
628 |
+
cfg=cfg,
|
629 |
+
use_gt_categories=sampler_cfg.USE_GROUND_TRUTH_CATEGORIES,
|
630 |
+
embedder=embedder,
|
631 |
+
confidence_channel="coarse_segm_confidence",
|
632 |
+
count_per_class=sampler_cfg.COUNT_PER_CLASS,
|
633 |
+
search_proportion=0.5,
|
634 |
+
),
|
635 |
+
)
|
636 |
+
data_sampler.register_sampler("pred_densepose", "gt_masks", MaskFromDensePoseSampler())
|
637 |
+
return data_sampler
|
638 |
+
|
639 |
+
raise ValueError(f"Unknown data sampler type {sampler_cfg.TYPE}")
|
640 |
+
|
641 |
+
|
642 |
+
def build_data_filter(cfg: CfgNode):
|
643 |
+
if cfg.TYPE == "detection_score":
|
644 |
+
min_score = cfg.MIN_VALUE
|
645 |
+
return ScoreBasedFilter(min_score=min_score)
|
646 |
+
raise ValueError(f"Unknown data filter type {cfg.TYPE}")
|
647 |
+
|
648 |
+
|
649 |
+
def build_inference_based_loader(
|
650 |
+
cfg: CfgNode,
|
651 |
+
dataset_cfg: CfgNode,
|
652 |
+
model: torch.nn.Module,
|
653 |
+
embedder: Optional[torch.nn.Module] = None,
|
654 |
+
) -> InferenceBasedLoader:
|
655 |
+
"""
|
656 |
+
Constructs data loader based on inference results of a model.
|
657 |
+
"""
|
658 |
+
dataset = build_bootstrap_dataset(dataset_cfg.DATASET, dataset_cfg.IMAGE_LOADER)
|
659 |
+
meta = MetadataCatalog.get(dataset_cfg.DATASET)
|
660 |
+
training_sampler = TrainingSampler(len(dataset))
|
661 |
+
data_loader = torch.utils.data.DataLoader(
|
662 |
+
dataset, # pyre-ignore[6]
|
663 |
+
batch_size=dataset_cfg.IMAGE_LOADER.BATCH_SIZE,
|
664 |
+
sampler=training_sampler,
|
665 |
+
num_workers=dataset_cfg.IMAGE_LOADER.NUM_WORKERS,
|
666 |
+
collate_fn=trivial_batch_collator,
|
667 |
+
worker_init_fn=worker_init_reset_seed,
|
668 |
+
)
|
669 |
+
return InferenceBasedLoader(
|
670 |
+
model,
|
671 |
+
data_loader=data_loader,
|
672 |
+
data_sampler=build_data_sampler(cfg, dataset_cfg.DATA_SAMPLER, embedder),
|
673 |
+
data_filter=build_data_filter(dataset_cfg.FILTER),
|
674 |
+
shuffle=True,
|
675 |
+
batch_size=dataset_cfg.INFERENCE.OUTPUT_BATCH_SIZE,
|
676 |
+
inference_batch_size=dataset_cfg.INFERENCE.INPUT_BATCH_SIZE,
|
677 |
+
category_to_class_mapping=meta.category_to_class_mapping,
|
678 |
+
)
|
679 |
+
|
680 |
+
|
681 |
+
def has_inference_based_loaders(cfg: CfgNode) -> bool:
|
682 |
+
"""
|
683 |
+
Returns True, if at least one inferense-based loader must
|
684 |
+
be instantiated for training
|
685 |
+
"""
|
686 |
+
return len(cfg.BOOTSTRAP_DATASETS) > 0
|
687 |
+
|
688 |
+
|
689 |
+
def build_inference_based_loaders(
|
690 |
+
cfg: CfgNode, model: torch.nn.Module
|
691 |
+
) -> Tuple[List[InferenceBasedLoader], List[float]]:
|
692 |
+
loaders = []
|
693 |
+
ratios = []
|
694 |
+
embedder = build_densepose_embedder(cfg).to(device=model.device) # pyre-ignore[16]
|
695 |
+
for dataset_spec in cfg.BOOTSTRAP_DATASETS:
|
696 |
+
dataset_cfg = get_bootstrap_dataset_config().clone()
|
697 |
+
dataset_cfg.merge_from_other_cfg(CfgNode(dataset_spec))
|
698 |
+
loader = build_inference_based_loader(cfg, dataset_cfg, model, embedder)
|
699 |
+
loaders.append(loader)
|
700 |
+
ratios.append(dataset_cfg.RATIO)
|
701 |
+
return loaders, ratios
|
702 |
+
|
703 |
+
|
704 |
+
def build_video_list_dataset(meta: Metadata, cfg: CfgNode):
|
705 |
+
video_list_fpath = meta.video_list_fpath
|
706 |
+
video_base_path = meta.video_base_path
|
707 |
+
category = meta.category
|
708 |
+
if cfg.TYPE == "video_keyframe":
|
709 |
+
frame_selector = build_frame_selector(cfg.SELECT)
|
710 |
+
transform = build_transform(cfg.TRANSFORM, data_type="image")
|
711 |
+
video_list = video_list_from_file(video_list_fpath, video_base_path)
|
712 |
+
keyframe_helper_fpath = getattr(cfg, "KEYFRAME_HELPER", None)
|
713 |
+
return VideoKeyframeDataset(
|
714 |
+
video_list, category, frame_selector, transform, keyframe_helper_fpath
|
715 |
+
)
|
716 |
+
|
717 |
+
|
718 |
+
class _BootstrapDatasetFactoryCatalog(UserDict):
|
719 |
+
"""
|
720 |
+
A global dictionary that stores information about bootstrapped datasets creation functions
|
721 |
+
from metadata and config, for diverse DatasetType
|
722 |
+
"""
|
723 |
+
|
724 |
+
def register(self, dataset_type: DatasetType, factory: Callable[[Metadata, CfgNode], Dataset]):
|
725 |
+
"""
|
726 |
+
Args:
|
727 |
+
dataset_type (DatasetType): a DatasetType e.g. DatasetType.VIDEO_LIST
|
728 |
+
factory (Callable[Metadata, CfgNode]): a callable which takes Metadata and cfg
|
729 |
+
arguments and returns a dataset object.
|
730 |
+
"""
|
731 |
+
assert dataset_type not in self, "Dataset '{}' is already registered!".format(dataset_type)
|
732 |
+
self[dataset_type] = factory
|
733 |
+
|
734 |
+
|
735 |
+
BootstrapDatasetFactoryCatalog = _BootstrapDatasetFactoryCatalog()
|
736 |
+
BootstrapDatasetFactoryCatalog.register(DatasetType.VIDEO_LIST, build_video_list_dataset)
|
densepose/data/combined_loader.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
import random
|
4 |
+
from collections import deque
|
5 |
+
from typing import Any, Collection, Deque, Iterable, Iterator, List, Sequence
|
6 |
+
|
7 |
+
Loader = Iterable[Any]
|
8 |
+
|
9 |
+
|
10 |
+
def _pooled_next(iterator: Iterator[Any], pool: Deque[Any]):
|
11 |
+
if not pool:
|
12 |
+
pool.extend(next(iterator))
|
13 |
+
return pool.popleft()
|
14 |
+
|
15 |
+
|
16 |
+
class CombinedDataLoader:
|
17 |
+
"""
|
18 |
+
Combines data loaders using the provided sampling ratios
|
19 |
+
"""
|
20 |
+
|
21 |
+
BATCH_COUNT = 100
|
22 |
+
|
23 |
+
def __init__(self, loaders: Collection[Loader], batch_size: int, ratios: Sequence[float]):
|
24 |
+
self.loaders = loaders
|
25 |
+
self.batch_size = batch_size
|
26 |
+
self.ratios = ratios
|
27 |
+
|
28 |
+
def __iter__(self) -> Iterator[List[Any]]:
|
29 |
+
iters = [iter(loader) for loader in self.loaders]
|
30 |
+
indices = []
|
31 |
+
pool = [deque()] * len(iters)
|
32 |
+
# infinite iterator, as in D2
|
33 |
+
while True:
|
34 |
+
if not indices:
|
35 |
+
# just a buffer of indices, its size doesn't matter
|
36 |
+
# as long as it's a multiple of batch_size
|
37 |
+
k = self.batch_size * self.BATCH_COUNT
|
38 |
+
indices = random.choices(range(len(self.loaders)), self.ratios, k=k)
|
39 |
+
try:
|
40 |
+
batch = [_pooled_next(iters[i], pool[i]) for i in indices[: self.batch_size]]
|
41 |
+
except StopIteration:
|
42 |
+
break
|
43 |
+
indices = indices[self.batch_size :]
|
44 |
+
yield batch
|
densepose/data/dataset_mapper.py
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
3 |
+
|
4 |
+
import copy
|
5 |
+
import logging
|
6 |
+
from typing import Any, Dict, List, Tuple
|
7 |
+
import torch
|
8 |
+
|
9 |
+
from detectron2.data import MetadataCatalog
|
10 |
+
from detectron2.data import detection_utils as utils
|
11 |
+
from detectron2.data import transforms as T
|
12 |
+
from detectron2.layers import ROIAlign
|
13 |
+
from detectron2.structures import BoxMode
|
14 |
+
from detectron2.utils.file_io import PathManager
|
15 |
+
|
16 |
+
from densepose.structures import DensePoseDataRelative, DensePoseList, DensePoseTransformData
|
17 |
+
|
18 |
+
|
19 |
+
def build_augmentation(cfg, is_train):
|
20 |
+
logger = logging.getLogger(__name__)
|
21 |
+
result = utils.build_augmentation(cfg, is_train)
|
22 |
+
if is_train:
|
23 |
+
random_rotation = T.RandomRotation(
|
24 |
+
cfg.INPUT.ROTATION_ANGLES, expand=False, sample_style="choice"
|
25 |
+
)
|
26 |
+
result.append(random_rotation)
|
27 |
+
logger.info("DensePose-specific augmentation used in training: " + str(random_rotation))
|
28 |
+
return result
|
29 |
+
|
30 |
+
|
31 |
+
class DatasetMapper:
|
32 |
+
"""
|
33 |
+
A customized version of `detectron2.data.DatasetMapper`
|
34 |
+
"""
|
35 |
+
|
36 |
+
def __init__(self, cfg, is_train=True):
|
37 |
+
self.augmentation = build_augmentation(cfg, is_train)
|
38 |
+
|
39 |
+
# fmt: off
|
40 |
+
self.img_format = cfg.INPUT.FORMAT
|
41 |
+
self.mask_on = (
|
42 |
+
cfg.MODEL.MASK_ON or (
|
43 |
+
cfg.MODEL.DENSEPOSE_ON
|
44 |
+
and cfg.MODEL.ROI_DENSEPOSE_HEAD.COARSE_SEGM_TRAINED_BY_MASKS)
|
45 |
+
)
|
46 |
+
self.keypoint_on = cfg.MODEL.KEYPOINT_ON
|
47 |
+
self.densepose_on = cfg.MODEL.DENSEPOSE_ON
|
48 |
+
assert not cfg.MODEL.LOAD_PROPOSALS, "not supported yet"
|
49 |
+
# fmt: on
|
50 |
+
if self.keypoint_on and is_train:
|
51 |
+
# Flip only makes sense in training
|
52 |
+
self.keypoint_hflip_indices = utils.create_keypoint_hflip_indices(cfg.DATASETS.TRAIN)
|
53 |
+
else:
|
54 |
+
self.keypoint_hflip_indices = None
|
55 |
+
|
56 |
+
if self.densepose_on:
|
57 |
+
densepose_transform_srcs = [
|
58 |
+
MetadataCatalog.get(ds).densepose_transform_src
|
59 |
+
for ds in cfg.DATASETS.TRAIN + cfg.DATASETS.TEST
|
60 |
+
]
|
61 |
+
assert len(densepose_transform_srcs) > 0
|
62 |
+
# TODO: check that DensePose transformation data is the same for
|
63 |
+
# all the datasets. Otherwise one would have to pass DB ID with
|
64 |
+
# each entry to select proper transformation data. For now, since
|
65 |
+
# all DensePose annotated data uses the same data semantics, we
|
66 |
+
# omit this check.
|
67 |
+
densepose_transform_data_fpath = PathManager.get_local_path(densepose_transform_srcs[0])
|
68 |
+
self.densepose_transform_data = DensePoseTransformData.load(
|
69 |
+
densepose_transform_data_fpath
|
70 |
+
)
|
71 |
+
|
72 |
+
self.is_train = is_train
|
73 |
+
|
74 |
+
def __call__(self, dataset_dict):
|
75 |
+
"""
|
76 |
+
Args:
|
77 |
+
dataset_dict (dict): Metadata of one image, in Detectron2 Dataset format.
|
78 |
+
|
79 |
+
Returns:
|
80 |
+
dict: a format that builtin models in detectron2 accept
|
81 |
+
"""
|
82 |
+
dataset_dict = copy.deepcopy(dataset_dict) # it will be modified by code below
|
83 |
+
image = utils.read_image(dataset_dict["file_name"], format=self.img_format)
|
84 |
+
utils.check_image_size(dataset_dict, image)
|
85 |
+
|
86 |
+
image, transforms = T.apply_transform_gens(self.augmentation, image)
|
87 |
+
image_shape = image.shape[:2] # h, w
|
88 |
+
dataset_dict["image"] = torch.as_tensor(image.transpose(2, 0, 1).astype("float32"))
|
89 |
+
|
90 |
+
if not self.is_train:
|
91 |
+
dataset_dict.pop("annotations", None)
|
92 |
+
return dataset_dict
|
93 |
+
|
94 |
+
for anno in dataset_dict["annotations"]:
|
95 |
+
if not self.mask_on:
|
96 |
+
anno.pop("segmentation", None)
|
97 |
+
if not self.keypoint_on:
|
98 |
+
anno.pop("keypoints", None)
|
99 |
+
|
100 |
+
# USER: Implement additional transformations if you have other types of data
|
101 |
+
# USER: Don't call transpose_densepose if you don't need
|
102 |
+
annos = [
|
103 |
+
self._transform_densepose(
|
104 |
+
utils.transform_instance_annotations(
|
105 |
+
obj, transforms, image_shape, keypoint_hflip_indices=self.keypoint_hflip_indices
|
106 |
+
),
|
107 |
+
transforms,
|
108 |
+
)
|
109 |
+
for obj in dataset_dict.pop("annotations")
|
110 |
+
if obj.get("iscrowd", 0) == 0
|
111 |
+
]
|
112 |
+
|
113 |
+
if self.mask_on:
|
114 |
+
self._add_densepose_masks_as_segmentation(annos, image_shape)
|
115 |
+
|
116 |
+
instances = utils.annotations_to_instances(annos, image_shape, mask_format="bitmask")
|
117 |
+
densepose_annotations = [obj.get("densepose") for obj in annos]
|
118 |
+
if densepose_annotations and not all(v is None for v in densepose_annotations):
|
119 |
+
instances.gt_densepose = DensePoseList(
|
120 |
+
densepose_annotations, instances.gt_boxes, image_shape
|
121 |
+
)
|
122 |
+
|
123 |
+
dataset_dict["instances"] = instances[instances.gt_boxes.nonempty()]
|
124 |
+
return dataset_dict
|
125 |
+
|
126 |
+
def _transform_densepose(self, annotation, transforms):
|
127 |
+
if not self.densepose_on:
|
128 |
+
return annotation
|
129 |
+
|
130 |
+
# Handle densepose annotations
|
131 |
+
is_valid, reason_not_valid = DensePoseDataRelative.validate_annotation(annotation)
|
132 |
+
if is_valid:
|
133 |
+
densepose_data = DensePoseDataRelative(annotation, cleanup=True)
|
134 |
+
densepose_data.apply_transform(transforms, self.densepose_transform_data)
|
135 |
+
annotation["densepose"] = densepose_data
|
136 |
+
else:
|
137 |
+
# logger = logging.getLogger(__name__)
|
138 |
+
# logger.debug("Could not load DensePose annotation: {}".format(reason_not_valid))
|
139 |
+
DensePoseDataRelative.cleanup_annotation(annotation)
|
140 |
+
# NOTE: annotations for certain instances may be unavailable.
|
141 |
+
# 'None' is accepted by the DensePostList data structure.
|
142 |
+
annotation["densepose"] = None
|
143 |
+
return annotation
|
144 |
+
|
145 |
+
def _add_densepose_masks_as_segmentation(
|
146 |
+
self, annotations: List[Dict[str, Any]], image_shape_hw: Tuple[int, int]
|
147 |
+
):
|
148 |
+
for obj in annotations:
|
149 |
+
if ("densepose" not in obj) or ("segmentation" in obj):
|
150 |
+
continue
|
151 |
+
# DP segmentation: torch.Tensor [S, S] of float32, S=256
|
152 |
+
segm_dp = torch.zeros_like(obj["densepose"].segm)
|
153 |
+
segm_dp[obj["densepose"].segm > 0] = 1
|
154 |
+
segm_h, segm_w = segm_dp.shape
|
155 |
+
bbox_segm_dp = torch.tensor((0, 0, segm_h - 1, segm_w - 1), dtype=torch.float32)
|
156 |
+
# image bbox
|
157 |
+
x0, y0, x1, y1 = (
|
158 |
+
v.item() for v in BoxMode.convert(obj["bbox"], obj["bbox_mode"], BoxMode.XYXY_ABS)
|
159 |
+
)
|
160 |
+
segm_aligned = (
|
161 |
+
ROIAlign((y1 - y0, x1 - x0), 1.0, 0, aligned=True)
|
162 |
+
.forward(segm_dp.view(1, 1, *segm_dp.shape), bbox_segm_dp)
|
163 |
+
.squeeze()
|
164 |
+
)
|
165 |
+
image_mask = torch.zeros(*image_shape_hw, dtype=torch.float32)
|
166 |
+
image_mask[y0:y1, x0:x1] = segm_aligned
|
167 |
+
# segmentation for BitMask: np.array [H, W] of bool
|
168 |
+
obj["segmentation"] = image_mask >= 0.5
|
densepose/data/datasets/__init__.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
|
3 |
+
from . import builtin # ensure the builtin datasets are registered
|
4 |
+
|
5 |
+
__all__ = [k for k in globals().keys() if "builtin" not in k and not k.startswith("_")]
|
densepose/data/datasets/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (386 Bytes). View file
|
|
densepose/data/datasets/__pycache__/builtin.cpython-310.pyc
ADDED
Binary file (575 Bytes). View file
|
|
densepose/data/datasets/__pycache__/chimpnsee.cpython-310.pyc
ADDED
Binary file (1.04 kB). View file
|
|
densepose/data/datasets/__pycache__/coco.cpython-310.pyc
ADDED
Binary file (11.7 kB). View file
|
|
densepose/data/datasets/__pycache__/dataset_type.cpython-310.pyc
ADDED
Binary file (501 Bytes). View file
|
|
densepose/data/datasets/__pycache__/lvis.cpython-310.pyc
ADDED
Binary file (7.86 kB). View file
|
|
densepose/data/datasets/builtin.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
2 |
+
from .chimpnsee import register_dataset as register_chimpnsee_dataset
|
3 |
+
from .coco import BASE_DATASETS as BASE_COCO_DATASETS
|
4 |
+
from .coco import DATASETS as COCO_DATASETS
|
5 |
+
from .coco import register_datasets as register_coco_datasets
|
6 |
+
from .lvis import DATASETS as LVIS_DATASETS
|
7 |
+
from .lvis import register_datasets as register_lvis_datasets
|
8 |
+
|
9 |
+
DEFAULT_DATASETS_ROOT = "datasets"
|
10 |
+
|
11 |
+
|
12 |
+
register_coco_datasets(COCO_DATASETS, DEFAULT_DATASETS_ROOT)
|
13 |
+
register_coco_datasets(BASE_COCO_DATASETS, DEFAULT_DATASETS_ROOT)
|
14 |
+
register_lvis_datasets(LVIS_DATASETS, DEFAULT_DATASETS_ROOT)
|
15 |
+
|
16 |
+
register_chimpnsee_dataset(DEFAULT_DATASETS_ROOT) # pyre-ignore[19]
|