indoor_eval.py 10.5 KB
Newer Older
dingchang's avatar
dingchang committed
1
# Copyright (c) OpenMMLab. All rights reserved.
yinchimaoliang's avatar
yinchimaoliang committed
2
3
import numpy as np
import torch
zhangwenwei's avatar
zhangwenwei committed
4
5
from mmcv.utils import print_log
from terminaltables import AsciiTable
yinchimaoliang's avatar
yinchimaoliang committed
6

7

liyinhao's avatar
liyinhao committed
8
9
def average_precision(recalls, precisions, mode='area'):
    """Calculate average precision (for single or multiple scales).
yinchimaoliang's avatar
yinchimaoliang committed
10
11

    Args:
12
        recalls (np.ndarray): Recalls with shape of (num_scales, num_dets)
wangtai's avatar
wangtai committed
13
            or (num_dets, ).
14
        precisions (np.ndarray): Precisions with shape of
wangtai's avatar
wangtai committed
15
            (num_scales, num_dets) or (num_dets, ).
liyinhao's avatar
liyinhao committed
16
17
18
        mode (str): 'area' or '11points', 'area' means calculating the area
            under precision-recall curve, '11points' means calculating
            the average precision of recalls at [0, 0.1, ..., 1]
yinchimaoliang's avatar
yinchimaoliang committed
19
20

    Returns:
wangtai's avatar
wangtai committed
21
        float or np.ndarray: Calculated average precision.
yinchimaoliang's avatar
yinchimaoliang committed
22
    """
liyinhao's avatar
liyinhao committed
23
24
25
    if recalls.ndim == 1:
        recalls = recalls[np.newaxis, :]
        precisions = precisions[np.newaxis, :]
wuyuefeng's avatar
wuyuefeng committed
26
27
28
29

    assert recalls.shape == precisions.shape
    assert recalls.ndim == 2

liyinhao's avatar
liyinhao committed
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
    num_scales = recalls.shape[0]
    ap = np.zeros(num_scales, dtype=np.float32)
    if mode == 'area':
        zeros = np.zeros((num_scales, 1), dtype=recalls.dtype)
        ones = np.ones((num_scales, 1), dtype=recalls.dtype)
        mrec = np.hstack((zeros, recalls, ones))
        mpre = np.hstack((zeros, precisions, zeros))
        for i in range(mpre.shape[1] - 1, 0, -1):
            mpre[:, i - 1] = np.maximum(mpre[:, i - 1], mpre[:, i])
        for i in range(num_scales):
            ind = np.where(mrec[i, 1:] != mrec[i, :-1])[0]
            ap[i] = np.sum(
                (mrec[i, ind + 1] - mrec[i, ind]) * mpre[i, ind + 1])
    elif mode == '11points':
        for i in range(num_scales):
            for thr in np.arange(0, 1 + 1e-3, 0.1):
                precs = precisions[i, recalls[i, :] >= thr]
                prec = precs.max() if precs.size > 0 else 0
                ap[i] += prec
            ap /= 11
yinchimaoliang's avatar
yinchimaoliang committed
50
    else:
liyinhao's avatar
liyinhao committed
51
52
        raise ValueError(
            'Unrecognized mode, only "area" and "11points" are supported')
yinchimaoliang's avatar
yinchimaoliang committed
53
54
55
    return ap


56
def eval_det_cls(pred, gt, iou_thr=None):
zhangwenwei's avatar
zhangwenwei committed
57
58
    """Generic functions to compute precision/recall for object detection for a
    single class.
liyinhao's avatar
liyinhao committed
59
60

    Args:
61
        pred (dict): Predictions mapping from image id to bounding boxes
wangtai's avatar
wangtai committed
62
63
64
            and scores.
        gt (dict): Ground truths mapping from image id to bounding boxes.
        iou_thr (list[float]): A list of iou thresholds.
liyinhao's avatar
liyinhao committed
65
66

    Return:
67
        tuple (np.ndarray, np.ndarray, float): Recalls, precisions and
wangtai's avatar
wangtai committed
68
            average precision.
yinchimaoliang's avatar
yinchimaoliang committed
69
70
    """

wuyuefeng's avatar
wuyuefeng committed
71
72
    # {img_id: {'bbox': box structure, 'det': matched list}}
    class_recs = {}
yinchimaoliang's avatar
yinchimaoliang committed
73
74
    npos = 0
    for img_id in gt.keys():
wuyuefeng's avatar
wuyuefeng committed
75
76
77
78
79
80
81
82
        cur_gt_num = len(gt[img_id])
        if cur_gt_num != 0:
            gt_cur = torch.zeros([cur_gt_num, 7], dtype=torch.float32)
            for i in range(cur_gt_num):
                gt_cur[i] = gt[img_id][i].tensor
            bbox = gt[img_id][0].new_box(gt_cur)
        else:
            bbox = gt[img_id]
83
        det = [[False] * len(bbox) for i in iou_thr]
yinchimaoliang's avatar
yinchimaoliang committed
84
85
86
87
88
89
90
91
92
93
94
        npos += len(bbox)
        class_recs[img_id] = {'bbox': bbox, 'det': det}

    # construct dets
    image_ids = []
    confidence = []
    ious = []
    for img_id in pred.keys():
        cur_num = len(pred[img_id])
        if cur_num == 0:
            continue
wuyuefeng's avatar
wuyuefeng committed
95
        pred_cur = torch.zeros((cur_num, 7), dtype=torch.float32)
yinchimaoliang's avatar
yinchimaoliang committed
96
97
98
99
        box_idx = 0
        for box, score in pred[img_id]:
            image_ids.append(img_id)
            confidence.append(score)
wuyuefeng's avatar
wuyuefeng committed
100
            pred_cur[box_idx] = box.tensor
yinchimaoliang's avatar
yinchimaoliang committed
101
            box_idx += 1
wuyuefeng's avatar
wuyuefeng committed
102
103
        pred_cur = box.new_box(pred_cur)
        gt_cur = class_recs[img_id]['bbox']
yinchimaoliang's avatar
yinchimaoliang committed
104
105
        if len(gt_cur) > 0:
            # calculate iou in each image
wuyuefeng's avatar
wuyuefeng committed
106
            iou_cur = pred_cur.overlaps(pred_cur, gt_cur)
yinchimaoliang's avatar
yinchimaoliang committed
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
            for i in range(cur_num):
                ious.append(iou_cur[i])
        else:
            for i in range(cur_num):
                ious.append(np.zeros(1))

    confidence = np.array(confidence)

    # sort by confidence
    sorted_ind = np.argsort(-confidence)
    image_ids = [image_ids[x] for x in sorted_ind]
    ious = [ious[x] for x in sorted_ind]

    # go down dets and mark TPs and FPs
    nd = len(image_ids)
122
123
    tp_thr = [np.zeros(nd) for i in iou_thr]
    fp_thr = [np.zeros(nd) for i in iou_thr]
yinchimaoliang's avatar
yinchimaoliang committed
124
125
    for d in range(nd):
        R = class_recs[image_ids[d]]
126
        iou_max = -np.inf
wuyuefeng's avatar
wuyuefeng committed
127
        BBGT = R['bbox']
yinchimaoliang's avatar
yinchimaoliang committed
128
129
        cur_iou = ious[d]

wuyuefeng's avatar
wuyuefeng committed
130
        if len(BBGT) > 0:
yinchimaoliang's avatar
yinchimaoliang committed
131
            # compute overlaps
wuyuefeng's avatar
wuyuefeng committed
132
            for j in range(len(BBGT)):
yinchimaoliang's avatar
yinchimaoliang committed
133
134
                # iou = get_iou_main(get_iou_func, (bb, BBGT[j,...]))
                iou = cur_iou[j]
135
136
                if iou > iou_max:
                    iou_max = iou
yinchimaoliang's avatar
yinchimaoliang committed
137
138
                    jmax = j

139
140
        for iou_idx, thresh in enumerate(iou_thr):
            if iou_max > thresh:
yinchimaoliang's avatar
yinchimaoliang committed
141
                if not R['det'][iou_idx][jmax]:
142
                    tp_thr[iou_idx][d] = 1.
yinchimaoliang's avatar
yinchimaoliang committed
143
144
                    R['det'][iou_idx][jmax] = 1
                else:
145
                    fp_thr[iou_idx][d] = 1.
yinchimaoliang's avatar
yinchimaoliang committed
146
            else:
147
                fp_thr[iou_idx][d] = 1.
yinchimaoliang's avatar
yinchimaoliang committed
148
149

    ret = []
150
    for iou_idx, thresh in enumerate(iou_thr):
yinchimaoliang's avatar
yinchimaoliang committed
151
        # compute precision recall
152
153
        fp = np.cumsum(fp_thr[iou_idx])
        tp = np.cumsum(tp_thr[iou_idx])
154
        recall = tp / float(npos)
yinchimaoliang's avatar
yinchimaoliang committed
155
156
        # avoid divide by zero in case the first detection matches a difficult
        # ground truth
157
158
159
        precision = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
        ap = average_precision(recall, precision)
        ret.append((recall, precision, ap))
yinchimaoliang's avatar
yinchimaoliang committed
160
161
162
163

    return ret


wuyuefeng's avatar
wuyuefeng committed
164
def eval_map_recall(pred, gt, ovthresh=None):
liyinhao's avatar
liyinhao committed
165
    """Evaluate mAP and recall.
yinchimaoliang's avatar
yinchimaoliang committed
166
167

    Generic functions to compute precision/recall for object detection
yinchimaoliang's avatar
yinchimaoliang committed
168
        for multiple classes.
yinchimaoliang's avatar
yinchimaoliang committed
169
170

    Args:
wuyuefeng's avatar
wuyuefeng committed
171
172
        pred (dict): Information of detection results,
            which maps class_id and predictions.
173
        gt (dict): Information of ground truths, which maps class_id and
wangtai's avatar
wangtai committed
174
            ground truths.
175
        ovthresh (list[float], optional): iou threshold. Default: None.
yinchimaoliang's avatar
yinchimaoliang committed
176
177

    Return:
liyinhao's avatar
liyinhao committed
178
        tuple[dict]: dict results of recall, AP, and precision for all classes.
yinchimaoliang's avatar
yinchimaoliang committed
179
180
    """

181
    ret_values = {}
182
183
    for classname in gt.keys():
        if classname in pred:
184
185
            ret_values[classname] = eval_det_cls(pred[classname],
                                                 gt[classname], ovthresh)
186
187
    recall = [{} for i in ovthresh]
    precision = [{} for i in ovthresh]
yinchimaoliang's avatar
yinchimaoliang committed
188
189
    ap = [{} for i in ovthresh]

190
    for label in gt.keys():
yinchimaoliang's avatar
yinchimaoliang committed
191
        for iou_idx, thresh in enumerate(ovthresh):
liyinhao's avatar
liyinhao committed
192
            if label in pred:
193
                recall[iou_idx][label], precision[iou_idx][label], ap[iou_idx][
194
                    label] = ret_values[label][iou_idx]
yinchimaoliang's avatar
yinchimaoliang committed
195
            else:
zhangwenwei's avatar
zhangwenwei committed
196
197
198
                recall[iou_idx][label] = np.zeros(1)
                precision[iou_idx][label] = np.zeros(1)
                ap[iou_idx][label] = np.zeros(1)
yinchimaoliang's avatar
yinchimaoliang committed
199

200
    return recall, precision, ap
yinchimaoliang's avatar
yinchimaoliang committed
201
202


wuyuefeng's avatar
wuyuefeng committed
203
204
205
206
207
208
def indoor_eval(gt_annos,
                dt_annos,
                metric,
                label2cat,
                logger=None,
                box_mode_3d=None):
liyinhao's avatar
liyinhao committed
209
    """Indoor Evaluation.
yinchimaoliang's avatar
yinchimaoliang committed
210
211
212
213

    Evaluate the result of the detection.

    Args:
214
        gt_annos (list[dict]): Ground truth annotations.
wuyuefeng's avatar
wuyuefeng committed
215
216
        dt_annos (list[dict]): Detection annotations. the dict
            includes the following keys
217

liyinhao's avatar
liyinhao committed
218
            - labels_3d (torch.Tensor): Labels of boxes.
jshilong's avatar
jshilong committed
219
            - bboxes_3d (:obj:`BaseInstance3DBoxes`):
220
                3D bounding boxes in Depth coordinate.
liyinhao's avatar
liyinhao committed
221
            - scores_3d (torch.Tensor): Scores of boxes.
222
        metric (list[float]): IoU thresholds for computing average precisions.
jshilong's avatar
jshilong committed
223
        label2cat (tuple): Map from label to category.
224
        logger (logging.Logger | str, optional): The way to print the mAP
zhangwenwei's avatar
zhangwenwei committed
225
            summary. See `mmdet.utils.print_log()` for details. Default: None.
yinchimaoliang's avatar
yinchimaoliang committed
226
227

    Return:
228
        dict[str, float]: Dict of results.
yinchimaoliang's avatar
yinchimaoliang committed
229
    """
wuyuefeng's avatar
wuyuefeng committed
230
231
232
233
234
235
236
237
    assert len(dt_annos) == len(gt_annos)
    pred = {}  # map {class_id: pred}
    gt = {}  # map {class_id: gt}
    for img_id in range(len(dt_annos)):
        # parse detected annotations
        det_anno = dt_annos[img_id]
        for i in range(len(det_anno['labels_3d'])):
            label = det_anno['labels_3d'].numpy()[i]
jshilong's avatar
jshilong committed
238
            bbox = det_anno['bboxes_3d'].convert_to(box_mode_3d)[i]
wuyuefeng's avatar
wuyuefeng committed
239
240
241
242
243
244
245
246
247
248
249
250
251
            score = det_anno['scores_3d'].numpy()[i]
            if label not in pred:
                pred[int(label)] = {}
            if img_id not in pred[label]:
                pred[int(label)][img_id] = []
            if label not in gt:
                gt[int(label)] = {}
            if img_id not in gt[label]:
                gt[int(label)][img_id] = []
            pred[int(label)][img_id].append((bbox, score))

        # parse gt annotations
        gt_anno = gt_annos[img_id]
jshilong's avatar
jshilong committed
252
253
254

        gt_boxes = gt_anno['gt_bboxes_3d']
        labels_3d = gt_anno['gt_labels_3d']
wuyuefeng's avatar
wuyuefeng committed
255
256
257
258
259
260
261
262
263

        for i in range(len(labels_3d)):
            label = labels_3d[i]
            bbox = gt_boxes[i]
            if label not in gt:
                gt[label] = {}
            if img_id not in gt[label]:
                gt[label][img_id] = []
            gt[label][img_id].append(bbox)
liyinhao's avatar
liyinhao committed
264

wuyuefeng's avatar
wuyuefeng committed
265
    rec, prec, ap = eval_map_recall(pred, gt, metric)
zhangwenwei's avatar
zhangwenwei committed
266
267
268
269
270
    ret_dict = dict()
    header = ['classes']
    table_columns = [[label2cat[label]
                      for label in ap[0].keys()] + ['Overall']]

liyinhao's avatar
liyinhao committed
271
    for i, iou_thresh in enumerate(metric):
zhangwenwei's avatar
zhangwenwei committed
272
273
        header.append(f'AP_{iou_thresh:.2f}')
        header.append(f'AR_{iou_thresh:.2f}')
liyinhao's avatar
liyinhao committed
274
        rec_list = []
liyinhao's avatar
liyinhao committed
275
        for label in ap[i].keys():
wuyuefeng's avatar
wuyuefeng committed
276
277
278
279
            ret_dict[f'{label2cat[label]}_AP_{iou_thresh:.2f}'] = float(
                ap[i][label][0])
        ret_dict[f'mAP_{iou_thresh:.2f}'] = float(
            np.mean(list(ap[i].values())))
zhangwenwei's avatar
zhangwenwei committed
280
281
282
283
284

        table_columns.append(list(map(float, list(ap[i].values()))))
        table_columns[-1] += [ret_dict[f'mAP_{iou_thresh:.2f}']]
        table_columns[-1] = [f'{x:.4f}' for x in table_columns[-1]]

liyinhao's avatar
liyinhao committed
285
        for label in rec[i].keys():
wuyuefeng's avatar
wuyuefeng committed
286
287
            ret_dict[f'{label2cat[label]}_rec_{iou_thresh:.2f}'] = float(
                rec[i][label][-1])
liyinhao's avatar
liyinhao committed
288
            rec_list.append(rec[i][label][-1])
wuyuefeng's avatar
wuyuefeng committed
289
        ret_dict[f'mAR_{iou_thresh:.2f}'] = float(np.mean(rec_list))
zhangwenwei's avatar
zhangwenwei committed
290
291
292
293
294
295
296
297
298
299
300
301

        table_columns.append(list(map(float, rec_list)))
        table_columns[-1] += [ret_dict[f'mAR_{iou_thresh:.2f}']]
        table_columns[-1] = [f'{x:.4f}' for x in table_columns[-1]]

    table_data = [header]
    table_rows = list(zip(*table_columns))
    table_data += table_rows
    table = AsciiTable(table_data)
    table.inner_footing_row_border = True
    print_log('\n' + table.table, logger=logger)

liyinhao's avatar
liyinhao committed
302
    return ret_dict