-
Notifications
You must be signed in to change notification settings - Fork 5
/
process_kitti.py
222 lines (183 loc) · 7.18 KB
/
process_kitti.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import argparse
import multiprocessing
import os
import os.path as osp
from collections import defaultdict
from glob import glob
import joblib
import matplotlib.cm as cm
import numba
import numpy as np
import torch
from PIL import Image
from tqdm import tqdm
from datasets.kitti import KITTIOdometry
# support semantic kitti only for this script
labelmap = {
0: 0, # "unlabeled"
1: 0, # "outlier" mapped to "unlabeled" --------------------------mapped
10: 1, # "car"
11: 2, # "bicycle"
13: 5, # "bus" mapped to "other-vehicle" --------------------------mapped
15: 3, # "motorcycle"
16: 5, # "on-rails" mapped to "other-vehicle" ---------------------mapped
18: 4, # "truck"
20: 5, # "other-vehicle"
30: 6, # "person"
31: 7, # "bicyclist"
32: 8, # "motorcyclist"
40: 9, # "road"
44: 10, # "parking"
48: 11, # "sidewalk"
49: 12, # "other-ground"
50: 13, # "building"
51: 14, # "fence"
52: 0, # "other-structure" mapped to "unlabeled" ------------------mapped
60: 9, # "lane-marking" to "road" ---------------------------------mapped
70: 15, # "vegetation"
71: 16, # "trunk"
72: 17, # "terrain"
80: 18, # "pole"
81: 19, # "traffic-sign"
99: 0, # "other-object" to "unlabeled" ----------------------------mapped
252: 1, # "moving-car" to "car" ------------------------------------mapped
253: 7, # "moving-bicyclist" to "bicyclist" ------------------------mapped
254: 6, # "moving-person" to "person" ------------------------------mapped
255: 8, # "moving-motorcyclist" to "motorcyclist" ------------------mapped
256: 5, # "moving-on-rails" mapped to "other-vehicle" --------------mapped
257: 5, # "moving-bus" mapped to "other-vehicle" -------------------mapped
258: 4, # "moving-truck" to "truck" --------------------------------mapped
259: 5, # "moving-other"-vehicle to "other-vehicle" ----------------mapped
}
_n_classes = max(labelmap.values()) + 1
_colors = cm.turbo(np.asarray(range(_n_classes)) / (_n_classes - 1))[:, :3] * 255
palette = list(np.uint8(_colors).flatten())
@numba.jit
def scatter(arrary, index, value):
for (h, w), v in zip(index, value):
arrary[h, w] = v
return arrary
def projection(source, grid, order, H, W):
assert source.ndim == 2, source.ndim
C = source.shape[1]
proj = np.zeros((H, W, C))
proj = np.asarray(proj, dtype=source.dtype)
proj = scatter(proj, grid[order], source[order])
return proj
def process_point_clouds(point_path, H=64, W=2048):
save_dir = lambda x: x.replace("dataset/sequences", "dusty-gan/sequences")
# setup point clouds
points = np.fromfile(point_path, dtype=np.float32).reshape((-1, 4))
xyz = points[:, :3] # xyz
x = xyz[:, 0]
y = xyz[:, 1]
z = xyz[:, 2]
depth = np.linalg.norm(xyz, ord=2, axis=1)
order = np.argsort(-depth)
# the i-th quadrant
# suppose the points are ordered counterclockwise
quads = np.zeros_like(x)
quads[(x >= 0) & (y >= 0)] = 0 # 1st
quads[(x < 0) & (y >= 0)] = 1 # 2nd
quads[(x < 0) & (y < 0)] = 2 # 3rd
quads[(x >= 0) & (y < 0)] = 3 # 4th
# split between the 3rd and 1st quadrants
diff = np.roll(quads, 1) - quads
(start_inds,) = np.where(diff == 3) # number of lines
inds = list(start_inds) + [len(quads)] # add the last index
# vertical grid
line_idx = 63 # ...0
grid_h = np.zeros_like(x)
for i in reversed(range(len(start_inds))):
grid_h[inds[i] : inds[i + 1]] = line_idx
line_idx -= 1
# horizontal grid
yaw = -np.arctan2(y, x) # [-pi,pi]
grid_w = (yaw / np.pi + 1) / 2 % 1 # [0,1]
grid_w = np.floor(grid_w * W)
grid = np.stack((grid_h, grid_w), axis=-1).astype(np.int32)
proj = projection(points, grid, order, H, W)
save_path = save_dir(point_path).replace(".bin", ".npy")
os.makedirs(osp.dirname(save_path), exist_ok=True)
np.save(save_path, proj)
# for semantic kitti
label_path = point_path.replace("/velodyne", "/labels")
label_path = label_path.replace(".bin", ".label")
if osp.exists(label_path):
labels = np.fromfile(label_path, dtype=np.int32).reshape((-1, 1))
labels = np.vectorize(labelmap.__getitem__)(labels & 0xFFFF)
labels = projection(labels, grid, order, H, W)
save_path = save_dir(label_path).replace(".label", ".png")
os.makedirs(osp.dirname(save_path), exist_ok=True)
labels = Image.fromarray(np.uint8(labels[..., 0]), mode="P")
labels.putpalette(palette)
labels.save(save_path)
def mean(tensor, dim):
tensor = tensor.clone()
kwargs = {"dim": dim, "keepdim": True}
valid = (~tensor.isnan()).float()
tensor[tensor.isnan()] = 0
tensor = torch.sum(tensor * valid, **kwargs) / valid.sum(**kwargs)
return tensor
@torch.no_grad()
def compute_avg_angles(loader):
max_depth = loader.dataset.max_depth
summary = defaultdict(float)
for item in tqdm(loader):
xyz_batch = item["xyz"]
x = xyz_batch[:, [0]]
y = xyz_batch[:, [1]]
z = xyz_batch[:, [2]]
depth = torch.sqrt(x ** 2 + y ** 2 + z ** 2) * max_depth
valid = (depth > 1e-8).float()
summary["total_data"] += len(valid)
summary["total_valid"] += valid.sum(dim=0) # (1,64,2048)
r = torch.sqrt(x ** 2 + y ** 2)
pitch = torch.atan2(z, r)
yaw = torch.atan2(y, x)
summary["pitch"] += torch.sum(pitch * valid, dim=0)
summary["yaw"] += torch.sum(yaw * valid, dim=0)
summary["pitch"] = summary["pitch"] / summary["total_valid"]
summary["yaw"] = summary["yaw"] / summary["total_valid"]
angles = torch.cat([summary["pitch"], summary["yaw"]], dim=0)
mean_pitch = mean(summary["pitch"], 2).expand_as(summary["pitch"])
mean_yaw = mean(summary["yaw"], 1).expand_as(summary["yaw"])
mean_angles = torch.cat([mean_pitch, mean_yaw], dim=0)
mean_valid = summary["total_valid"] / summary["total_data"]
valid = (mean_valid > 0).float()
angles[angles.isnan()] = 0.0
angles = valid * angles + (1 - valid) * mean_angles
assert angles.isnan().sum() == 0
return angles, mean_valid
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--root-dir", type=str, required=True)
args = parser.parse_args()
# 2D maps
split_dirs = sorted(glob(osp.join(args.root_dir, "dataset/sequences", "*")))
H, W = 64, 2048
for split_dir in tqdm(split_dirs):
point_paths = sorted(glob(osp.join(split_dir, "velodyne", "*.bin")))
joblib.Parallel(
n_jobs=multiprocessing.cpu_count(), verbose=10, pre_dispatch="all"
)(
[
joblib.delayed(process_point_clouds)(point_path, H, W)
for point_path in point_paths
]
)
# average angles
dataset = KITTIOdometry(
root=osp.join(args.root_dir, "dusty-gan"),
split="train",
shape=(H, W),
)
loader = torch.utils.data.DataLoader(
dataset,
batch_size=64,
num_workers=4,
drop_last=False,
)
N = len(dataset)
angles, valid = compute_avg_angles(loader)
torch.save(angles, osp.join(args.root_dir, "angles.pt"))