66a 3D bounding box.
77
88Example:
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
1212import math
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
4344def 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