Skip to content

Commit 4614733

Browse files
Add Kitti object detection dataset (#128)
* Fix missing argument * more import bugfixes (#126) * fix missing attribute 'utils' * removed import statements for unused modules * fix conflict * add kitti dataset * address reviews Co-authored-by: Benjamin Ummenhofer <[email protected]>
1 parent b268eee commit 4614733

10 files changed

+276
-17
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ For eg.
173173
python scripts/semseg.py torch -c ml3d/configs/randlanet_semantickitti.yml --dataset.dataset_path <path-to-dataset> --dataset.use_cache True
174174
175175
# Launch testing for KPConv on Toronto3D with tensorflow.
176-
python scripts/semseg.py tf -c ml3d/configs/kpconv_toronto3d.yml --dataset.dataset_path <path-to-dataset> --model.ckpt_path <path-to-checkpoint>
176+
python scripts/semseg.py tf -c ml3d/configs/kpconv_toronto3d.yml --split test --dataset.dataset_path <path-to-dataset> --model.ckpt_path <path-to-checkpoint>
177177
```
178178
For further help, run `python scripts/semseg.py --help`.
179179

ml3d/datasets/__init__.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
from .toronto3d import Toronto3D
99
from .customdataset import Custom3D
1010
from .semantic3d import Semantic3D
11-
from .utils import *
11+
from .kitti import KITTI
12+
from . import utils
1213
__all__ = [
1314
'SemanticKITTI', 'S3DIS', 'Toronto3D', 'ParisLille3D', 'Semantic3D',
14-
'Custom3D', 'utils'
15+
'Custom3D', 'utils', 'KITTI'
1516
]

ml3d/datasets/customdataset.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import numpy as np
2-
import pandas as pd
32
import os, sys, glob, pickle
43
from pathlib import Path
54
from os.path import join, exists, dirname, abspath
6-
from tqdm import tqdm
75
import random
86
from plyfile import PlyData, PlyElement
97
from sklearn.neighbors import KDTree

ml3d/datasets/kitti.py

+271
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
import numpy as np
2+
import os, argparse, pickle, sys
3+
from os.path import exists, join, isfile, dirname, abspath, split
4+
from pathlib import Path
5+
from glob import glob
6+
import logging
7+
import yaml
8+
9+
from .base_dataset import BaseDataset
10+
from ..utils import Config, make_dir, DATASET
11+
12+
logging.basicConfig(
13+
level=logging.INFO,
14+
format='%(levelname)s - %(asctime)s - %(module)s - %(message)s',
15+
)
16+
log = logging.getLogger(__name__)
17+
18+
19+
class KITTI(BaseDataset):
20+
"""
21+
KITTI 3D dataset for Object Detection, used in visualizer, training, or test
22+
"""
23+
24+
def __init__(self,
25+
dataset_path,
26+
name='KITTI',
27+
cache_dir='./logs/cache',
28+
use_cache=False,
29+
val_split=3712,
30+
**kwargs):
31+
"""
32+
Initialize
33+
Args:
34+
dataset_path (str): path to the dataset
35+
kwargs:
36+
"""
37+
super().__init__(dataset_path=dataset_path,
38+
name=name,
39+
cache_dir=cache_dir,
40+
use_cache=use_cache,
41+
val_split=val_split,
42+
**kwargs)
43+
44+
cfg = self.cfg
45+
46+
self.name = cfg.name
47+
self.dataset_path = cfg.dataset_path
48+
self.num_classes = 3
49+
self.label_to_names = self.get_label_to_names()
50+
51+
self.all_files = glob(
52+
join(cfg.dataset_path, 'training', 'velodyne', '*.bin'))
53+
self.train_files = []
54+
self.val_files = []
55+
56+
for f in self.all_files:
57+
idx = int(Path(f).name.replace('.bin', ''))
58+
if idx < cfg.val_split:
59+
self.train_files.append(f)
60+
else:
61+
self.val_files.append(f)
62+
63+
self.test_files = glob(
64+
join(cfg.dataset_path, 'testing', 'velodyne', '*.bin'))
65+
66+
@staticmethod
67+
def get_label_to_names():
68+
label_to_names = {0: 'Car', 1: 'Pedestrian', 2: 'Cyclist', 3: 'Van'}
69+
return label_to_names
70+
71+
@staticmethod
72+
def read_lidar(path):
73+
assert Path(path).exists()
74+
75+
return np.fromfile(path, dtype=np.float32).reshape(-1, 4)
76+
77+
@staticmethod
78+
def read_label(path):
79+
if not Path(path).exists():
80+
return None
81+
82+
with open(path, 'r') as f:
83+
lines = f.readlines()
84+
objects = [Object3d(line) for line in lines]
85+
return objects
86+
87+
@staticmethod
88+
def read_calib(path):
89+
assert Path(path).exists()
90+
91+
with open(path, 'r') as f:
92+
lines = f.readlines()
93+
obj = lines[2].strip().split(' ')[1:]
94+
P2 = np.array(obj, dtype=np.float32)
95+
96+
obj = lines[3].strip().split(' ')[1:]
97+
P3 = np.array(obj, dtype=np.float32)
98+
99+
obj = lines[4].strip().split(' ')[1:]
100+
R0 = np.array(obj, dtype=np.float32)
101+
102+
obj = lines[5].strip().split(' ')[1:]
103+
Tr_velo_to_cam = np.array(obj, dtype=np.float32)
104+
105+
return {
106+
'P2': P2.reshape(3, 4),
107+
'P3': P3.reshape(3, 4),
108+
'R0': R0.reshape(3, 3),
109+
'Tr_velo2cam': Tr_velo_to_cam.reshape(3, 4)
110+
}
111+
112+
def get_split(self, split):
113+
return KITTISplit(self, split=split)
114+
115+
def get_split_list(self, split):
116+
cfg = self.cfg
117+
dataset_path = cfg.dataset_path
118+
file_list = []
119+
120+
if split in ['train', 'training']:
121+
return self.train_files
122+
seq_list = cfg.training_split
123+
elif split in ['test', 'testing']:
124+
return self.test_files
125+
elif split in ['val', 'validation']:
126+
return val_files
127+
elif split in ['all']:
128+
return self.train_files + self.val_files + self.test_files
129+
else:
130+
raise ValueError("Invalid split {}".format(split))
131+
132+
def is_tested():
133+
pass
134+
135+
def save_test_result():
136+
pass
137+
138+
139+
class KITTISplit():
140+
141+
def __init__(self, dataset, split='train'):
142+
self.cfg = dataset.cfg
143+
path_list = dataset.get_split_list(split)
144+
log.info("Found {} pointclouds for {}".format(len(path_list), split))
145+
146+
self.path_list = path_list
147+
self.split = split
148+
self.dataset = dataset
149+
150+
def __len__(self):
151+
return len(self.path_list)
152+
153+
def get_data(self, idx):
154+
pc_path = self.path_list[idx]
155+
label_path = pc_path.replace('velodyne',
156+
'label_2').replace('.bin', '.txt')
157+
calib_path = label_path.replace('label_2', 'calib')
158+
159+
pc = self.dataset.read_lidar(pc_path)
160+
label = self.dataset.read_label(label_path)
161+
calib = self.dataset.read_calib(calib_path)
162+
163+
data = {
164+
'point': pc,
165+
'feat': None,
166+
'calib': calib,
167+
'label': label,
168+
}
169+
170+
return data
171+
172+
def get_attr(self, idx):
173+
pc_path = self.path_list[idx]
174+
name = Path(pc_path).name.split('.')[0]
175+
176+
attr = {'name': name, 'path': pc_path, 'split': self.split}
177+
return attr
178+
179+
180+
class Object3d(object):
181+
"""
182+
Stores object specific details like bbox coordinates, occlusion etc.
183+
"""
184+
185+
def __init__(self, line):
186+
label = line.strip().split(' ')
187+
self.src = line
188+
self.cls_type = label[0]
189+
self.cls_id = self.cls_type_to_id(self.cls_type)
190+
self.truncation = float(label[1])
191+
self.occlusion = float(
192+
label[2]
193+
) # 0:fully visible 1:partly occluded 2:largely occluded 3:unknown
194+
self.alpha = float(label[3])
195+
self.box2d = np.array((float(label[4]), float(label[5]), float(
196+
label[6]), float(label[7])),
197+
dtype=np.float32)
198+
self.h = float(label[8])
199+
self.w = float(label[9])
200+
self.l = float(label[10])
201+
self.loc = np.array(
202+
(float(label[11]), float(label[12]), float(label[13])),
203+
dtype=np.float32)
204+
self.dis_to_cam = np.linalg.norm(self.loc)
205+
self.ry = float(label[14])
206+
self.score = float(label[15]) if label.__len__() == 16 else -1.0
207+
self.level_str = None
208+
self.level = self.get_kitti_obj_level()
209+
210+
@staticmethod
211+
def cls_type_to_id(cls_type):
212+
"""
213+
get object id from name.
214+
"""
215+
type_to_id = {'Car': 1, 'Pedestrian': 2, 'Cyclist': 3, 'Van': 4}
216+
if cls_type not in type_to_id.keys():
217+
return -1
218+
return type_to_id[cls_type]
219+
220+
def get_kitti_obj_level(self):
221+
"""
222+
determines the difficulty level of object.
223+
"""
224+
height = float(self.box2d[3]) - float(self.box2d[1]) + 1
225+
226+
if height >= 40 and self.truncation <= 0.15 and self.occlusion <= 0:
227+
self.level_str = 'Easy'
228+
return 0 # Easy
229+
elif height >= 25 and self.truncation <= 0.3 and self.occlusion <= 1:
230+
self.level_str = 'Moderate'
231+
return 1 # Moderate
232+
elif height >= 25 and self.truncation <= 0.5 and self.occlusion <= 2:
233+
self.level_str = 'Hard'
234+
return 2 # Hard
235+
else:
236+
self.level_str = 'UnKnown'
237+
return -1
238+
239+
def generate_corners3d(self):
240+
"""
241+
generate corners3d representation for this object
242+
:return corners_3d: (8, 3) corners of box3d in camera coord
243+
"""
244+
l, h, w = self.l, self.h, self.w
245+
x_corners = [l / 2, l / 2, -l / 2, -l / 2, l / 2, l / 2, -l / 2, -l / 2]
246+
y_corners = [0, 0, 0, 0, -h, -h, -h, -h]
247+
z_corners = [w / 2, -w / 2, -w / 2, w / 2, w / 2, -w / 2, -w / 2, w / 2]
248+
249+
R = np.array([[np.cos(self.ry), 0, np.sin(self.ry)], [0, 1, 0],
250+
[-np.sin(self.ry), 0,
251+
np.cos(self.ry)]])
252+
corners3d = np.vstack([x_corners, y_corners, z_corners]) # (3, 8)
253+
corners3d = np.dot(R, corners3d).T
254+
corners3d = corners3d + self.loc
255+
return corners3d
256+
257+
def to_str(self):
258+
print_str = '%s %.3f %.3f %.3f box2d: %s hwl: [%.3f %.3f %.3f] pos: %s ry: %.3f' \
259+
% (self.cls_type, self.truncation, self.occlusion, self.alpha, self.box2d, self.h, self.w, self.l,
260+
self.loc, self.ry)
261+
return print_str
262+
263+
def to_kitti_format(self):
264+
kitti_str = '%s %.2f %d %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f' \
265+
% (self.cls_type, self.truncation, int(self.occlusion), self.alpha, self.box2d[0], self.box2d[1],
266+
self.box2d[2], self.box2d[3], self.h, self.w, self.l, self.loc[0], self.loc[1], self.loc[2],
267+
self.ry)
268+
return kitti_str
269+
270+
271+
DATASET._register_module(KITTI)

ml3d/datasets/parislille3d.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import numpy as np
2-
import pandas as pd
32
import os, sys, glob, pickle
43
from pathlib import Path
54
from os.path import join, exists, dirname, abspath
6-
from tqdm import tqdm
75
import random
86
from plyfile import PlyData, PlyElement
97
from sklearn.neighbors import KDTree

ml3d/datasets/s3dis.py

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import os, sys, glob, pickle
44
from pathlib import Path
55
from os.path import join, exists, dirname, abspath
6-
from tqdm import tqdm
76
import random
87
from plyfile import PlyData, PlyElement
98
from sklearn.neighbors import KDTree

ml3d/datasets/semantic3d.py

-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
import os, sys, glob, pickle
44
from pathlib import Path
55
from os.path import join, exists, dirname, abspath
6-
from tqdm import tqdm
76
import random
87
from plyfile import PlyData, PlyElement
98
from sklearn.neighbors import KDTree
10-
from tqdm import tqdm
119
import logging
1210

1311
from .utils import DataProcessing as DP

ml3d/datasets/toronto3d.py

-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
import os, sys, glob, pickle
44
from pathlib import Path
55
from os.path import join, exists, dirname, abspath
6-
from tqdm import tqdm
76
import random
87
from plyfile import PlyData, PlyElement
98
from sklearn.neighbors import KDTree
10-
from tqdm import tqdm
119
import logging
1210

1311
from .base_dataset import BaseDataset

ml3d/tf/utils/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"""Utils for tensorflow networks."""
1+
"""Utils for tensorflow networks."""

version.txt

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
11
OPEN3D_VERSION_MAJOR 0
2-
<<<<<<< HEAD
32
OPEN3D_VERSION_MINOR 12
4-
=======
5-
OPEN3D_VERSION_MINOR 11
6-
>>>>>>> master
73
OPEN3D_VERSION_PATCH 0

0 commit comments

Comments
 (0)