Spaces:
Running
on
Zero
Running
on
Zero
File size: 7,985 Bytes
c614b0f |
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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# Multi-HMR
# Copyright (c) 2024-present NAVER Corp.
# CC BY-NC-SA 4.0 license
import torch
import numpy as np
from itertools import product
def compute_prf1(count, miss, fp):
"""
Code modified from https://github.com/Arthur151/ROMP/blob/4eebd3647f57d291d26423e51f0d514ff7197cb3/simple_romp/evaluation/RH_evaluation/evaluation.py#L90
"""
if count == 0:
return 0, 0, 0
all_tp = count - miss
all_fp = fp
all_fn = miss
if all_tp == 0:
return 0., 0., 0.
all_f1_score = round(all_tp / (all_tp + 0.5 * (all_fp + all_fn)), 2)
all_recall = round(all_tp / (all_tp + all_fn), 2)
all_precision = round(all_tp / (all_tp + all_fp), 2)
return 100. * all_precision, 100.* all_recall, 100. * all_f1_score
def match_2d_greedy(
pred_kps,
gtkp,
valid_mask,
imgPath=None,
baseline=None,
iou_thresh=0.05,
valid=None,
ind=-1):
'''
Code modified from: https://github.com/Arthur151/ROMP/blob/4eebd3647f57d291d26423e51f0d514ff7197cb3/simple_romp/trace2/evaluation/eval_3DPW.py#L232
matches groundtruth keypoints to the detection by considering all possible matchings.
:return: best possible matching, a list of tuples, where each tuple corresponds to one match of pred_person.to gt_person.
the order within one tuple is as follows (idx_pred_kps, idx_gt_kps)
'''
predList = np.arange(len(pred_kps))
gtList = np.arange(len(gtkp))
# get all pairs of elements in pred_kps, gtkp
# all combinations of 2 elements from l1 and l2
combs = list(product(predList, gtList))
errors_per_pair = {}
errors_per_pair_list = []
for comb in combs:
vmask = valid_mask[comb[1]]
assert vmask.sum()>0, print('no valid points')
errors_per_pair[str(comb)] = np.linalg.norm(pred_kps[comb[0]][vmask, :2] - gtkp[comb[1]][vmask, :2], 2)
errors_per_pair_list.append(errors_per_pair[str(comb)])
gtAssigned = np.zeros((len(gtkp),), dtype=bool)
opAssigned = np.zeros((len(pred_kps),), dtype=bool)
errors_per_pair_list = np.array(errors_per_pair_list)
bestMatch = []
excludedGtBecauseInvalid = []
falsePositiveCounter = 0
while np.sum(gtAssigned) < len(gtAssigned) and np.sum(
opAssigned) + falsePositiveCounter < len(pred_kps):
found = False
falsePositive = False
while not(found):
if sum(np.inf == errors_per_pair_list) == len(
errors_per_pair_list):
print('something went wrong here')
minIdx = np.argmin(errors_per_pair_list)
minComb = combs[minIdx]
# compute IOU
iou = get_bbx_overlap(
pred_kps[minComb[0]], gtkp[minComb[1]]) #, imgPath, baseline)
# if neither prediction nor ground truth has been matched before and iou
# is larger than threshold
if not(opAssigned[minComb[0]]) and not(
gtAssigned[minComb[1]]) and iou >= iou_thresh:
#print(imgPath + ': found matching')
found = True
errors_per_pair_list[minIdx] = np.inf
else:
errors_per_pair_list[minIdx] = np.inf
# if errors_per_pair_list[minIdx] >
# matching_threshold*headBboxs[combs[minIdx][1]]:
if iou < iou_thresh:
#print(
# imgPath + ': false positive detected using threshold')
found = True
falsePositive = True
falsePositiveCounter += 1
# if ground truth of combination is valid keep the match, else exclude
# gt from matching
if not(valid is None):
if valid[minComb[1]]:
if not falsePositive:
bestMatch.append(minComb)
opAssigned[minComb[0]] = True
gtAssigned[minComb[1]] = True
else:
gtAssigned[minComb[1]] = True
excludedGtBecauseInvalid.append(minComb[1])
elif not falsePositive:
# same as above but without checking for valid
bestMatch.append(minComb)
opAssigned[minComb[0]] = True
gtAssigned[minComb[1]] = True
bestMatch = np.array(bestMatch)
# add false positives and false negatives to the matching
# find which elements have been successfully assigned
opAssigned = []
gtAssigned = []
for pair in bestMatch:
opAssigned.append(pair[0])
gtAssigned.append(pair[1])
opAssigned.sort()
gtAssigned.sort()
falsePositives = []
misses = []
# handle false positives
opIds = np.arange(len(pred_kps))
# returns values of oIds that are not in opAssigned
notAssignedIds = np.setdiff1d(opIds, opAssigned)
for notAssignedId in notAssignedIds:
falsePositives.append(notAssignedId)
gtIds = np.arange(len(gtList))
# returns values of gtIds that are not in gtAssigned
notAssignedIdsGt = np.setdiff1d(gtIds, gtAssigned)
# handle false negatives/misses
for notAssignedIdGt in notAssignedIdsGt:
if not(valid is None): # if using the new matching
if valid[notAssignedIdGt]:
#print(imgPath + ': miss')
misses.append(notAssignedIdGt)
else:
excludedGtBecauseInvalid.append(notAssignedIdGt)
else:
#print(imgPath + ': miss')
misses.append(notAssignedIdGt)
return bestMatch, falsePositives, misses # tuples are (idx_pred_kps, idx_gt_kps)
def get_bbx_overlap(p1, p2):
"""
Code modifed from https://github.com/Arthur151/ROMP/blob/4eebd3647f57d291d26423e51f0d514ff7197cb3/simple_romp/trace2/evaluation/eval_3DPW.py#L185
"""
min_p1 = np.min(p1, axis=0)
min_p2 = np.min(p2, axis=0)
max_p1 = np.max(p1, axis=0)
max_p2 = np.max(p2, axis=0)
bb1 = {}
bb2 = {}
bb1['x1'] = min_p1[0]
bb1['x2'] = max_p1[0]
bb1['y1'] = min_p1[1]
bb1['y2'] = max_p1[1]
bb2['x1'] = min_p2[0]
bb2['x2'] = max_p2[0]
bb2['y1'] = min_p2[1]
bb2['y2'] = max_p2[1]
assert bb1['x1'] < bb1['x2']
assert bb1['y1'] < bb1['y2']
assert bb2['x1'] < bb2['x2']
assert bb2['y1'] < bb2['y2']
# determine the coordinates of the intersection rectangle
x_left = max(bb1['x1'], bb2['x1'])
y_top = max(bb1['y1'], bb2['y1'])
x_right = min(bb1['x2'], bb2['x2'])
y_bottom = min(bb1['y2'], bb2['y2'])
# The intersection of two axis-aligned bounding boxes is always an
# axis-aligned bounding box
intersection_area = max(0, x_right - x_left + 1) * \
max(0, y_bottom - y_top + 1)
# compute the area of both AABBs
bb1_area = (bb1['x2'] - bb1['x1'] + 1) * (bb1['y2'] - bb1['y1'] + 1)
bb2_area = (bb2['x2'] - bb2['x1'] + 1) * (bb2['y2'] - bb2['y1'] + 1)
# compute the intersection over union by taking the intersection
# area and dividing it by the sum of prediction + ground-truth
# areas - the interesection area
iou = intersection_area / float(bb1_area + bb2_area - intersection_area)
return iou
class AverageMeter(object):
"""
Code mofied from https://github.com/pytorch/examples/blob/main/imagenet/main.py#L423
Computes and stores the average and current value
"""
def __init__(self, name, fmt=':f'):
self.name = name
self.fmt = fmt
self.reset()
def reset(self):
self.val = 0
self.avg = 0
self.sum = 0
self.count = 0
def update(self, val, n=1):
if type(val) == torch.Tensor:
val = val.detach()
self.val = val
self.sum += val * n
self.count += n
self.avg = self.sum / self.count
def __str__(self):
fmtstr = '{name} {val' + self.fmt + '} ({avg' + self.fmt + '})'
return fmtstr.format(**self.__dict__)
|