Skip to content

Commit aeee3aa

Browse files
authored
Fix the bug in the eval code, and add ADD and ADD-S metric to the evaluation (#32)
1 parent aa667e6 commit aeee3aa

2 files changed

Lines changed: 79 additions & 17 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# Objectron Dataset
55

6-
**Objectron is a dataset of short object centeric video clips with pose annotations.**
6+
**Objectron is a dataset of short object centric video clips with pose annotations.**
77

88
<img src="docs/images/objectron_samples.gif" width="600px">
99

objectron/dataset/eval.py

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
a 3D bounding box.
77
88
Example:
9-
python3 -m objectron.dataset.eval --eval_data=.../chair_train* --report_file=.../report.txt
9+
python3 -m objectron.dataset.eval --eval_data=.../chair_test* --report_file=.../report.txt
1010
"""
1111

1212
import math
@@ -38,6 +38,7 @@
3838
_MAX_PIXEL_ERROR = 20.
3939
_MAX_AZIMUTH_ERROR = 30.
4040
_MAX_POLAR_ERROR = 20.
41+
_MAX_DISTANCE = 0.2 # In meters
4142
_NUM_BINS = 21
4243

4344
def safe_divide(i1, i2):
@@ -63,11 +64,15 @@ def __init__(self, height = 640, width = 480):
6364
self._azimuth_thresholds = np.linspace(
6465
0.0, _MAX_AZIMUTH_ERROR, num=_NUM_BINS)
6566
self._polar_thresholds = np.linspace(0.0, _MAX_POLAR_ERROR, num=_NUM_BINS)
67+
self._add_thresholds = np.linspace(0.0, _MAX_DISTANCE, num=_NUM_BINS)
68+
self._adds_thresholds = np.linspace(0.0, _MAX_DISTANCE, num=_NUM_BINS)
6669

6770
self._iou_ap = metrics.AveragePrecision(_NUM_BINS)
6871
self._pixel_ap = metrics.AveragePrecision(_NUM_BINS)
6972
self._azimuth_ap = metrics.AveragePrecision(_NUM_BINS)
7073
self._polar_ap = metrics.AveragePrecision(_NUM_BINS)
74+
self._add_ap = metrics.AveragePrecision(_NUM_BINS)
75+
self._adds_ap = metrics.AveragePrecision(_NUM_BINS)
7176

7277
#
7378
#
@@ -131,6 +136,7 @@ def evaluate(self, batch):
131136
if (visibility > self._vis_thresh and
132137
self._is_visible(instance[0]) and instance_3d[0, 2] < 0):
133138
num_instances += 1
139+
134140
# We don't have negative examples in evaluation.
135141
if num_instances == 0:
136142
continue
@@ -139,6 +145,8 @@ def evaluate(self, batch):
139145
azimuth_hit_miss = metrics.HitMiss(self._azimuth_thresholds)
140146
polar_hit_miss = metrics.HitMiss(self._polar_thresholds)
141147
pixel_hit_miss = metrics.HitMiss(self._pixel_thresholds)
148+
add_hit_miss = metrics.HitMiss(self._add_thresholds)
149+
adds_hit_miss = metrics.HitMiss(self._adds_thresholds)
142150

143151
num_matched = 0
144152
for box in boxes:
@@ -147,26 +155,36 @@ def evaluate(self, batch):
147155
if index >= 0:
148156
num_matched += 1
149157
pixel_error = self.evaluate_2d(box_point_2d, instances[index])
150-
151158
# If you only compute the 3D bounding boxes from RGB images,
152159
# your 3D keypoints may be upto scale. However the ground truth
153160
# is at metric scale. There is a hack to re-scale your box using
154161
# the ground planes (assuming your box is sitting on the ground).
155162
# However many models learn to predict depths and scale correctly.
156163
#scale = self.compute_scale(box_point_3d, plane)
157164
#box_point_3d = box_point_3d * scale
158-
azimuth_error, polar_error, iou = self.evaluate_3d(box_point_3d, instances_3d[index])
159-
iou_hit_miss.record_hit_miss(iou)
160-
pixel_hit_miss.record_hit_miss(pixel_error, greater=False)
161-
azimuth_hit_miss.record_hit_miss(azimuth_error, greater=False)
162-
polar_hit_miss.record_hit_miss(polar_error, greater=False)
163-
164-
if num_matched > 0:
165-
self._iou_ap.append(iou_hit_miss, num_instances)
166-
self._pixel_ap.append(pixel_hit_miss, num_instances)
167-
self._azimuth_ap.append(azimuth_hit_miss, num_instances)
168-
self._polar_ap.append(polar_hit_miss, num_instances)
169-
self._matched += num_matched
165+
azimuth_error, polar_error, iou, add, adds= self.evaluate_3d(box_point_3d, instances_3d[index])
166+
else:
167+
pixel_error = _MAX_PIXEL_ERROR
168+
azimuth_error = _MAX_AZIMUTH_ERROR
169+
polar_error = _MAX_POLAR_ERROR
170+
iou = 0.
171+
add = _MAX_DISTANCE
172+
adds = _MAX_DISTANCE
173+
174+
iou_hit_miss.record_hit_miss(iou)
175+
add_hit_miss.record_hit_miss(add)
176+
adds_hit_miss.record_hit_miss(adds)
177+
pixel_hit_miss.record_hit_miss(pixel_error, greater=False)
178+
azimuth_hit_miss.record_hit_miss(azimuth_error, greater=False)
179+
polar_hit_miss.record_hit_miss(polar_error, greater=False)
180+
181+
self._iou_ap.append(iou_hit_miss, len(instances))
182+
self._pixel_ap.append(pixel_hit_miss, len(instances))
183+
self._azimuth_ap.append(azimuth_hit_miss, len(instances))
184+
self._polar_ap.append(polar_hit_miss, len(instances))
185+
self._add_ap.append(add_hit_miss, len(instances))
186+
self._adds_ap.append(adds_hit_miss, len(instances))
187+
self._matched += num_matched
170188

171189
def evaluate_2d(self, box, instance):
172190
"""Evaluates a pair of 2D projections of 3D boxes.
@@ -194,11 +212,14 @@ def evaluate_3d(self, box_point_3d, instance):
194212
instance: A 9*3 array of an annotated box, in metric level.
195213
196214
Returns:
197-
The 3D IoU (float)
215+
A tuple containing the azimuth error, polar error, 3D IoU (float),
216+
average distance error, and average symmetric distance error.
198217
"""
199218
azimuth_error, polar_error = self.evaluate_viewpoint(box_point_3d, instance)
219+
avg_distance, avg_sym_distance = self.compute_average_distance(box_point_3d,
220+
instance)
200221
iou = self.evaluate_iou(box_point_3d, instance)
201-
return azimuth_error, polar_error, iou
222+
return azimuth_error, polar_error, iou, avg_distance, avg_sym_distance
202223

203224
def compute_scale(self, box, plane):
204225
"""Computes scale of the given box sitting on the plane."""
@@ -265,6 +286,31 @@ def compute_ray(self, box):
265286
transform = np.matmul(box_oct, box_cct_inv)
266287
return transform[:3, 3:].reshape((3))
267288

289+
def compute_average_distance(self, box, instance):
290+
"""Computes Average Distance (ADD) metric."""
291+
add_distance = 0.
292+
for i in range(Box.NUM_KEYPOINTS):
293+
delta = np.linalg.norm(box[i, :] - instance[i, :])
294+
add_distance += delta
295+
add_distance /= Box.NUM_KEYPOINTS
296+
297+
298+
# Computes the symmetric version of the average distance metric.
299+
# From PoseCNN https://arxiv.org/abs/1711.00199
300+
# For each keypoint in predicttion, search for the point in ground truth
301+
# that minimizes the distance between the two.
302+
add_sym_distance = 0.
303+
for i in range(Box.NUM_KEYPOINTS):
304+
# Find nearest vertex in instance
305+
distance = np.linalg.norm(box[i, :] - instance[0, :])
306+
for j in range(Box.NUM_KEYPOINTS):
307+
d = np.linalg.norm(box[i, :] - instance[j, :])
308+
if d < distance:
309+
distance = d
310+
add_sym_distance += distance
311+
add_sym_distance /= Box.NUM_KEYPOINTS
312+
313+
return add_distance, add_sym_distance
268314

269315
def compute_viewpoint(self, box):
270316
"""Computes viewpoint of a 3D bounding box.
@@ -454,13 +500,29 @@ def report_array(f, label, array):
454500
f.write('{:.4f},\t'.format(threshold * 0.1))
455501
f.write('\n')
456502
report_array(f, 'AP @Polar : ', self._polar_ap.aps)
503+
f.write('\n')
504+
505+
f.write('ADD Thresh : ')
506+
for threshold in self._add_thresholds:
507+
f.write('{:.4f},\t'.format(threshold))
508+
f.write('\n')
509+
report_array(f, 'AP @ADD : ', self._add_ap.aps)
510+
f.write('\n')
511+
512+
f.write('ADDS Thresh : ')
513+
for threshold in self._adds_thresholds:
514+
f.write('{:.4f},\t'.format(threshold))
515+
f.write('\n')
516+
report_array(f, 'AP @ADDS : ', self._adds_ap.aps)
457517

458518
def finalize(self):
459519
"""Computes average precision curves."""
460520
self._iou_ap.compute_ap_curve()
461521
self._pixel_ap.compute_ap_curve()
462522
self._azimuth_ap.compute_ap_curve()
463523
self._polar_ap.compute_ap_curve()
524+
self._add_ap.compute_ap_curve()
525+
self._adds_ap.compute_ap_curve()
464526

465527
def _is_visible(self, point):
466528
"""Determines if a 2D point is visible."""

0 commit comments

Comments
 (0)