-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathconfusion_matrix.py
97 lines (75 loc) · 3.77 KB
/
confusion_matrix.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
import numpy as np
def box_iou_calc(boxes1, boxes2):
# https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
"""
Return intersection-over-union (Jaccard index) of boxes.
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
Arguments:
boxes1 (Array[N, 4])
boxes2 (Array[M, 4])
Returns:
iou (Array[N, M]): the NxM matrix containing the pairwise
IoU values for every element in boxes1 and boxes2
This implementation is taken from the above link and changed so that it only uses numpy..
"""
def box_area(box):
# box = 4xn
return (box[2] - box[0]) * (box[3] - box[1])
area1 = box_area(boxes1.T)
area2 = box_area(boxes2.T)
lt = np.maximum(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2]
rb = np.minimum(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2]
inter = np.prod(np.clip(rb - lt, a_min=0, a_max=None), 2)
return inter / (area1[:, None] + area2 - inter) # iou = inter / (area1 + area2 - inter)
class ConfusionMatrix:
def __init__(self, num_classes: int, CONF_THRESHOLD=0.3, IOU_THRESHOLD=0.5):
self.matrix = np.zeros((num_classes + 1, num_classes + 1))
self.num_classes = num_classes
self.CONF_THRESHOLD = CONF_THRESHOLD
self.IOU_THRESHOLD = IOU_THRESHOLD
def process_batch(self, detections, labels: np.ndarray):
"""
Return intersection-over-union (Jaccard index) of boxes.
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
Arguments:
detections (Array[N, 6]), x1, y1, x2, y2, conf, class
labels (Array[M, 5]), class, x1, y1, x2, y2
Returns:
None, updates confusion matrix accordingly
"""
gt_classes = labels[:, 0].astype(np.int16)
try:
detections = detections[detections[:, 4] > self.CONF_THRESHOLD]
except IndexError or TypeError:
# detections are empty, end of process
for i, label in enumerate(labels):
gt_class = gt_classes[i]
self.matrix[self.num_classes, gt_class] += 1
return
detection_classes = detections[:, 5].astype(np.int16)
all_ious = box_iou_calc(labels[:, 1:], detections[:, :4])
want_idx = np.where(all_ious > self.IOU_THRESHOLD)
all_matches = [[want_idx[0][i], want_idx[1][i], all_ious[want_idx[0][i], want_idx[1][i]]]
for i in range(want_idx[0].shape[0])]
all_matches = np.array(all_matches)
if all_matches.shape[0] > 0: # if there is match
all_matches = all_matches[all_matches[:, 2].argsort()[::-1]]
all_matches = all_matches[np.unique(all_matches[:, 1], return_index=True)[1]]
all_matches = all_matches[all_matches[:, 2].argsort()[::-1]]
all_matches = all_matches[np.unique(all_matches[:, 0], return_index=True)[1]]
for i, label in enumerate(labels):
gt_class = gt_classes[i]
if all_matches.shape[0] > 0 and all_matches[all_matches[:, 0] == i].shape[0] == 1:
detection_class = detection_classes[int(all_matches[all_matches[:, 0] == i, 1][0])]
self.matrix[detection_class, gt_class] += 1
else:
self.matrix[self.num_classes, gt_class] += 1
for i, detection in enumerate(detections):
if not all_matches.shape[0] or ( all_matches.shape[0] and all_matches[all_matches[:, 1] == i].shape[0] == 0 ):
detection_class = detection_classes[i]
self.matrix[detection_class, self.num_classes] += 1
def return_matrix(self):
return self.matrix
def print_matrix(self):
for i in range(self.num_classes + 1):
print(' '.join(map(str, self.matrix[i])))