Spaces:
Sleeping
Sleeping
[email protected]
commited on
Commit
·
930daa4
1
Parent(s):
6b2433c
metric works, not fully integrated
Browse files- __pycache__/box_metrics.cpython-39.pyc +0 -0
- __pycache__/utils.cpython-39.pyc +0 -0
- box_metrics.py +229 -0
- compute.py +88 -0
- test.py +26 -0
- utils.py +173 -0
__pycache__/box_metrics.cpython-39.pyc
ADDED
Binary file (5.9 kB). View file
|
|
__pycache__/utils.cpython-39.pyc
ADDED
Binary file (4.85 kB). View file
|
|
box_metrics.py
ADDED
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import evaluate
|
2 |
+
import datasets
|
3 |
+
import motmetrics as mm
|
4 |
+
import numpy as np
|
5 |
+
from seametrics.payload import Payload
|
6 |
+
import torch
|
7 |
+
from utils import bbox_iou, bbox_bep
|
8 |
+
import datasets
|
9 |
+
|
10 |
+
# _DESCRIPTION = """\
|
11 |
+
# The box-metrics package provides a set of metrics to evaluate
|
12 |
+
# the performance of object detection algorithms in ther of sizing and positioning
|
13 |
+
# of the bounding boxes."""
|
14 |
+
|
15 |
+
# _KWARGS_DESCRIPTION = """
|
16 |
+
# Calculates how good are predictions given some references, using certain scores
|
17 |
+
# Args:
|
18 |
+
# predictions: list of predictions to score. Each predictions
|
19 |
+
# should be a string with tokens separated by spaces.
|
20 |
+
# references: list of reference for each prediction. Each
|
21 |
+
# reference should be a string with tokens separated by spaces.
|
22 |
+
# max_iou (`float`, *optional*):
|
23 |
+
# If specified, this is the minimum Intersection over Union (IoU) threshold to consider a detection as a true positive.
|
24 |
+
# Default is 0.5.
|
25 |
+
# """
|
26 |
+
|
27 |
+
# _CITATION = """\
|
28 |
+
# @InProceedings{huggingface:module,
|
29 |
+
# title = {A great new module},
|
30 |
+
# authors={huggingface, Inc.},
|
31 |
+
# year={2020}
|
32 |
+
# }\
|
33 |
+
# @article{milan2016mot16,
|
34 |
+
# title={Are object detection assessment criteria ready for maritime computer vision?},
|
35 |
+
# author={Dilip K. Prasad1, Deepu Rajan and Chai Quek},
|
36 |
+
# journal={arXiv:1809.04659v1},
|
37 |
+
# year={2018}
|
38 |
+
# }
|
39 |
+
# """
|
40 |
+
|
41 |
+
_CITATION = """\
|
42 |
+
@InProceedings{huggingface:module,
|
43 |
+
title = {A great new module},
|
44 |
+
authors={huggingface, Inc.},
|
45 |
+
year={2020}
|
46 |
+
}\
|
47 |
+
@article{milan2016mot16,
|
48 |
+
title={MOT16: A benchmark for multi-object tracking},
|
49 |
+
author={Milan, Anton and Leal-Taix{\'e}, Laura and Reid, Ian and Roth, Stefan and Schindler, Konrad},
|
50 |
+
journal={arXiv preprint arXiv:1603.00831},
|
51 |
+
year={2016}
|
52 |
+
}
|
53 |
+
"""
|
54 |
+
|
55 |
+
_DESCRIPTION = """\
|
56 |
+
The MOT Metrics module is designed to evaluate multi-object tracking (MOT)
|
57 |
+
algorithms by computing various metrics based on predicted and ground truth bounding
|
58 |
+
boxes. It serves as a crucial tool in assessing the performance of MOT systems,
|
59 |
+
aiding in the iterative improvement of tracking algorithms."""
|
60 |
+
|
61 |
+
|
62 |
+
_KWARGS_DESCRIPTION = """
|
63 |
+
Calculates how good are predictions given some references, using certain scores
|
64 |
+
Args:
|
65 |
+
predictions: list of predictions to score. Each predictions
|
66 |
+
should be a string with tokens separated by spaces.
|
67 |
+
references: list of reference for each prediction. Each
|
68 |
+
reference should be a string with tokens separated by spaces.
|
69 |
+
max_iou (`float`, *optional*):
|
70 |
+
If specified, this is the minimum Intersection over Union (IoU) threshold to consider a detection as a true positive.
|
71 |
+
Default is 0.5.
|
72 |
+
"""
|
73 |
+
|
74 |
+
# @evaluate.utils.file_utils.add_start_docstrings(_DESCRIPTION, _KWARGS_DESCRIPTION)
|
75 |
+
class BoxMetrics(evaluate.Metric):
|
76 |
+
|
77 |
+
def __init__(self, max_iou: float = 0.01, **kwargs):
|
78 |
+
# super().__init__(**kwargs)
|
79 |
+
self.max_iou = max_iou
|
80 |
+
self.boxes = {}
|
81 |
+
self.gt_field = "ground_truth_det"
|
82 |
+
|
83 |
+
|
84 |
+
def _info(self):
|
85 |
+
# TODO: Specifies the evaluate.EvaluationModuleInfo object
|
86 |
+
return evaluate.MetricInfo(
|
87 |
+
# This is the description that will appear on the modules page.
|
88 |
+
module_type="metric",
|
89 |
+
description=_DESCRIPTION,
|
90 |
+
citation=_CITATION,
|
91 |
+
inputs_description=_KWARGS_DESCRIPTION,
|
92 |
+
# This defines the format of each prediction and reference
|
93 |
+
features=datasets.Features({
|
94 |
+
"predictions": datasets.Sequence(
|
95 |
+
datasets.Sequence(datasets.Value("float"))
|
96 |
+
),
|
97 |
+
"references": datasets.Sequence(
|
98 |
+
datasets.Sequence(datasets.Value("float"))
|
99 |
+
)
|
100 |
+
}),
|
101 |
+
# Additional links to the codebase or references
|
102 |
+
codebase_urls=["http://github.com/path/to/codebase/of/new_module"],
|
103 |
+
reference_urls=["http://path.to.reference.url/new_module"]
|
104 |
+
)
|
105 |
+
|
106 |
+
|
107 |
+
def add_payload(self, payload: Payload):
|
108 |
+
"""Convert a payload to the format of the tracking metrics library"""
|
109 |
+
self.add(payload)
|
110 |
+
|
111 |
+
def add(self, payload: Payload):
|
112 |
+
self.gt_field = payload.gt_field_name
|
113 |
+
for sequence in payload.sequences:
|
114 |
+
self.boxes[sequence] = {}
|
115 |
+
target = payload.sequences[sequence][self.gt_field]
|
116 |
+
resolution = payload.sequences[sequence]["resolution"]
|
117 |
+
target_tm = self.payload_labels_to_tm(target, resolution)
|
118 |
+
self.boxes[sequence][self.gt_field] = target_tm
|
119 |
+
|
120 |
+
for model in payload.models:
|
121 |
+
preds = payload.sequences[sequence][model]
|
122 |
+
preds_tm = self.payload_preds_to_rm(preds, resolution)
|
123 |
+
self.boxes[sequence][model] = preds_tm
|
124 |
+
|
125 |
+
def compute(self):
|
126 |
+
"""Compute the metric value"""
|
127 |
+
|
128 |
+
output = {}
|
129 |
+
|
130 |
+
for sequence in self.boxes:
|
131 |
+
ious = []
|
132 |
+
beps = []
|
133 |
+
bottom_x = []
|
134 |
+
bottom_y = []
|
135 |
+
widths = []
|
136 |
+
heights = []
|
137 |
+
output[sequence] = {}
|
138 |
+
|
139 |
+
target = self.boxes[sequence][self.gt_field]
|
140 |
+
for model in self.boxes[sequence]:
|
141 |
+
preds = self.boxes[sequence][model]
|
142 |
+
|
143 |
+
for i in range(len(preds)):
|
144 |
+
|
145 |
+
target_tm_bbs = target[i][:, 1:]
|
146 |
+
pred_tm_bbs = preds[i][:, :4]
|
147 |
+
|
148 |
+
if target_tm_bbs.shape[0] == 0 or pred_tm_bbs.shape[0] == 0:
|
149 |
+
continue
|
150 |
+
|
151 |
+
for t_box in target_tm_bbs:
|
152 |
+
iou = bbox_iou(t_box.unsqueeze(0), pred_tm_bbs, xywh=False)
|
153 |
+
bep = bbox_bep(t_box.unsqueeze(0), pred_tm_bbs, xywh=False)
|
154 |
+
matches = pred_tm_bbs[iou.squeeze(1) > self.max_iou]
|
155 |
+
|
156 |
+
bep = bep[iou>self.max_iou]
|
157 |
+
iou = iou[iou>self.max_iou]
|
158 |
+
|
159 |
+
if torch.any(iou <= 0):
|
160 |
+
raise ValueError("IoU should be greater than 0, pls contact code maintainer")
|
161 |
+
if torch.any(bep <= 0):
|
162 |
+
raise ValueError("BEP should be greater than 0, pls contact code maintainer")
|
163 |
+
|
164 |
+
ious.extend(iou.tolist())
|
165 |
+
beps.extend(bep.tolist())
|
166 |
+
|
167 |
+
for match in matches:
|
168 |
+
t_xc = (match[0].item()+match[2].item())/2
|
169 |
+
p_xc = (t_box[0].item()+t_box[2].item())/2
|
170 |
+
t_w = t_box[2].item()-t_box[0].item()
|
171 |
+
p_w = match[2].item()-match[0].item()
|
172 |
+
t_h = t_box[3].item()-t_box[1].item()
|
173 |
+
p_h = match[3].item()-match[1].item()
|
174 |
+
|
175 |
+
|
176 |
+
bottom_x.append(abs(t_xc-p_xc))
|
177 |
+
widths.append(abs(t_w-p_w))
|
178 |
+
bottom_y.append(abs(t_box[1].item()-match[1].item()))
|
179 |
+
heights.append(abs(t_h-p_h))
|
180 |
+
|
181 |
+
output[sequence][model] = {
|
182 |
+
"iou_mean": np.mean(ious),
|
183 |
+
"bep_mean": np.mean(beps),
|
184 |
+
"bottom_x_mean": np.mean(bottom_x),
|
185 |
+
"bottom_y_mean": np.mean(bottom_y),
|
186 |
+
"width_mean": np.mean(widths),
|
187 |
+
"height_mean": np.mean(heights),
|
188 |
+
"bottom_x_std": np.std(bottom_x),
|
189 |
+
"bottom_y_std": np.std(bottom_y),
|
190 |
+
"width_std": np.std(widths),
|
191 |
+
"height_std": np.std(heights)
|
192 |
+
}
|
193 |
+
return output
|
194 |
+
|
195 |
+
@staticmethod
|
196 |
+
def payload_labels_to_tm(labels, resolution):
|
197 |
+
"""Convert the labels of a payload sequence to the format of torch metrics"""
|
198 |
+
target_tm = []
|
199 |
+
for frame in labels:
|
200 |
+
target_tm_frame = []
|
201 |
+
for det in frame:
|
202 |
+
label = 0
|
203 |
+
box = det["bounding_box"]
|
204 |
+
x1, y1, x2, y2 = box[0], box[1], box[0]+box[2], box[1]+box[3]
|
205 |
+
x1, y1, x2, y2 = x1*resolution.width, y1*resolution.height, x2*resolution.width, y2*resolution.height
|
206 |
+
target_tm_frame.append([label, x1, y1, x2, y2])
|
207 |
+
target_tm.append(torch.tensor(target_tm_frame) if len(target_tm_frame) > 0 else torch.empty((0, 5)))
|
208 |
+
|
209 |
+
return target_tm
|
210 |
+
|
211 |
+
@staticmethod
|
212 |
+
def payload_preds_to_rm(preds, resolution):
|
213 |
+
"""Convert the predictions of a payload sequence to the format of torch metrics"""
|
214 |
+
preds_tm = []
|
215 |
+
for frame in preds:
|
216 |
+
pred_tm_frame = []
|
217 |
+
for det in frame:
|
218 |
+
label = 0
|
219 |
+
box = det["bounding_box"]
|
220 |
+
x1, y1, x2, y2 = box[0], box[1], box[0]+box[2], box[1]+box[3]
|
221 |
+
x1, y1, x2, y2 = x1*resolution.width, y1*resolution.height, x2*resolution.width, y2*resolution.height
|
222 |
+
conf = 1
|
223 |
+
pred_tm_frame.append([x1, y1, x2, y2, conf, label])
|
224 |
+
preds_tm.append(torch.tensor(pred_tm_frame) if len(pred_tm_frame) > 0 else torch.empty((0, 6)))
|
225 |
+
|
226 |
+
return preds_tm
|
227 |
+
|
228 |
+
|
229 |
+
|
compute.py
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import numpy as np
|
3 |
+
from utils import BoxMetrics, concat_labels, concat_preds
|
4 |
+
import fiftyone as fo
|
5 |
+
from seametrics.fo_utils.utils import fo_to_payload
|
6 |
+
from const import INDEX_MAPPING, CLASS_MAPPING, INDEX_MAPPING_INV
|
7 |
+
from tqdm import tqdm
|
8 |
+
|
9 |
+
tags = ["WHALES"]
|
10 |
+
cameras = ["thermal_narrow"]
|
11 |
+
|
12 |
+
dataset_name = "SENTRY_VIDEOS_DATASET_QA"
|
13 |
+
#dataset_name = "SENTRY_VIDEOS_DATASET_QA"
|
14 |
+
model = "cerulean-level-17_11_2023_RL_SPLIT_ep147_CNN"
|
15 |
+
det_gt_field = "ground_truth_det"
|
16 |
+
|
17 |
+
cm = BoxMetrics(nc=10, conf=0, iou_thres=0)
|
18 |
+
|
19 |
+
if dataset_name == "SAILING_DATASET_QA":
|
20 |
+
cameras = ["thermal_left"]
|
21 |
+
dataset_view = fo.load_dataset(dataset_name).match_tags(tags).select_group_slices(cameras).filter_labels(f"{model}", True, only_matches=False)
|
22 |
+
sequences = dataset_view.distinct("sequence")
|
23 |
+
if dataset_name == "SENTRY_VIDEOS_DATASET_QA":
|
24 |
+
cameras = ["thermal_wide"]
|
25 |
+
dataset_view = fo.load_dataset(dataset_name).match_tags(tags).select_group_slices(cameras).filter_labels(f"frames.{model}", True, only_matches=False)
|
26 |
+
sequences = dataset_view.distinct("sequence")
|
27 |
+
|
28 |
+
for sequence in tqdm(sequences):
|
29 |
+
payload = fo_to_payload(dataset = dataset_name,
|
30 |
+
gt_field = det_gt_field,
|
31 |
+
models = [model],
|
32 |
+
tracking_mode = True,
|
33 |
+
sequence_list = [sequence],
|
34 |
+
excluded_classes = ["BIRD"],)
|
35 |
+
|
36 |
+
target = payload["sequences"][sequence][det_gt_field]
|
37 |
+
preds = payload["sequences"][sequence][model]
|
38 |
+
resolution = payload["sequences"][sequence]["resolution"]
|
39 |
+
target_tm = []
|
40 |
+
preds_tm = []
|
41 |
+
for frame in target:
|
42 |
+
target_tm_batch = []
|
43 |
+
for det in frame:
|
44 |
+
if CLASS_MAPPING[det["label"]] is not None:
|
45 |
+
label = INDEX_MAPPING[CLASS_MAPPING[det["label"]]]-1
|
46 |
+
else:
|
47 |
+
continue
|
48 |
+
box = det["bounding_box"]
|
49 |
+
x1, y1, x2, y2 = box[0], box[1], box[0]+box[2], box[1]+box[3]
|
50 |
+
x1, y1, x2, y2 = x1*resolution[1], y1*resolution[0], x2*resolution[1], y2*resolution[0]
|
51 |
+
target_tm_batch.append([label, x1, y1, x2, y2])
|
52 |
+
target_tm.append(torch.tensor(target_tm_batch) if len(target_tm_batch) > 0 else torch.empty((0, 5)))
|
53 |
+
|
54 |
+
for frame in preds:
|
55 |
+
pred_tm_batch = []
|
56 |
+
for det in frame:
|
57 |
+
label = INDEX_MAPPING[det["label"]]-1
|
58 |
+
box = det["bounding_box"]
|
59 |
+
x1, y1, x2, y2 = box[0], box[1], box[0]+box[2], box[1]+box[3]
|
60 |
+
x1, y1, x2, y2 = x1*resolution[1], y1*resolution[0], x2*resolution[1], y2*resolution[0]
|
61 |
+
conf = 1
|
62 |
+
pred_tm_batch.append([x1, y1, x2, y2, conf, label])
|
63 |
+
|
64 |
+
preds_tm.append(torch.tensor(pred_tm_batch) if len(pred_tm_batch) > 0 else torch.empty((0, 6)))
|
65 |
+
|
66 |
+
for i in range(len(target_tm)):
|
67 |
+
target_batch = target_tm[i]
|
68 |
+
pred_batch = preds_tm[i]
|
69 |
+
cm.process_batch(pred_batch, target_batch)
|
70 |
+
|
71 |
+
print("SUMMARY: ")
|
72 |
+
print("\nmodel: ", model)
|
73 |
+
print("\nconfusion matrix: ")
|
74 |
+
print(cm.matrix.astype(int))
|
75 |
+
|
76 |
+
tp = cm.matrix[:-1, :-1].sum()
|
77 |
+
fp = cm.matrix[:-1, -1].sum()
|
78 |
+
fn = cm.matrix[-1, :-1].sum()
|
79 |
+
print("\nTP: ", tp, "FP: ", fp, "FN: ", fn, "support: ", tp + fn)
|
80 |
+
#Detection Rates:
|
81 |
+
print("\nDetection Rates:")
|
82 |
+
for i in range(10):
|
83 |
+
tp = cm.matrix[:-1, i].sum()
|
84 |
+
fn = cm.matrix[-1, i].sum()
|
85 |
+
if tp + fn == 0:
|
86 |
+
print(f"{INDEX_MAPPING_INV[i+1]}: NaN")
|
87 |
+
else:
|
88 |
+
print(f"{INDEX_MAPPING_INV[i+1]}: {tp/(tp+fn)}")
|
test.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import numpy as np
|
3 |
+
import fiftyone as fo
|
4 |
+
from box_metrics import BoxMetrics
|
5 |
+
from seametrics.fo_utils.utils import fo_to_payload
|
6 |
+
from tqdm import tqdm
|
7 |
+
|
8 |
+
tags = ["WHALES"]
|
9 |
+
dataset_name = "SENTRY_VIDEOS_DATASET_QA"
|
10 |
+
model = "cerulean-level-17_11_2023_RL_SPLIT_ep147_CNN"
|
11 |
+
det_gt_field = "ground_truth_det"
|
12 |
+
|
13 |
+
dataset = fo.load_dataset(dataset_name)
|
14 |
+
dataset_view = fo.load_dataset(dataset_name).match_tags(tags) if tags else fo.load_dataset(dataset_name)
|
15 |
+
sequences = dataset_view.distinct("sequence")
|
16 |
+
|
17 |
+
bbox_metric = BoxMetrics(max_iou=0.01)
|
18 |
+
payload = fo_to_payload(dataset = dataset_name,
|
19 |
+
gt_field = det_gt_field,
|
20 |
+
models = [model],
|
21 |
+
tracking_mode = True,
|
22 |
+
sequence_list = sequences)
|
23 |
+
print(payload)
|
24 |
+
bbox_metric.add_payload(payload)
|
25 |
+
result = bbox_metric.compute()
|
26 |
+
print(result)
|
utils.py
ADDED
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import numpy as np
|
3 |
+
import math
|
4 |
+
|
5 |
+
def bbox_bep(box1, box2, xywh=True, eps=1e-7, bep1 = True):
|
6 |
+
"""
|
7 |
+
Calculates bottom edge proximity between two boxes
|
8 |
+
|
9 |
+
Input shapes are box1(1,4) to box2(n,4)
|
10 |
+
|
11 |
+
Implementation of bep2 from
|
12 |
+
Are object detection assessment criteria ready for maritime computer vision?
|
13 |
+
"""
|
14 |
+
|
15 |
+
# Get the coordinates of bounding boxes
|
16 |
+
if xywh: # transform from xywh to xyxy
|
17 |
+
(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
|
18 |
+
w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
|
19 |
+
b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
|
20 |
+
b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
|
21 |
+
else: # x1, y1, x2, y2 = box1
|
22 |
+
b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
|
23 |
+
b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
|
24 |
+
w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)
|
25 |
+
w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)
|
26 |
+
|
27 |
+
# Bottom edge distance (absolute value)
|
28 |
+
# xb = torch.abs(b2_x2 - b1_x1)
|
29 |
+
xb = torch.min(b2_x2-b1_x1, b1_x2-b2_x1)
|
30 |
+
xa = w2 - xb
|
31 |
+
xc = w1 - xb
|
32 |
+
ybe = torch.abs(b2_y2 - b1_y2)
|
33 |
+
|
34 |
+
X2 = xb/(xb+xa)
|
35 |
+
Y2 = 1-ybe/h2
|
36 |
+
|
37 |
+
X1 = xb/(xb+xa+xc+eps)
|
38 |
+
Y1 = 1-ybe/(torch.max(h2,h1)+eps)
|
39 |
+
|
40 |
+
bep = X1*Y1 if bep1 else X2*Y2
|
41 |
+
|
42 |
+
return bep
|
43 |
+
|
44 |
+
def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
|
45 |
+
"""
|
46 |
+
Calculates IoU, GIoU, DIoU, or CIoU between two boxes, supporting xywh/xyxy formats.
|
47 |
+
|
48 |
+
Input shapes are box1(1,4) to box2(n,4).
|
49 |
+
"""
|
50 |
+
|
51 |
+
# Get the coordinates of bounding boxes
|
52 |
+
if xywh: # transform from xywh to xyxy
|
53 |
+
(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
|
54 |
+
w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
|
55 |
+
b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
|
56 |
+
b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
|
57 |
+
else: # x1, y1, x2, y2 = box1
|
58 |
+
b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
|
59 |
+
b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
|
60 |
+
w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)
|
61 |
+
w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)
|
62 |
+
|
63 |
+
# Intersection area
|
64 |
+
inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * (
|
65 |
+
b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)
|
66 |
+
).clamp(0)
|
67 |
+
|
68 |
+
# Union Area
|
69 |
+
union = w1 * h1 + w2 * h2 - inter + eps
|
70 |
+
|
71 |
+
# IoU
|
72 |
+
iou = inter / union
|
73 |
+
if CIoU or DIoU or GIoU:
|
74 |
+
cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width
|
75 |
+
ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height
|
76 |
+
if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
|
77 |
+
c2 = cw**2 + ch**2 + eps # convex diagonal squared
|
78 |
+
rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center dist ** 2
|
79 |
+
if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
|
80 |
+
v = (4 / math.pi**2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)
|
81 |
+
with torch.no_grad():
|
82 |
+
alpha = v / (v - iou + (1 + eps))
|
83 |
+
return iou - (rho2 / c2 + v * alpha) # CIoU
|
84 |
+
return iou - rho2 / c2 # DIoU
|
85 |
+
c_area = cw * ch + eps # convex area
|
86 |
+
return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf
|
87 |
+
return iou # IoU
|
88 |
+
|
89 |
+
class BoxMetrics:
|
90 |
+
# Updated version of https://github.com/kaanakan/object_detection_confusion_matrix
|
91 |
+
def __init__(self):
|
92 |
+
self.preds_tm = []
|
93 |
+
self.target_tm = []
|
94 |
+
self.bottom_x = []
|
95 |
+
self.bottom_y = []
|
96 |
+
self.widths = []
|
97 |
+
self.heights = []
|
98 |
+
self.ious = []
|
99 |
+
self.beps = []
|
100 |
+
|
101 |
+
def add_batch(self, preds, target):
|
102 |
+
"""
|
103 |
+
Return intersection-over-union (Jaccard index) of boxes.
|
104 |
+
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
|
105 |
+
Arguments:
|
106 |
+
detections torch(Array[N, 6]), x1, y1, x2, y2, conf, class
|
107 |
+
labels torch(Array[M, 5]), class, x1, y1, x2, y2
|
108 |
+
Returns:
|
109 |
+
None, updates confusion matrix accordingly
|
110 |
+
"""
|
111 |
+
self.preds_tm.extend(preds)
|
112 |
+
self.target_tm.extend(target)
|
113 |
+
|
114 |
+
def compute(self):
|
115 |
+
"""
|
116 |
+
Computes bbox iou, bep and location/size statistics
|
117 |
+
"""
|
118 |
+
|
119 |
+
for i in range(len(self.target_tm)):
|
120 |
+
target_batch_boxes = self.target_tm[i][:, 1:]
|
121 |
+
pred_batch_boxes = self.preds_tm[i][:, :4]
|
122 |
+
|
123 |
+
if pred_batch_boxes.shape[0] == 0:
|
124 |
+
continue
|
125 |
+
|
126 |
+
if target_batch_boxes.shape[0] == 0:
|
127 |
+
continue
|
128 |
+
|
129 |
+
for t_box in target_batch_boxes:
|
130 |
+
iou = bbox_iou(t_box.unsqueeze(0), pred_batch_boxes, xywh=False)
|
131 |
+
bep = bbox_bep(t_box.unsqueeze(0), pred_batch_boxes, xywh=False)
|
132 |
+
|
133 |
+
matches = pred_batch_boxes[iou.squeeze(1) > 0.1]
|
134 |
+
|
135 |
+
bep = bep[iou > 0]
|
136 |
+
iou = iou[iou > 0]
|
137 |
+
# if any iou value is 0 or less, raise error
|
138 |
+
if torch.any(iou <= 0):
|
139 |
+
raise ValueError("IoU values must be greater than 0.")
|
140 |
+
#same for bep
|
141 |
+
if torch.any(bep <= 0):
|
142 |
+
print(t_box.unsqueeze(0))
|
143 |
+
print(pred_batch_boxes)
|
144 |
+
print(bep)
|
145 |
+
print(iou)
|
146 |
+
raise ValueError("BEP values must be greater than 0.")
|
147 |
+
|
148 |
+
self.ious.extend(iou.tolist())
|
149 |
+
self.beps.extend(bep.tolist())
|
150 |
+
|
151 |
+
for match in matches:
|
152 |
+
t_xc = (match[0].item()+match[2].item())/2
|
153 |
+
p_xc = (t_box[0].item()+t_box[2].item())/2
|
154 |
+
t_w = t_box[2].item()-t_box[0].item()
|
155 |
+
p_w = match[2].item()-match[0].item()
|
156 |
+
t_h = t_box[3].item()-t_box[1].item()
|
157 |
+
p_h = match[3].item()-match[1].item()
|
158 |
+
|
159 |
+
self.bottom_x.append(p_xc - t_xc)
|
160 |
+
self.bottom_y.append(match[3].item()-t_box[3].item())
|
161 |
+
self.widths.append(p_w-t_w)
|
162 |
+
self.heights.append(p_h-t_h)
|
163 |
+
|
164 |
+
return {"iou_mean": np.mean(self.ious),
|
165 |
+
"bep_mean": np.mean(self.beps),
|
166 |
+
"bottom_x_std": np.std(self.bottom_x),
|
167 |
+
"bottom_y_std": np.std(self.bottom_y),
|
168 |
+
"widths_std": np.std(self.widths),
|
169 |
+
"heights_std": np.std(self.heights),
|
170 |
+
"bottom_x_mean": np.mean(self.bottom_x),
|
171 |
+
"bottom_y_mean": np.mean(self.bottom_y),
|
172 |
+
"widths_mean": np.mean(self.widths),
|
173 |
+
"heights_mean": np.mean(self.heights)}
|